2020-12-31

返回主页
小新是也
一只上进的程序猿
博客园 首页 新随笔 联系 订阅 管理随笔 - 11 文章 - 2 评论 - 6
Redis 设计与实现 7:五大数据类型之列表
列表对象有 3 种编码:ziplist、linkedlist、quicklist。

ziplist 和 linkedlist 是 3.2 版本之前的编码。
quicklist 是 3.2 版本新增的编码,ziplist 和 linkedlist 在 3.2 版本及后续版本将不再是列表对象的编码。
编码定义如下(server.h):

#define OBJ_ENCODING_LINKEDLIST 4
#define OBJ_ENCODING_ZIPLIST 5
#define OBJ_ENCODING_QUICKLIST 9
虽然 ziplist 和 linkedlist 不再被列表对象作为编码,但是我们还是有必要了解的。因为 quicklist 也是基于 ziplist 和 linkedlist 改良的。

ziplist
压缩列表 ziplist 在之前的文章 Redis 设计与实现 5:压缩列表 ziplist 有介绍过,结构如下:
ziplist 的结构

我们使用命令操作列表的元素的时候,实际上就是在操作 entry 的数据。下面我们来举个栗子:

redis> RPUSH list_key 1 “ab” “d”
如果 list_key 用 ziplist 编码,那么结构如下图:
list ziplist 编码实例结构

linkedlist
链表 linkedlist 的数据结构如下(adlist.h),跟普通的链表差不多:

typedef struct list {
// 头结点
listNode *head;
// 尾节点
listNode *tail;
// 复制链表节点的值
void *(*dup)(void *ptr);
// 释放链表节点的值
void (*free)(void *ptr);
// 对比链表节点所保存的值跟输入的值是否相等
int (*match)(void *ptr, void *key);
// 链表包含的节点数
unsigned long len;
} list;
链表节点的结构也很简单:

typedef struct listNode {
// 前置节点
struct listNode *prev;
// 后置节点
struct listNode *next;
// 当前节点的值
void *value;
} listNode;
结构示意图如下:
list ziplist 编码结构图
数据将存储在 listNode 的 value 中,数据是一个字符串对象,用 redisObject 包裹着 sds。
例如可能是 embstr 编码的 sds :
string embstr 编码示意图

下面我们来举个栗子:

redis> RPUSH list_key 1 “ab” “d”
假如 list_key 的编码是 linkedlist,那么结构如下图:
list linkedlist 编码示例结构图

quicklist
快速列表 quicklist 是 3.2 版本新添加的编码类型,结合了 ziplist 和 linkedlist 的一种编码。
同时在 3.2 版本中,列表也废弃了 ziplist 和 linkedlist。

通过上面的介绍,我们可以看出。双向链表的内存开销很大,每个节点的地址不连续,容易产生内存碎片,quicklist 利用 ziplist减少节点数量,但 ziplist 插入和删除数都很麻烦,复杂度高,为避免长度较长的 ziplist修改时带来的内存拷贝开销,通过配置项配置合理的 ziplist长度。

quicklist 的结构如下:
list quicklist 编码结构图
从上图可以看出,quicklist 跟 linkedlist 最大的不同就是,quicklist 的值指向的是 ziplist!ziplist 可比之前的 redisObject 节省了非常多的内存!
从另一个角度看,他就是把一个长的 ziplist 切割成多个小的 ziplist。

代码实现在 quicklist.h:

typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
// 所有 ziplist 中所有的节点数
unsigned long count;
// quicklistNode 的数量
unsigned long len;
// 限定 ziplist 的最大大小,可通过配置文件配置
int fill : QL_FILL_BITS;
// 压缩程度,0 表示不压缩,可通过配置文件配置
unsigned int compress : QL_COMP_BITS;
// …
} quicklist;
配置一:fill (控制 ziplist 大小)

太长的 ziplist 增删的复杂度高,所以 quicklist 用 fill 参数来控制 ziplist 的大小,它是通过配置文件的list-max-ziplist-size配置。

当数字为正数,表示:每个节点的 ziplist 最多包含的 entry 个数。
当数字为负数:
-1:每个节点的 ziplist 字节大小不能超过4kb
-2:每个节点的 ziplist 字节大小不能超过8kb (redis默认值)
-3:每个节点的 ziplist 字节大小不能超过16kb
-4:每个节点的 ziplist 字节大小不能超过32kb
-5:每个节点的 ziplist 字节大小不能超过64kb
配置二:compress (控制压缩程度)

因为链表的特性,一般首尾两端操作较频繁,中部操作相对较少,所以 redis 提供压缩深度配置:list-compress-depth,也就是属性 compress 。

0:表示都不压缩。这是Redis的默认值。
1:表示 quicklist 两端各有1个节点不压缩,中间的节点压缩。
2:表示 quicklist 两端各有2个节点不压缩,中间的节点压缩。
3:表示 quicklist 两端各有3个节点不压缩,中间的节点压缩。
quicklist 节点

typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
// 不设置压缩数据参数 recompress 时指向一个 ziplist 结构
// 设置压缩数据参数recompress 时指向 quicklistLZF 结构
unsigned char zl;
// ziplist 的字节数
unsigned int sz;
// ziplist 中包含的节点数量
unsigned int count : 16;
// 编码。1 表示压缩过,2 表示没压缩
unsigned int encoding : 2;
unsigned int container : 2; /
NONE1 or ZIPLIST2 */
// 标记 quicklist 节点的 ziplist 之前是否被解压缩过
// 如果recompress 为 1,则等待被再次压缩
unsigned int recompress : 1;
// …
} quicklistNode;
压缩过的 ziplist 结构

typedef struct quicklistLZF {
// 表示被 LZF 算法压缩后的 ziplist 的大小
unsigned int sz;
// 压缩后的 ziplist 的数组,柔性数组
char compressed[];
} quicklistLZF;
quick 的常用操作

  1. 插入

(1) quicklist 可以在头部或者尾部插入数据:quicklist.c/quicklistPushHead、quicklist.c/quicklistPushTail,我们就挑一个从头部插入的代码来看看吧(插入尾部的代码也是差不多的)(代码格式略微调整了一下):

int quicklistPushHead(quicklist *quicklist, void *value, size_t sz) {
quicklistNode *orig_head = quicklist->head;
// 判断头结点上的 ziplist 大小是否没超过限制
if (likely(_quicklistNodeAllowInsert(quicklist->head, quicklist->fill, sz))) {
// 没超过限制,就插入到 ziplist 中。ziplistPush 是 ziplist.c 的方法
quicklist->head->zl = ziplistPush(quicklist->head->zl, value, sz, ZIPLIST_HEAD);
quicklistNodeUpdateSz(quicklist->head);
} else {
// ziplist 超过大小限制,则创新创建一个新的 quicklistNode
quicklistNode *node = quicklistCreateNode();
// 再创建新的 ziplist,然后把 ziplist 放到节点中
node->zl = ziplistPush(ziplistNew(), value, sz, ZIPLIST_HEAD);
quicklistNodeUpdateSz(node);
// 新的 quicklistNode 插入原来的头结点上,成为新的头结点
_quicklistInsertNodeBefore(quicklist, quicklist->head, node);
}
quicklist->count++;
quicklist->head->count++;
return (orig_head != quicklist->head);
}
(2) quicklist 也可以从任意指定的位置插入:quicklist.c/_quicklistInsert,实现相对来说比较复杂,我们就用文字说明(代码太长,感兴趣的读者自己去读吧):

当前节点是 NULL:创建一个新的节点,插入就好。
当前节点的 ziplist 大小没有超过限制时:直接插入到 ziplist 就好。
当前节点的 ziplist 大小超过限制时:
如果插入的位置是 ziplist 的两端:
如果相邻的节点的 ziplist 大小没有超过限制,那么就插入到相邻节点的 ziplist 中。
如果相邻的节点的 ziplist 大小也超过限制,这时需要创建一个新的节点插入。
如果插入的位置是 ziplist 的中间:
则需要把当前 ziplist 从插入位置 分裂 (_quicklistSplitNode) 为两个节点,然后把数据插入第二个节点上。
2. 查找

quicklist 支持通过 index 查找元素:quicklist.c/quicklistIndex。
查找的本质就是遍历,先查看quicklistNode 的长度判断 index 是否在这个节点中,如果不是则跳到下个节点。
当定位到节点之后,对节点里面的 ziplist 进行遍历查找 (ziplistIndex)。

3 删除

(1) 指定值的删除,quicklist.c/quicklistDelEntry
这个指定的值的信息 quicklistEntry 的结构如下:

typedef struct quicklistEntry {
// 指向当前 quicklist 的指针
const quicklist *quicklist;
// 指向当前 quicklistNode 节点的指针
quicklistNode *node;
// 指向当前 ziplist 的指针
unsigned char *zi;
// 指向当前 ziplist 的字符串 vlaue 成员
unsigned char *value;
// 当前 ziplist 的整数 value 成员
long long longval;
// 当前 ziplist 的字节数大小
unsigned int sz;
// 在 ziplist 的偏移量
int offset;
} quicklistEntry;
具体的删除代码如下(做了一些删减):

void quicklistDelEntry(quicklistIter *iter, quicklistEntry *entry) {
quicklistNode *prev = entry->node->prev;
quicklistNode *next = entry->node->next;
// 通过 quicklistEntry 可以定位到 ziplist 中的元素位置,然后进行删除
// quicklist -> quicklistNode -> ziplist -> ziplistEntry
int deleted_node = quicklistDelIndex((quicklist *)entry->quicklist, entry->node, &entry->zi);
// 下面是迭代器的参数调整,此处忽略…
}
(2) 区间元素 index 删除: quicklist.c/quicklistDelRange(代码太长了,就不晾出来了)
先通过遍历找元素,会判断是否可以删除整个节点 entry.offset == 0 && extent >= node->count,可以的话不用遍历里面的ziplist直接删除整个节点。
否则计算出当前节点ziplist 要删除的范围,通过 ziplistDeleteRange 函数删除。

重点回顾
列表对象有 3 种编码:ziplist、linkedlist、quicklist。
quicklist 是 3.2 后新增的用于替代 ziplist 和 linkedlist 的编码。
ziplist 节省内存,但是太长的话性能低下。linkedlist 占用内存太多。
quicklist 可以看成由多个 ziplist 组成的 linkedlist,性能高,节省内存。
作者:小新是也
链接:http://www.cnblogs.com/chenchuxin
来源:博客园
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
分类: Redis
标签: redis
好文要顶 关注我 收藏该文
小新是也
关注 - 16
粉丝 - 8
+加关注
0 0
« 上一篇: Redis 设计与实现 6:五大数据类型之字符串
posted @ 2020-12-31 12:59 小新是也 阅读(31) 评论(0) 编辑 收藏
刷新评论刷新页面返回顶部
登录后才能发表评论,立即 登录 或 注册, 访问 网站首页
写给园友们的一封求助信
【推荐】News: 大型组态、工控、仿真、CADGIS 50万行VC++源码免费下载
【推荐】博客园x丝芙兰-圣诞特别活动:圣诞选礼,美力送递
【推荐】新一代 NoSQL 数据库,Aerospike专区新鲜入驻

相关博文:
· Redis-安装redis
· Redis
· Redis
· redis
· redis
» 更多推荐…

最新 IT 新闻:
· 学霸君暴雷背后:行业畸形、分食“遗骸”、两极分化
· 美团取消支付宝渠道引发反垄断诉讼:是正常商业行为还是侵犯消费者选择?
· Google Doodle纪念民权活动家伊丽莎白·佩拉特罗维奇
· 索尼互动娱乐在新加坡成立新总部 强化东南亚运营
· 中国新冠病毒疫苗获批上市:符合条件群众实现应接尽接
» 更多新闻…
公告

昵称: 小新是也
园龄: 5年7个月
粉丝: 8
关注: 16
+加关注
< 2020年12月 >
日 一 二 三 四 五 六
29 30 1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31 1 2
3 4 5 6 7 8 9
搜索

找找看

谷歌搜索
常用链接

我的随笔
我的评论
我的参与
最新评论
我的标签
我的标签

redis(8)
docker(3)
Jave(1)
mongodb(1)
redis-cluster(1)
apollo(1)
随笔分类

docker(3)
Java(2)
MongoDB(1)
Redis(7)
随笔档案

2020年12月(7)
2018年2月(2)
2018年1月(2)
最新评论

  1. Re:Redis 设计与实现 3:字符串 SDS
    极行资源网 www.jixingd.com
    –百度极行网

  2. Re:MongoDB投影有$slice如何只显示该字段
    感谢🙏,解决我这个问题了。
    –一个大金宝

  3. Re:Apollo阿波罗配置中心docker
    @ 气质优雅的猪官方只支持java、.net,不过有第三方支持…
    –小新是也

  4. Re:Apollo阿波罗配置中心docker
    Apollo阿波罗配置中心对 node.js支持吗?
    –气质优雅的猪

  5. Re:如何用docker部署redis cluster
    @ edynetwork_mode是host的话就不用再写ports了,7001到7006就是对应的各个redis的端口了…
    –小新是也
    阅读排行榜

  6. 如何用docker部署redis cluster(6128)

  7. Apollo阿波罗配置中心docker(3817)

  8. docker搭建私服(1122)

  9. MongoDB投影有$slice如何只显示该字段(1038)

  10. Redis 设计与实现 1:数据库 redisDb(441)
    评论排行榜

  11. Apollo阿波罗配置中心docker(2)

  12. Redis 设计与实现 3:字符串 SDS(1)

  13. 如何用docker部署redis cluster(1)

  14. MongoDB投影有$slice如何只显示该字段(1)
    推荐排行榜

  15. Redis 设计与实现 3:字符串 SDS(2)

  16. Redis 设计与实现 4:字典 dict(1)

  17. 如何用docker部署redis cluster(1)
    Copyright © 2020 小新是也
    Powered by .NET 5.0 on Kubernetes

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值