carlosfu
作者carlosfu·2022-03-04 09:59
软件开发工程师·快手

Redis7--ziplist的替代者listpack

字数 4606阅读 1157评论 0赞 1

一、ziplist简介

ziplist是一种连续内存空间并且有序的压缩链表,其主要的数据结构如下图:

结合以上数据结构的内存模型图,我们可以看出ziplist具有的一些优势与问题:

优势问题
1. 具有极高的内存利用率1. 每次增/删/改需要realloc连续内存空间并进行移动
2. 连锁更新问题
3. 查找时间复杂度过高的问题 (o(n))
4. ziplist总内存大小限制(4G)
5. 元素个数超过65535时统计ziplist元素个数带来的遍历统计问题

> 什么时候可能会触发连锁更新呢?

向ziplist中增加、删除、修改数据内容、合并ziplist场景。

> 为什么会有连锁更新的问题?

从上图的数据结构中可以看到,在实际数据的内存结构前有prevlen与encoding字段,当其发生变化时可能会导致内存不连续,为了保证内存中数据的连续,所以可能会触发连锁更新。

二、listpack简介

由于ziplist存在不可避免的问题 -- 连锁更新问题, 所以在Redis 5版本中,推出了ziplist替代版本listpack。

1. ziplist整体的结构与listpack的整体结构对比


从以上整体的对比图可以看到,其实两者结构相差不大,listpack相对于ziplist,没有了指向末尾节点地址的偏移量,这样也可以解决_ziplist内存长度限制的问题_。

2. entry结构对比

其数据结构对比如下图:

从以上结构中可以看到,listpack移除了prevlen,在data后新增了backlen,两者有着本质区别:

  • 在ziplist中,prevlen代表前一个entry节点长度的偏移量;
  • 在listpack中,backlen代表的是本entry节点的回朔起始地址长度的偏移量

Entry这样设计具有以下一些优势

  • 在遍历最后一个entry时可以通过lp+totallen快速定位到lp尾地址,然后使用backlen快速定位到last entry的起始地址;*并且可以将head中的last offset字段节省出来;
  • 由于backlen仅代表了回朔本entry起始地址长度的偏移量,所以在增/删/改时,无需再关心前一个节点的长度,_仅需要整体移动entry即可,所以不会涉及到内存的连锁更新_;具体的代码流程这里不再复述;

三、listpack替换quicklist收益

1. list相关命令性能对比(quicklist with ziplist -> quicklist with listpack)

> 注意:以下测试结果摘自redis-pr #9740.

commandquicklist listpack(rps)quicklist ziplist(rps)percentage(positive: performance enhancement, negative: performance degradation)
LPUSH106929 rps, p50=0.223103535.75 rps, p50=0.2233.2%
LRANGE_10065737.58 rps, p50=0.37567333.27 rps, p50=0.375-2.3%
LRANGE_30030900.44 rps, p50=0.83931346.00 rps, p50=0.823-1.4%
LRANGE_50022126.59 rps, p50=1.17522689.63 rps, p50=1.159-1.4%
LRANGE_60019443.90 rps, p50=1.34319690.08 rps, p50=1.327-1.2%
RPUSH109075.04 rps, p50=0.223108283.71 rps, p50=0.2230.7%
LPOP108636.61 rps, p50=0.223110778.77 rps, p50=0.2231.9%
RPOP109757.44 rps, p50=0.223109206.08 rps, p50=0.2230.5%

从以上结果可以看到,_lpush/rpush/lpop/rpop性能略有提升_,这得益于listpack避免了连锁更新的问题,符合预期;但是_lrange性能下降_是因为在lpNext与lpGet函数中需要计算整个entry的长度(包括计算data长度与backlen)以及进行有效性验证,在代码逻辑上相较于ziplistNext/ziplistGet要复杂一些。

2. hash/zset类命令性能影响

由于hash与zset类命令,具有两种编码模式,默认为ziplist,在entry个数达到配置的默认值时,会将数据结构分别转换为hash与skiplist,所以_在entry个数较少时,性能影响不大;entry个数越长、变更更频繁,新版本会具有更好的性能_,而且在实际业务场景中,这个值都不会被配置的很大,所以这里也不再测试,仅从理论上推测性能影响。

3. 关于RDB的性能影响

由于ziplist和listpack都是一段连续的内存,所以在写入和加载上,性能不会有明显的差异。

4. RDB加载时ziplist转换为listpack时的耗时

低版本的RDB在redis7中加载时,会自动将ziplist转换为listpack,其整体加载耗时会大约增加 9%-54% 左右

具体耗时结果如下:

  • List(38%-46%)
注意:以下数据摘自 redis-pr #9740
list sizequicklist length per listquicklist ziplist loading time(second)quicklist listpack loading and convert time(second)convert cost time(second)degradation percentagerdb size
2500005129.05313.4684.41548.7%(memory limit reach)3.7G
2500002564.7526.8502.09844.1%1.9G
2500001282.5553.7221.16745.6%946M
250000641.3851.9990.61444.3%486M
5000002569.21413.8474.63350.2%(memory limit reach)3.7G
5000001284.9907.1702.1843.6%1.9G
500000642.8683.9611.09338.1%971M
500000321.7032.3650.66238.8%505M

  • hash (29%-54%)
注意:以下数据摘自 redis-pr #8887
num of keysentries num of one ziplistentry sizeloading time without convert(second)rdb loading time with convert(second)degradation percentage
1 million1288 bytes5.89312.74753.77%
1 million12816 bytes7.26314.72750.68%
1 million12824 bytes9.79416.05839.01%
1 million12832 bytes11.63618.53737.23%
2 million648 bytes7.23414.36649.64%
2 million6416 bytes8.75916.47846.84%
2 million6424 bytes11.09518.37239.61%
2 million6432 bytes14.41320.60730.06%
4 million328 bytes9.45216.88644.02%
4 million3216 bytes10.68518.97543.69%
4 million3224 bytes13.47420.82435.30%
4 million3232 bytes16.04122.71229.37%

  • Zset (9%-16%)
注意:以下数据摘自 redis-pr #9366
key numzset lengthloading without convertloading with convert(after optimize)loading with convert(before optimize)degradation percentage
5000001285.352s13.82115.64811.68%
500000642.983s7.2258.44314.43%
500000321.629s3.8354.2179.06%
100000012810.977s27.02631.96515.45%
1000000646.022s14.2816.47713.33%
1000000323.317s7.5228.70813.62%

四、ziplist替换为listpack后带来的连锁变更

1. 相关配置变更

* 新增list-max-listpack-size, 是list-max-ziplist-size的别名;  
  • 新增hash-max-listpack-entries, 是hash-max-ziplist-entries的别名;
  • 新增hash-max-listpack-value, 是hash-max-ziplist-value的别名;
  • 新增zset-max-listpack-entries, 是zset-max-ziplist-entries的别名;
  • 新增zset-max-listpack-value, 是zset-max-ziplist-value的别名;

_由于新增配置均是别名,所以原有针对ziplist的相关配置,均可在redis7中自动生效,无需再做配置修改_。

2. RDB相关

  • RDB类型新增

    • RDB_TYPE_HASH_LISTPACK
    • define RDB_TYPE_ZSET_LISTPACK
    • define RDB_TYPE_LIST_QUICKLIST_2
  • 版本号提升至 10

由于RDB类型和版本号的变更,所以需要更新迁移工具,解析并支持新的类型。

五、参考链接

  1. listpack migration -- https://github.com/redis/redis/issues/8702
  2. replace ziplist with listpack in quicklist -- https://github.com/redis/redis/pull/9740
  3. Replace all usage of ziplist with listpack for t_hash -- https://github.com/redis/redis/pull/8887
  4. Replace all usage of ziplist with listpack for t_zset -- https://github.com/redis/redis/pull/9366

作者:黄威

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

1

添加新评论0 条评论

Ctrl+Enter 发表

本文隶属于专栏

最佳实践
不同的领域,都有先行者,实践者,用他们的最佳实践来加速更多企业的建设项目落地。

作者其他文章

相关文章

相关问题

相关资料

X社区推广