从源码上聊聊Redis-String、List的结构实现

本文探讨Redis中的String和List数据类型,重点分析它们的底层结构、内存管理和优化策略。String采用sds实现,支持二进制安全,具有节省空间的embstr编码。List则基于双向链表或压缩列表实现,适用于消息队列场景,通过扩容和压缩列表避免内存浪费,但在多消费者场景下有局限。Redis 3.2引入QuickList和ListPack进一步优化性能和内存使用。
摘要由CSDN通过智能技术生成

数据类型

本文的数据类型只讲底层结构和部分机制,不讲具体的使用,使用的话自行bing,但是会提一些应用场景

 

string

观其面

 

kv结构,最大长度512M,底层数据结构为int和sds(简单动态字符串)

  • sds可以保存text数据和bin数据 使用len属性的值判断字符串是否结束,所有api都会以二进制形式处理sds存放在buf[]中的数据
  • 采用len属性记录字符串长度,复杂度为O(1)
  • sds api安全,append不会造成bof

字符串对象的内部encoding有3种

  • 如果是整数值,且能用long表示,那么对象会将整数值保存在ptr种,并将void*转会为long,设置encoding为int
  • 当类型是string的时候得分两种情况

 

其实吧,从这里就可以看出Redis对于字符字符串的管理还是挺不错的,你量少?行,那么给你分配连续的空间直接管理,量多?纳闷从新哪一个空间来管理。

好处可想而知:

  • embstrencoding将创建字符串对象所需的内存分配次数从 raw encoding的两次降低为一次;
  • 释放 embstrencoding的字符串对象同样只需要调用一次内存释放函数;
  • 因为embstrencoding的字符串对象的所有数据都保存在一块连续的内存里面可以更好的利用 CPU 缓存提升性能。

但是embstr也有缺点:

  • 如果字符串的长度增加需要重新分配内存时,整个redisObject和sds都需要重新分配空间,所以embstr-encoding的字符串对象实际上是只读的,redis没有为embstrencoding的字符串对象编写任何相应的修改程序。当我们对embstrencoding的字符串对象执行任何修改命令(例如append)时,程序会先将对象的encoding从embstr转换成raw,然后再执行修改命令。

应用场景

  • 缓存对象
  • 计数
  • 分布式锁
  • 共享Session

这里分布式锁不太建议用string来实现,虽然Redis在1.6之后支持了setnx原子操作,不需要使用Lua脚本,但是任然没有解决可重入性问题,具体的解决方案使用Map,感兴趣的话可以看下我之前的文章:Redis分布式锁深入分析 – Karos (wzl1.top)

究其身

下面是RedisObject数据结构

 

 

这里LRU和LFU是啥?补补os吧,上链接:操作系统-超20000字的“总结” – Karos (wzl1.top)

type:4是啥?这里表示对象类型,后面的4是位域(这里还是做下补充吧C 位域 | 菜鸟教程 (runoob.com))

在说sds之前,我们先来讨论一下C语言字符串的缺点吧:

  • 获取字符串长度的时间复杂度为 O(N);
  • 字符串的结尾是以 “\0” 字符标识,字符串里面不能包含有 “\0” 字符,因此不能保存二进制数据;
  • 字符串操作函数不高效且不安全,比如有缓冲区溢出的风险,有可能会造成程序运行终止;

前两点,不说,就说最后一个吧,虽然能够接受,还是要解释一下。

在C语言中,对字符串的各个操作都要通过函数进行,并且每个可修改字符串在定义的时候就已经固定了大小(感觉说的有点问题,好久没玩儿C了,一直用的都是C++的string,hhh~)

举个常见的例子,字符串拼接函数

arduino复制代码 char *strcat(char *dest, const char* src);

如果dest预留的长度小于src的长度,那么很有可能产生overflow,那么Redis的增强我们来看看吧

 

现在来对sds数据结构仔细说说吧,仔细看其实就是那几个玩意儿:

  • len,记录了字符串长度。这样获取字符串长度的时候,只需要返回这个成员变量值就行,时间复杂度只需要 O(1)。
  • alloc,分配给字符数组的空间长度。这样在修改字符串的时候,可以通过 alloc - len 计算出剩余的空间大小,可以用来判断空间是否满足修改需求,如果不满足的话,就会自动将 SDS 的空间扩展至执行修改所需的大小,然后才执行实际的修改操作,所以使用 SDS 既不需要手动修改 SDS 的空间大小,也不会出现前面所说的缓冲区溢出的问题。
  • flags,用来表示不同类型的 SDS。一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64,后面在说明区别之处。
  • buf[],字符数组,用来保存实际数据。不仅可以保存字符串,也可以保存二进制数据。
  • 因为 SDS 不需要用 “\0” 字符来标识字符串结尾了,而是有个专门的 len 成员变量来记录长度,所以可存储包含 “\0” 的数据。但是 SDS 为了兼容部分 C 语言标准库的函数, SDS 字符串结尾还是会加上 “\0” 字符。因此, SDS 的 API 都是以处理二进制的方式来处理 SDS 存放在 buf[] 里的数据,程序不会对其中的数据做任何限制,数据写入的时候时什么样的,它被读取时就是什么样的。通过使用二进制安全的 SDS,而不是 C 字符串,使得 Redis 不仅可以保存文本数据,也可以保存任意格式的二进制数据。

节省空间

SDS 结构中有个 flags 成员变量,表示的是 SDS 类型。

Redis 一共设计了 5 种类型,分别是 sdshdr5、sdshdr8、sdshdr16、sdshdr32 和 sdshdr64。

这 5 种类型的主要区别就在于,它们数据结构中的 len 和 alloc 成员变量的数据类型不同

为什么这样设计?

主要是为了能灵活保存不同大小的字符串,从而有效节省内存空间。比如,在保存小字符串时,结构头占用空间也比较少。

冷知识,这里还用了 __attribute__ ((packed))取消结构体在编译过程中的优化对齐,按照实际占用字

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值