Redis编码优化
一、压缩列表ziplist
1.1 ziplist数据结构
- 压缩列表是一种顺序存储的数据结构,为节约内存而设计。当存储的内容是数量比较少或者比较短的时候,Redis就会使用压缩列表存储,比如列表,Hash,跳表都有可能。
- 其优点是节约内存,不过因为复杂度是O(N),元素规模不宜过大
1.2 ziplist结构示图
- ziplist是由特殊的编码和连续内存块组成的顺序型数据结构,如下:
- 右图中可以看到压缩列表通过几个描述字段来表示压缩列表的长度和元素个数,主体元素紧密排布。
- 压缩列表内部没有指针,但是通过字段长度来实现遍历,比如得到前面一个元素,就是当前地址减去previous_entry_length就得到前一元素的地址,这样实现元素遍历。
- 编码:encoding包含内容的编码和长度信息,其高位包含编码信息,低位包含content的长度信息
- 内容:content是value部分
1.3 ziplist连锁更新
- 压缩列表中每个节点通过previous_entry_length记录前一个节点的长度,如果小于254只需要1个字节,如果大于则需要5字节,因此假设其中一个节点重新设置大于254,则其后面的节点长度字段都会变化,由此引发后面连锁的更新,删除也可能触发连锁更新。连锁更新会降低性能,但是因为压缩列表的元素一般不多,因此对性能几乎没有影响。
1.4 ziplist复杂度
- 复杂度:ziplist的操作命令最坏复杂度是O(N2),平均是O(N),前后获取节点是O(1),因为保存了前面节点的长度。
二、Redis对象和编码
2.1 Redis对象数据结构
- Redis中每一个对象都是由RedisObj来表示,其表示了数据的相关属性以及指向数据的指针,如下:
typedef struct redisObject{
//数据类型
unsigned type:4;
//编码
unsigned encoding:4;
//引用计数
int refcount;
//最后访问时间
unsigned lru:22;
//值指针
void *ptr;
} robj;
字段 | 含义 | 相关命令 |
---|
type | 对象类型 | type keyName |
encoding | 对象编码 | object encoding keyName |
refcount | 对象引用计数 | object refcount keyName |
lru | 对象最后访问时间 | object idletime keyName |
object encoding keyName
127.0.0.1:6379> OBJECT encoding name
"embstr"
2.2 编码类型
- 对于Redis的五种数据类型,都有至少2种不同的编码实现,通过Object Encoding xx 查看xx的编码方式
基础类型 | 编码类型 | Object Encoding命令输出 | 转换条件 |
---|
String | 整数 <-> SDS <-> embstr | int/embstr/raw | 满足整数则使用整数,否则使用embstr(长度小于39,分配和释放次数减少)/SDS RAW(大于39) |
List | ziplist <-> 双向链表 | ziplist/linkedlist | 字符串元素小与64并且元素小与512个使用ziplist |
Hash | ziplist <-> 字典 | ziplist/hashtable | 所有键值长度小于64且键值对少于512使用ziplist |
Set | 整数集合 <-> 字典 | intset/hashtable | 全为整数且不超过512个则使用整数集合,反之使用字典(value为null) |
ZSet | ziplist <-> 跳表 | ziplist/skiplist | 集合元素长度均小于64且元素少于128时使用ziplist,反之使用跳表 |
2.3 转换触发机制
- String:对于整数的类型,如果不满足了就会触发编码转换,比如123的字符串变为了123a,就会转换为embstr。embstr内存块是连续的并且分配和释放只需要一次调用,适合短字符串。
- List转换:字符串元素小于64并且元素少于512个则使用ziplist,反之使用双向链表,值可配置
list-max-ziplist-value
list-max-ziplist-entries
- Hash:所有键和值长度都小于64且键值对少于512,使用ziplist,反之使用字典
hash-max-ziplist-value
hash-max-ziplist-entries
- Set:元素全为整数且不超过512个则使用整数集合,反之使用字典,此时值都会置为NULL,保留key作为集合元素
set-max-intset-entries
- ZSet:集合元素长度均小于64且元素少于128时使用ziplist,反之使用跳表;
zset-max-ziplist-value
zset-max-ziplist-entries
- 这里补充一点,有序集合内部会维护一个字典保存元素->分值的映射,因为跳表适合根据分值进行范围查询,其擅长通过分值来操作,但不擅长通过对象查找分数,而这个就是字典擅长的,二者互补,保证了分值范围检索和对象检索分值这2个特性。
三、其他
3.1 Redis类型检查
- Redis在执行一个命令的时候会检查该命令是否可以作用在该键上,比如rpush只能对list操作,检查是通过对象的type字段来实现的
3.2 内存回收
- redisObject内部通过一个refCount来做引用计数,当对象不被使用的时候会减少为0,然后释放内存,通过object refcount xx 查看xx的引用计数
3.3 对象共享
- Redis只对包含整数值的字符串对象做了共享对象,Value相同时,将key指向同样的vaule,默认0-9999一共会有1万个共享对象。
- 注意redis不包含字符串的共享对象,因为判定字符串是否相等复杂度比价高,相对整数更加苛刻,消耗性能。
3.4 对象空转时长
- redisObject的lru字段记录了对象最后一次被访问的时间,object idletime xx得到xx对象未被访问的时长。
- 注意服务器设置了maxmemory的时候,如果使用lru回收算法,会将空转时长最大的优先淘汰,注意开启了lru淘汰的时候共享对象会失效。
四、小结
- 主要了解编码格式,有助于我们优化内存,比如对于hash或者列表我们尽量用较短的key,元素个数拆分少一点之类,对于可以使用整数的场景,尽量使用整数,可以复用内部的共享对象。
五、参考