缓存设计
缓存击穿
数据过期的时候一下子涌入大量的请求,在缓存中不存在。
- 加互斥锁,只允许一个线程去更新缓存数据
- 异步预热缓存
缓存穿透
- 请求的数据根本不存在于缓存和数据库中,每次请求都会打到数据库,导致数据库压力剧增。
- 缓存空对象。对不存在的数据也缓存一个空值,设置较短过期时间。查同一个不存在的商品进行空值延期
- redisson布隆过滤器。
- 参数校验。请求前先校验 userId 是否合理,比如正整数、存在合法范围
缓存雪崩
大量缓存同时失效(或者缓存层支撑不住或者宕机),请求直接打到数据库,造成数据库瞬间崩溃或响应变慢。
- 过期时间随机化。避免大量 key 同时过期(比如加上随机 1~5 分钟)
- 设置热点数据永远不过期。
- 缓存预热或提前加载。系统启动时或定时任务提前加载热门缓存
热点缓存key重建优化
使用分布式锁和dcl(双重检查),只允许一个线程去更新缓存数据。好处是全局一把锁并且一把锁只锁一个对应需要重建的数据。
优化:确定得出数据的时间可以使用trylock避免大量的锁逻辑
缓存与数据库双写不一致
- 双写不一致
- 读写并发不一致
解决方案:
- 一致性要求不高,设置合理的过期时间+每隔一段时间触发读的主动更新
- 延迟双删。缺点会让每个更新操作都延时
- 高一致性的情况。对更新缓存的操作加分布式锁,优化可以使用分布式读写锁(因为写数据库要更新缓存,读数据库也要更新缓存(在缓存没对应数据的情况))
- 引入中间件canal监听binlog修改缓存
读多写少的情况加入缓存提高性能,写多读多的情况又不能容忍缓存数据不一致就不要加缓存了。
开发规范与性能优化
键值设计
- key名设计:可读性、可管理性和简洁性。以业务名或者数据库名为前缀用冒号分隔拼接,key名称不宜过长。不要包含特殊字符
- value设计:拒绝bigkey(防止网卡流量、慢查询)
字符串类型为超过10KB,非字符串类型元素超过5000属于大key - bigkey的危害,getall导致redis阻塞,网络拥塞,过期删除时候没有过期异步删除会阻塞redis
- 如何优化bigkey
- 对大key数据拆分经过hash分开存入big list: list1、list2、...listN
- 如果bigkey不可避免,也要尽量避免getall和直接删除所有字段,可以分批删除
- 控制key的生命周期,使用expire设置过期时间
- 选择适合的数据类型
命令使用
- O(N)命令关注N的数明确N的值。有遍历的需求可以使用scan代替。
- 禁用命令,禁止线上使用keys、flushall、flushdb等,通过redis的rename机制禁掉命令
- 合理使用select,redis的多数据库较弱,使用数字进行区分,很多客户端支持较差,同时多业务用多数据库实际还是单线程处理,会有干扰。
- 使用批量操作提高效率例如mget、mset、pipeline,但要注意控制一次批量操作的元素个数(例如500以内,实际也和元素字节数有关)。
原生命令是原子操作,pipeline是非原子操作。pipeline可以打包不同的命令,原生命令做不到。pipeline需要客户端和服务端同时支持。
- Redis事务功能较弱,不建议过多使用,可以用lua替代
客户端使用
- 避免多个应用使用一个Redis实例
- 使用带有连接池的数据库,可以有效控制连接,同时提高效率
有需要时可以对连接池预热
-
高并发下添加熔断功能
-
设置合理的密码,如有必要可以使用SSL加密访问
-
Redis过期键有三种清除策略
- 被动删除:当读/写一个已经过期的key时,会触发惰性删除策略,直接删除掉这个过期key
- 主动删除:由于惰性删除策略无法保证冷数据被及时删掉,所以Redis会定期主动淘汰一批已过期的key
- 当前已用内存超过maxmemory限定时,触发主动清理策略
- 过期时间的key
- volatile-ttl:在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
- volatile-random:就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
- volatile-lru:会使用 LRU 算法筛选设置了过期时间的键值对删除。
- volatile-lfu:会使用 LFU 算法筛选设置了过期时间的键值对删除。
- 所有的key
- allkeys-random:从所有键值对中随机选择并删除数据。
- allkeys-lru:使用 LRU 算法在所有数据中进行筛选删除。
- allkeys-lfu:使用 LFU 算法在所有数据中进行筛选删除。
- 不处理
- noeviction:不会剔除任何数据,在内存上限时拒绝所有写入操作并返回客户端错误信息"(error) OOM command not allowed when used memory",此时Redis只响应读操作。
- LRU 算法(Least Recently Used,最近最少使用)淘汰很久没被访问过的数据,以最近一次访问时间作为参考。(默认推荐)
- LFU 算法(Least Frequently Used,最不经常使用)淘汰最近一段时间被访问次数最少的数据,以次数作为参考。
当存在热点数据时,这时使用LFU可能更好点。
LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降。
策略 | 核心思想 | 应对场景 |
---|---|---|
LRU | 淘汰最近最少使用的键 | 应对时间敏感的缓存访问模式 |
LFU | 淘汰历史访问频率最低的键 | 应对长期热点数据的缓存使用场景 |
配置淘汰算法
conf
maxmemory 256mb
maxmemory-policy allkeys-lru
bash
CONFIG SET maxmemory 256mb
CONFIG SET maxmemory-policy volatile-lru
- 不配置最大内存的情况,redis会无限制的使用虚拟内存(使用硬盘模拟内存),性能下降。