常见问题
大key
-
定义:
- 数据量过大:string类型的value 超过10 KB
- 成员数过多:hash、list、set、zset元素超过5000个
- 成员的数据量过大:hash、list、set、zset元素的value 总大小超过10 MB
-
造成的问题:
-
读写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 失效:
- 缓存数据“永远不过期”,一定时期更新一次
- 使用软过期(即缓存到期不直接删除,而是请求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开销都较大,可以实际测试两者性能后再决定,一般选字节缓存