本文正在参加「技术专题19期 漫谈数据库技术」活动
前言
后端用Redis做缓存的场景很常见,而Redis的淘汰策略也有很多。那么你知道该如何选择淘汰策略吗?本文给你答案。
Redis有哪些淘汰策略
首先,我们要设置Redis的缓存大小,命令:CONFIG SET maxmemory 4gb,或者在redis.conf配置文件直接设置:maxmemory 4gb。
建议将缓存容量设置为总数据量的15%-30%,平衡访问性能和内存空间开销。
redis的淘汰策略有以下几种:
| 淘汰策略 | 策略释义 |
|---|---|
| noeviction | 不会数据淘汰:缓存满了不再接收写请求,返回错误 |
| volatile-random | 对设置了过期时间的数据随机删除 |
| volatile-ttl | 根据过期时间的先后删除设置了过期时间的 |
| volatile-lru | 使用LRU算法筛选设置了过期时间的数据然后删除 |
| volatile-lfu(Redis 4.0 后) | 使用LFU算法筛选设置了过期时间的数据然后删除 |
| allkeys-lru | 使用LRU算法对所有数据筛选后删除 |
| allkeys-random | 随机对所有数据筛选后删除 |
| allkeys-lfu(Redis4.0后) | 使用LFU算法对所有数据筛选后删除 |
LRU算法
LRU算法,就是筛选最近最少使用的数据,优先淘汰这些数据。核心思想就是“若数据最近被访问过,将来被访问的几率也会更高”。
LRU的底层是hash表+双向链表。hash表保证了查询的时间复杂度是O(1),而双向链表则保证了节点插入及删除的时间复杂度也为O(1)。
结合下图,我们就能知道LRU是如何筛选数据的了。
- LRU GET操作:当访问数据时,若数据节点存在,LRU会将该数据节点移动到链表的头部,并返回节点的值;
- LRU PUT操作:当节点不存在时,会新增节点,并将节点放在链表的头部;若节点存在,这时会更新节点,并将节点移动到链表头部。
仔细看的话,可以发现LRU算法有缺陷:
- 它用链表来管理所有的缓存数据,数据量大的时候会带来很多空间开销。
- 数据在访问时,LRU需要将数据移动到链表头部。大量数据的链表移动会很耗时,在Redis服务器中自然会降低Redis的性能。
因此,Redis权衡了这些影响,没有照搬LRU算法来淘汰数据,而是简化了LRU算法运用之。
Redis的近似LRU算法
Redis的做法是,键值对数据结构RedisObject会有lru字段,它记录了每个数据的最近一次被访问的Redis时钟(可理解成时间戳)。然后在Redis决定要淘汰数据时,它的策略是这样的:
- 首次淘汰:随机取出最多N个数据放入待淘汰数据集合,源码里叫做evictionPoolEntry;
- 再次淘汰:再随机取出最多N个数据,只要数据的lru比待淘汰数据集合的任一数据的lru小,就将数据填充至待淘汰数据集合;
- 执行淘汰:取出待淘汰数据集合中lru最小的一条数据淘汰掉。
在Redis中我们可通过配置maxmemory-samples参数来控制上面描述的集合数据个数N。比如设置500个数据作为待淘汰数据集合,CONFIG SET maxmemory-samples 500。
从这种做法可以看出,Redis不需为所有数据维护一个链表,也不要每次有数据访问都去移动链表了,性能也就保证了下来。
小结
redis有8种淘汰策略,在我们选择Redis的淘汰策略时,可以参考下面方法:
- 无特殊需求,优先考虑allkeys-lru策略。这样可以将最近访问的数据留在缓存中,提升访问性能;
- 没有冷热数据区分,可使用allkeys-random策略。
- 若需求中有置顶的要求,可考虑volatile-lru策略。对置顶的数据不设置过期时间,这样就能保证不被淘汰掉。