redis
数据库每个键值对都是由对象组成。
数据库键总是一个字符串对象;
而数据库键的值则可以是字符串对象、列表对象、哈希对象、集合对象,有序集合对象。
字符串
redis
没有直接使用c
语言传统字符串,而是自己构建了一种名为简单动态字符串(SDS)
的抽象类型。主要是为了解决'\0'
的问题。
1 | struct sdshdr { |
这样做的好处(长度、结束都由len
判断,分配预留free
):
- 可以常数复杂度获取字符串长度
- 杜绝缓冲区溢出
- 减少修改字符串时带来的内存重分配次数
- 二进制安全(二进制安全就是输入任何字节都能正确处理, 即使包含零值字节)
- 兼容部分c字符串函数
链表
链表提供了高效和节点重排能力,以及顺序性的节点访问方式,并且可以通过增删节点来灵活地调整链表的长度。
链表在redis
中的应用非常广泛,比如列表键的底层实现之一就是链表。当一个列表键包含了数量比较多的元素,又或者列表中包含的元素都是比较长的字符串时,redis
就会使用链表为列表键的底层实现。
1 | typedef struct listNode { |
1 | typedef struct list { |
redis
的链表实现的特性:
- 双端, 获取某个节点的前置节点和后置节点的复杂度都是O(1)
- 无环:表头节点的prev指针和表尾节点的next指针都指向NULL,对链表的访问以NULL为终点
- 带表头表尾指针,获取俩复杂度为O(1)
- 带链表长度计数器,获取节点数量的复杂度为O(1)
- 多态:链表节点使用void*指针来保存节点值,并且可以通过list结构的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以用于保存各种不同类型的值。
字典
redis
的数据库就是使用字典来作为底层实现的,对数据库的增、删、查、改操作也是构建在对字典的操作之上的。
除了用来表示数据库之外,字典还是哈希键的底层实现之一,当一个哈希键包含的键值对比较多,又或者键值对中的元素都比较长的字符串时,redis就会使用字典作为哈希键的底层实现。
哈希
1 | typedef struct dictEntry { |
1 | typedef struct dictht { |
字典
1 | typedef struct dict { |
type
属性和privdata
属性是针对不同类型的键值对,为创建多态字典而设置的。ht
属性是一个包含两个项的数组,数组中的每个项都是一个dictht
哈希表,一般情况下,字典只使用ht[0]
哈希表,ht[1]
哈希表只会在对ht[0]
哈希表进行rehash
时使用。rehashidx
它记录了rehash
目前的进度,如果目前没有在进行rehash
,那么它的值为-1。
跳跃表(skiplist)
跳跃表是一种有序数据结构,它通过在每个节点中维持多个指向其他节点的指针,从而达到快速访问节点的目的。
跳跃表支持平均O(logN)
、最坏O(N)
复杂度的节点查找,还可以通过顺序性操作来批量处理节点。
在大部分情况下,跳跃表的效率可以和平衡树相媲美,并且因为跳跃表的实现比平衡树要来得更为简单,所以有不少程序都使用跳跃表来代替平衡树。
redis
使用跳跃表作为有序集合键的底层实现之一。
和链表、字曲等数据结构被广泛地应用在redis
内部不同,redis
只在两个地方用到了跳跃表,一个是实现有序集合键,另一个是在集群节点中用作内部数据结构。
1 | typedef struct zskiplistNode { |
1 | typedef struct zskiplist { |
整数集合
整数集合是集合键的底层实现之一,当一个集合只包含整数值元素,并且这个集合的元素数量不多时,redis
就会使用整数集合作为集合键的底层实现。
1 | typedef struct intset { |
压缩列表(为了节约内存)
压缩列表是列表键和哈希键的底层实现之一。
当一个列表键只包含少量列表项,并且每个列表要么就是小整数值,要么就是长度比较短的字符串,那么redis
就会使用压缩列表来做列表键的底层实现。
压缩列表是由一系列特殊编码的连续内存块组成的顺序型数据结构。一个压缩列表可以包含任意多个节点(entry)
,每个节点可以保存一个字节数组或者一个整数值。
1 | unsigned char *ziplistNew(void) { |
redis
并没有直接使用这些数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统。redis
还在这对象系统中构建了一个引用计数技术实现的内存回收机制。
1 | typedef struct redisObject { |
参考
《redis 设计与实现》
Redis 2.9源码