redis笔记-2- 内存回收

认清事实

redis 作为 数据库 or 缓存的区别

作为缓存,存储 热点数据。需要考虑 redis里的数据 怎么能随着业务变化,只保留热点数据,因为 内存大小是有限的,是 redis 的瓶颈。

过期策略

redis 提供了 过期策略:
> 主动淘汰: 默认每隔100ms定时轮训找出一定比例 设置过期时间的key,做判断,如果过期则删除。
> 被动淘汰:被再次访问是,判断是否过期,过期则删除

基于 定期策略 和 被动删除,还是会存在 很多过期的key 在内存堆积,导致内存耗尽。 redis提供了淘汰策略。

淘汰策略

allkeys-lru: 当内存不足以容纳新写的入数据时,在 键空间中,移除最近最少使用的key(最常用)
volatile-lru:当内存不足以容纳新写的入数据时,在 设置过期时间的 建空间中,移除最近最少使用的的key
allkey-lfu: 在带有过期时间的键空间中 选择 最不常用的key 进行删除
volatile-lfu: 在所有的建中选择最不常用的key 进行删除
allkeys-random:当内存不足以容纳新写的入数据时,在 键空间中,随机移除 随机某个key
volatile-random:内存不足以容纳新写的入数据时,在设置过期的键空间中,随机移除某个key
volatile-ttl:内存不足以容纳新写的入数据时,在设置了过期时间的键空间中,有更咋骨气时间的key优先
noeviction: (默认策略)。当内存不足以容纳 新写入的数据时,新写入操作会报错。
注:如果没有符合前提条件的 key 被淘汰,那么 volatile-lru、volatile-random 、volatile-ttl 相当于 noeviction(不做内存回收).

LRU淘汰原理

如果基于传统 LRU 算法实现 Redis LRU 会有什么问题?

需要额外的数据结构存储,消耗内存。
Redis LRU 对传统的 LRU 算法进行了改良,通过**随机采样**来调整算法的精度。
如果淘汰策略是 LRU,则根据配置的采样值 maxmemory_samples(默认是 5 个),随机从数据库中选择 m 个 key, 淘汰其中热度最低的 key 对应的缓存数据。所以采样参数m配置的数值越大, 就越能精确的查找到待淘汰的缓存数据,但是也消耗更多的CPU计算,执行效率降低。

如何找出热度最低的数据

Redis 中所有对象结构都有一个 lru 字段, 且使用了 unsigned 的低 24 位,这个字段用来记录对象的热度。对象被创建时会记录 lru 值。在被访问的时候也会更新 lru 的值,但是不是获取系统当前的时间戳,而是设置为全局变量 server.lruclock 的值。
源码:server.h

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
    * LFU data (least significant 8 bits frequency
    * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

server.lruclock 的值怎么来的?

Redis 中 有 个 定 时 处 理 的 函 数 serverCron , 默 认 每 100 毫 秒 调 用 函 数,updateCachedTime 更新一次全局变量的 server.lruclock 的值,它记录的是当前 unix时间戳。

源码:server.c

void updateCachedTime(void) {
    time_t unixtime = time(NULL);
    atomicSet(server.unixtime,unixtime);
    server.mstime = mstime();
    struct tm tm;
    localtime_r(&server.unixtime,&tm);
    server.daylight_active = tm.tm_isdst;
}

为什么不获取精确的时间而是放在全局变量中?不会有延迟的问题吗?

函数 lookupKey 中更新数据的 lru 热度值时,就不用每次调用系统函数 time,可以提高执行效率。

当对象里面已经有了 LRU 字段的值,就可以评估对象的热度了。
函数 estimateObjectIdleTime 评估指定对象的 lru 热度,思想就是对象的 lru 值和全局的 server.lruclock 的差值越大(越久没有得到更新), 该对象热度越低。

源码 evict.c

/* Given an object returns the min number of milliseconds the object was never
* requested, using an approximated LRU algorithm. */
unsigned long long estimateObjectIdleTime(robj *o) {
    unsigned long long lruclock = LRU_CLOCK();
    if (lruclock >= o->lru) {
        return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;
    } else {
        return (lruclock + (LRU_CLOCK_MAX - o->lru)) *
        LRU_CLOCK_RESOLUTION;
    }
}

server.lruclock 只有 24 位,按秒为单位来表示才能存储 194 天。当超过 24bit 能表示的最大时间的时候,它会从头开始计算。

server.h

define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */
在这种情况下,可能会出现对象的 lru 大于 server.lruclock 的情况,如果这种情况出现那么就两个相加而不是相减来求最久的 key.

为什么不用常规的哈希表+双向链表的方式实现?需要额外的数据结构,消耗资源。而 Redis LRU 算法在 sample 为 10 的情况下,已经能接近传统 LRU 算法了。
除了消耗资源之外,传统 LRU 还有什么问题?
假设 A 在 10 秒内被访问了 5 次,而 B 在 10 秒内被访问了 3 次。因为 B 最后一次被访问的时间比 A 要晚,在同等的情况下,A 反而先被回收。

需要基于频率的淘汰机制

LFU淘汰原理

typedef struct redisObject {
    unsigned type:4;
    unsigned encoding:4;
    unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
    * LFU data (least significant 8 bits frequency
    * and most significant 16 bits access time). */
    int refcount;
    void *ptr;
} robj;

当这 24 bits 用作 LFU 时,其被分为两部分:
    高 16 位用来记录访问时间(单位为分钟,ldt,last decrement time)
    低 8 位用来记录访问频率,简称 counter(logc,logistic counter)
counter 是用基于概率的对数计数器实现的,8 位可以表示百万次的访问频率。对象被读写的时候,lfu 的值会被更新。

db.c——lookupKey

void updateLFU(robj *val) {
    unsigned long counter = LFUDecrAndReturn(val);
    counter = LFULogIncr(counter);
    val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}

增长的速率由,lfu-log-factor 越大,counter 增长的越慢.

redis.conf 配置文件中的设置:
```
lfu-log-factor 10   #增长的速率
lfu-decay-time 1    #衰减因子

```
如果计数器只会递增不会递减,也不能体现对象的热度。没有被访问的时候,计数器怎么递减呢?
减少的值由衰减因子 lfu-decay-time(分钟)来控制,如果值是 1 的话,N 分钟没有访问就要减少 N。

最大内存设置

redis.conf的参数配置:
`# maxmemory <byte>`
如果不设置 maxmemory 或者 设置为0, 64位系统不限制内存, 32位系统最多使用 3GB
动态修改配置:
`config set maxmemory 2GB`

参考: 咕泡学院 redis课程

posted @ 2020-03-29 21:59  小烽  阅读(227)  评论(0)    收藏  举报