一、缓存的几种更新策略
从下面的表格看,缓存的更新策略大致分为三种,本文将从一致性和维护成本两个方面对于三种缓存更新策略进行简要说明,因为这些东西比较理论和抽象,如哪里说得不对,欢迎拍砖。
注:
(1) 一致性:缓存和真实数据源(例如mysql, hbase, elasticsearch等等)是否存在一段时间数据的不一致。
(2) 维护成本: 开发人员的开发和维护成本。
策略 | 一致性 | 维护成本 |
LRU/LIRS/FIFO算法剔除 | 最差 | 低 |
超时剔除 | 较差 | 较低 |
主动更新 | 强 | 高 |
二、LRU/LFU/FIFO算法剔除
1. 使用场景:
通常用于缓存使用量超过了预设的最大值时候(缓存空间不够),如何对现有的数据进行清理。例如FIFO会把最新进入缓存的数据清理出去, LRU会把最近最少使用的数据清理掉。
例如:Memcache使用的是LRU,具体Memcache如何实现的,这里就不在赘述了,网上资料多的是。
例如:Redis使用maxmemory-policy这个配置作为内存最大值后对于数据的更新策略。
配置名 | 含义 | 默认值 |
maxmemory | 最大可用内存 | 不使用该配置,也就对内存使用无限制 |
maxmemory-policy | 内存不够时,淘汰策略 | volatile-lru |
- volatile-lru -> 用lru算法删除过期的键值
- allkeys-lru -> 用lru算法删除所有键值
- volatile-random -> 随机删除过期的键值
- allkeys-random -> 随机删除任何键值
- volatile-ttl -> 删除最近要到期的键值
- noeviction -> 不删除键,只返回一个错误
2. 常用算法:
这里不再赘述,常用的算法有如下几种:
FIFO[first in first out]
LFU[Less Frequently Used]
LRU[Least Recently used]
3. 一致性
可以想象,要清理哪些数据,不是由开发者决定(只能决定大致方向:策略算法),数据的一致性是最差的。
4. 维护成本
这些算法不需要开发者自己来实现,通常只需要配置最大maxmemory和对应的策略即可。
开发者只需要有这个东西,知道是什么意思,选择自己需要的算法,算法的实现是由缓存服务器实现的。
三、超时剔除
1. 使用场景:
就是我们通常做的缓存数据过期时间设置,例如redis和memcache都提供了expire这样的API,来设置K-V的过期时间。
一般来说业务可以容忍一段时间内(例如一个小时),缓存数据和真实数据(例如:mysql, hbase等等)数据不一致(一般来说,缓存可以提高访问速度降低后端负载),那么我们可以对一个数据设置一定时间的过期时间,在数据过期后,再从真实数据源获取数据,重新放到缓存中,继续设置过期时间。
例如: 一个视频的描述信息,我们可以容忍一个小时内数据不一致,但是涉及到钱的方面,如果不一致可想而知。
2. 一致性:
一段时间内(取决于过期时间)存在数据一致性问题,即缓存数据和真实数据源数据不一致。
3. 维护成本
用户的维护成本不是很高,只需要设置expire过期时间即可(前提是你的业务允许这段时间可能发生的数据不一致)。
四、主动更新
1. 使用背景:
业务对于数据的一致性要求很高,需要在真实数据更新后,立即更新缓存数据。
具体做法:例如可以利用消息系统或者其他方式(比如数据库触发器,或者其他数据源的listener机制来完成)通知缓存更新。
2. 一致性:
可以想象一致性最高(几乎接近强一致),但是有个问题:如果主动更新发生了问题,那么这条数据很可能很长时间不会更新了(所以可以结合超时剔除一起使用,下面最佳实践会说到)
3. 维护成本:
相当高,用户需要自己来完成更新(需要一定量的代码,从某种程度上加大了系统的复杂性),需要自己检查数据是否真的更新了之类的工作。
五、最佳实践
其实最佳实践就是组合使用:
1. 一般来说我们都需要配置超过最大缓存后的更新策略(例如:LRU)以及最大内存,这样可以保证系统可以继续运行(例如redis可能存在OOM问题)(极端情况下除外,数据一致性要求极高)。
2. 一般来说我们需要把超时剔除和主动更新组合使用,那样即使主动更新出了问题,也能保证过期时间后,缓存就被清除了(不至于永远都是脏数据)。