一、缓存特征
一)命中率
命中数/(命中数+没有命中数)
二)最大元素(空间)
代表缓存中可以存放的最大元素的数量,一旦缓存中元素的数量超过这个值,或者缓存数据所占的空间超过了最大支持的空间,将会触发缓存清空策略。根据不同的场景,合理设置最大元素(空间)的值,在一定程度上可以提高缓存的命中率,从而更有效的使用缓存。
三)清空策略
FINO(先进先出)、LFU(最少使用)、LRU(最近最少使用)、过期时间、随机等
- FINO(先进先出):最先进入缓存的数据,在缓存空间不够或超出最大元素限制的情况下,会有限被清除掉,以腾出新的空间来接收新的数据。这种策略的算法主要是比较缓存元素的创建时间,在数据实时性较高的场景下,可以选择这种策略,优先保证最新策略可用。
- LFU(最少使用):无论元素是否过期,根据元素的被使用次数来判断,清除使用次数最少的元素来释放空间。算法主要是比较元素的命中次数,在保证高频数据有效的场景下,可以选择这种策略。
- LRU(最近最少使用):无论元素是否过期,根据元素最后一次被使用的时间戳,清除最远使用时间戳的元素,释放空间。算法主要是比较元素最近一次被获取的时间,在热点数据场景下,可以选择这种策略。
- 过期时间:根据过期时间判断,清理过期时间最长的元素,或清理最近要过期的元素。
二、缓存命中率影响因素
一)业务场景和业务需求
缓存王往往适合读多写少的场景。业务需求对实时性的要求,直接回影响到缓存的过期时间和更新策略。实时性要求越低,就越适合缓存。在相同key和相同请求数的情况下,缓存的时间越长,命中率越高。
二)缓存的设计(粒度和策略)
通常情况下,缓存的粒度越小,命中率越高。缓存的更新和命中策略也会影响缓存的命中率,当数据发生变化时,直接更新缓存的值会比移除缓存或使缓存过期的命中率更高。
三)缓存的容量和基础设施
缓存的容量有限,则容易引起缓存失效和被淘汰(目前多数的缓存框架或中间件都采用了LRU算法)。同时,缓存的技术选型也是至关重要的,比如采用应用内置的本地缓存就比较容易出现单机瓶颈,而采用分布式缓存则容易扩展。所以需要做好系统容量规划,并考虑是否可扩展。此外,不同的缓存框架或中间,其效率和稳定性也是存在差异的。
四)其他因素
当缓存节点发生故障时,需要避免缓存失效并最大程度降低影响,这种特殊情况也是架构师需要考虑的。业内比较典型的做法是通过一致性hash算法,或者通过节点冗余的方式。
误区:既然业务需求对数据时效性要求很高,而缓存时间又会影响到缓存命中率,那么系统就别用缓存了。其实这忽略了一个重要因素——并发。通常来讲,在相同缓存时间和key的情况下,并发越高,缓存的收益越高,即便缓存时间很短。
提高缓存命中率的方法
从架构师的角度,需要应用尽可能的通过缓存直接获取数据,并避免缓存失效。这也是比较考验架构师能力的,需要在业务需求、缓存力度、缓存策略、技术选型等各个方面去通盘考虑并做权衡。尽量的聚焦在高频访问且时效性要求不高的热点业务上,通过缓存预加热、增加存储容量、调整存储容量、调整缓存力度、更细缓存等手段来提高命中率。
三、缓存的使用与设计
参考:http://carlosfu.iteye.com/blog/2269678
一)缓存的受益与成本
1、受益
1、加速读写
- 通过缓存加速读写速度:CPU L1/L2/L3 Cache、Linux page Cache加速硬盘读写、浏览器缓存、Ehcache缓存数据库结果
2、降低后端负载
- 后端服务器通过前端缓存降低负载:业务端使用Redis降低后端MySQL负载等
2、成本
- 数据不一致:缓存层与数据层有时间窗口不一致,和更新策略有关
- 代码维护成本:多了一层缓存逻辑
- 运维成本:例如Redis cluster
3、使用场景
1、降低后端负载
- 对高消耗的SQL:join结果集/分组统计过缓存
2、加速请求相应
- 利用Redis/memcache优化IO响应时间
3、大量写合并为批量写
- 计数器先Redis累加再批量写DB
二)缓存的更新策略
1、缓存的更新策略主要有三种:
- LRU/LFU/FIFO算法剔除:例如maxmemory-plicy
- 超时剔除:例如expire(金融性信息不能使用)
- 主动更新:开发控制生命周期
2、三种缓存的更新策略对比
3、策略选择
- 低一致性:最大内存和淘汰策略
- 高一致性:超时剔除和主动更新相结合,最大内存和淘汰策略兜底。
三)缓存粒度控制
1、缓存粒度控制介绍
1、从MySQL获取用户信息
2、设置用户信息缓存
3、缓存粒度
全部属性
部分重要属性
2、缓存粒度控制-三个角度
- 通用性:全量属性更好
- 占用空间:部分属性更好
- 代码维护:表面上全量属性更好
综合考虑后,选择:大多数场景使用部分属性就行了。
四)缓存穿透优化
1、缓存穿透问题
大量请求不命中,请求访问缓存层没有数据,请求直接打到存储层,这样就失去缓存的意义(保护存储层),会加大存储层的压力
2、产生穿透的原因
- 业务带包自身问题:业务逻辑有问题
- 恶意攻击、爬虫等等:通过一些渠道知道业务url的规则或者不知道,强制访问触发到代码写的接口,接口触发到缓存,给的ID根本不在缓存范围内,必然出产生穿透问题
3、如何发现
- 业务的响应时间(时间更长(监控系统统计指标))
- 业务本身问题
- 相关指标:总调用数、缓存层命中数、存储层命中数
4、解决方法
1、缓存空对象
问题:
- 需要更多的键
- 缓存层和存储层数据“短期”不一致
2、布隆过滤器拦截(适用于更新频繁的数据)
五)无底洞问题优化
1、问题描述
Facebook的工作人员反应2010年已达到3000个memcached节点,储存数千G的缓存。
他们发现一个问题--memcached的连接效率下降了,于是添加memcached节点,添加完之后,并没有好转。称为“无底洞”现象
2、问题关键点
由于节点扩容,网络IO从o(1)变成了O(Node)
具体解释:键值数据库或者缓存系统,由于通常采用hash函数将key映射到对应的实例,造成key的分布与业务无关,但是由于数据量、访问量的需求,需要使用分布式后(无论是客户端一致性哈性、redis-cluster、codis),批量操作比如批量获取多个key(例如redis的mget操作),通常需要从不同实例获取key值,相比于单机批量操作只涉及到一次网络操作,分布式批量操作会涉及到多次网络io。
1、问题关键点
- 更多的机器!=更高的性能
- 批量接口需求(mget,mset等操作)
- 数据增长与水平扩展需求
2、无底洞带来的危害
- 客户端一次批量操作会涉及多次网络操作,也就意味着批量操作会随着实例的增多,耗时会不断增大。
- 服务端网络连接次数变多,对实例的性能也有一定影响。
3、优化IO的几种方法
- 命令本身优化:例如慢查询keys、hgetall bigkey
- 减少网络通信次数:mget n次网络变成node次
- 降低接入成本:例如客户端长连接:连接池、NIO等
4、总结
- 用一句通俗的话总结:更多的机器不代表更多的性能,所谓“无底洞”就是说投入越多不一定产出越多。
- 分布式又是不可以避免的,因为我们的网站访问量和数据量越来越大,一个实例根本坑不住,所以如何高效的在分布式缓存和存储批量获取数据是一个难点。
六)缓存雪崩优化
参考链接:http://carlosfu.iteye.com/blog/2249316
七)热点key重建优化
1、问题描述
热点key + 较长的重建时间
这个过程会有大量的线程做缓存重建,查询数据源
2、三个目标
- 减少重缓存的次数
- 数据尽可能一直
- 减少潜在危险
3、两个解决
1、互斥锁
已解决问题:不需要大量重建缓存
产生问题:有等待的过程,大量的线程夯住
2、永远不过期
- 缓存层面:没有设置过期时间(没有expire)
- 功能层面:为每个value添加逻辑过期时间,但发现超过逻辑过期时间后,会使用单独的线程去构建缓存
问题:会出现数据不一致的情况(没等待数据缓存完,就来取数据,会取到老的数据)
3、两种方案对比