对于Redis的String类型的value,采用的是SDS数据结构。但是对于value是8字节的Long类型的value则使用的是int编码。
也就是说,这种一对一,非集合类型的value有俩种编码方式。
- value是Long类型的(只要是数字就行),保存方式是int编码
- value中有字符,则使用SDS。
- 对于SDS也有两种编码,根据字符串长度区分,小于等于44字节则使用embstr。大于则使用raw编码。
SDS结构:
元数据(len(4字节,表示已用长度),alloc(4B,表示已分配实际长度大于len)), buf(字符数组+’/0’结尾)
RedisObject:
描述各个数据结构共同的元数据。比如说最后访问时间,被引用的次数等。每一个value都有一个redisobject,里面有8字节的元数据和8字节的指针,指向他们。
对于String类型的int编码,和embstr、raw编码的内存优化:
- 对于int编码,redisobject指针直接赋值为整数数据,省了一个指针的内存开销
- 对于embstr,则redisobject和sds是一块连续的内存,也就是说只申请一次,避免碎片
- 对于raw则redisobject和sds是分开的,需要申请两次内存。并且指针会指向sds内存。
redis使用一个全局哈希表维护所有数据。而全局哈希表中会有一个dictEntry结构体,类似hashmap中的node。来表示一个键值对。有三个指针key,value,next都是4字节。且redis使用的内存分配库是jemalloc,类似hashmap的容量,会找一个最接近需要字节数的2的n次幂来分配内存。24也就是分配32字节。
总结:
Sting类型会消耗很多字节来保存元数据。假设一个2字节的key和5字节的value 数据,需要 9 (len+alloc+buf(’/0’)) + 16(redisobject) + 32(dictEntry结构体 中的key,value,next指针) + 2+5(原始数据消耗)= 64字节。而必要消耗才7字节,用了很多额外保存元数据。
一个10位数的key和10为value数字。redisobject 8字节元数据,8字节Long直接赋值。16字节 key+value就是32字节。对应的DictEntry结构体32字节,加起来也有64字节。
所以String类型很消耗内存。即使是int编码。
可以使用集合类型的压缩列表,要符合压缩列表的编码要求。
除了String类型的内存消耗和它的三种编码之外
依旧有一些优点:
- 元数据,len和alloc。比较c语言字符串,len可以让求字符串长度变的简单。并且保证了二进制数据的安全性。c语言字符串需要遍历到’\0’,二进制中可能会有冲突。alloc已经分配的长度,会大于len。保证不需要频繁申请内存。也保证空间的惰性释放。
- 使用了buf字符数组,也就是c的字符串的表示。可以使用一部分c的字符串函数。
- 空间预分配,也就是多分配一部分内存。规则是:小于1m则*2 大于1m则多分配1m。