一、redis翻车

1.1 雪崩

定义

image.png
高并发时缓存挂了,请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。重启数据库,但是数据库立马又被新的流量给打死了。这就是缓存雪崩。

解决
  1. 事前:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。
  2. 事中:本地 ehcache 缓存 + hystrix 限流&降级,避免 MySQL 被打死。
  3. 事后:redis 持久化,一旦重启,自动从磁盘上加载数据,快速恢复缓存数据。
    image.png
  4. 用户发送一个请求,系统 A 收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。
  5. 限流组件,可以设置每秒的请求,有多少能通过组件,剩余的未通过的请求,怎么办?走降级!可以返回一些默认的值,或者友情提示,或者空白的值。
  6. 数据库绝对不会死,限流组件确保了每秒只有多少个请求能通过。只要请求可以被处理,就意味着系统没死,对用户来说,可能就是点击几次刷不出来页面,但是多点几次,就可以刷出来一次。

1.2 穿透

定义

image.png
大量恶意请求,缓存中查不到,每次去数据库里查也查不到(如查询条件为负id),请求每次都视缓存于无物,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

解决

只要没查到,就写一个空值到缓存里去,然后设置一个过期时间。下次有相同的key来访问的时候,在缓存失效之前都可以直接从缓存中取数据。

1.3 击穿

定义

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决
  1. 若缓存的数据是基本不会发生更新的,则可尝试将该热点数据设置为永不过期。
  2. 若缓存的数据更新不频繁,且缓存刷新的整个流程耗时较少的情况下,则可以采用基于 redis、zookeeper 等分布式中间件的分布式互斥锁,或者本地互斥锁以保证仅少量的请求能请求数据库并重新构建缓存,其余线程则在锁释放后能访问到新缓存。
  3. 若缓存的数据更新频繁或者缓存刷新的流程耗时较长的情况下,可以利用定时线程在缓存过期前主动的重新构建缓存或者延后缓存的过期时间,以保证所有的请求能一直访问到对应的缓存。

二、redis字符串

Redis虽然是用C语言写的,但却没有直接用C语言的字符串,而是自己实现了一套简单动态字符串(Simple Dynamic String)。目的就是为了提升速度,提升性能。

2.1 结构

struct sdshdr{
    //  记录已使用长度
    int len;
    // 记录空闲未使用的长度
    int free;
    // 字符数组
    char[] buf;
};

image.png
Redis的字符串也会遵守C语言的字符串的实现规则,即最后一个字符为空字符。然而这个空字符不会被计算在len里头。

2.2 特点

SDS的最厉害最奇妙之处在于它的Dynamic。动态变化长度的步骤如下:

  1. 计算出大小是否足够
  2. 开辟空间至满足所需大小
  3. 开辟与已使用大小len相同长度的空闲free空间(至多开辟1M的free空间)

2.3 优势

  1. 快速获取字符串长度
  2. 避免缓冲区溢出(因为会先计算大小是否足够并开辟空间)
  3. 降低空间分配次数提升内存使用效率
    1. 空间预分配:优先使用free空间,free空间够用则不开辟新空间
    2. 惰性空间回收:适用于字符串缩减操作,Redis不会立即回收减少的部分,而是会分配给下一个需要内存的程序。

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议