数据结构
查看键的内部编码方式:object encoding key
Redis的键值都使用redisObject结构体保存,redisObject的结构:
typedef struct redisObject {
unsigned type:4;
unsigned notused:2; /* Not used */
unsigned encoding:4;
unsigned lru:22; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} ;
其中
refcount:键值被引用数量
type字:键值的数据类型:
define REDIS_STRING 0
define REDIS_LIST 1
define REDIS_SET 2
define REDIS_ZSET 3
define REDIS_HASH 4
encoding:键值的内部编码方式
define REDIS_ENCODING_RAW 0 /* Raw representation */
define REDIS_ENCODING_INT 1 /* Encoded as integer */
define REDIS_ENCODING_HT 2 /* Encoded as hash table */
define REDIS_ENCODING_ZIPMAP 3 /* Encoded as zipmap */
define REDIS_ENCODING_LINKEDLIST/* Encoded as regular linked list */
define REDIS_ENCODING_ZIPLIST 5 /* Encoded as ziplist */
define REDIS_ENCODING_INTSET 6 /* Encoded as intset */
define REDIS_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
针对每种数据类型分别介绍其内部编码规则及优化方式:
一、字符串类型
Redis使用一个sdshdr类型的变量来存储字符串,redisObject的ptr字段指向的是该变量的地址。
struct sdshdr {
int len;
int free;
char buf[];
};
len:字符串的长度
free:buf中的剩余空间
buf:字符串的内容
执行SET key foobar时,存储键值需要占用的空间是sizeof(redisObject) +sizeof(sdshdr) + strlen(“foobar”) = 30字节。
当键值内容可以用整数表示,Redis会将键值转换成long类型存储。如SET key 123456,实际占用的空间是sizeof(redisObject) = 16字节。
Redis启动后会预先建立10000个分别存储从0到9999这些数字的redisObject类型变量作为共享对象,如果要设置的字符串键值在这10000个数字内(如SET key1123)则可以直接引用共享对象而不用再建立一个redisObject了,也就是说存储键值占用的空间是0字节。
提示:当通过配置文件参数maxmemory设置了Redis可用的最大空间大小时,Redis不会使用共享对象,因为每一个键值都需要使用一个redisObject来记录其LRU信息。
二、散列类型
配置:
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
当散列类型键的字段个数<hash-max-ziplist-entries&&每个字段名和字段值的长度都<hash-max-ziplist-value(单位为字节), Redis会使用REDIS_ ENCODING_ZIPLIST来存储该键,否则就会使用REDIS_ENCODING_HT。
转换时机:
当键值变更后Redis都会根据条件判断是否需要转换。
REDIS_ENCODING_HT:
编码为散列表,时间复杂度O(1),其字段和字段值都是使用redisObject存储(可使用字符串类型键值的优化)。
备注:Redis的键名没有使用redisObject存储,因为绝大多数情况下键名都不会是纯数字。
REDIS_ENCODING_ZIPLIST:
编码类型更紧凑,牺牲部分读取性能以换取极高的空间利用率,适合在元素较少时使用。
zlbytes:表示整个结构占用的空间(uint32_t类型)
zltail:最后一个元素的偏移(uint32_t类型),使得程序可以直接定位到尾部元素而无需遍历整个结构,执行从尾部弹出(对列表类型而言)等操作时速度更快
zllen:元素的数量(uint16_t类型)
zlend:一个单字节标识,标记结构的末尾,值永远是255
每个元素由4个部分组成。
1.前一个元素的大小:以实现倒序查找,当前一个元素<254字节时,占用1个字节,否则占用5个字节。
2,3.元素的编码类型和元素的大小
当元素<=63个字节,元素的编码类型是ZIP_STR_06B(即0<<6),同时第三个部分用6个二进制位来记录元素的长度,
第二、三个部分总占用空间是1字节。
当元素>63&&元素<=16383字节时,第二、三个部分总占用空间是2字节
当元素>16383字节时,第二、三个部分总占用空间是5字节。
4.元素的实际内容:如果元素可以转换成数字,Redis会使用相应的数字类型来存储以节省空间,并且元素的编码类型和大小来使用数字的类型(int16_t、int32_t等)。
使用REDIS_ENCODING_ZIPLIST编码存储散列类型时元素的排列方式是:元素1存储字段1,元素2存储字段值1,依次类推,如图4-8所示。
需要执行HSET hkey foo anothervalue时Redis需要从头开始找到值为foo的元素(查找时每次都会跳过一个元素以保证只查找字段名),找到后删除其下一个元素,并将新值anothervalue插入删除和插入都需要移动后面的内存数据,而且查找操作也需要遍历才能完成,所以散列键中数据多时性能将很低,hash-max-ziplist-entries和hash-max-ziplist-value两个参数应设置小些。
三、列表类型
配置:
list-max-ziplist-entries 512
list-max-ziplist-value 64
REDIS_ENCODING_LINKEDLIST:
双向链表,链表中的每个元素是用redisObject存储。
REDIS_ENCODING_ZIPLIST:
支持倒序访问,采用此种编码方式时获取两端的数据依然较快。
四、集合类型
配置:
set-max-intset-entries参数指定值(默认是512)
REDIS_ENCODING_INTSET:
以有序的方式存储元素,使得可以使用二分算法查找元素
编码存储结构体intset:
typedef struct intset {
uint32_t encoding;
uint32_t length;
int8_t contents[];
} intset;
contents:集合中的元素值,根据encoding的不同,每个元素占用的字节大小不同。默认的encoding是INTSET_ENC_INT16(即2个字节)。
五、有序集合类型
配置:
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
REDIS_ENCODING_SKIPLIST:
散列表和跳跃列表(skip list)
散列表用来存储(元素值->元素分数的映射)以实现O(1)时间复杂度的ZSCORE等命令。
跳跃表用来存储(元素的分数->元素值的映射)以实现排序的功能。
REDIS_ENCODING_ZIPLIST:
编码时有序集合
存储的方式按照“元素1的值,元素1的分数,元素2的值,元素2的分数”的顺序排列,并且分数是有序的。