「一发入魂」Redis五大数据类型实现原理

本章内容

Redis对象

Redis使用对象来表示数据库中的键和值。在数据库中创建一个键值对,至少需要创建两个对象:1)键对象;2)值对象。每个对象通过redisObject结构来表示。

redisObject结构:

typedef struct redisObject{
    // 类型
    unsigned type:4;
    // 编码
    unsigned encoding:4;
    // 指向底层数据结构的指针
    void *ptr;
    // 引用计数
    int refcount;
    // 记录最后一次被访问的时间
    unsigned lru:22;
} robj;

其中:

  • type:对象类型。
  • encoding:对象编码。
  • ptr:指向底层数据结构的指针。
  • refcount:引用计数。
  • lru:最后一次被访问的时间。

Redis对象属性

对象类型

对象类型(type属性)查看命令:

type key

对象类型种类:

对象编码与ptr指针

对象编码(encoding属性)查看命令:

object encoding key

对象编码用来指定数据结构,而ptr指针用来指向底层数据结构。

对象编码与数据结构对应关系:

在Redis对象中,每种对象类型至少对应两种不同的编码:

引用计数

1)内存回收

Redis对象中的refcount属性(引用计数)用于实现内存回收。

Redis采用引用计数法实现内存回收:

  • 1)创建一个新对象,refcount属性值初始化为1。
  • 2)对象被一个程序使用,refcount属性值加1。
  • 3)对象不再被一个程序使用,refcount属性值减1。
  • 4)当对象的引用计数值变为0时,对象所占用的内存就会被释放。

2)内存共享

Redis对象中的refcount属性除了能实现内存回收外,还可以用于内存共享。

如:执行命令set k1 100,创建一个key为k1、值为100的字符串对象,再执行命令set k2 100。

Redis的处理方式:

  • 1)将k2的值指针指向一个已经存在的值对象(k1的值:100);
  • 2)将被共享的值对象(redisObject)的refcount属性值+1。

注意:Redis的共享对象目前只支持整数值的字符串对象。

最后一次被访问时间

1)计算对象空转时长

Redis对象中的lru属性记录了对象最后一次被命令程序访问的时间。

对象空转时长查看命令:

127.0.0.1:6379> object idletime prices
(integer) 2863

对象空转时长=当前时间-lru属性记录的时间。

2)配合内存回收

Redis对象中的lru属性除了计算对象空转时长以外,还可以配合内存回收。

当Redis开启maxmemory选项,且内存回收算法为volatile-lru或allkeys—lru时,如果内存占用超过maxmemory参数设定的值,那么Redis会优先释放空转时间最长的对象。

字符串对象

字符串是Redis中最基本的数据类型,所有的key以及其他几种数据类型构成的元素都是字符串。字符串的长度不能超过512M。

字符串对象对应的编码有三种:

  • int:保存long类型的整数值。
  • embstr:保存长度小于44个字节的字符串。
  • raw:保存长度大于44个字节的字符串。

embstr和raw的区别:

  • 对象编码为embstr时,只需为对象分配一次内存空间(redisObject和sds连续存储)。
  • 对象编码为raw时,需要为对象分配两次内存空间(redisObject和sds各分配一次内存空间)。

如图所示:

embstr编码的优点:

  • 创建时少分配一次内存空间,删除时少释放一次内存空间。
  • 对象的所有数据连在一起,寻找方便。

embstr编码的缺点:

  • 当字符串长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配内存。因此,Redis中embstr为只读。

编码转换:

  • 1)使用int编码时,若保存的值不再是整数或者整数大小超过long的范围,Redis会自动将int编码转化为raw编码。
  • 2)使用embstr编码时,由于Redis中embstr为只读,若修改embstr编码对应的对象,Redis会先将embstr编码转化为raw编码,再进行修改。因此,只要修改embstr编码对应的对象,无论长度是否达到44个字节,修改后对象的编码都会变成raw。

列表对象

列表对象是一个按顺序排序、可以从头部或尾部添加元素的列表,它的底层是一个链表结构。

列表对象对应的编码有两种:

  • ziplist:压缩列表。
  • linkedlist:双端链表。

例如:创建一个key为java,value为hello world的列表。执行命令:

127.0.0.1:6379> rpush java hello world
(integer) 2
127.0.0.1:6379> lpop java 2
1) "hello"
2) "world"

如图所示:

当列表对象同时满足以下两个条件时,使用ziplist编码,否则使用linkedlist编码:

  • 1)列表中每个元素长度小于64字节。
  • 2)列表中保存元素个数小于512。

以上两个条件通过设置redis.conf配置文件中list-max-ziplist-value和list-max-ziplist-entries参数值进行配置。

哈希对象

哈希对象是一个键值对集合,键是一个字符串,值是一个健值对集合。

哈希对象对应的编码有两种:

  • ziplist:压缩列表。
  • hashtable:字典。

例如:创建一个key为java,value为name Json,age 27的哈希表。执行命令:

127.0.0.1:6379> hset java name Json
(integer) 1
127.0.0.1:6379> hset java age 27
(integer) 1
127.0.0.1:6379> hget java name
"Json"
127.0.0.1:6379> hget java age
"27"

如图所示:

当哈希对象同时满足以下两个条件时,使用ziplist编码,否则使用hashtable编码:

  • 1)哈希表中每个元素长度小于64字节。
  • 2)哈希表中保存元素个数小于512。

以上第二个条件通过设置redis.conf配置文件中set-max-intset-entries参数值进行配置。

集合对象

集合对象是一个字符串类型的无序集合。

集合与列表的区别:

  • 集合中的元素无序,不能通过索引来操作元素;列表中的元素有序,可以通过索引操作元素。
  • 集合中的元素不能有重复;列表中的元素可重复。

集合对象对应的编码有两种:

  • intset:整数集合。
  • hashtable:字典。

例如:创建一个key为people,value为Json 27 male的无序集合。执行命令:

127.0.0.1:6379> sadd nums 1 2 3
(integer) 3
127.0.0.1:6379> smembers nums
1) "1"
2) "2"
3) "3"
127.0.0.1:6379> 

如图所示:

例如:创建一个key为people,value为Json 27 male的无序集合。执行命令:

127.0.0.1:6379> sadd people Json 27 male
(integer) 3
127.0.0.1:6379> smembers people
1) "27"
2) "male"
3) "Json"
127.0.0.1:6379> 

如图所示:

当集合对象同时满足以下两个条件时,使用intset编码,否则使用hashtable编码:

  • 1)集合中所有元素都是整数。
  • 2)集合中所有元素数量小于512。

以上第二个条件通过设置redis.conf配置文件中set-max-intset-entries参数值进行配置。

有序集合对象

有序集合对象是一个字符串类型且有序的集合,每个元素关联一个分数(score),其中元素不允许重复,但分数可以重复。

有序集合对象对应的编码有两种:

  • ziplist:压缩列表。
  • skiplist:zset结构。

zset结构:

typedef struct zset{
    //跳跃表
    zskiplist *zsl;
    //字典
    dict *dice;
} zset;

其中:

  • 跳跃表节点的object属性保存元素,跳跃表节点的score属性保存元素关联的的分数。
  • 字典的键保存元素,字典的值保存元素关联的分数。

这两种数据结构通过指针来共享相同元素和分数,因此,不会产生重复元素。

有序集合单独使用跳跃表或字典实现,同时使用跳跃表和字典的原因:

  • 如果单独使用字典,虽然能以O(1)的时间复杂度查找元素关联的分数,但是字典是以无序的方式来保存元素,每次进行范围操作时都需要进行排序。
  • 如果单独使用跳跃表,虽然能执行范围操作,但是查找元素关联的分数时复杂度由O(1)变成了O(logN)。

例如:创建一个key为price,value为6.5 apple,5.0 banana的有序集合。执行命令:

127.0.0.1:6379> zadd prices 6.5 apple 5.0 banana
(integer) 2
127.0.0.1:6379> zrangebyscore prices 5.0 7.0
1) "banana"
2) "apple"

如图所示:

注意:有序集合中的元素会按照元素关联的分数从小到大进行存储。

当有序集合对象同时满足以下两个条件时,使用ziplist编码,否则使用skiplist编码:

  • 1)有序集合中每个元素长度小于64字节。
  • 2)有序集合中所有元素数量小于128。

以上两个条件通过设置redis.conf配置文件中zset-max-ziplist-entries和zset-max-ziplist-value参数值进行配置。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值