转载本文请联系冲爸洽谈公众号。众所周知,Redis是一个内存级别的KV数据库。所有操作都在内存中进行,数据库数据通过异步操作定时刷入硬盘存储。所以是纯内存操作,Redis性能非常好,每秒可以处理10万次以上的读写操作。虽然是内存数据库,但是它的数据可以持久化,支持丰富的数据类型。因为是内存级别的操作,受物理内存限制,所以Redis提供了删除过期键和内存淘汰的策略,这样在一定程度上可以避免达到内存上限。在这篇文章中,我们首先介绍如何设置一个key的过期时间,然后再介绍这些过期key的处理策略,然后分析redis在内存达到上限时采取的策略。Redis设置过期时间有四种方式:expirekeyseconds:设置key在N秒后过期;pexpirekeymilliseconds:设置key在n毫秒后过期;expirekeytimestamp:设置key在某个时间戳后过期(精确到秒)pexpireatkeymillisecondstimestamp:设置key在一个时间戳后过期(精确到毫秒)。我们来看看具体命令的用法。expire:N秒后过期127.0.0.1:6379>setkeyvalueOK127.0.0.1:6379>expirekey100(integer)1127.0.0.1:6379>ttlkey(integer)93命令全称TTL是timetolive,意思是密钥在N中,秒后过期。例如上面的结果93表示key在93s后过期。pexpire:N毫秒后过期127.0.0.1:6379>setkey2value2OK127.0.0.1:6379>pexpirekey2100000(integer)1127.0.0.1:6379>pttlkey2(integer)94524pexpirekey2100000表示key2设置为100000秒后过期.expireat:某个时间戳过期后(精确到秒)127.0.0.1:6379>setkey3value3OK127.0.0.1:6379>expireatkey31630644399(integer)1127.0.0.1:6379>ttlkey3(integer)67expiredKey31630644399(精确到秒)Expired使用TTL查询可以发现Key3会在67s后过期。在redis中可以使用time命令查询当前时间的时间戳(精确到秒),例如:127.0.0.1:6379>time1)"1630644526"2)"239640"pexpireat:在某个时间戳过期(精确到毫秒)127.0.0.1:6379>setkey4value4OK127.0.0.1:6379>pexpireatkey41630644499740(integer)1127.0.0.1:6379>pttlkey4(integer)3522其中pexpireatkey41630644499740表示key4的expiration3时间戳在449之后milliseconds(exacttimestamp44939064)使用TTL查询可以发现key4会在3522ms后过期。value为字符串时的过期设置value为字符串时直接操作过期时间有几种方式,如下:setkeyvalueexseconds:N秒后过期setkeyvalueexmilliseconds:设置key过期后n毫秒;setexkeysecondsvalue:设置指定key的值及其过期时间。如果密钥已经存在,SETEX命令将替换旧值。设置kv对在N秒后过期127.0.0.1:6379>setkvex100OK127.0.0.1:6379>ttlk(整数)97设置kv对在N毫秒后过期127.0.0.1:6379>setk2v2px100000OK127.0.0.1:6379>pttlk2(整数)92483使用setex设置127.0.0.1:6379>setexk3100v3OK127.0.0.1:6379>ttlk3(integer)91取消过期使用命令:persistkey去除key值的过期时间,如下代码所示:127.0.0.1:6379>ttlk3(integer)97127.0.0.1:6379>persistk3(integer)1127.0.0.1:6379>ttlk3(integer)-1可以看出,第一次使用TTL查询K3,会97秒后过期。使用persist命令查询K3的生命周期结果为-1,表示K3永不过期。过期策略Redis有三种删除过期键的策略:定时删除、定时删除和惰性删除。定时删除创建一个计时器。当key设置了过期时间,过期时间到了,定时器任务执行key删除操作。优点:节省内存,到时候删除,快速释放不必要的内存占用缺点:CPU压力大,此时CPU负载再高,都会占用CPU,影响响应时间redis服务器和指令吞吐量周期性删除默认情况下,redis每隔100ms随机选择一些有过期时间的key,检查是否过期,如果过期则删除。请注意,这是随机选择的。为什么是随机的?如果redis存储了几十万个key,每隔100ms遍历所有有过期时间的key,会给CPU带来很大的负载。优点:您可以通过限制执行删除操作的时间和频率来减少删除操作对CPU的影响。另外,定时删除也可以有效释放过期键占用的内存。缺点:难以确定执行删除操作的时间和频率。如果执行得太频繁,定时删除策略就变得和定时删除策略一样,对CPU不友好。如果执行的太少,和懒删除一样,过期键占用的内存不会及时释放。另外,最重要的是,在获取key的时候,如果某个key的过期时间到了,但是还没有进行定时删除,那么会返回这个key的值,这是业务报错不能容忍。懒删除和定时删除可能会导致很多过期的key到时候被删除。于是就有了惰性删除。如果你的过期key没有被定时删除删除,它还??停留在内存中,除非你的系统检查key,否则它会被redis删除。这称为惰性删除。expireIfNeeded(),检查数据是否过期,执行get时调用。优点:节省CPU性能,发现必须删除时才删除。缺点:内存压力大,出现长时间占用内存的数据。换句话说,懒删除就是用存储空间换取处理器性能。综合以上三种策略的优缺点,redis采用了一种折衷的删除策略,即采用常规删除+惰性删除策略。1.定时删除,使用定时器监控key,超时自动删除。虽然及时释放了内存,但是却消耗了大量的CPU资源。在大并发请求下,CPU应该花时间处理请求,而不是删除key,那么如果不采用这种策略,定时删除+惰性删除是如何实现的呢?2、周期性删除,redis默认每100ms检查一次,是否有过期的key,有过期的key就删除。需要注意的是,redis并不是每100ms检查一次所有的key,而是随机抽取检查一次(如果每100ms检查一次所有的key,redis就不会卡了)。因此,如果只采用定时删除策略,到时候很多键都不会被删除。3、懒删除,也就是说当你拿到一个key的时候,如果设置了过期时间,redis会检查这个key是否已经过期?如果过期,此时将被删除。但是,这种解决方案仍然有缺点:如果不定期删除密钥。那你没有及时请求key,也就是懒删除没有生效。这样redis的内存就会越来越高。那么就要采用内存淘汰机制。内存淘汰策略maxmemory用于指定Redis可以使用的最大内存。可以在redis.conf文件中设置,也可以在运行过程中通过CONFIGSET命令动态修改。例如设置100MB的内存限制,可以在redis.conf文件中配置如下:maxmemory100mb以上命令设置了redis内存的上限。当内存中的数据量达到设定的上限时,需要采取一定的淘汰策略。否则会影响redis的正常访问。为了更好地实现这一点,必须针对不同的应用场景提供不同的策略。下面,我们就介绍一下redis支持的几种内存淘汰策略。Redis提供了以下几种策略供用户选择,其中noeviction策略默认为。noeviction:当内存不足以容纳新写入的数据时,新的写操作会报错。allkeys-lru:当内存不足以容纳新写入的数据时,在key空间中,移除最近最少使用的key。allkeys-random:当内存不足以容纳新写入的数据时,从key空间中随机移除一个key。volatile-lru:当内存不足以容纳新写入的数据时,移除key空间中设置过期时间的最近最少使用的key。volatile-random:当内存不足以容纳新写入的数据时,从key空间中随机移除一个key,并设置过期时间。volatile-ttl:当内存不足以容纳新写入的数据时,在设置了过期时间的key空间中,先删除过期时间较早的key。需要注意的是,如果没有设置expirekey且不满足先决条件,那么volatile-lru、volatile-random和volatile-ttl策略的行为与noeviction(不删除)基本相同。Redis并没有使用完整的LRU算法。被自动驱逐的key不一定是最符合LRU特性的key。而是通过近似的LRU算法提取少量密钥样本,然后删除访问时间最早的密钥。驱逐算法从Redis3.0开始有了很大的优化,使用pool作为候选。这大大提高了算法的效率,更接近于真正的LRU算法。在Redis的LRU算法中,可以通过设置样本数来调优算法的准确率。maxmemory-samples5及以上是Redis的六种淘汰策略。关于这六种策略的使用,用户需要根据自己的实际需要选择合理的淘汰策略。下面读者可以根据自己的需要结合笔者的经验选择攻略。当部分数据被频繁访问而其余数据相对不频繁,或者数据使用频率不可预测时,设置allkeys-lru比较合适。如果所有的数据访问概率大致相等,可以选择allkeys-random。如果开发者需要通过设置不同的ttl来确定数据过期的先后顺序,此时可以选择volatile-ttl策略。如果希望有些数据可以长期保存,有些数据可以淘汰,最好选择volatile-lru或者volatile-random。由于设置expire会额外消耗内存,如果打算避免Redis内存浪费在这一项上,可以选择allkeys-lru策略,这样就可以不再设置过期时间,高效使用内存。从经验上来说,对于redis的操作要慎之又慎。不要放垃圾数据,及时清理无用数据。尝试设置密钥的过期时间。为时效键设置过期时间,使用redis自带的过期键清理策略,减少过期键的内存占用。同时,也可以减少业务上的麻烦,免去定期人工清洁的需要。单键不宜过大。获取这种key时网络传输延迟会比较大,需要分配的输出缓冲区也比较大。定期清洗也容易造成比较高的延迟。最好通过业务、数据压缩等方式对业务进行拆分,避免产生这么大的key。如果不同的业务共享一个业务,最好使用不同的逻辑db来分隔。这是因为Redis的过期键清理策略和强制淘汰策略会遍历每个db。将密钥分布在不同的数据库中有助于及时清理过期密钥。另外,针对不同的业务使用不同的dbs,也有助于排查问题,及时下线使用无用数据。
