Redis常用数据结构及内部编码

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与平衡树、哈希表的比较

  1. skiplist和各种平衡树(如AVL、红黑树等)的元素是有序排列的,而哈希表不是有序的。因此,在哈希表上只能做单个key的查找,不适宜做范围查找。所谓范围查找,指的是查找那些大小在指定的两个值之间的所有节点。
  2. 在做范围查找的时候,平衡树比skiplist操作要复杂。在平衡树上,我们找到指定范围的小值之后,还需要以中序遍历的顺序继续寻找其它不超过大值的节点。如果不对平衡树进行一定的改造,这里的中序遍历并不容易实现。而在skiplist上进行范围查找就非常简单,只需要在找到小值之后,对第1层链表进行若干步的遍历就可以实现。
  3. 平衡树的插入和删除操作可能引发子树的调整,逻辑复杂,而skiplist的插入和删除只需要修改相邻节点的指针,操作简单又快速。
  4. 从内存占用上来说,skiplist比平衡树更灵活一些。一般来说,平衡树每个节点包含2个指针(分别指向左右子树),而skiplist每个节点包含的指针数目平均为1/(1-p),具体取决于参数p的大小。如果像Redis里的实现一样,取p=1/4,那么平均每个节点包含1.33个指针,比平衡树更有优势。
  5. 查找单个key,skiplist和平衡树的时间复杂度都为O(log n),大体相当;而哈希表在保持较低的哈希值冲突概率的前提下,查找时间复杂度接近O(1),性能更高一些。所以我们平常使用的各种Map或dictionary结构,大都是基于哈希表实现的。
  6. 从算法实现难度上来比较,skiplist比平衡树要简单得多。

发布了50 篇原创文章 · 获赞 2 · 访问量 2293

猜你喜欢

转载自blog.csdn.net/eafun_888/article/details/104714572