Redis 是什么

Redis 是一个 key-value 存储系统,它适合放一些频繁使用,比较热的数据。,redis 存储在内存里,redis 既可以用来做持久存储,也可以做缓存,而目前大多数公司的存储都是 mysql + redis,mysql 作为主存储,redis 作为辅助存储被用作缓存,加快访问读取的速度,提高性能

Redis 的11大风险

一、大 key

定义:

  • 某个 key 所存储的内容很大,比如一个 key 存了1M的内容
  • hash、set、list 中存储过多的元素

风险:

  • 单个 size 太大,并发高容易把 redis 带宽打满
  • 写大key会导致超时严重,甚至阻塞服务

应对:

  • 把缓存内容做行列拆分
  • 可以对存储元素按一定规则进行分类,分散存储到多个redis实例中

    redis是单线程,操作bigkey比较耗时,那么阻塞 redis的可能性增大

二、hot key

定义:在一段时间内,该 key 的访问量远远高于其他的 key,导致大部分的访问流量在经过 proxy 分片之后,都集中访问到某一个 redis 实例上

风险:会导致某个 redis 机器负载偏高,甚至倍被击垮

应对:

  • 将应用服务加本地缓存,而且最好单机本地缓存要能存放完该 key 的所有业务数据
  • 在 proxy 加功能,实现主动发现及识别热点 key,并主动缓存其值
  • 利用分片算法的特性,对 key 进行打散处理

    三、缓存击穿

    定义:某个并发高的 key 失效瞬间大量请求回源到 DB (存在的key瞬间失效)
    风险:有可能 DB 被拖垮,影响其它业务
    应对:设置超长有效期,并通过互斥锁串行回源 db (要注意缓存失效时间)
    1. public String get(key) {
    2. String value = redis.get(key);
    3. if (value == null) { //代表缓存值过期
    4. //设置3min的超时,防止del操作失败的时候,下次缓存过期一直不能load db
    5. if (redis.setnx(key_mutex, 1, 3 * 60) == 1) { //代表设置成功
    6. value = db.get(key);
    7. redis.set(key, value, expire_secs);
    8. redis.del(key_mutex);
    9. } else { //这个时候代表同时候的其他线程已经load db并回设到缓存了,这时候重试获取缓存值即可
    10. sleep(100);
    11. get(key); //重试
    12. }
    13. } else {
    14. return value;
    15. }
    16. }

    四、缓存雪崩

    情况一

    定义:某个时间节点,大量 key 失效,导致大量请求从缓存中获取不到数据,而回源到 DB
    风险:有可能 DB 瞬间被拖垮,影响其它业务

应对:

  • 回源逻辑加锁,串行回源,保证同一个 key 只能单个线程回到db去查询
  • 缓存数据的过期时间增加设置随机因子,防止同一时间大量数据过期现象发生

2.jpg

情况二

原因:redis 故障宕机
风险:有可能 DB 瞬间被拖垮,影响其它业务

应对:

  • 服务熔断 或 请求限流机制(雪崩发生后)
  • 构建 Redis 缓存高可靠集群(事前)

3.png

五、缓存穿透

概念:某个不存在的 key 一直被访问,不停回源 DB 查询
风险:增加 DB 负载,高并发可能搞挂 DB
应对:

  • 缓存空值,将 null 变成一个值,但失效时间要短;
  • 布隆过滤器(如果过滤器判断不存在,则一定不存在,不需要回源;如果判断存在,但不一定存在)

    六、超时时间

    key 的超时时间设置是否合理,功能测试时需要考虑

    七、冷数据

    风险:大量冷数据读取时都需要先回到 DB 去查询,然后再写到 DB,响应时间长、DB 负载高
    应对:缓存预热

    八、缓存故障

    风险:当出现上述任何一种故障导致缓存及DB短时间无法恢复,如果没有兜底方案,则业务影响范围扩大
    应对:制定明确的业务熔断降级策略

    九、LRU 策略

    LRU

    定义:从时间维度,把最近被访问到的 key 保留,把最近最少使用的 key 删除
    核心思想:数据最近被访问过,那么将来被访问几率也更高

算法类型:

  • allkeys-lru:所有key使用LRU算法淘汰
  • allkeys-random:所有key使用随机淘汰
  • volatile-random:随机回收设置过期时间的键
  • volatile-ttl:设置了过期时间的key,根据过期时间淘汰,越早过期越早淘汰
  • noeviction:默认策略,当内存达到设置的最大值时,所有申请内存的操作都会报错(如set、push等),只读操作如get命令可以正常执行

    LFU

    定义:从使用频率的维度去筛选,意味着最频繁被访问的数据将来最有可能被访问到,需要保留
    核心思想:过去数据被访问多,那么将来被访问的频率也更高)

算法类型:

  • volatile-lfu:设置了过期时间的key使用LFU算法淘汰
  • allkeys-lfu:所有key使用LFU算法

算法选择依据:

  • volatile-lru、volatile-random 和 volatile-ttl 这三个淘汰策略使用的不是全量数据,有可能无法淘汰出足够的内存空间,在没有过期键或者没有设置超时属性的键情况下,这三种策略和 noeviction 差不多
  • 使用 allkeys-lru 策略:当预期请求符合一个幂次请求(二八法则等),比如一部分的子集元素比其它元素被访问更多时,可以选择这个策略
  • 使用 allkeys-random:循环连续的当问所有的键时,获取预期请求分布平均(所有元素被访问的概率都差不多
  • 使用 volatile-tll:要采取这个策略,缓存对象的TTL值最好有差异

相关配置项:

  • maxmemory:配置 redis 存储数据时指定限制内存大小,比如100m。当缓存消耗的内存超过这个数值时,将触发数据淘汰。该数据配置为0时,表示缓存的数据量没有限制,即LRU功能不生效。64位的系统默认值时0,32位的系统默认内存限制为3GB
  • mamemory_policy:触发系统淘汰后的淘汰策略
  • maxmemory_samples:随机采样的精度,也就是随机取出key的数目,该数值配置越大,越接近于真实的LRU算法,但是数值越大,相应的消耗也大,对性能有一定影响,样本默认值为5
  • lfu-log-factor:可以调整计数器counter的增长速度,lfu-log-factor 越大,counter 增长得越慢
  • lfu-decay-time:是一个以分钟为单位得数值,可以调整counter得减少数量

    十、持久化方

    AOF:日志回放
    RDB:内存快照

    十一、数据一致性

    主要考虑内存值更新的入口考虑是否全面,要保证每个触发 DB 变更的入口都要更新缓存
    DB 和缓存更新顺序的合理性