echo编辑整理,欢迎转载,转载请声明文章来源。欢迎添加echo微信(微信号:t2421499075)交流学习。 百战不败,依不自称常胜,百败不颓,依能奋力前行。——这才是真正的堪称强大!!!


什么是Redis淘汰机制

Redis内存淘汰机制其实简单讲就是将过期的数据或者很久没有访问,或者在一段时间内很少有访问的数据进行删除。它分为很多中,有主动的数据淘汰,如:用户设定过期时间。有被动的淘汰,比如:Redis数据占满了内存,这个时候就会将过期的数据或者很久没有访问的数据删除掉。

Redis的淘汰有哪些类型

  • 定时过期:每个设置过期时间的key都需要创建一个定时器,到过期时间就会立即清除。该策略可以立即清除过期的数据,对内存很友好;但是会占用大量的CPU资源去处理过期的数据,从而影响缓存的响应时间和吞吐量。
  • 惰性过期:只有当访问一个key时,才会判断该key是否已过期,过期则清除。该策略可以最大化地节省CPU资源,却对内存非常不友好。极端情况可能出现大量的过期key没有再次被访问,从而不会被清除,占用大量内存。
  • 定期过期:每隔一定的时间,会扫描一定数量的数据库的expires字典中一定数量的key,并清除其中已过期的key。该策略是前两者的一个折中方案。通过调整定时扫描的时间间隔和每次扫描的限定耗时,可以在不同情况下使得CPU和内存资源达到最优的平衡效果。
    (expires字典会保存所有设置了过期时间的key的过期时间数据,其中,key是指向键空间中的某个键的指针,value是该键的毫秒精度的UNIX时间戳表示的过期时间。键空间是指该Redis集群中保存的所有键。)

定时过期的问题:缓存雪崩

很多人可能对这个词很熟悉,因为有很多的商业案例展示了血淋淋的教训,但是也有一部分人估计没有接触过。缓存雪崩其实也不是什么新词,它主要的引起原因就是指缓存中数据大批量的到过期时间。定时过期它本身就有一个缺点,那就是会占用大量的CPU资源,如果我们主动设置过期时间的键过多,在同一时间过期,很有可能就会造就我们Redis挂掉。但是这并不是最可怕的,雪崩不仅仅影响自己,还在我们的业务中影响数据库。因为我们很多业务设计都是在我们Redis的数据过期之后,从新查询数据库,但我们Redis主动批量过期的时候,会有大量的请求发送到我们的数据库,很有可能导致我们的数据库也挂掉。这才是最大的问题所在。

解决方案:

  • 缓存数据的过期时间设置随机,防止同一时间大量数据过期现象发生。
  • 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  • 设置热点数据永远不过期。

怎么设置定期过期最大使用内存

定期过期的最大内存设置在我们的redis.conf文件中,我们可以在该文件中看到这个配置:maxmemory <bytes>,但是这个配置一般都是注释掉的,也就是说安装之后如果我们没有主动对他进行配置,那么他就不会有默认大小值。对应的它不设置的情况下,那么它可以使用多少的内存空间呢?这个跟系统有关。如果说我们将Redis安装在32位的系统上,它的最大使用内存空间应该是在3G左右,如果是64位的系统,那么可以将我们的内存占满。当然如果真正占满内存,这是一件比较恶劣的事情,不仅仅访问Redis的时候,我们不能在进行写的操作,而且我们系统本身的其他操作也会受到限制。所以我们可以采用命令来对它进行一个初始化的设置

config set maxmemory 268435456

使用命令进行设置之后我们需要重启Redis才能生效。当然我们也可以直接找到Redis的安装目录,然后使用vi命令,直接更改配置文件中的对应的该内容,更改完之后,重启即可。

定期过期的淘汰策略

  • volatile-lru:根据LRU算法生成的过期时间来删除。
  • allkeys-lru:根据LRU算法删除任何key。
  • volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  • allkeys-lfu:从所有键中驱逐使用频率最少的键
  • volatile-random:根据过期设置来随机删除key。
  • allkeys-random:无差别随机删。
  • volatile-ttl:根据最近过期时间来删除(辅以TTL)
  • noeviction:谁也不删,直接在写操作时返回错误。

随机淘汰策略

随机找hash桶再次hash指定位置的dictEntry即可。就是在场景REDIS_MAXMEMORY_VOLATILE_RANDOM和REDIS_MAXMEMORY_ALLKEYS_LRU情况下的待淘汰的key。我们可以一观它的源码:

dictEntry *dictGetRandomKey(dict *d)
{
    dictEntry *he, *orighe;
    unsigned int h;
    int listlen, listele;

    if (dictSize(d) == 0) return NULL;

    if (dictIsRehashing(d)) _dictRehashStep(d);

    if (dictIsRehashing(d)) {
        // T = O(N)
        do {
            h = random() % (d->ht[0].size+d->ht[1].size);
            he = (h >= d->ht[0].size) ? d->ht[1].table[h - d->ht[0].size] :
                                      d->ht[0].table[h];
        } while(he == NULL);
    } else {
        // T = O(N)
        do {
            h = random() & d->ht[0].sizemask;
            he = d->ht[0].table[h];
        } while(he == NULL);
    }

    /* Now we found a non empty bucket, but it is a linked
     * list and we need to get a random element from the list.
     * The only sane way to do so is counting the elements and
     * select a random index. */
    listlen = 0;
    orighe = he;
    while(he) {
        he = he->next;
        listlen++;
    }
    listele = random() % listlen;
    he = orighe;
    // T = O(1)
    while(listele--) he = he->next;

    return he;
}

TTL时间淘汰

for (k = 0; k < server.maxmemory_samples; k++) {
    sds thiskey;
    long thisval;

    de = dictGetRandomKey(dict);
    thiskey = dictGetKey(de);
    thisval = (long) dictGetVal(de);

    /* Expire sooner (minor expire unix timestamp) is better
     * candidate for deletion */
    if (bestkey == NULL || thisval < bestval) {
        bestkey = thiskey;
        bestval = thisval;
    }
}

思考:

基于一个数据结构做缓存,怎么实现一个LRU算法?

做一个有底线的博客主

11-11 11:20