[toc]
[toc]
本系列文章整理摘抄自
缓存设计前需要权衡成本和收益
收益
- 加速读写
- 降低后端负载
成本
- 数据不一致性
- 缓存层和存储层数据势必会有不一致的时间,需要考虑如何避免对业务造成的影响。
- 代码维护成本
- redis 和 mysql 两方代码都要考虑。
缓存如何更新?
基于内存的缓存不可能将所有的数据都做缓存,一般针对的都是热点数据。热点数据具有时效性,过了一定时间将成为非热点数据。
所以,缓存数据通常具有生命时长,到达指定的时间后,将被更新或者删除。
熟悉缓存的常用更新策略,才能针对业务场景做出合理的选择。
更新策略1: LRU/LFU/FIFO 算法
通常缓存量超过了预设的最大值时,将会采取以上策略,具体由 maxmemory-policy
参数指定。
一致性最差。
更新策略2: 超时剔除
通过给缓存数据设置过期时间,让其在过期时间后自动删除,例如Redis提供的expire
命令。
一致性取决于时间窗口。
更新策略3: 主动更新
当堆数据一致性要求高时,在更新存储层数据后,需要立即更新缓存中的数据。
一致性最好。
最佳实践方案
低一致性业务
配置最大内存 + 淘汰策略
高一致性业务
超时剔除 + 主动更新
缓存的粒度如何选?
对于缓存数据库的内容时,要考虑缓存全部字段,或者部分字段。
- 全部字段:
- 全部缓存占用空间过大
- 并不是所有字段都用的到
- 部分字段:
- 未来可能用到新字段
缓存预热
在系统刚上线,直接将数据加载进缓存系统,提前准备。
缓存穿透问题(针对无效查询)
概念描述
缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不命中。
产生的原因
- 问题1:业务代码或者数据出现问题
- 问题2:恶意攻击,爬虫造成的大量空命中
造成的影响
如果出现大量的缓存穿透,会对后端的数据库产生大流量的冲击,严重可使得数据库服务宕机。
通用解决方案
问题如何发现?
统计总调用数、 缓存层命中数、 存储层命中数,如果发现大量存储层空命中, 可能就是出现了缓存穿透问题。
问题如何预防?
问题1的解决. 缓存空对象 + 设置短过期时间
实时性高。
存储层不命中后,仍然将空对象保留到缓存层中,之后再访问这个数据将会从缓存中获取,这样就保护了后端数据源。
问题2的解决. 布隆过滤器拦截(缓存层之前对存在的key做保存)
实时性差。
访问缓存层和存储层之前,将存在的key用布隆过滤器提前保存起来, 做第一层拦截
缓存雪崩
概念描述
发生大规模的缓存失效的情况,或者缓存层宕机,大量流量冲入存储层。
产生的原因
缓存服务由于各种原因失效了。
造成的影响
数据库服务宕机。
通用解决方案
提前的规划: 保证缓存服务的高可用。
主从 + 哨兵, 集群。
Redis Sentinel和RedisCluster都实现了高可用
出现雪崩时的处理:隔离组件做限流和降级处理
- ehcache本地缓存 + Hystrix限流并降级,避免MySQL被搞死。
- 加锁排队(并发量不高的情况下)
Hystrix限流并降级的流程
海量的用户请求出现:
- 首先通过限流组件Hystrix限流(只有预定的请求进入存储层)
- 对于未通过的流量,直接导到 预定的降级处理方案,比如友好的提示等待。
3. 提前排演测试
缓存击(针对失效数据)-热点数据高并发访问时,失效来不及重建
概念描述
当热点数据并发访问量非常大,由于之前设置了过期时间,失效后难以短时间重建。
比如: 热点的娱乐新闻。
产生的原因
缓存服务由于各种原因失效了。
造成的影响
数据库服务宕机。
通用解决方案
1. 限制重建的线程数 - 互斥锁
并不是所有的线程都需要去重建,第一个遇到的线程重建,其他线程等待即可。
互斥锁的实现1:通过setnx 和 expire命令实现
1 | String get(String key) { |
互斥锁的实现2:通过watch和Redis的事务命令实现
2. 永不过期
但是会出现数据不一致的情况。
3. 错峰失效
不要让热点数据集中失效,而是一批一批,分时间段的失效