二、编码格式
(1)string 类型
string 的编码类型可能为:
- OBJ_ENCODING_INT int:long 类型整数;
- OBJ_ENCODING_RAW raw:sds 字符串;
- OBJ_ENCODING_EMBSTR embstr:嵌入式字符串(编码后长度小于 44 字节的字符串);
127.0.0.1:6379> SET str "1234567890 1234567890 1234567890 1234567890"
OK
127.0.0.1:6379> STRLEN str
(integer) 43
127.0.0.1:6379> OBJECT ENCODING str
"embstr"
127.0.0.1:6379> APPEND str _
(integer) 44
127.0.0.1:6379> OBJECT ENCODING str
"raw"
使用 embstr 编码是为了减少短字符串的内存分配次数,参考 redis 作者原话:
REDIS_ENCODING_EMBSTR_SIZE_LIMIT set to 39.
The new value is the limit for the robj + SDS header + string + null-term to stay inside the 64 bytes Jemalloc arena in 64 bits systems.
对比两者内存布局可以发现:
- embstr 是一个完整连续的内存块,只需要 1 次内存分配;
- raw 的内存是不连续的,需要申请 2 次内存;
<------------------------------------------ Jemalloc arena (64 bytes) ---------------------------------------------->
+-------------------------------------------------------------------------------+---------------------+--------------+
| redisObject (16 bytes) | sdshdr8 (3 bytes) | 45 bytes |
+--------------------+---------------------------------+-------+----------+-----+-----+-------+-------+---------+----+
| type(REDIS_STRING) | encoding(REDIS_ENCODING_EMBSTR) | lru | refcount | ptr | len | alloc | flags | buf | \0 |
+--------------------+---------------------------------+-------+----------+-----+-----+-------+-------+---------+----+
+--------------------+
| redisObject |
+--------------------+
| type |
| REDIS_STRING |
+--------------------+
| encoding |
| REDIS_ENCODING_RAW |
+--------------------+ +---------+
| ptr | ---> | sdshdr? |
+--------------------+ +---------+
| len |
+---------+
| alloc |
+---------+
| flags |
+---------++---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
| buf || T | h | e | r | e | | i | s | | n | o | | c | e | r | t | a |...|
+---------++---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
(2)list 类型
list 默认的编码类型为 OBJ_ENCODING_QUICKLIST quicklist:
- list-max-ziplist-size:每个 quicklist 节点上的 ziplist 长度
- list-compress-depth:quicklist 两端不压缩的节点数目
(3)hash 类型
hash 的编码类型有 OBJ_ENCODING_ZIPLIST ziplist 与 OBJ_ENCODING_HT hashtable,具体使用哪种编码受下面两个选项控制:
-
hash-max-ziplist-value:当 key 与 value 的长度都小于该值时使用 ziplist 编码(默认为 64)
-
hash-max-ziplist-entries:当 hash 中的元素数量小于该值时使用 ziplist 编码(默认为 512)
key 长度超过 64 的情况:
127.0.0.1:6379> HSET table x 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
(integer) 0
127.0.0.1:6379> OBJECT ENCODING table
"ziplist"
127.0.0.1:6379> HSET table x 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
(integer) 0
127.0.0.1:6379> OBJECT ENCODING table
"hashtable"
127.0.0.1:6379> DEL table
(integer) 1
127.0.0.1:6379> HSET table xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 'x'
(integer) 1
127.0.0.1:6379> OBJECT ENCODING table
"ziplist"
127.0.0.1:6379> HSET table xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 'x'
(integer) 1
127.0.0.1:6379> OBJECT ENCODING table
"hashtable"
value 长度超过 64 的情况:
127.0.0.1:6379> HSET table x 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
(integer) 0
127.0.0.1:6379> OBJECT ENCODING table
"ziplist"
127.0.0.1:6379> HSET table x 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
(integer) 0
127.0.0.1:6379> OBJECT ENCODING table
"hashtable"
127.0.0.1:6379> DEL table
(integer) 1
127.0.0.1:6379> HSET table xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 'x'
(integer) 1
127.0.0.1:6379> OBJECT ENCODING table
"ziplist"
127.0.0.1:6379> HSET table xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 'x'
(integer) 1
127.0.0.1:6379> OBJECT ENCODING table
"hashtable"
元素数量度超过 512 的情况:
127.0.0.1:6379> EVAL "for i=1,512 do redis.call('HSET', KEYS[1], i, i) end" 1 numbers
(nil)
127.0.0.1:6379> HLEN numbers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING numbers
"ziplist"
127.0.0.1:6379> DEL numbers
(integer) 1
127.0.0.1:6379> EVAL "for i=1,513 do redis.call('HSET', KEYS[1], i, i) end" 1 numbers
(nil)
127.0.0.1:6379> HLEN numbers
(integer) 513
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"
(4)set 类型
set 的编码类型有 OBJ_ENCODING_INTSET intset 与 OBJ_ENCODING_HT hashtable,具体使用哪种编码受下面两个选项控制:
- 当 set 中的所有元素都是整数时考虑使用 intset 编码,否则只能使用 hashtable 编码
- set-max-intset-entries:当 set 中的元素数量小于该值时使用 intset 编码(默认为 512)
包含非整数元素的情况:
127.0.0.1:6379> SADD set 1 2
(integer) 2
127.0.0.1:6379> OBJECT ENCODING set
"intset"
127.0.0.1:6379> SADD set "ABC"
(integer) 1
127.0.0.1:6379> OBJECT ENCODING set
"hashtable"
元素数量度超过 512 的情况:
127.0.0.1:6379> EVAL "for i=1,512 do redis.call('SADD', KEYS[1], i, i) end" 1 numbers
(nil)
127.0.0.1:6379> SCARD numbers
(integer) 512
127.0.0.1:6379> OBJECT ENCODING numbers
"intset"
127.0.0.1:6379> DEL numbers
(integer) 1
127.0.0.1:6379> EVAL "for i=1,513 do redis.call('SADD', KEYS[1], i, i) end" 1 numbers
(nil)
127.0.0.1:6379> SCARD numbers
(integer) 513
127.0.0.1:6379> OBJECT ENCODING numbers
"hashtable"
(5)zset 类型
set 的编码类型有 OBJ_ENCODING_ZIPLIST ziplist 与 OBJ_ENCODING_SKIPLIST skiplist。
使用 ziplist 编码时,每个集合元素使用两个相邻的 entry 节点保存,第一个节点保存成员值 member,第二节点保存元素的分值 score,并且 entry 按照 score 从小到大进行排序:
+----------------------+
| redisObject |
+----------------------+
| type |
| REDIS_ZSET |
+----------------------+
| encoding |
| OBJ_ENCODING_ZIPLIST |
+----------------------+ +----------+----------+---------+--------------------+-------------------+-----+-----------------------+--------------------+-------+
| ptr | ---> | zlbytes | zltail | zllen | entry 1 (member 1) | entry 2 (score 1) | ... | entry 2N-1 (member N) | entry 2N (score N) | zlend |
+----------------------+ +----------+----------+---------+--------------------+-------------------+-----+-----------------------+--------------------+-------+
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> score increase >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
使用 skiplist 实现时,使用会使用一个名为 zset 的数据结构:
使用 skiplist 实现时,使用会使用一个名为 zset 的数据结构:
typedef struct zset {
dict *dict; // 维护 member -> score 的映射,查找给的成员的分值
zskiplist *zsl; // 按 score 大小保存了所有集合元素,支持范围操作
} zset; // dict 与 zsl 会共享成员与分值
+----------------------+ +--------+ +------------+ +---------+
| redisObject | +-->| dictht | | StringObj | -> | long |
+----------------------+ +-------+ | +--------+ +------------+ +---------+
| type | +-->| dict | | | table | --> | StringObj | -> | long |
| REDIS_ZSET | | +-------+ | +--------+ +------------+ +---------+
+----------------------+ | | ht[0] | --+ | StringObj | -> | long |
| encoding | +--------+ | +-------+ +-----+ +------------+ +---------+
| OBJ_ENCODING_ZIPLIST | | zset | | | L32 | -> NULL
+----------------------+ +--------+ | +-----+
| ptr | ---> | dict | --+ | ... |
+----------------------+ +--------+ +--------+ +-----+ +-----------+ +-----------+
| zsl | ---> | header | --> | L4 | -> | L4 | ------------------> | L4 | -> NULL
+--------+ +--------+ +-----+ +-----------+ +-----------+
| tail | | L3 | -> | L3 | ------------------> | L3 | -> NULL
+--------+ +-----+ +-----------+ +-----------+ +-----------+
| level | | L2 | -> | L2 | -> | L2 | -> | L2 | -> NULL
+--------+ +-----+ +-----------+ +-----------+ +-----------+
| length | | L1 | -> | L1 | -> | L1 | -> | L1 | -> NULL
+--------+ +-----+ +-----------+ +-----------+ +-----------+
NULL <- | BW | <- | BW | <- | BW |
+-----------+ +-----------+ +-----------+
| StringObj | | StringObj | | StringObj |
+-----------+ +-----------+ +-----------+
| long | | long | | long |
+-----------+ +-----------+ +-----------+
zset 具体使用哪种编码受下面两个选项控制:
- zset-max-ziplist-value:当 member 的长度都小于该值时使用 ziplist 编码(默认为 64)
- zset-max-ziplist-entries:当 zset 中的元素数量小于该值时使用 ziplist 编码(默认为 128)
三、Redis 整体结构
每个数据库都是一个 redisDb 结构体:
typedef struct redisDb {
dict *dict; /* 据库的键空间 keyspace */
dict *expires; /* 设置了过期时间的 key 集合 */
dict *blocking_keys; /* 客户端阻塞等待的 key 集合 (BLPOP)*/
dict *ready_keys; /* 已就绪的阻塞 key 集合 (PUSH) */
dict *watched_keys; /* 在事务中监控受监控的 key 集合 */
int id; /* 数据库 ID */
long long avg_ttl; /* 平均 TTL, just for stats */
unsigned long expires_cursor; /* 过期检测指针 */
list *defrag_later; /* 内存碎片回收列表 */
} redisDb;
redis 所有数据库都保存着 redisServer.db 数组中,redisServer.dbnum 保存了数据库的数量,简化后的内存布局大致如下:
+-------------+
| redisServer |
+-------------+ +------------+------+-------------+
| db | -> | redisDb[0] | .... | redisDb[15] |
+-------------+ +------------+------+-------------+
| dbnum | |
| 16 | |
+-------------+ | +---------+ +------------+
+->| redisDb | +-> | ListObject |
+---------+ +------------+ | +------------+
| dict | -> | StringObj | --+
+---------+ +------------+ +------------+
| expires | | StringObj | ----> | HashObject |
+---------+ +------------+ +------------+
| | StringObj | --+
| +------------+ | +------------+
| +-> | StringObj |
| +------------+
|
| +------------+ +-------------+
+----> | StringObj | -> | long |
+------------+ +-------------+
| StringObj | -> | long |
+------------+ +-------------+
至此,redis 的几种编码方式都介绍完毕,后续将对 redis 的一些其他细节进行分享,感谢观看。
本群免费分享学习资料(C/C++,Linux,golang,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,ffmpeg,TCP/IP,协程,DPDK,嵌入式)等。更多的学习资料请加qqun:1106675687。