Redis中内存淘汰算法实现

栏目: 数据库 · 发布时间: 5年前

内容简介:Redis的当内存达到Redis 3.0中已有淘汰机制:

Redis的 maxmemory 支持的内存淘汰机制使得其成为一种有效的缓存方案,成为 memcached 的有效替代方案。

当内存达到 maxmemory 后,Redis会按照 maxmemory-policy 启动淘汰策略。

Redis 3.0中已有淘汰机制:

  • noeviction
  • allkeys-lru
  • volatile-lru
  • allkeys-random
  • volatile-random
  • volatile-ttl
maxmemory-policy 含义 特性
noeviction 不淘汰 内存超限后写命令会返回错误(如OOM, del命令除外)
allkeys-lru 所有key的LRU机制 在所有key中按照最近最少使用LRU原则剔除key,释放空间
volatile-lru 易失key的LRU 仅以设置过期时间key范围内的LRU(如均为设置过期时间,则不会淘汰)
allkeys-random 所有key随机淘汰 一视同仁,随机
volatile-random 易失Key的随机 仅设置过期时间key范围内的随机
volatile-ttl 易失key的TTL淘汰 按最小TTL的key优先淘汰

其中LRU(less recently used)经典淘汰算法在 Redis 实现中有一定优化设计,来保证内存占用与实际效果的平衡,这也体现了工程应用是空间与时间的平衡性。

PS:值得注意的,在主从复制模式Replication下,从节点达到maxmemory时不会有任何异常日志信息,但现象为增量数据无法同步至从节点。

Redis 3.0中近似LRU算法

Redis中LRU是近似LRU实现,并不能取出理想LRU理论中最佳淘汰Key,而是通过从小部分采样后的样本中淘汰局部LRU键。

Redis 3.0中近似LRU算法通过增加待淘汰元素池的方式进一步优化,最终实现与精确LRU非常接近的表现。

精确LRU会占用较大内存记录历史状态,而近似LRU则用较小内存支出实现近似效果。

以下是理论LRU和近似LRU的效果对比:

Redis中内存淘汰算法实现

  • 按时间顺序接入不同键,此时最早写入也就是最佳淘汰键
  • 浅灰色区域:被淘汰的键
  • 灰色区域:未被淘汰的键
  • 绿色区域:新增写入的键

总结图中展示规律,

Theoretical LRU
Approx LRU Redis 3.0 10 samples
Approx LRU Redis 2.8 5 samples
Approx LRU Redis 3.0 5 samples

结论:

  • 通过图4和图3对比:得出 相同采样值下,3.0比2.8的LRU淘汰机制更接近理论LRU
  • 通过图4和图2对比:得出 增加采样值,在3.0中将进一步改善LRU淘汰效果逼近理论LRU
  • 对比图2和图1:在3.0中采样值为10时,效果非常接近理论LRU

采样值设置通过 maxmemory-samples 指定,可通过 CONFIG SET maxmemory-samples <count> 动态设置,也可启动配置中指定 maxmemory-samples <count>

源码解析

int freeMemoryIfNeeded(void){
    while (mem_freed < mem_tofree) {
        if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)
        return REDIS_ERR; /* We need to free memory, but policy forbids. */

        if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
                server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)
            {......}
        /* volatile-random and allkeys-random policy */
        if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||
                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)
            {......}
        /* volatile-lru and allkeys-lru policy */
        else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||
            server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)
        {
            // 淘汰池函数
            evictionPoolPopulate(dict, db->dict, db->eviction_pool);
            while(bestkey == NULL) {
                evictionPoolPopulate(dict, db->dict, db->eviction_pool);
                // 从后向前逐一淘汰
                for (k = REDIS_EVICTION_POOL_SIZE-1; k >= 0; k--) {
                    if (pool[k].key == NULL) continue;
                    de = dictFind(dict,pool[k].key); // 定位目标

                    /* Remove the entry from the pool. */
                    sdsfree(pool[k].key);
                    /* Shift all elements on its right to left. */
                    memmove(pool+k,pool+k+1,
                        sizeof(pool[0])*(REDIS_EVICTION_POOL_SIZE-k-1));
                    /* Clear the element on the right which is empty
                     * since we shifted one position to the left.  */
                    pool[REDIS_EVICTION_POOL_SIZE-1].key = NULL;
                    pool[REDIS_EVICTION_POOL_SIZE-1].idle = 0;

                    /* If the key exists, is our pick. Otherwise it is
                     * a ghost and we need to try the next element. */
                    if (de) {
                        bestkey = dictGetKey(de); // 确定删除键
                        break;
                    } else {
                        /* Ghost... */
                        continue;
                    }
                }
            }
        }
        /* volatile-ttl */
        else if (server.maxmemory_policy == EDIS_MAXMEMORY_VOLATILE_TTL) {......}

        // 最终选定待删除键bestkey
        if (bestkey) {
            long long delta;
            robj *keyobj = createStringObject(bestkey,sdslenbestkey)); // 目标对象
            propagateExpire(db,keyobj);
            latencyStartMonitor(eviction_latency); // 延迟监控开始
            dbDelete(db,keyobj); // 从db删除对象
            latencyEndMonitor(eviction_latency);// 延迟监控结束
            latencyAddSampleIfNeeded("eviction-del",iction_latency); // 延迟采样
            latencyRemoveNestedEvent(latency,eviction_latency);
            delta -= (long long) zmalloc_used_memory();
            mem_freed += delta; // 释放内存计数
            server.stat_evictedkeys++; // 淘汰key计数,info中可见
            notifyKeyspaceEvent(REDIS_NOTIFY_EVICTED, "evicted", keyobj, db->id); // 事件通知
            decrRefCount(keyobj); // 引用计数更新
            keys_freed++;
            // 避免删除较多键导致的主从延迟,在循环内同步
            if (slaves) flushSlavesOutputBuffers();
        }
    }
}

Redis 4.0中新的LFU算法

从Redis4.0开始,新增 LFU淘汰机制 ,提供更好缓存命中率。LFU(Least Frequently Used)通过记录键使用频率来定位最可能淘汰的键。

对比LRU与LFU的差别:

  • 在LRU中,某个键很少被访问,但在刚刚被访问后其被淘汰概率很低,从而出现这类异常持续存在的缓存;相对的,其他可能被访问的键会被淘汰
  • 而LFU中,按访问频次淘汰最少被访问的键

Redis 4.0中新增两种LFU淘汰机制:

  • volatile-lfu:设置过期时间的键按LFU淘汰
  • allkeys-lfu:所有键按LFU淘汰

LFU使用 Morris counters 计数器占用少量位数来评估每个对象的访问频率,并随时间更新计数器。此机制实现与近似LRU中采样类似。但与LRU不同,LFU提供明确参数来指定计数更新频率。

  • lfu-log-factor:0-255之间,饱和因子,值越小代表饱和速度越快
  • lfu-decay-time:衰减周期,单位分钟,计数器衰减的分钟数

The decay time is the obvious one, it is the amount of minutes a counter should be decayed, when sampled and found to be older than that value. A special value of 0 means: always decay the counter every time is scanned, and is rarely useful.

The counter logarithm factor changes how many hits are needed in order to saturate the frequency counter, which is just in the range 0-255. The higher the factor, the more accesses are needed in order to reach the maximum. The lower the factor, the better is the resolution of the counter for low accesses

+--------+------------+------------+------------+------------+------------+
| factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |
+--------+------------+------------+------------+------------+------------+
| 0      | 104        | 255        | 255        | 255        | 255        |
+--------+------------+------------+------------+------------+------------+
| 1      | 18         | 49         | 255        | 255        | 255        |
+--------+------------+------------+------------+------------+------------+
| 10     | 10         | 18         | 142        | 255        | 255        |
+--------+------------+------------+------------+------------+------------+
| 100    | 8          | 11         | 49         | 143        | 255        |
+--------+------------+------------+------------+------------+------------+

这两个因子形成一种平衡,通过少量访问 VS 多次访问 的评价标准最终形成对键重要性的评判。


以上所述就是小编给大家介绍的《Redis中内存淘汰算法实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

逆流而上

逆流而上

阿里巴巴集团成长集编委会 / 电子工业出版社 / 2017-11 / 59.00

本书是阿里巴巴集团荣耀背后的技术血泪史。全书通过分享业务运行过程中各个领域发生的典型“踩坑”案例,帮助大家快速提升自我及团队协作,学习到宝贵的处理经验及实践方案,为互联网生产系统的稳定共同努力。从基础架构、中间件、数据库、云计算、大数据等技术领域中不断积累经验,颠覆技术瓶颈,不断创新以适应不断增长的需求。 本书主要面向互联网技术从业人员和在校师生,使读者能够通过此书基本了解阿里在各技术领域的能力,......一起来看看 《逆流而上》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具