1,String: raw,int , embstr
2,hash: hashtable,ziplist
3:list:linkedlist,ziplist
4:set:hashtable,intset
5:zset:skiplist,ziplist
设计的好处:
1:可以改进内部编码,对外的数据结构和命令没有影响
2:多种内部编码实现可以在不同场景下发挥各自优势,例如 ziplist 比较节省内存,但列表元素多时性能会下降,这时Redis会根据配置选项将列表类型内存实现转换为linkedlist
String
Redis 是使用 SDS(“简单动态字符串”)这个结构体来存储字符串,
1:代码里定义了 5种 SDS结构体:
1:struct __attribute__ ((__packed__)) sdshdr5 { unsigned char flags; char buf[]; };
2:sdshdr8
3:sdshdr16
4:sdshdr32
5:sdshdr64
2:函数里主要字段:
len:字符串的长度(实际使用的长度)
alloc:分配内存的大小
flags:标志位,低三位表示类型,其余五位未使用
buf:字符数组
3:编码
int 编码:保存long 型的64位有符号整数,Redis会将键值转化为 long型来进行存储,对应 OBJ_ENCODING_INT 编码类型
embstr 编码:保存长度小于44字节的字符串 (OBJ_ENCODING_EMBSTR)
raw 编码:保存长度大于44字节的字符串(OBJ_ENCODING_RAW)
ziplist
1:ziplist结构
zlbytes:表示ziplist占用字节数,在执行resize操作时使用
zltail:表示最后节点的偏移量,也是避免了整体遍历list
zllen:表示ziplist节点个数(节点数超过65535,zllen字段值无效,需要遍历才能得到真实数量)
zlend:表示ziplist结束的标识符
2:ziplist节点数据结构(抽象)
每个压缩列表节点都由previous_entry_length、encoding、content三个部分组成(不是指实际结构体的字段)
previous_entry_length:前一个节点的长度,用来由后向前遍历,根据前一个节点的长度,可能需要一个或五个字节。
encoding:记录节点保存的数据类型和数据长度。
content:节点保存的数据内容。
ziplist采用了一段连续的内存来存储数据,相比hashtable减少了内存碎片,和指针的内存占用。而且当节点较少时,ziplist更容易被加载到CPU缓存中。
3:ziplist的优点
内存占用少 容易被加载到CPU缓存
结构紧凑 减少内存碎片
4:ziplist的缺点
连锁更新(头部插入 或 删除,对头部的操作主要是在用ziplist保存list类型对象时发送,保存hash对象不需要在头部更新。)
每次插入或修改引发的realloc操作会有更大的概率造成内存拷贝,从而降低性能。
一旦发生内存拷贝,内存拷贝的成本也相应增加,因为要拷贝更大的一块数据。
当ziplist数据项过多的时候,在它上面查找指定的数据项就会性能变得很低,因为ziplist上的查找需要进行遍历。
查询复杂度从O(1)变成O(N)(保存hash对象时),
5:ziplist配置
redis.conf的配置:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
当 哈希类型 元素个数 小于 hash-max-ziplist-entries 配置(默认 512 个)、同时 所有值 都 小于 hash-max-ziplist-value 配置(默认 64 字节)时,Redis 会使用 ziplist 作为 哈希 的 内部实现,ziplist 使用更加 紧凑的结构 实现多个元素的 连续存储,所以在 节省内存 方面比 hashtable 更加优秀。
Redis的hash之所以这样设计,是因为当ziplist变得很大的时候,它有如下几个缺点:
skiplist
1:skipList跳跃表
skipList使用多层链表的模式,某个节点会分配随机层数(level)记录指向往单个或多个后节点的指针,插入操作只需要修改插入节点前后的指针,而不需要对很多节点都进行调整。降低了时间复杂度,平均为O(logN),最坏复杂度为O(N),其性能一般情况下可以与平衡树相媲美。
2:概率
产生越高的节点层数,概率越低。定量的分析如下:
节点层数至少为1。而大于1的节点层数,满足一个概率分布。
节点层数恰好等于1的概率为1-p。
节点层数大于等于2的概率为p,而节点层数恰好等于2的概率为p(1-p)。
节点层数大于等于3的概率为p2,而节点层数恰好等于3的概率为p2(1-p)。
节点层数大于等于4的概率为p3,而节点层数恰好等于4的概率为p3(1-p)。
3:skiplist与平衡树、哈希表的比较
- skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
- 在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
- 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
- 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
- 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
- 从算法实现难度上来比较,skiplist比平衡树要简单得多。