Redis中的数据结构

字符串

SDS(simple dynamic string):redis自己构建的一种简单动态字符串,而没有直接使用C语言的字符串(在redis中C语言的字符串仅用在无需对字符串修改的地方,例如日志打印),SDS以空字符'\0'结尾,且不占用len里,会额外占用1字节空间,即使用长度为N+1的空间来表示长度为N的字符串数据结构如下:

  • int len:记录buf数组中已使用字节的数量,等于SDS所保存字符串的长度
  • int free:记录buf数组中未使用字节的数量
  • char buf[]:字节数组,用于保存字符串

SDS与C语言字符串的差异:

  • SDS获取字符串长度的复杂度由O(N)降低到了O(1),有助于strlen命令的执行
  • 避免缓冲区溢出:字符串拼接操作时可按长度分配空间,避免空间不足多次操作空间分配
  • 减少修改字符串带来的内存重分配次数:数组长度不一定就是字符数量+1,可包含未使用空间即free属性记录,通过未使用空间,SDS实现了空间预分配和惰性空间释放两种优化策略:
    • 空间预分配:在字符增加时不仅分配所需的空间,还会额外分配多余空间(如修改后长度小于1MB时,多余分配的空间free等于len,否则分配1MB);如果空间足够时将直接使用free空间而不再执行重新分配
    • 惰性空间释放:释放用于优化SDS的字符串缩短操作,缩短字符串时不会释放多余的空间,为将来再次使用避免重分配
  • 二进制安全:C语言字符串必须符合某种编码,且除末尾字符外其他不允许空字符,SDS则可存储空字符,其靠len判断是否结束而非空字符
  • SDS兼容部分C语言字符函数:SDS遵循空字符结尾规则,可以使用部分C语言函数

链表

列表对象list的底层实现之一(另一实现的ziplist)(元素长度存在大于等于64字节的或元素数量大于等于512个)

链表和链表节点listNode的实现数据结构:双向链表

  • listNode prev:
  • listNode next;
  • void value;可存储各种不同类型的值

list结构为链表提供了头尾指针以及链表长度len以及部分函数:

  • listNode head;
  • listNode tail;
  • long len;
  • dup:节点值复制函数
  • free:节点值释放函数
  • matcch:节点值对比函数

特性:双端,无环,带表头表尾指针,带链表长度,多态(配合3个函数可以存放各种不同类型的值)

字典

字典,符号表,关联数组,映射,map,保存键值对,底层使用哈希表实现

哈希表dictht数据结构:

  • dictEntry[] table;哈希表数组,每个dictEntry保存一个键值对
  • long size;哈希表大小
  • long sizemask;掩码,用于计算索引值,总是等于size-1
  • long used;已有节点数量

哈希表节点dictEntry数据结构:

  • void key;键
  • union:值,可以是一个指针,一个uint64_t的整数或者int64_t的整数
    • void val;
    • uint64_t u64;
    • int64_t s64;
  • dictEntry next;下个节点,形成链表,可以将多个哈希值相同的连接在一起,解决哈希冲突问题

字典dict数据结构:

  • dictType type;类型特定函数,为创建多态字典而设置的
  • void privdata;私有数据
  • dictht ht[2];哈希表,包含两个,字典只使用ht[0],ht[1]只会在对ht[0]进行rehash时使用
  • int trehashidx;rehash索引,记录了rehash进度,当rehash不在进行时值为-1

hash冲突采用链地址法,通过hash表节点的next指针单链表解决hash冲突,为何不用其他数据结构,比如红黑树?

  • 链表性能更好,红黑树对插入和删除性能不好
  • 内存消耗更少
  • 实现简单
  • 单线程难以支持较高复杂度
  • 冲突概率不高

hash表扩容与收缩条件,满足任意一个时执行:

  • 服务器没有执行BGSAVE或BGREWRITEAOF并且hash表负载因子大于等于1
  • 服务器正在执行BGSAVE或BGREWRITEAOF并且hash表负载因子大于等于5
  • 负载因子小于0.1时开始执行收缩操作

负载因子:load_factor = ht[0].used / ht[0].size

渐进式rehash:避免数据量过于庞大时rehash导致的服务器暂停太久

  • rehash过程中字典会同时使用ht[0]和ht[1]两个hash表
  • rehash过程中字典的删除,查找,更新会在两个hash表上进行
  • 新增键值对的操作一律保存到ht[1]中,ht[0]的数量只会减少不会增加

跳跃表

跳跃表由zskiplistNode和zskiplist两个数据结构定义,跳跃表仅用于有序集合zset

zskiplist结构保存跳跃表节点信息,包括节点数量,头尾指针等,表头节点header仅记录了各个层(32,即最高层),不存储数据;zskiplistNode结构用于表示跳跃表节点

  • header:指向跳跃表的表头节点
  • tail:指向跳跃表的表尾节点
  • level:记录层数最大的那个节点的层数
  • length:跳跃表长度,也就是节点数量
  • 层:节点中用L1,L2,L3...标记各个层,每个层包括前进指针和跨度,1~32之间
  • 后退指针:指向前一个节点,用于从表尾指针往前遍历
  • score:分值,跳跃表按分值从小到大排列,分值相同时按成员对象的大小排序
  • 成员对象obj:各个节点保存的对象

整数集合

整数集合intset是集合set的实现之一

可以保存int16_t,int32_t,int64_t的整数值,且保证不会出现重复元素

intset数据结构:

  • encoding:编码方式int16_t,int32_t,int64_t,决定存储元素值的范围
  • length:集合包含的元素数量,即数组长度
  • contents[];保存元素的数组,从小到大排列,不含重复项

当向一个int16_t的数组中插入一个int64_t类型的数据时,数组中的其他所有值也会升级为int64_t,升级过程:

  1. 根据新的元素类型计算集合数组的空间大小,并为新元素分配空间
  2. 将底层所有元素换成新元素相同的类型,维持有序性不变
  3. 将新元素放到正确位置上(触发升级要么大于所有当前元素要么小于,所有新元素会放在0或者length-1的位置上)

升级的好处:

  • 提升灵活性:通常不会将不同类型的元素放到同一数据结构中,升级操作可适应多种类型的场景
  • 节约内存:按16,32,64三种类型存储,避免使用过大的类型浪费内存

整数集合不支持降级

压缩列表

ziplist是列表键hash键的底层实现之一(保存元素长度均小于64字节且数量小于512个时)

一个压缩列表包含多个节点,每个节点保存一个字节数组和一个整数值:

属性 类型 长度 用途
zlbytes uint32_t 4字节 记录整个压缩列表占用的字节数:在对压缩列表进行内存重分配或计算zlend的位置时使用
zltail uint32_t 4字节 记录压缩列表表尾节点距离列表起始地址有多少字节,通过这个偏移量可以计算出表尾节点地址
zllen uint16_t 2字节 记录列表节点数量,当值等于uint16_MAX时需要遍历整个列表才可以得到
entryX 列表节点 不定 列表的各个节点,节点长度有节点保存的内容决定
zlend uint8_t 1字节 特殊值0xff,用于标记压缩列表的末端

节点entry的构成:

  • previous:前一字节长度(1字节或5字节:长度小于2541字节,大于等于时5字节),可通过其计算出前一节点的起始地址,用于反向遍历
  • entry_length
  • encoding:记录了保存数据的类型及长度,可以是1字节,2字节或5字节,最高2位是字节数组编码,其他位为数组长度
  • content:保存节点值,可以是字节数组或者整数,类型及长度由节点encoding决定

字节数组:长度可以分三类:encoding记录

  • 长度小于等于63(2的6次方-1)字节的字节数组
  • 长度小于等于16383(2的14次方-1)字节的字节数组
  • 长度小于等于4294967295(2的32次方-1)字节的字节数组

整数值是以下6种长度之一:数组长度记录在encoding中

  • 4位长,介于0~12之间的无符号整数
  • 1字节长的有符号整数
  • 3字节长的有符号整数
  • int16_t类型整数
  • int32_t类型整数
  • int64_t类型整数

连锁更新:压缩列表中多个连续的长度在250~253字节之间的节点在头节点长度增加时,会导致previous属性值升级5字节而导致连锁更新反应

posted @   Abserver  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
历史上的今天:
2020-06-17
点击右上角即可分享
微信分享提示