(1)redis的缓存失效策略和主键失效机制

作为缓存系统都要定期清理无效数据,就需要一个主键失效和淘汰策略.

在Redis当中,有生存期的key被称为volatile。在创建缓存时,要为给定的key设置生存期,当key过期的时候(生存期为0),它可能会被删除。

1.过期时间跟着key走,与值无关
在Redis中,带有过期时间的key被称为『易失的』(volatile)。 过期时间可以通过使用 DEL命令来删除整个key来移除,或者被 SET和 GETSET命令覆写(overwrite),这意味着,如果一个命令只是修改(alter)一个带过期时间的 key的值而不是用一个新的 key值来代替(replace)它的话,那么过期时间不会被改变。比如说,对一个 key执行 INCR命令,对一个列表进行 LPUSH命令,或者对一个哈希表执行 HSET命令,这类操作都不会修改 key本身的过期时间。

2.设置永久有效期
使用PERSIST命令可以清除超时,使其变成一个永久的key。

3.rename命令对有效期影响
如果key被RENAME命令修改,相关的超时时间会转移到新key上面。
如果key被RENAME命令修改,比如原来就存在Key_A,然后调用RENAME Key_B Key_A命令,这时不管原来Key_A是永久的还是设置为超时的,都会由Key_B的有效期状态覆盖。

4.刷新过期时间
对已经有过期时间的key执行EXPIRE操作,将会更新它的过期时间。
  
5.最大缓存配置
在 redis 中,允许用户设置最大使用内存大小server.maxmemory默认为0,没有指定最大缓存,如果有新的数据添加,超过最大内存,则会使redis崩溃,所以一定要设置。redis 内存数据集大小上升到一定大小的时候,就会实行数据淘汰策略。redis 提供 6种数据淘汰策略:

volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰

volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰

allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

no-enviction(驱逐):禁止驱逐数据
  注意这里的6种机制,volatile和allkeys规定了是对已设置过期时间的数据集淘汰数据还是从全部数据集淘汰数据,后面的lru、ttl以及random是三种不同的淘汰策略,再加上一种no-enviction永不回收的策略。
  
  使用策略规则:
  1、如果数据呈现幂律分布,也就是一部分数据访问频率高,一部分数据访问频率低,则使用allkeys-lru
  2、如果数据呈现平等分布,也就是所有的数据访问频率都相同,则使用allkeys-random
  三种数据淘汰策略:
  ttl和random比较容易理解,实现也会比较简单。主要是Lru最近最少使用淘汰策略,设计上会对key 按失效时间排序,然后取最先失效的key进行淘汰.

(2)Redis内部数据结构的实现

在Redis内部,有非常多的数据结构:sds(简单动态字符串),list,intset(整数集合),hash(字典),zskiplist(跳跃表),ziplist(压缩表)等。
1.simple dynamic string:一种简单动态字符串,而sdshdr封装了C原生字符串,并在其基础上,增加了一些功能,使之后对它的调用简单易懂可扩展。

sds 的具体实现结构adshdr,len表示sds的长度,alloc表示分配了的长度,这样方便扩展;free 空闲的长度;flags标志来判断使用哪个类型;buf[]则作为sds的真正储存数组。

2.list

Redis中,list的实现是一个双端链表,这样可以方便的获取其前后的节点值,方便之后对节点的查找;Redis通过list来对listNode进行持有,分别记录list的头尾节点list长度,可在O(n)的时间复杂度上进行查找;

redisTemplate set 覆盖 redis set key会覆盖吗_Redis


list在Redis中运用相当广泛,除了实现列表外,发布和订阅、慢查询、监视器等功能也使用了链表来获取,另外,Redis服务器还使用链表来持有 多个客户端的状态信息,以及用链表来构建客户端输出缓冲区。

3.dict
dictEntry是最核心的字典结构的节点结构,它保存了key和value的内容;另外,next指针是为了解决hash冲突,字典结构的hash冲突解决方法是拉链法,对于hashcode重复的节点以链表的形式存储。

dictht是节点dictEntry的持有者,将dictEntry结构串起来,table就是hash表,其实dictEntry *table[]这样的书写方式更容易理解些,size就是table数组的长度,used标志已有节点的数目。

dict是最外层的字典结构的接口形式,type标志类型,privdata标志其私有数据,dict持有两个dictht结构,一个用来存储数据,一个用来在rehash时使用,rehashidx标志是否正在rehash(因为Redis中rehash是一个渐近的过程,正在rehash的时候rehashidx记录rehash的阶段,否则为-1。

redisTemplate set 覆盖 redis set key会覆盖吗_数据集_02


注:因为dictEntry节点组成的链表没有子项链表尾部的指针,所以新加的节点一般都加在链表的头部,排在已有节点的前面,因为这样的时间复杂度为O(1)。

4.intset
当一个集合元素只有整数并且数量元素不多的时候,可以选择用整数集合来作为其底层实现。整数集合的数据结构如上所示。
重点说一下这个contents数组,它存储集合中的内容,并且以从小到大的顺序排列,并保证其没有重复的元素。虽然定义中其类型为int8_t,但具体编码方式还是取决于encoding。

当最大的数在相关范围之内是便会对应不同的数据类型,但是如果移除了这个最大取值,不会降级。int_6, int_32,int_64

分范围定义其类型有两个好处:提高其灵活性,节约内存。但是也增加了升级的开销。

在Redis 中,整数集合的应用范围不是很广,只在实现集合时用到。

5.zskiplist(跳跃表)
对于不了解跳跃表的可以去这个地方看看,了解一下:
http://blog.nosqlfan.com/html/3041.html
跳表是一种实现起来很简单,单层多指针的链表,它查找效率很高,堪比优化过的二叉平衡树,且比平衡树的实现,简单的多的多。

6.ziplist(压缩表)

ziplist是一个编码后的列表,是由一系列特殊编码的连续内存块组成的顺序型数据结构,特殊的设计使得内存操作非常有效率,此列表可以同时存放字符串和整数类型,列表可以在头尾各边支持推加和弹出操作在O(1)常量时间,但是,因为每次操作涉及到内存的重新分配释放,所以加大了操作的复杂性 。

zlentry是实际存储数据的节点。一个ziplist可以有多个zlentry节点,具体形式如下:

redisTemplate set 覆盖 redis set key会覆盖吗_Redis_03


redisTemplate set 覆盖 redis set key会覆盖吗_数据集_04


压缩表在Redis中的应用只存在于hash和list结构的实现中,为了在存储时节省内存。