当我作为一个初学者使用redis的时候犯过两个常识性的小错误,同时两个错误让我更加了解redis,所以有句话说得很对:不能够把我击倒的,终将使我更加强大。接下来我们就一起来回顾下这两个错误。
这个问题是这样的,有次我在redis里面存储了大量的数据,但是在查询的时候,有些数据竟然查询不到,这些数据明明就是写进去了的啊,但是查询的时候为什么会消失了呢?
原因是我一次性写入的数据量太大了,导致数据是写进入了,但是有一部分被删掉了。因为redis是内存缓存,同时redis主要是基于内存来进行高性能、高并发的读写操作的,不是磁盘存储,内存缓存的存储是很珍贵的,比如说我们一台机器有5G的内存,那么你一次性存10G的数据进去,当然只能够保留一部分,删除一部分。
所以说,这是缓存的一个最基本的概念,数据是会过期的,要么是你自己设置个过期时间,要么是数据超过内存容量,redis自己给干掉。
当我明白了数据为什么会消失之后,我就尝试给数据设置过期时间,这里我遇到了第二个问题。我发现为啥好多数据明明应该过期了,查又查不到,但是又好像没删除,发现redis内存占用还是很高呢?
这个问题我先不回答,我们先来看看redis是怎么删除过期数据的,看完这个你就知道为什么过期数据,还保留在redis里面没有被删除。
首先我们来说说redis删除过期数据的方式:定期删除+惰性删除。
所谓定期删除,指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。
注意:这里可不是每隔100ms就遍历所有的设置过期时间的key,如果那样做,简直就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。假设redis里放了10万个key,都设置了过期时间,你每隔几百毫秒,就检查10万个key,那redis基本上就死了。
但是问题是,定期删除可能会导致很多过期key到了时间并没有被删除掉,那咋整呢?
所以这里要说到惰性删除了。惰性删除就是说,在你获取某个key的时候,redis会检查一下这个key是不是设置了过期时间,如果设置了过期时间,那么是否过期了?如果过期了,此时就会删除这个key,不会给你返回任何东西。并不是key到时间就被删除掉,而是你查询这个key的时候,redis再懒惰的检查一下。
通过上述两种手段结合起来,基本能保证过期的key一定会被干掉。简单的说,你的过期key,靠定期删除没有被删除掉,还停留在内存里,占用着你的内存,除非你的系统去查一下那个key,才会被redis给删除掉。
通过redis删除数据的方式就回答了上面我的那些数据为什么都过期了查不到,还占redis内存的问题。
但是实际上这还是有问题的,如果定期删除漏掉了很多过期key,然后你也没及时去查,也就是没走惰性删除,那么大量过期key就会堆积在内存里,导致redis内存块耗尽了,咋整?
这个就体现redis的另外一项策略机制:内存淘汰策略。
我们先来说说redis 3.0的6种内存淘汰策略:
volatile-lru:从设置过期时间的数据集中挑选出最近最少使用的数据淘汰。没有设置过期时间的key不会被淘汰,这样就可以在增加内存空间的同时保证需要持久化的数据不会丢失。
volatile-ttl:除了淘汰机制采用LRU,策略基本上与volatile-lru相似,从设置过期时间的数据集中挑选将要过期的数据淘汰,ttl值越大越优先被淘汰。
volatile-random:从已设置过期时间的数据集中任意选择数据淘汰。
allkeys-lru:从数据集中挑选最近最少使用的数据淘汰,该策略要淘汰的key面向的是全体key集合,而非过期的key集合。
allkeys-random:从数据集中选择任意数据淘汰,当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。
no-enviction:禁止驱逐数据,当内存不足以容纳新入数据时,新写入操作就会报错,请求可以继续进行,线上任务也不能持续进行,采用no-enviction策略可以保证数据不被丢失,这也是系统默认的一种淘汰策略。
上述是Redis的6种淘汰策略,关于使用这6种策略,开发者还需要根据自身系统特征,正确选择或修改驱逐。
如果在Redis中,数据有一部分访问频率较高,其余部分访问频率较低,或者无法预测数据的使用频率时,设置allkeys-lru是比较合适的。
如果所有数据访问概率大致相等时,可以选择allkeys-random。
如果研发者需要通过设置不同的ttl来判断数据过期的先后顺序,此时可以选择volatile-ttl策略。
如果希望一些数据能长期被保存,而一些数据可以被淘汰掉时,选择volatile-lru或volatile-random都是比较不错的。
由于设置expire会消耗额外的内存,如果计划避免Redis内存在此项上的浪费,可以选用allkeys-lru 策略,这样就可以不再设置过期时间,高效利用内存了。
区分不同的淘汰策略选择不同的key,Redis淘汰策略主要分为LRU淘汰、TTL淘汰、随机淘汰三种机制。
LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。
在服务器配置中保存了 lru 计数器 server.lrulock,会定时(redis 定时程序 serverCorn())更新,server.lrulock 的值是根据 server.unixtime 计算出来进行排序的,然后选择最近使用时间最久的数据进行删除。另外,从 struct redisObject 中可以发现,每一个 redis 对象都会设置相应的 lru。每一次访问数据,会更新对应redisObject.lru。
在Redis中,LRU算法是一个近似算法,默认情况下,Redis会随机挑选5个键,并从中选择一个最久未使用的key进行淘汰。在配置文件中,按maxmemory-samples选项进行配置,选项配置越大,消耗时间就越长,但结构也就越精准。
Redis 数据集数据结构中保存了键值对过期时间的表,即 redisDb.expires。与 LRU 数据淘汰机制类似,TTL 数据淘汰机制中会先从过期时间的表中随机挑选几个键值对,取出其中 ttl 值最大的键值对淘汰。同样,TTL淘汰策略并不是面向所有过期时间的表中最快过期的键值对,而只是随机挑选的几个键值对。
在随机淘汰的场景下获取待删除的键值对,随机找hash值再次hash指定位置的dictEntry即可。
Redis中的淘汰机制都是几近于算法实现的,主要从性能和可靠性上做平衡,所以并不是完全可靠,所以开发者们在充分了解Redis淘汰策略之后还应在平时多主动设置或更新key的expire时间,主动删除没有价值的数据,提升Redis整体性能和空间。