
Redis成本优化-版本升级-1.SDS优化历史
做了一阵子的成本优化,一直想整理些文章发出来,由于比较懒且“怂”一直没弄,最近决定还是整理下,本文是该系列文章的第一篇。(相关目录见文末)
一、几个实验
1. 写入100万条string键值对:key、value均为小于32字节字符串(测试用16字节)
版本 | 2.8.24 | 3.0.7 | 3.2.13 | 4.0.14 | 5.0.14 | 6.0.20 | 6.2.15 | 7.0.12 |
容量MB | 114.87 | 114.87 | 91.98 | 92.04 | 92.04 | 92.00 | 92.04 | 92.10 |
2. 写入100万条string键值对:key、value均为小于39字节字符串(测试用36字节)
版本 | 2.8.24 | 3.0.7 | 3.2.13 | 4.0.14 | 5.0.14 | 6.0.20 | 6.2.15 | 7.0.12 |
容量MB | 145.38 | 145.38 | 122.50 | 122.56 | 122.56 | 122.56 | 122.56 | 122.62 |
3.写入100万条string键值对:key、value均为39到44字节之间的字符串(测试用42字节)
版本 | 2.8.24 | 3.0.7 | 3.2.13 | 4.0.14 | 5.0.14 | 6.0.20 | 6.2.15 | 7.0.12 |
容量MB | 175.90 | 175.90 | 137.76 | 137.82 | 137.82 | 137.78 | 137.82 | 137.88 |
4.写入100万条键值对:key、value均为大于44字节(测试用100字节)
版本 | 2.8.24 | 3.0.7 | 3.2.13 | 4.0.14 | 5.0.14 | 6.0.20 | 6.2.15 | 7.0.12 |
容量MB | 267.45 | 267.42 | 259.83 | 259.89 | 259.89 | 259.89 | 259.89 | 259.95 |
5.写入100万条键值对:key为字符串(100字节),value为数字(>10000的数字)
版本 | 2.8.24 | 3.0.7 | 3.2.13 | 4.0.14 | 5.0.14 | 6.0.20 | 6.2.15 | 7.0.12 |
容量MB | 160.64 | 175.90 | 168.28 | 168.34 | 153.08 | 153.08 | 153.08 | 153.14 |
初步结论:
- Redis 3.2版本相比于之前版本,Redis容量有较大降幅。
- Redis 5.0版本在value为数字的场景下,Redis容量有较大降幅。
二、什么是SDS
SDS全称是Simple Dynamic String(简单动态字符串),它对C语言中的字符串实现进行了扩展。
一图胜千言万语,SDS和C字符串相比多了一个前缀sdshdr结构体,它的定义:
例如如下两图说明了上述参数的含义:
SDS好处如下:
- 1.计算长度简单:SDS计算字符串长度复杂度是o(1),C语言是O(n)
- 2.二进制安全:SDS不以\0作为字符串结束标识(用free、len),即使字符串包含了\0这样特殊字符,它也是安全的。
- 3.安全扩展:SDS定义了一系列函数保证字符串不会出现越界等情况。
关于SDS是什么网上有很多文章,这里就不再赘述了。
三、SDS的相关优化
1. embedded string(since 3.0)
(1) 一个例子
(2) 对于非数字的字符串
- 当小于等于39字节(注意这里是Redis 3),Redis使用embstr来实现。
- 当大于39字节(注意这里是Redis 3),Redis使用raw来实现。
两者的区别是redisObject和embstr是一块连续内存,redisObject和raw通常是不连续的,如下图所示:
(3) 优势:embstr申请或释放内存都是一次,raw需要两次,同时由于是连续内存,查询速度会更快。
(4) 注意:embstr并没有节省内存:
数据规模 | 2.8.24 | 3.0.7 |
写入100万条键值对:key、value均为16字节 | 114.87 | 114.87 |
写入100万条键值对:key、value均为36字节 | 145.38 | 145.38 |
2. sdshdr拆分(since 3.2)
(1) sdshdr重大优化:如果字符串比较小(例如就几个字节),但是len、free会占用8个字节,确实是有一些浪费。
Redis 3.0:
于是在Redis3.2做了如下优化:按照字符串长度的不同,记录字符串len和free的类型会做相应变化:char、uint8_t、uint16_t、uint32_t、uint64_t。
Redis 3.2+:
(2) 以sdshdr8为例子进行说明,它的结构如下,从图中可以看到为什么Redis 3的embstr界限是39字节、Redis 3.2的embstr界限是44字节(修复图中Redis 4为Redis 3.2+)
3. 数字优化(since 5.0.6)
Redis在执行set相关命令(setnx、setex、setnx、append)会调用object.c的tryObjectEncoding确认下value是否可以转成对应的编码已节省空间,下面是Redis 4.0.14的代码
Redis 5.0.6对齐做了如下优化:https://github.com/redis/redis/commit/2f8a0749
如果是20个字节以内的long类型,如果是非raw类型,可以转成embstr并针对性的做优化:
如果是在longmin和longmax之间,会优化成如下:ptr直接存储数字
四、结论
- Redis 3.0的embstr减少了一次内存分配,可以提升整体性能,但是并未为做成本上的优化。
- Redis 3.2的sdshdr拆分是sds最大的成本优化,因为sds是复杂数据结构以及网络处理等的基础数据结构。
- Redis 5.0.6关于数字的优化虽然只改动了几行代码,但是针对数字类型非常明显。
五、几个思考问题(欢迎来讨论)
- 为什么embstr的界限是39或者44字节?
- 第一节的实验五的数字为什么要大于10000?
- Redis 7.0似乎容量上有小幅上涨?
文章转载自公众号:Redis开发运维实战
