redis是一个开源的、基于内存的存储结构[使用C语言编写],通常被用于缓存、消息订阅等场景。

redis的特点

  • 基于内存:redis借助RAM提供高速的数据访问,比磁盘要快几个量级

redis的基础数据类型

redis是键值对数据库,redis中的键均为字符串,redis中最基础的五种数据类型:stringlsitsetzsethash
redis中使用redisObject来描述所有的key和value,redisObject的结构体定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct redisObject {
// 数据类型, 占4bit
unsigned type:4;
// 编码格式 占4bit
unsigned encoding:4;
// 此对象最后一次被访问的时间 占24bit
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */
// 引用计数
int refcount;
// 指向底层的数据实例的指针
void *ptr;
};

其中, type 标识数据类型,占4个比特,encoding标识具体的编码格式,占4个比特,lru占24个字节,用以记录最近一次访问时间,refcount为引用计数,*ptr则指向底层的数据结构实现。

type

redisObject.type的取值如下:

1
2
3
4
5
6
/* The actual Redis Object */
#define OBJ_STRING 0 /* String object. */
#define OBJ_LIST 1 /* List object. */
#define OBJ_SET 2 /* Set object. */
#define OBJ_ZSET 3 /* Sorted set object. */
#define OBJ_HASH 4 /* Hash object. */

在客户端中,可以使用type命令获取每种数据类型的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# string
182.168.168.226:6379> set age 18
-> Redirected to slot [741] located at 182.168.106.129:6379
OK
182.168.106.129:6379> type age
string

# list
182.168.106.129:6379> lpush friends a b c
(integer) 3
182.168.106.129:6379> type friends
list

# set
182.168.106.129:6379> sadd ids 1 2 3
(integer) 3
182.168.106.129:6379> type ids
set


# zset
182.168.106.129:6379> zadd info 100 mqray 90 lmq
(integer) 2
182.168.168.226:6379> type info
zset

# hash
182.168.168.226:6379> hset myhset name mqray
(integer) 1
182.168.50.160:6379> type myhset
hash

encoding

在客户端中,可以使用object encoding key 获取到对象所使用的编码格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
182.168.106.129:6379> object encoding age
"int"
182.168.106.129:6379> object encoding name
-> Redirected to slot [5798] located at 182.168.168.226:6379
"embstr"
182.168.168.226:6379> object encoding friends
-> Redirected to slot [420] located at 182.168.106.129:6379
"quicklist"
182.168.50.160:6379> object encoding ids
-> Redirected to slot [3296] located at 182.168.106.129:6379
"intset"
182.168.106.129:6379> object encoding info
-> Redirected to slot [5642] located at 182.168.168.226:6379
"ziplist"
182.168.168.226:6379> object encoding myhset
-> Redirected to slot [13092] located at 182.168.50.160:6379
"ziplist"

redisObject.encoding取值如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* Objects encoding. Some kind of objects like Strings and Hashes can be
* internally represented in multiple ways. The 'encoding' field of the object
* is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#define OBJ_ENCODING_INT 1 /* Encoded as integer */
#define OBJ_ENCODING_HT 2 /* Encoded as hash table */
#define OBJ_ENCODING_ZIPMAP 3 /* No longer used: old hash encoding. */
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. */
#define OBJ_ENCODING_ZIPLIST 5 /* No longer used: old list/hash/zset encoding. */
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset */
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist */
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of listpacks */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
#define OBJ_ENCODING_LISTPACK 11 /* Encoded as a listpack */

在redis中,对于每种数据类型type,对用有多种编码encoding实现, 其映射关系如图所示:

redis可以根据不同的使用场景来对对象使用不同的编码,大大提高的redis的灵活性和效率.

类型检查与命令多态
内存回收
对象共享

lru

记录了此对象最后一次被命令程序访问的时间。用当前时间减去lru即为此对象的空转时长。可以使用 object idletime获取对象的空转时长。

1
2
182.168.50.160:6379> object idletime friends
(integer) 108

如果服务器开启了maxmemory选项,且服务器所使用的内存回收算法为volatile-lru或者allkeys-lru时,当服务器所使用的内存超出maxmemory设定的上限时,服务器会优先释放空转时长高的数据库键,以此回收内存。
其中,redis所使用的内存淘汰策略配置包括如下:

1
2
3
4
5
6
7
8
9
10
11
# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached. You can select one from the following behaviors:
#
# volatile-lru -> Evict using approximated LRU, only keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU, only keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key having an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.

通过配置maxmemory-policy以调整内存回收策略,默认值为noeviction,即不回收

refcount

  • 当新创建一个对象时,该对象的refcount属性被设置为1;
  • 当对一个对象共享时,该对象的refcount属性 +1;
  • 消除一个对象引用后,该对象的refcount属性 -1;
  • 当对象的refcount为0时,此redisObject及它指向的数据结构的内存将被释放。

string

string 类型是二进制安全的,可以用于存储任何数据。所谓的二进制安全,是指该二进制在写入时是什么样的,读取时就是怎样。反例就是c语言中字符串默认以\0结尾,而读取时不显示末尾的\0

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
set key value
// 为多个键设置值
mset key value [key value]
// 只有在key不尊在的情况下将key的值设置为value
setnx key value
// 将键key的值设置为value,并将key的过期时间设置为 seconds;如果key已存在,则覆盖已有值
setex key seconds value

// 以毫秒形式设置key的过期时间
psetex key millseconds value

// 获取key对应的值,不存在返回nil
get key
// 获取过期时间
ttl key

// 返回key对应值的字符串长度, key不存在时返回0; key对应值不为string时,返回错误
strlen key
// 如果key对应值是字符串,则将value追加到键的末尾,返回追加之后键key的值的长度
append key value

// 如果键key的值为int,则+1;如果不存在则先初始化为0,在执行incr;如果不为int,返回错误
incr key
// key 是int时,增量修改
incrby key increment
// 为键key存储的值加上浮点数increment
incrbyfloat key increment

sds 简单动态字符串

尽管redis由c实现,但是redis中并未使用cyuyan中的string,而是使用其封装作为redis中的字符串实现。

1
2
3
4
5
6
7
8
9
struct sdshdr {

// buf 中已占用空间的长度
int len;
// buf 中剩余可用空间的长度
int free;
// 数据空间
char buf[];
};

在redis3.2之后,为了更好地内存优化,sdshdr又被分为如下几种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
typedef char *sds;

/* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr16 {
uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 {
uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};
struct __attribute__ ((__packed__)) sdshdr64 {
uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */
char buf[];
};

type

string 对应的 数据类型为 string

1
2
3
4
5
6
182.168.168.226:6379> type long
string
182.168.168.226:6379> type name
string
182.168.168.226:6379> type age
string

encoding

string类型对应三种编码格式,分别为embstrintraw

int

如果一个字符串对象保存的是整数值,且这个整数值可以用long类型标识,那么此字符串对象会将这个整数值保存在redisObject.*ptr中,并将encoding设置为int

1
2
3
4
182.168.168.226:6379> set age 18
OK
182.168.106.129:6379> object encoding age
"int"

对应的redisObject如图所示:

raw

如果该字符串的长度大于44个字节,则将其对象编码设置为raw

1
2
3
4
5
6
182.168.106.129:6379> set long org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert
OK
182.168.106.129:6379> strlen long
(integer) 83
182.168.168.226:6379> object encoding long
"raw"
embstr

如果该字符串的长度小于等于44个字节,将使用embstr编码方式保存这个字符串值
embstr是专门用于保存短字符串的编码方式,和raw一样使用redisObject和sdshdr结构标识字符串对象,但是raw会调用两次内存分配函数分别创建redisObject和sdshdr结构,而embstr则只需要调用一次内存分配函数分配一块连续的空间,空间中依次包含redisObject和sdshdr。

1
2
3
4
5
6
10.65.196.57:32084> set name mqray
OK
182.168.168.226:6379> strlen name
(integer) 5
182.168.50.160:6379> object encoding name
"embstr"
编码转换

int、embstr编码的字符串在条件满足的情况下会被转化为raw编码的字符串
原先值为int的对象,在执行诸如append key value后将会变为raw编码的对象。且不会变为embstr编码的对象,因为redis没有提供embstr编码字符串的修改程序。
原先为embstr编码的对象,执行命令时,会先将对象修改为raw编码的字符串,再执行修改命令。

字符串分配源码

创建字符串对象时,会根据字符串对象的长度来使用不同的函数来创建。字符串长度大于44字节时,使用createRawStringObject创建,否则使用createEmbeddedStringObject创建。

1
2
3
4
5
6
7
8
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT)
return createEmbeddedStringObject(ptr,len);
else
return createRawStringObject(ptr,len);
}

createEmbeddedStringObject函数源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* Create a string object with encoding OBJ_ENCODING_EMBSTR, that is
* an object where the sds string is actually an unmodifiable string
* allocated in the same chunk as the object itself. */
robj *createEmbeddedStringObject(const char *ptr, size_t len) {
robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr8)+len+1);
struct sdshdr8 *sh = (void*)(o+1);

o->type = OBJ_STRING;
o->encoding = OBJ_ENCODING_EMBSTR;
o->ptr = sh+1;
o->refcount = 1;
o->lru = 0;

sh->len = len;
sh->alloc = len;
sh->flags = SDS_TYPE_8;
if (ptr == SDS_NOINIT)
sh->buf[len] = '\0';
else if (ptr) {
memcpy(sh->buf,ptr,len);
sh->buf[len] = '\0';
} else {
memset(sh->buf,0,len+1);
}
return o;
}

createRawStringObject函数内部通过调用createObject函数创建字符串对象,源码如下:

1
2
3
4
5
/* Create a string object with encoding OBJ_ENCODING_RAW, that is a plain
* string object where o->ptr points to a proper sds string. */
robj *createRawStringObject(const char *ptr, size_t len) {
return createObject(OBJ_STRING, sdsnewlen(ptr,len));
}
1
2
3
4
5
6
7
8
9
robj *createObject(int type, void *ptr) {
robj *o = zmalloc(sizeof(*o));
o->type = type;
o->encoding = OBJ_ENCODING_RAW;
o->ptr = ptr;
o->refcount = 1;
o->lru = 0;
return o;
}

redis内存管理基石 zmalloc [埋点预留]

redis内存管理基石

list

redis使用c语言实现,没有内置链表数据结构,redis使用双向链表实现了链表,可以将元素添加到列表头部或者尾部。

常用命令

常见的list操作如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 将一个值或多个值的value插入到 列表键key的表头;如果key不存在,则创建后执行lpush;如果不是list类型,则返回错误
lpush key value [value...]

// 当key存在,且type为list时,将value插入到列表键key的表头,命令执行后返回列表长度;
lpushx key value

// 将一个值或多个值插入到列表尾部
rpush key value [value...]

// 将value插入到列表键的表尾
rpushx key value

// 移除key的表头元素
lpop key

// 移除key的表尾元素
rpop key

// 返回列表长度
llen key

// 返回 列表key中,下标为index的元素,0代表第一个元素
lindex key index

// 返回列表 key 中指定区间的元素,0为第一个,-1为最后一个
lrange key start stop

实际操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
182.168.50.160:6379> lpush char a b c d e f
(integer) 6
182.168.168.226:6379> LRANGE char 0 -1
1) "f"
2) "e"
3) "d"
4) "c"
5) "b"
6) "a"
182.168.168.226:6379> OBJECT encoding char
"quicklist"
182.168.168.226:6379> llen char
(integer) 6
182.168.168.226:6379> lpop key 1
(nil)
182.168.50.160:6379> lpop char
"f"
182.168.168.226:6379> llen char
(integer) 5
182.168.168.226:6379> rpop char
"a"
182.168.168.226:6379> llen char
(integer) 4

type

redis中,列表对象对应的redisObject中type为list

1
2
182.168.168.226:6379> type char 
list

encoding

redis 3.2之前,列表对象采用的编码方式为 ziplistlinkedlsit,数据量小时使用ziplist,数据量大时则使用linkedlist。
由于链表的附加空间相对较高,prev和next指针占用16字节,每个节点的内存单独分配,会加剧内存的碎片化,影响内存管理效率。
后续的版本中,则使用quicklist替代了ziplistlinkedlsit。[为什么有这种优化?]

ziplist

压缩列表时为了节约内存而设计,由一系列页数编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意数量节点,每个节点可以保存一个字节数组或者整数值。
压缩链表的组成如下:

- zlbytes:整个 ziplist 占用的内存字节数 - zltail:到达 ziplist 表尾节点的偏移量 - zllen:ziplist 中节点的数量 - entryX: ziplist 所保存的节点 - zlend: 用于标记 ziplist 的末端
linkedlsit
quicklist

quicklist实际上是ziplist和linkedlist的混合体,将linkedlist按段区分,每个节点使用ziplist来紧凑存储,多个ziplist之间使用双向指针相连。
在64位机器上占用40个字节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/* quicklist is a 40 byte struct (on 64-bit systems) describing a quicklist.
* 'count' is the number of total entries.
* 'len' is the number of quicklist nodes.
* 'compress' is: 0 if compression disabled, otherwise it's the number
* of quicklistNodes to leave uncompressed at ends of quicklist.
* 'fill' is the user-requested (or default) fill factor.
* 'bookmarks are an optional feature that is used by realloc this struct,
* so that they don't consume memory when not used. */
typedef struct quicklist {
quicklistNode *head;
quicklistNode *tail;
unsigned long count; /* total count of all entries in all listpacks */
unsigned long len; /* number of quicklistNodes */
signed int fill : QL_FILL_BITS; /* fill factor for individual nodes */
unsigned int compress : QL_COMP_BITS; /* depth of end nodes not to compress;0=off */
unsigned int bookmark_count: QL_BM_BITS;
quicklistBookmark bookmarks[];
} quicklist;

```C
/* quicklistNode is a 32 byte struct describing a listpack for a quicklist.
* We use bit fields keep the quicklistNode at 32 bytes.
* count: 16 bits, max 65536 (max lp bytes is 65k, so max count actually < 32k).
* encoding: 2 bits, RAW=1, LZF=2.
* container: 2 bits, PLAIN=1 (a single item as char array), PACKED=2 (listpack with multiple items).
* recompress: 1 bit, bool, true if node is temporary decompressed for usage.
* attempted_compress: 1 bit, boolean, used for verifying during testing.
* extra: 10 bits, free for future use; pads out the remainder of 32 bits */
typedef struct quicklistNode {
struct quicklistNode *prev;
struct quicklistNode *next;
unsigned char *entry;
size_t sz; /* entry size in bytes */
unsigned int count : 16; /* count of items in listpack */
unsigned int encoding : 2; /* RAW==1 or LZF==2 */
unsigned int container : 2; /* PLAIN==1 or PACKED==2 */
unsigned int recompress : 1; /* was this node previous compressed? */
unsigned int attempted_compress : 1; /* node can't compress; too small */
unsigned int dont_compress : 1; /* prevent compression of entry that will be used later */
unsigned int extra : 9; /* more bits to steal for future usage */
} quicklistNode;
1
2
3
4
5
6
7
8
9
/* quicklistLZF is a 8+N byte struct holding 'sz' followed by 'compressed'.
* 'sz' is byte length of 'compressed' field.
* 'compressed' is LZF data with total (compressed) length 'sz'
* NOTE: uncompressed length is stored in quicklistNode->sz.
* When quicklistNode->entry is compressed, node->entry points to a quicklistLZF */
typedef struct quicklistLZF {
size_t sz; /* LZF size in bytes*/
char compressed[];
} quicklistLZF;
编码转换

如果列表元素小于512个,且列表的每个元素的所占空间均小于64字节,redis则使用ziplist作为底层数据结构;
否则将使用 linkedlist作为底层实现。
上述列表元素值由list-max-ziplist-entries配置,所占空间由list-max-ziplist-value配置。

使用场景

列表被用于多种功能,比如列表键、发布订阅、慢查询、监视器等

消息队列: 消息队列必须满足三个要求 消息顺序、处理重复的消息、保证消息可靠性
[不推荐,redis官方推荐使用stream]
使用lpop+rpush完成,反之亦可

  1. 消息保序
  2. 如何处理重复数据
  3. 如何保证消息可靠性
    参见: 如何保证消息可靠性

hash

redis中哈希用以存储字符串域和值之间的映射,通常被用来存储对象。

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 为字典key 设置field=key
hset key field value
// 仅当 field 不存在时设置
hsetnx key field value
// 批量设置
hmset key field value [feild value]
// 从hash对象中获取field的value
hget key field
// 获取多个field的值
hmget key field [field2]
// 判断 field 是否存在
hexists key field
// 删除field元素
hdel key field
// 获取key中 field个数
hlen key
hstrlen key field
// 如果field值为int类型,则将value + inc
hincrby key field inc
// 获取哈希表中所有的域
hkeys key
// 获取哈希表中所有的域的值
hvals key
// 获取整个哈希表的元素
hgetall key

实际操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
123.122.10.231:6379> hset profile name mqray
(integer) 1
123.122.10.231:6379> hmset profile age 18 gender male
OK
123.122.10.231:6379> hget profile name
"mqray"
123.122.10.231:6379> hmget profile name gender
1) "mqray"
2) "male"
123.122.10.231:6379> HEXISTS profile name
(integer) 1
123.122.10.231:6379> HEXISTS profile height
(integer) 0
123.122.10.231:6379> hlen profile
(integer) 3
123.122.10.231:6379> hincrby profile age 2
(integer) 20
123.122.10.231:6379> hkeys profile
1) "name"
2) "age"
3) "gender"
123.122.10.231:6379> hgetall profile
1) "name"
2) "mqray"
3) "age"
4) "20"
5) "gender"
6) "male"

type

上述命令操作是哈希表的命令,使用type命令查看此对象的类型

1
2
123.122.10.231:6379> type profile
hash

encoding

哈希中采用两种编码格式:ziplisthashtable
上述哈希对象所采用的编码格式为ziplist

1
2
123.122.10.231:6379> object encoding profile 
"ziplist"
ziplist

ziplist译作压缩列表,其定义位于redis/src/ziplist.h

hashtable
编码转换

当哈希对象同时满足如下两个条件时,使用ziplist编码。

  1. 哈希对象保存的所有键值对和值的字符串长度都小于64字节。
  2. 哈希对象保存的字符串键值对数量少于512个。
    否则使用hashtable编码。
    如上参数分别由 hash-max-listpack-valuEhash-max-listpack-entries两个参数控制。

使用场景

redis-hash使用场景

set

set(集合)是redis中的无需字符串集合,添加、查找、删除的时间复杂度为O(1) [为什么时间复杂度是-1?]

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 
sadd key value
sismember key value
spop key
srandmember
srem key value
smove key value
scard key
smembers key

// 求交集,如果其中key为空集则交集为空
sinter key [key1]

// 将 sinter key [key1] 求得的交集保存至 dest中
sinterstore dest key [key1]

// 并集
sunion key [key1]

// 将 sunion key [key1] 的结果保存至 dest, 如果dest存在,则覆盖
sunionstore dest key [key1]

// 返回差集
sdiff key [key1]

// 将 sdiff key [key1] 结果 保存至 dest
sdiffstore dest key [key1]

实际操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
182.168.168.226:6379> sadd dep xdr
-> Redirected to slot [3555] located at 182.168.106.129:6379
(integer) 1
182.168.106.129:6379> sadd dep edr
(integer) 1
182.168.106.129:6379> smembers dep
1) "xdr"
2) "edr"
182.168.106.129:6379> sadd dep af
(integer) 1
182.168.106.129:6379> sadd dep cwpp
(integer) 1
182.168.50.160:6379> SMEMBERS dep
1) "cwpp"
2) "xdr"
3) "af"
4) "edr

encoding

集合对象采用的编码方式为intsethashtable
当使用intset编码作为集合对象的底层实现时,集合对象包含的所有元素都被保留在整数集合中。
而使用hashtable编码作为底层实现时,字典的每一个键都是一个字符串对象,每个字符串对象包含一个集合元素,其值则全部被设置为NULL

1
2
3
4
182.168.106.129:6379> type dep
set
182.168.106.129:6379> OBJECT encoding dep
"hashtable"

如上设置的集合 dep, 其采用的编码即是hashtable

intset

如下是一个使用inset编码的集合实例:

1
2
3
4
123.122.228.34:6379> sadd nums 1 2 3
(integer) 3
123.122.10.231:6379> object encoding nums
"intset"

intset的源码如下:

1
2
3
4
5
typedef struct intset {
uint32_t encoding; // 编码方式
uint32_t length; // 集合包含的元素
int8_t contents[]; // 保存的元素数组
} intset;
hashtable
编码转换

当集合set对象同时满足如下两个条件时,集合的底层实现为intset

  1. 集合保存的所有元素都是整数值
  2. 集合对象保存的元素数量不超过512个
    任意一个条件不满足时,则使用hashtable编码。
    其中第2点中的元素个数限制由 set-max-intset-entries限制,默认是512个元素。

使用场景

  1. 使用交并集获取共同好友等。

zset

zset即有序集合,和set一样,存储string类型的元素集合,不允许重复的元素。zset中的每一个元素都会关联一个double类型的score。redis通过score为集合中的成员进行从小到大的排列。

常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 将一个或多个memeber及其score加入到zset key中
zadd key score member [score member...]
// 获取zset 中 成员member的score
zscore key member
// 为有序集 key 的成员 member的score 值加上增量 inc
zincrby key inc member
// 返回有序集合 key 的基数
zcard key
// 返回有序集合中score值在min和max之间的元素个数
zcount key min max
// 返回有序集合,指定区间内的成员
zrange key start stop
// 返回有序集合中member的排名,按score递增排列
zrank key member
// 移除语序集合中的一个或多个成员,不存在则忽略
zrem key member [member]

实际操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
123.122.10.231:6379> zadd rank 100 mqray 90 lmq 
(integer) 2
123.122.10.231:6379> zscore mqray
(error) ERR wrong number of arguments for 'zscore' command
123.122.10.231:6379> zscore rank mqray
"100"
123.122.10.231:6379> zincrby rank 100 mqray
"200"
123.122.10.231:6379> zcard rank
(integer) 2
123.122.10.231:6379> zadd rank lm 199
(error) ERR value is not a valid float
123.122.10.231:6379> zadd rank 199 lm
(integer) 1
123.122.10.231:6379> zcard rank
(integer) 3
123.122.10.231:6379> zcount rank
123.122.10.231:6379> zcount rank 100 200
(integer) 2
123.122.10.231:6379> zrem rank lm
(integer) 1
123.122.10.231:6379> zrange rank 0 -1
1) "lmq"
2) "mqray

type

上述的对象rank,其数据类型为zset

1
2
123.122.10.231:6379> type rank
zset

encoding

zset所采用两种编码方式,分别为:ziplistzskiplist

1
2
3
4
typedef struct zset {
dict *dict;
zskiplist *zsl;
} zset;
ziplist

参见4.3.1的说明

zskiplist

redis的跳表由redis.h/zskiplistNoderedis.h/zskiplist两个结构定义,其中:
zskiplistNode表示跳跃表节点,zskiplist则用于保存跳跃表节点的相关信息,比如节点数量,指向prev和post节点的指针。

1
2
3
4
5
typedef struct zskiplist {
struct zskiplistNode *header, *tail;
unsigned long length;
int level;
} zskiplist;

header: 指向跳跃表的表头节点
tail: 指向跳跃表的表尾节点
level:记录跳跃表内,层数最大的那个节点的层数[表头节点的层数不计算在内]
length: 记录跳跃表的长度,即目前跳跃表中包含的节点数量

1
2
3
4
5
6
7
8
9
typedef struct zskiplistNode {
sds ele;
double score; // 分值
struct zskiplistNode *backward; // 后退指针
struct zskiplistLevel {
struct zskiplistNode *forward; // 前进指针
unsigned long span; // 跨度
} level[];
} zskiplistNode;

如下是一个跳跃表实例:

编码转换

当有序集合对象同时满足如下两个条件时,采用ziplist编码:

  1. 有序集合对象保存的元素数量小于128个;
  2. 有序集合保存的所有元素的长度都小于64字节;
    否则,使用skiplist编码。上述两个值分别由配置zset-max-listpack-entrieszset-max-listpack-value控制。

使用场景

  1. 排行榜: 比如按关注数、浏览量等进行排名比较。

引用

1.redis.src.server.h
2.对象机制讲解
3.redis.conf
4. redis命令参考
5. redis设计与实现
6. redis.object.c
7. redis.sds.h
8. redis快速链表
9. redis.quicklist.h
10. redis.intset.h