使用本地缓存/Redis 常见问题

121 阅读6分钟

常见问题

大key

  • 定义:

    • 数据量过大:string类型的value 超过10 KB
    • 成员数过多:hash、list、set、zset元素超过5000个
    • 成员的数据量过大:hash、list、set、zset元素的value 总大小超过10 MB

参考如何找出优化大Key与热Key,产生的原因和问题_云数据库 Redis-阿里云帮助中心

  • 造成的问题:

    • 读写redis 性能变差,主要是

      • 读写大key 占用太多实例IO 和网络带宽,影响其他操作速度
      • 删除大key 时,再分配内存等操作延迟过大,且会阻塞主线程,造成主库较长时间的阻塞,进而可能引发同步中断或主从切换
    • 其他redis 性能也会变差,包括主从同步、持久化、故障恢复、扩缩容等

    • 大key 占用过多内存,可能导致重要的key 被逐出,还可能导致单实例OOM

    • 不同实例内存占用不均衡

  • 监控报警机制:

    • 有在线和离线的机制,一般在线的监控大key 的机制比较消耗资源,准确性与时效性差
    • 详细机制可参考快速找出大Key和热Key
  • 常见产生原因:

    • 在不适用的场景下使用Redis,导致value 过大,如存放大体积二进制文件型数据
    • 业务上线前规划设计不足,没有对Key中的成员进行合理的拆分,造成个别Key中的成员数量过多
    • 未清理无效数据,造成如hash、list、set、zset 类型Key 中的成员持续不断地增加
  • 解决方法:

    • 使用redis 前评估是否可能有大key 问题,并针对处理(使用其他数据库,或者设置清理无效数据的机制)
    • 拆分key,即把数据分散在多个key 中存储
    • 压缩value,如用protocol buffer 替代json 进行序列化,value 压缩后再存储
    • 异步删除大key(临时方法)(对应unlink 命令,以非阻塞的方式,逐步地清理指定Key)

热key

  • 定义:

    • qps * 数据量 比较高的key,例如

      • 一个string类型的value 为50 B,但有超过10w qps的get 请求的key
      • 一个hash 类型,拥有1w 个元素,且有超过5000 qps的hgetall 请求的key
    • CPU 使用时间高的Key:例如一个zset 类型,拥有1w 个元素,有超过5000 qps的zrange 请求的key

  • 造成的问题:

    • 流量倾斜,导致redis 分片单实例收到的请求过多,使得该分片请求实验增加,甚至无法正常处理请求
    • 热key过期时,或者请求大量失败时,容易引发缓存击穿
  • 解决方法:

    • 增加备份key(比如热key是keyA,可以增加几个value相同的keyA1、keyA2、keyA3,随机选择一个key请求)
    • 使用多级缓存(比如本地缓存 + redis + abase)
    • 开启热key承载(用proxy 缓存热key 请求对应返回值,通过多台proxy+缓存,来保护server)

缓存穿透、缓存击穿与缓存雪崩

定义

  • 英文名:

    • 缓存穿透(cache penetration)
    • 缓存击穿(cache breakdown、invalid hotpot)
    • 缓存雪崩(cache avalanche)
  • 定义:

    • 缓存穿透:对空key 的大量访问,造成大量请求到数据库,击垮数据库
    • 缓存击穿:某一key 失效时,造成大量请求到数据库,击垮数据库
    • 缓存雪崩:多个key 同时过期,造成大量请求到数据库,击垮数据库

异同辨析

  • 相同点:都是大量请求不走缓存,直接到数据库,导致数据库被击垮

  • 不同点:主要是产生原因不一样

    • 缓存穿透:对空key 的大量访问
    • 缓存击穿:热key 失效,通常是过期
    • 缓存雪崩:多个key 同时失效,如key 集中过期/删除、流量突增、redis宕机/新redis集群

解决方法

  • 三者共同的解决方法:拦截对数据库的请求:

    • 拦截对数据库的相同的请求,即互斥访问数据库(singleflight、redis分布式锁)
    • 拦截对数据库的过量的请求,即限流
  • 缓存击穿、缓存雪崩 共同的解决方法:

    • 避免key 失效影响数据库:使用多级缓存(比如本地缓存 + redis + abase),

    • 避免key 失效:

      1. 缓存数据“永远不过期”,一定时期更新一次
      2. 使用软过期(即缓存到期不直接删除,而是请求db更新缓存)
  • 缓存穿透 特有的解决方法:避免空key 请求到数据库,方法包括

    • 校验请求参数
    • 保存空数据对应的key
    • 使用布隆过滤器,存在于过滤器中的key才查下游(不支持会删除key的场景,因为布隆过滤器不支持删除)
  • 缓存雪崩 特有的解决方法:

    • 避免key 集中失效:缓存数据过期时间随机不一致
    • 针对新redis集群问题,预热缓存

缓存污染

  • 概念:将不经常访问的数据放置到缓存存储空间中,以至于高频访问的数据无法放置到缓存中

  • 解决方法:

    • 不缓存不经常访问的数据
    • 使用较好的缓存淘汰算法,如Two Queues(2Q) 等算法

Two Queues(2Q) 算法原理:

  • 使用两个缓存队列,一个是FIFO 队列,一个是LRU 队列
  • 新缓存数据,加入FIFO 队列,等第二次被访问后,才进入LRU 队列

数据一致性

分类解法
数据库与缓存的数据一致性写数据库成功后删除缓存
不同节点间的本地缓存数据一致性使用一致性hash,相同请求路由到相同的节点

关注指标

  • 性能:命中率(一个命中率很低的缓存系统没有存在的价值)、读写延时
  • 资源消耗:空间占用率、CPU 消耗
  • 数据一致性

字节缓存VS对象缓存

  • 区别:
优点缺点
字节缓存GC消耗小(可以自己管理一块内存不参与GC,还有压舱石降低GC 频率作用)有序列化开销
对象缓存无序列化开销GC消耗大(无法自己管理一块内存)有浅拷贝问题(不能随意修改缓存中取出的数据)
  • 实践选择:字节缓存(序列化开销)vs 对象缓存(GC开销)

    • 少量对象&&大量请求:字节缓存 > 对象缓存(对象少,所以使用字节缓存GC 开销较小;请求多,所以使用对象缓存序列化开销较大)
    • 大量对象&&少量请求:对象缓存 > 字节缓存(对象多,所以使用字节缓存GC 开销较大;请求少,所以使用对象缓存序列化开销较小)
    • 少量对象&&少量请求:序列化和GC开销都较小,所以选啥都可以,建议选对象缓存,使用比较简单
    • 大量对象&&大量请求:序列化和GC开销都较大,可以实际测试两者性能后再决定,一般选字节缓存

参考资料

如何找出优化大Key与热Key,产生的原因和问题_云数据库 Redis-阿里云帮助中心