缓存的收益和成本分析
收益:1)加速读写 2)降低后端负载
成本:1)数据不一致性 2)代码维护成本 3)运维成本
使用场景:1)开销大的复杂计算 2)加速请求响应
缓存更新策略的选择和使用场景
- LRU/LFU/FIFO算法剔除:当缓存使用量大于预设的最大值时候,对现有数据进行剔除,一致性最差,维护成本低
- 超时剔除:过期后自动删除,业务可以容忍一段时间缓存和存储层数据不一致,例如视频描述,但是贸易问题不可用,存在一致性问题,维护成本不高。
- 主动更新:对数据一致性要求高,如信息系统或者其他方式通知缓存更新,一致性最高,维护成本高
//最佳实践:低一致性业务采用配置最大内存和淘汰策略的方式;高一致性业务结合使用超时剔除和主动更新,这样即使主动更新有问题,也保证过期后删除脏数据。
缓存粒度控制方法(如缓存用户数据的哪些)
通用性:很长时间内应用只需要几个重要的属性
空间占用:缓存全部会造成内存浪费,网络流量大,甚至阻塞,序列化和反序列化导致cpu开销大。
代码维护:部分数据加字段时需要修改业务代码,修改后需要刷新缓存数据。
综合来说就是性能和维护成本的权衡
穿透问题优化
缓存穿透是指查询一个根本不存在的数据,流程:1)缓存miss 2)redis miss,不写入缓存 3)返回空结果
缓存穿透导致请求要到存储层查询,使后端存储负载加大,导致宕机。现象:通过统计调用、缓存命中、存储层命中,出现大量存储层空命中
出现原因:业务代码或数据有问题;恶意攻击、爬虫等造成大量空命中
解决缓存穿透
- 缓存空对象:当存储层不命中后,将空对象保存到缓存,再次访问时返回。
存在问题:内存空间消耗(设置一个短时间的过期事件);存在数据不一致性(利用消息系统或者其他方式清除掉缓存的空对象) - 布隆过滤器拦截:在访问前将存在的key用布隆过滤器提前保存,作为第一层拦截(例如:新用户由于没有历史行为,导致业务中不存在推荐数据,为此将所有有推荐数据的用户做成一个过滤器,如果不存在id,则不访问存储层)可以用bitmap实现布隆过滤器
存在问题:数据命中不高、数据相对固定、实用性低(数据集较大的业务场景),代码维护较为复杂,但缓存空间占用少
//对比:缓存空对象适用数据频繁变化实时性高,布隆过滤器适用数据相对固定实时性低
无底洞问题优化
无底洞问题:facebook为满足业务要求添加新的内存缓存节点,发现性能没好转反而下降。
键值数据库采用哈希将key映射到节点上,造成key的分布与业务无关,扩容导致键值分布到更多的节点上,批量操作需要在不同节点上获取,分布式批量操作涉及多次网络时间
IO优化思路:
- 命令本身优化,优化sql语句
- 减少网络通信次数
- 降低接入成本,如客户端用长连接/连接池,NIO(通道缓冲区选择器)等
降低网络通信次数方法
单节点获取n个字符串方法:n次get(n 网络,n get)、1次pipeline get(1网络 n get)、1次mget(1网络 1mget)
集群中四种分布式批量操作方式:
1、串行命令:逐次执行n个get命令,n网络 + n get
2、串行IO:根据槽和节点的映射,根据node归档,执行node次网络和n次命令
3、并行IO:采用多线程优化串行IO,将网络时间变为O(1)
4、hash_tag:将多个key强制分配到1个节点,1次网络+n次命令
方案 | 优点 | 缺点 | 网络IO |
---|---|---|---|
串行命令 | 编程简单;keys少,可满足性能要求 | 大量keys请求延迟严重 | O(keys) |
串行IO | 编程简单;少量节点,性能满足要求 | 大量node延迟严重 | O(nodes) |
并行IO | 并行,延迟取决于最慢的节点 | 编程复杂;多线程,问题定位难 | O(max_slow(nodes)) |
hash_tag | 性能最高 | 业务维护成本较高;容易出现数据倾斜 | O(1) |
开发中根据优缺点进行分析,只有最合适的方案,没有最好的方案。
雪崩问题优化
缓存雪崩:当缓存不能提供服务时,大量请求到存储层,导致级联宕机。
1)保证缓存层服务器的高可用性:哨兵、集群
2)依赖隔离组件为后端限流并降级:比如当个性化推荐不可用时候,可以降级补充热点数据。项目中对各种数据库和外部接口进行隔离,运行在各自的线程池中,如用Hystrix进行隔离
3)提前演练:项目上线前,演练缓存层宕机等情况
热点key重建优化
采用缓存+过期时间的策略时,性能好,但当遇见key使一个热点key,并发量高,同时重建缓存不能短时间内完成(设计复杂sql,多次io,多个依赖等)时候,当缓存失效的瞬间,大量线程重建缓存,导致宕机。
目标:减少重建缓存的次数、数据尽可能一致、较少的潜在危险
- 互斥锁:只允许一个线程重建缓存,其他线程等待,重新从缓存获取数据 set ex nx返回值来判断是否有其他线程在重建
- 永不过期:不设置过期时间,设置逻辑过期时间,超时后使用单独的线程区构建,构建时候有数据不一致问题(构建一个互斥key来获取重建权)