基础的set相关的命令,t_set.c

sadd——saddCommand

示例:SADD KEY_NAME VALUE1..VALUEN

  • 被添加到集合中的新元素的数量,不包括被忽略的元素。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
void saddCommand(redisClient *c) {
robj *set;
int j, added = 0;
// 取出集合对象
set = lookupKeyWrite(c->db,c->argv[1]);
// 对象不存在,创建一个新的,并将它关联到数据库
if (set == NULL) {
set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set);
// 对象存在,检查类型
} else {
if (set->type != REDIS_SET) {
addReply(c,shared.wrongtypeerr);
return;
}
}
// 将所有输入元素添加到集合中
for (j = 2; j < c->argc; j++) {
c->argv[j] = tryObjectEncoding(c->argv[j]);
// 只有元素未存在于集合时,才算一次成功添加
if (setTypeAdd(set,c->argv[j])) added++;
}
// 如果有至少一个元素被成功添加,那么执行以下程序
if (added) {
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[1],c->db->id);
}
// 将数据库设为脏
server.dirty += added;
// 返回添加元素的数量
addReplyLongLong(c,added);
}
/*
* 多态 add 操作
*
* 添加成功返回 1 ,如果元素已经存在,返回 0 。
*/
int setTypeAdd(robj *subject, robj *value) {
long long llval;
// 字典
if (subject->encoding == REDIS_ENCODING_HT) {
// 将 value 作为键, NULL 作为值,将元素添加到字典中
if (dictAdd(subject->ptr,value,NULL) == DICT_OK) {
incrRefCount(value);
return 1;
}
// intset
} else if (subject->encoding == REDIS_ENCODING_INTSET) {

// 如果对象的值可以编码为整数的话,那么将对象的值添加到 intset 中
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
uint8_t success = 0;
subject->ptr = intsetAdd(subject->ptr,llval,&success);
if (success) {
/* Convert to regular set when the intset contains
* too many entries. */
// 添加成功
// 检查集合在添加新元素之后是否需要转换为字典
if (intsetLen(subject->ptr) > server.set_max_intset_entries)
setTypeConvert(subject,REDIS_ENCODING_HT);
return 1;
}
// 如果对象的值不能编码为整数,那么将集合从 intset 编码转换为 HT 编码
// 然后再执行添加操作
} else {
/* Failed to get integer from object, convert to regular set. */
setTypeConvert(subject,REDIS_ENCODING_HT);
/* The set *was* an intset and this value is not integer
* encodable, so dictAdd should always work. */
redisAssertWithInfo(NULL,value,dictAdd(subject->ptr,value,NULL) == DICT_OK);
incrRefCount(value);
return 1;
}
// 未知编码
} else {
redisPanic("Unknown set encoding");
}
// 添加失败,元素已经存在
return 0;
}

srem——sremCommand

示例:SREM KEY MEMBER1..MEMBERN

  • 被成功移除的元素的数量,不包括被忽略的元素。
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
void sremCommand(redisClient *c) {
robj *set;
int j, deleted = 0, keyremoved = 0;
// 取出集合对象
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
// 删除输入的所有元素
for (j = 2; j < c->argc; j++) {

// 只有元素在集合中时,才算一次成功删除
if (setTypeRemove(set,c->argv[j])) {
deleted++;
// 如果集合已经为空,那么删除集合对象
if (setTypeSize(set) == 0) {
dbDelete(c->db,c->argv[1]);
keyremoved = 1;
break;
}
}
}
// 如果有至少一个元素被成功删除,那么执行以下程序
if (deleted) {
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
// 发送事件通知
if (keyremoved)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],
c->db->id);
// 将数据库设为脏
server.dirty += deleted;
}

// 将被成功删除元素的数量作为回复
addReplyLongLong(c,deleted);
}

smove——smoveCommand

示例:SMOVE SOURCE DESTINATION MEMBER

  • 如果成员元素被成功移除,返回 1 。 如果成员元素不是 source 集合的成员,并且没有任何操作对 destination 集合执行,那么返回 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
void smoveCommand(redisClient *c) {
robj *srcset, *dstset, *ele;
// 取出源集合
srcset = lookupKeyWrite(c->db,c->argv[1]);
// 取出目标集合
dstset = lookupKeyWrite(c->db,c->argv[2]);
// 编码元素
ele = c->argv[3] = tryObjectEncoding(c->argv[3]);
/* If the source key does not exist return 0 */
// 源集合不存在,直接返回
if (srcset == NULL) {
addReply(c,shared.czero);
return;
}
/* If the source key has the wrong type, or the destination key
* is set and has the wrong type, return with an error. */
// 如果源集合的类型错误
// 或者目标集合存在、但是类型错误
// 那么直接返回
if (checkType(c,srcset,REDIS_SET) ||
(dstset && checkType(c,dstset,REDIS_SET))) return;
/* If srcset and dstset are equal, SMOVE is a no-op */
// 如果源集合和目标集合相等,那么直接返回
if (srcset == dstset) {
addReply(c,shared.cone);
return;
}
/* If the element cannot be removed from the src set, return 0. */
// 从源集合中移除目标元素
// 如果目标元素不存在于源集合中,那么直接返回
if (!setTypeRemove(srcset,ele)) {
addReply(c,shared.czero);
return;
}
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"srem",c->argv[1],c->db->id);
/* Remove the src set from the database when empty */
// 如果源集合已经为空,那么将它从数据库中删除
if (setTypeSize(srcset) == 0) {
// 删除集合对象
dbDelete(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
signalModifiedKey(c->db,c->argv[2]);
// 将数据库设为脏
server.dirty++;
/* Create the destination set when it doesn't exist */
// 如果目标集合不存在,那么创建它
if (!dstset) {
dstset = setTypeCreate(ele);
dbAdd(c->db,c->argv[2],dstset);
}
/* An extra key has changed when ele was successfully added to dstset */
// 将元素添加到目标集合
if (setTypeAdd(dstset,ele)) {
// 将数据库设为脏
server.dirty++;
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sadd",c->argv[2],c->db->id);
}
// 回复 1
addReply(c,shared.cone);
}

sismember——sismemberCommand

示例:SISMEMBER KEY VALUE

  • 如果成员元素是集合的成员,返回 1 。 如果成员元素不是集合的成员,或 key 不存在,返回 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
28
29
30
31
32
33
void sismemberCommand(redisClient *c) {
robj *set;
// 取出集合对象
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return;
// 编码输入元素
c->argv[2] = tryObjectEncoding(c->argv[2]);
// 检查是否存在
if (setTypeIsMember(set,c->argv[2]))
addReply(c,shared.cone);
else
addReply(c,shared.czero);
}
/*
* 多态 ismember 操作
*/
int setTypeIsMember(robj *subject, robj *value) {
long long llval;
// HT
if (subject->encoding == REDIS_ENCODING_HT) {
return dictFind((dict*)subject->ptr,value) != NULL;
// INTSET
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
if (isObjectRepresentableAsLongLong(value,&llval) == REDIS_OK) {
return intsetFind((intset*)subject->ptr,llval);
}
// 未知编码
} else {
redisPanic("Unknown set encoding");
}
// 查找失败
return 0;
}

scard——scardCommand

示例:SCARD KEY_NAME

  • 集合的数量。 当集合 key 不存在时,返回 0 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void scardCommand(redisClient *c) {
robj *o;
// 取出集合
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,o,REDIS_SET)) return;
// 返回集合的基数
addReplyLongLong(c,setTypeSize(o));
}
/*
* 集合多态 size 函数
*/
unsigned long setTypeSize(robj *subject) {
if (subject->encoding == REDIS_ENCODING_HT) {
return dictSize((dict*)subject->ptr);
} else if (subject->encoding == REDIS_ENCODING_INTSET) {
return intsetLen((intset*)subject->ptr);
} else {
redisPanic("Unknown set encoding");
}
}

spop——spopCommand

示例:SPOP KEY

  • 被移除的随机元素。 当集合不存在或是空集时,返回 nil 。
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
42
43
44
45
46
47
48
49
50
51
void spopCommand(redisClient *c) {
robj *set, *ele, *aux;
int64_t llele;
int encoding;
// 取出集合
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
// 从集合中随机取出一个元素
encoding = setTypeRandomElement(set,&ele,&llele);
// 将被取出元素从集合中删除
if (encoding == REDIS_ENCODING_INTSET) {
ele = createStringObjectFromLongLong(llele);
set->ptr = intsetRemove(set->ptr,llele,NULL);
} else {
incrRefCount(ele);
setTypeRemove(set,ele);
}
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"spop",c->argv[1],c->db->id);
/* Replicate/AOF this command as an SREM operation */
// 将这个命令当作 SREM 来传播,防止产生有害的随机性,导致数据不一致
// (不同的服务器随机删除不同的元素)
aux = createStringObject("SREM",4);
rewriteClientCommandVector(c,3,aux,c->argv[1],ele);
decrRefCount(ele);
decrRefCount(aux);
// 返回回复
addReplyBulk(c,ele);
// 如果集合已经为空,那么从数据库中删除它
if (setTypeSize(set) == 0) {
// 删除集合
dbDelete(c->db,c->argv[1]);
// 发送事件通知
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",c->argv[1],c->db->id);
}
// 发送键修改信号
signalModifiedKey(c->db,c->argv[1]);
// 将数据库设为脏
server.dirty++;
}
int setTypeRandomElement(robj *setobj, robj **objele, int64_t *llele) {
if (setobj->encoding == REDIS_ENCODING_HT) {
dictEntry *de = dictGetRandomKey(setobj->ptr);
*objele = dictGetKey(de);
} else if (setobj->encoding == REDIS_ENCODING_INTSET) {
*llele = intsetRandom(setobj->ptr);
} else {
redisPanic("Unknown set encoding");
}
return setobj->encoding;
}

srandmember——srandmemberCommand

示例:SRANDMEMBER KEY [count]

  • 只提供集合 key 参数时,返回一个元素;如果集合为空,返回 nil 。 如果提供了 count 参数,那么返回一个数组;如果集合为空,返回空数组。
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
void srandmemberCommand(redisClient *c) {
robj *set, *ele;
int64_t llele;
int encoding;
// 如果带有 count 参数,那么调用 srandmemberWithCountCommand 来处理
if (c->argc == 3) {
srandmemberWithCountCommand(c);
return;
// 参数错误
} else if (c->argc > 3) {
addReply(c,shared.syntaxerr);
return;
}
// 随机取出单个元素就可以了
// 取出集合对象
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,set,REDIS_SET)) return;
// 随机取出一个元素
encoding = setTypeRandomElement(set,&ele,&llele);
// 回复
if (encoding == REDIS_ENCODING_INTSET) {
addReplyBulkLongLong(c,llele);
} else {
addReplyBulk(c,ele);
}
}

sinter——sinterCommand

示例:SINTER KEY KEY1..KEYN

  • 交集成员的列表。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
void sinterCommand(redisClient *c) {
sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
}


void sinterGenericCommand(redisClient *c, robj **setkeys, unsigned long setnum, robj *dstkey) {
// 集合数组
robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si;
robj *eleobj, *dstset = NULL;
int64_t intobj;
void *replylen = NULL;
unsigned long j, cardinality = 0;
int encoding;
for (j = 0; j < setnum; j++) {
// 取出对象
// 第一次执行时,取出的是 dest 集合
// 之后执行时,取出的都是 source 集合
robj *setobj = dstkey ?
lookupKeyWrite(c->db,setkeys[j]) :
lookupKeyRead(c->db,setkeys[j]);
// 对象不存在,放弃执行,进行清理
if (!setobj) {
zfree(sets);
if (dstkey) {
if (dbDelete(c->db,dstkey)) {
signalModifiedKey(c->db,dstkey);
server.dirty++;
}
addReply(c,shared.czero);
} else {
addReply(c,shared.emptymultibulk);
}
return;
}

// 检查对象的类型
if (checkType(c,setobj,REDIS_SET)) {
zfree(sets);
return;
}
// 将数组指针指向集合对象
sets[j] = createSetObject;
}
/* Sort sets from the smallest to largest, this will improve our
* algorithm's performance */
// 按基数对集合进行排序,这样提升算法的效率
qsort(sets,setnum,sizeof(robj*),qsortCompareSetsByCardinality);
/* The first thing we should output is the total number of elements...
* since this is a multi-bulk write, but at this stage we don't know
* the intersection set size, so we use a trick, append an empty object
* to the output list and save the pointer to later modify it with the
* right length */
// 因为不知道结果集会有多少个元素,所有没有办法直接设置回复的数量
// 这里使用了一个小技巧,直接使用一个 BUFF 列表,
// 然后将之后的回复都添加到列表中
if (!dstkey) {
replylen = addDeferredMultiBulkLength(c);
} else {
/* If we have a target key where to store the resulting set
* create this key with an empty set inside */
dstset = createIntsetObject();
}
/* Iterate all the elements of the first (smallest) set, and test
* the element against all the other sets, if at least one set does
* not include the element it is discarded */
// 遍历基数最小的第一个集合
// 并将它的元素和所有其他集合进行对比
// 如果有至少一个集合不包含这个元素,那么这个元素不属于交集
si = setTypeInitIterator(sets[0]);
while((encoding = setTypeNext(si,&eleobj,&intobj)) != -1) {
// 遍历其他集合,检查元素是否在这些集合中存在
for (j = 1; j < setnum; j++) {
// 跳过第一个集合,因为它是结果集的起始值
if (sets[j] == sets[0]) continue;
// 元素的编码为 INTSET
// 在其他集合中查找这个对象是否存在
if (encoding == REDIS_ENCODING_INTSET) {
/* intset with intset is simple... and fast */
if (sets[j]->encoding == REDIS_ENCODING_INTSET &&
!intsetFind((intset*)sets[j]->ptr,intobj))
{
break;
/* in order to compare an integer with an object we
* have to use the generic function, creating an object
* for this */
} else if (sets[j]->encoding == REDIS_ENCODING_HT) {
eleobj = createStringObjectFromLongLong(intobj);
if (!setTypeIsMember(sets[j],eleobj)) {
decrRefCount(eleobj);
break;
}
decrRefCount(eleobj);
}
// 元素的编码为 字典
// 在其他集合中查找这个对象是否存在
} else if (encoding == REDIS_ENCODING_HT) {
/* Optimization... if the source object is integer
* encoded AND the target set is an intset, we can get
* a much faster path. */
if (eleobj->encoding == REDIS_ENCODING_INT &&
sets[j]->encoding == REDIS_ENCODING_INTSET &&
!intsetFind((intset*)sets[j]->ptr,(long)eleobj->ptr))
{
break;
/* else... object to object check is easy as we use the
* type agnostic API here. */
} else if (!setTypeIsMember(sets[j],eleobj)) {
break;
}
}
}
/* Only take action when all sets contain the member */
// 如果所有集合都带有目标元素的话,那么执行以下代码
if (j == setnum) {
// SINTER 命令,直接返回结果集元素
if (!dstkey) {
if (encoding == REDIS_ENCODING_HT)
addReplyBulk(c,eleobj);
else
addReplyBulkLongLong(c,intobj);
cardinality++;
// SINTERSTORE 命令,将结果添加到结果集中
} else {
if (encoding == REDIS_ENCODING_INTSET) {
eleobj = createStringObjectFromLongLong(intobj);
setTypeAdd(dstset,eleobj);
decrRefCount(eleobj);
} else {
setTypeAdd(dstset,eleobj);
}
}
}
}
setTypeReleaseIterator(si);
// SINTERSTORE 命令,将结果集关联到数据库
if (dstkey) {
/* Store the resulting set into the target, if the intersection
* is not an empty set. */
// 删除现在可能有的 dstkey
int deleted = dbDelete(c->db,dstkey);
// 如果结果集非空,那么将它关联到数据库中
if (setTypeSize(dstset) > 0) {
dbAdd(c->db,dstkey,dstset);
addReplyLongLong(c,setTypeSize(dstset));
notifyKeyspaceEvent(REDIS_NOTIFY_SET,"sinterstore",
dstkey,c->db->id);
} else {
decrRefCount(dstset);
addReply(c,shared.czero);
if (deleted)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
dstkey,c->db->id);
}
signalModifiedKey(c->db,dstkey);
server.dirty++;
// SINTER 命令,回复结果集的基数
} else {
setDeferredMultiBulkLength(c,replylen,cardinality);
}
zfree(sets);
}

sinterstore——sinterstoreCommand

示例:SINTERSTORE DESTINATION_KEY KEY KEY1..KEYN

  • 返回存储交集的集合的元素数量。
1
2
3
void sinterstoreCommand(redisClient *c) {
sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]);
}

sunion——sunionCommand

示例:SUNION KEY KEY1..KEYN

  • 并集成员的列表。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
void sunionCommand(redisClient *c) {
sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,REDIS_OP_UNION);
}
/*
* 命令的类型
*/
#define REDIS_OP_UNION 0
#define REDIS_OP_DIFF 1
#define REDIS_OP_INTER 2
void sunionDiffGenericCommand(redisClient *c, robj **setkeys, int setnum, robj *dstkey, int op) {
// 集合数组
robj **sets = zmalloc(sizeof(robj*)*setnum);
setTypeIterator *si;
robj *ele, *dstset = NULL;
int j, cardinality = 0;
int diff_algo = 1;
// 取出所有集合对象,并添加到集合数组中
for (j = 0; j < setnum; j++) {
robj *setobj = dstkey ?
lookupKeyWrite(c->db,setkeys[j]) :
lookupKeyRead(c->db,setkeys[j]);
// 不存在的集合当作 NULL 来处理
if (!setobj) {
sets[j] = NULL;
continue;
}
// 有对象不是集合,停止执行,进行清理
if (checkType(c,setobj,REDIS_SET)) {
zfree(sets);
return;
}
// 记录对象
sets[j] = setobj;
}
/* Select what DIFF algorithm to use.
*
* 选择使用那个算法来执行计算
*
* Algorithm 1 is O(N*M) where N is the size of the element first set
* and M the total number of sets.
*
* 算法 1 的复杂度为 O(N*M) ,其中 N 为第一个集合的基数,
* 而 M 则为其他集合的数量。
*
* Algorithm 2 is O(N) where N is the total number of elements in all
* the sets.
*
* 算法 2 的复杂度为 O(N) ,其中 N 为所有集合中的元素数量总数。
*
* We compute what is the best bet with the current input here.
*
* 程序通过考察输入来决定使用那个算法
*/
if (op == REDIS_OP_DIFF && sets[0]) {
long long algo_one_work = 0, algo_two_work = 0;
// 遍历所有集合
for (j = 0; j < setnum; j++) {
if (sets[j] == NULL) continue;
// 计算 setnum 乘以 sets[0] 的基数之积
algo_one_work += setTypeSize(sets[0]);
// 计算所有集合的基数之和
algo_two_work += setTypeSize(sets[j]);
}
/* Algorithm 1 has better constant times and performs less operations
* if there are elements in common. Give it some advantage. */
// 算法 1 的常数比较低,优先考虑算法 1
algo_one_work /= 2;
diff_algo = (algo_one_work <= algo_two_work) ? 1 : 2;
if (diff_algo == 1 && setnum > 1) {
/* With algorithm 1 it is better to order the sets to subtract
* by decreasing size, so that we are more likely to find
* duplicated elements ASAP. */
// 如果使用的是算法 1 ,那么最好对 sets[0] 以外的其他集合进行排序
// 这样有助于优化算法的性能
qsort(sets+1,setnum-1,sizeof(robj*),
qsortCompareSetsByRevCardinality);
}
}
/* We need a temp set object to store our union. If the dstkey
* is not NULL (that is, we are inside an SUNIONSTORE operation) then
* this set object will be the resulting object to set into the target key
*
* 使用一个临时集合来保存结果集,如果程序执行的是 SUNIONSTORE 命令,
* 那么这个结果将会成为将来的集合值对象。
*/
dstset = createIntsetObject();
// 执行的是并集计算
if (op == REDIS_OP_UNION) {
/* Union is trivial, just add every element of every set to the
* temporary set. */
// 遍历所有集合,将元素添加到结果集里就可以了
for (j = 0; j < setnum; j++) {
if (!sets[j]) continue; /* non existing keys are like empty sets */
si = setTypeInitIterator(sets[j]);
while((ele = setTypeNextObject(si)) != NULL) {
// setTypeAdd 只在集合不存在时,才会将元素添加到集合,并返回 1
if (setTypeAdd(dstset,ele)) cardinality++;
decrRefCount(ele);
}
setTypeReleaseIterator(si);
}
// 执行的是差集计算,并且使用算法 1
} else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 1) {
/* DIFF Algorithm 1:
*
* 差集算法 1 :
*
* We perform the diff by iterating all the elements of the first set,
* and only adding it to the target set if the element does not exist
* into all the other sets.
*
* 程序遍历 sets[0] 集合中的所有元素,
* 并将这个元素和其他集合的所有元素进行对比,
* 只有这个元素不存在于其他所有集合时,
* 才将这个元素添加到结果集。
*
* This way we perform at max N*M operations, where N is the size of
* the first set, and M the number of sets.
*
* 这个算法执行最多 N*M 步, N 是第一个集合的基数,
* 而 M 是其他集合的数量。
*/
si = setTypeInitIterator(sets[0]);
while((ele = setTypeNextObject(si)) != NULL) {
// 检查元素在其他集合是否存在
for (j = 1; j < setnum; j++) {
if (!sets[j]) continue; /* no key is an empty set. */
if (sets[j] == sets[0]) break; /* same set! */
if (setTypeIsMember(sets[j],ele)) break;
}
// 只有元素在所有其他集合中都不存在时,才将它添加到结果集中
if (j == setnum) {
/* There is no other set with this element. Add it. */
setTypeAdd(dstset,ele);
cardinality++;
}
decrRefCount(ele);
}
setTypeReleaseIterator(si);
// 执行的是差集计算,并且使用算法 2
} else if (op == REDIS_OP_DIFF && sets[0] && diff_algo == 2) {
/* DIFF Algorithm 2:
*
* 差集算法 2 :
*
* Add all the elements of the first set to the auxiliary set.
* Then remove all the elements of all the next sets from it.
*
* 将 sets[0] 的所有元素都添加到结果集中,
* 然后遍历其他所有集合,将相同的元素从结果集中删除。
*
* This is O(N) where N is the sum of all the elements in every set.
*
* 算法复杂度为 O(N) ,N 为所有集合的基数之和。
*/
for (j = 0; j < setnum; j++) {
if (!sets[j]) continue; /* non existing keys are like empty sets */
si = setTypeInitIterator(sets[j]);
while((ele = setTypeNextObject(si)) != NULL) {
// sets[0] 时,将所有元素添加到集合
if (j == 0) {
if (setTypeAdd(dstset,ele)) cardinality++;
// 不是 sets[0] 时,将所有集合从结果集中移除
} else {
if (setTypeRemove(dstset,ele)) cardinality--;
}
decrRefCount(ele);
}
setTypeReleaseIterator(si);
/* Exit if result set is empty as any additional removal
* of elements will have no effect. */
if (cardinality == 0) break;
}
}
/* Output the content of the resulting set, if not in STORE mode */
// 执行的是 SDIFF 或者 SUNION
// 打印结果集中的所有元素
if (!dstkey) {
addReplyMultiBulkLen(c,cardinality);
// 遍历并回复结果集中的元素
si = setTypeInitIterator(dstset);
while((ele = setTypeNextObject(si)) != NULL) {
addReplyBulk(c,ele);
decrRefCount(ele);
}
setTypeReleaseIterator(si);
decrRefCount(dstset);
// 执行的是 SDIFFSTORE 或者 SUNIONSTORE
} else {
/* If we have a target key where to store the resulting set
* create this key with the result set inside */
// 现删除现在可能有的 dstkey
int deleted = dbDelete(c->db,dstkey);
// 如果结果集不为空,将它关联到数据库中
if (setTypeSize(dstset) > 0) {
dbAdd(c->db,dstkey,dstset);
// 返回结果集的基数
addReplyLongLong(c,setTypeSize(dstset));
notifyKeyspaceEvent(REDIS_NOTIFY_SET,
op == REDIS_OP_UNION ? "sunionstore" : "sdiffstore",
dstkey,c->db->id);
// 结果集为空
} else {
decrRefCount(dstset);
// 返回 0
addReply(c,shared.czero);
if (deleted)
notifyKeyspaceEvent(REDIS_NOTIFY_GENERIC,"del",
dstkey,c->db->id);
}
signalModifiedKey(c->db,dstkey);
server.dirty++;
}
zfree(sets);
}

sunionstore——sunionstoreCommand

示例:SUNIONSTORE DESTINATION KEY KEY1..KEYN

  • 结果集中的元素数量。
1
2
3
void sunionstoreCommand(redisClient *c) {
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_UNION);
}

sdiff——sdiffCommand

示例:SDIFF FIRST_KEY OTHER_KEY1..OTHER_KEYN

  • 包含差集成员的列表。
1
2
3
void sdiffCommand(redisClient *c) {
sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,REDIS_OP_DIFF);
}

sdiffstore——sdiffstoreCommand

示例:SDIFFSTORE DESTINATION_KEY KEY1..KEYN

  • 结果集中的元素数量。
1
2
3
void sdiffstoreCommand(redisClient *c) {
sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],REDIS_OP_DIFF);
}

smembers——sinterCommand

示例:SMEMBERS key

  • 集合中的所有成员。
1
2
3
void sinterCommand(redisClient *c) {
sinterGenericCommand(c,c->argv+1,c->argc-1,NULL);
}

sscan——sscanCommand

示例:SSCAN key cursor [MATCH pattern] [COUNT count]

  • 数组列表。
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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
void sscanCommand(redisClient *c) {
robj *set;
unsigned long cursor;
if (parseScanCursorOrReply(c,c->argv[2],&cursor) == REDIS_ERR) return;
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL ||
checkType(c,set,REDIS_SET)) return;
scanGenericCommand(c,set,cursor);
}

/* This command implements SCAN, HSCAN and SSCAN commands.
*
* 这是 SCAN 、 HSCAN 、 SSCAN 命令的实现函数。
*
* If object 'o' is passed, then it must be a Hash or Set object, otherwise
* if 'o' is NULL the command will operate on the dictionary associated with
* the current database.
*
* 如果给定了对象 o ,那么它必须是一个哈希对象或者集合对象,
* 如果 o 为 NULL 的话,函数将使用当前数据库作为迭代对象。
*
* When 'o' is not NULL the function assumes that the first argument in
* the client arguments vector is a key so it skips it before iterating
* in order to parse options.
*
* 如果参数 o 不为 NULL ,那么说明它是一个键对象,函数将跳过这些键对象,
* 对给定的命令选项进行分析(parse)。
*
* In the case of a Hash object the function returns both the field and value
* of every element on the Hash.
*
* 如果被迭代的是哈希对象,那么函数返回的是键值对。
*/
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
int rv;
int i, j;
char buf[REDIS_LONGSTR_SIZE];
list *keys = listCreate();
listNode *node, *nextnode;
long count = 10;
sds pat;
int patlen, use_pattern = 0;
dict *ht;
/* Object must be NULL (to iterate keys names), or the type of the object
* must be Set, Sorted Set, or Hash. */
// 输入类型检查
redisAssert(o == NULL || o->type == REDIS_SET || o->type == REDIS_HASH ||
o->type == REDIS_ZSET);
/* Set i to the first option argument. The previous one is the cursor. */
// 设置第一个选项参数的索引位置
// 0 1 2 3
// SCAN OPTION <op_arg> SCAN 命令的选项值从索引 2 开始
// HSCAN <key> OPTION <op_arg> 而其他 *SCAN 命令的选项值从索引 3 开始
i = (o == NULL) ? 2 : 3; /* Skip the key argument if needed. */
/* Step 1: Parse options. */
// 分析选项参数
while (i < c->argc) {
j = c->argc - i;
// COUNT <number>
if (!strcasecmp(c->argv[i]->ptr, "count") && j >= 2) {
if (getLongFromObjectOrReply(c, c->argv[i+1], &count, NULL)
!= REDIS_OK)
{
goto cleanup;
}
if (count < 1) {
addReply(c,shared.syntaxerr);
goto cleanup;
}
i += 2;
// MATCH <pattern>
} else if (!strcasecmp(c->argv[i]->ptr, "match") && j >= 2) {
pat = c->argv[i+1]->ptr;
patlen = sdslen(pat);
/* The pattern always matches if it is exactly "*", so it is
* equivalent to disabling it. */
use_pattern = !(pat[0] == '*' && patlen == 1);
i += 2;
// error
} else {
addReply(c,shared.syntaxerr);
goto cleanup;
}
}
/* Step 2: Iterate the collection.
*
* Note that if the object is encoded with a ziplist, intset, or any other
* representation that is not a hash table, we are sure that it is also
* composed of a small number of elements. So to avoid taking state we
* just return everything inside the object in a single call, setting the
* cursor to zero to signal the end of the iteration. */
// 如果对象的底层实现为 ziplist 、intset 而不是哈希表,
// 那么这些对象应该只包含了少量元素,
// 为了保持不让服务器记录迭代状态的设计
// 我们将 ziplist 或者 intset 里面的所有元素都一次返回给调用者
// 并向调用者返回游标(cursor) 0
/* Handle the case of a hash table. */
ht = NULL;
if (o == NULL) {
// 迭代目标为数据库
ht = c->db->dict;
} else if (o->type == REDIS_SET && o->encoding == REDIS_ENCODING_HT) {
// 迭代目标为 HT 编码的集合
ht = o->ptr;
} else if (o->type == REDIS_HASH && o->encoding == REDIS_ENCODING_HT) {
// 迭代目标为 HT 编码的哈希
ht = o->ptr;
count *= 2; /* We return key / value for this type. */
} else if (o->type == REDIS_ZSET && o->encoding == REDIS_ENCODING_SKIPLIST) {
// 迭代目标为 HT 编码的跳跃表
zset *zs = o->ptr;
ht = zs->dict;
count *= 2; /* We return key / value for this type. */
}
if (ht) {
void *privdata[2];
/* We pass two pointers to the callback: the list to which it will
* add new elements, and the object containing the dictionary so that
* it is possible to fetch more data in a type-dependent way. */
// 我们向回调函数传入两个指针:
// 一个是用于记录被迭代元素的列表
// 另一个是字典对象
// 从而实现类型无关的数据提取操作
privdata[0] = keys;
privdata[1] = o;
do {
cursor = dictScan(ht, cursor, scanCallback, privdata);
} while (cursor && listLength(keys) < count);
} else if (o->type == REDIS_SET) {
int pos = 0;
int64_t ll;
while(intsetGet(o->ptr,pos++,&ll))
listAddNodeTail(keys,createStringObjectFromLongLong(ll));
cursor = 0;
} else if (o->type == REDIS_HASH || o->type == REDIS_ZSET) {
unsigned char *p = ziplistIndex(o->ptr,0);
unsigned char *vstr;
unsigned int vlen;
long long vll;
while(p) {
ziplistGet(p,&vstr,&vlen,&vll);
listAddNodeTail(keys,
(vstr != NULL) ? createStringObject((char*)vstr,vlen) :
createStringObjectFromLongLong(vll));
p = ziplistNext(o->ptr,p);
}
cursor = 0;
} else {
redisPanic("Not handled encoding in SCAN.");
}
/* Step 3: Filter elements. */
node = listFirst(keys);
while (node) {
robj *kobj = listNodeValue(node);
nextnode = listNextNode(node);
int filter = 0;
/* Filter element if it does not match the pattern. */
if (!filter && use_pattern) {
if (sdsEncodedObject(kobj)) {
if (!stringmatchlen(pat, patlen, kobj->ptr, sdslen(kobj->ptr), 0))
filter = 1;
} else {
char buf[REDIS_LONGSTR_SIZE];
int len;
redisAssert(kobj->encoding == REDIS_ENCODING_INT);
len = ll2string(buf,sizeof(buf),(long)kobj->ptr);
if (!stringmatchlen(pat, patlen, buf, len, 0)) filter = 1;
}
}
/* Filter element if it is an expired key. */
if (!filter && o == NULL && expireIfNeeded(c->db, kobj)) filter = 1;
/* Remove the element and its associted value if needed. */
if (filter) {
decrRefCount(kobj);
listDelNode(keys, node);
}
/* If this is a hash or a sorted set, we have a flat list of
* key-value elements, so if this element was filtered, remove the
* value, or skip it if it was not filtered: we only match keys. */
if (o && (o->type == REDIS_ZSET || o->type == REDIS_HASH)) {
node = nextnode;
nextnode = listNextNode(node);
if (filter) {
kobj = listNodeValue(node);
decrRefCount(kobj);
listDelNode(keys, node);
}
}
node = nextnode;
}
/* Step 4: Reply to the client. */
addReplyMultiBulkLen(c, 2);
rv = snprintf(buf, sizeof(buf), "%lu", cursor);
redisAssert(rv < sizeof(buf));
addReplyBulkCBuffer(c, buf, rv);
addReplyMultiBulkLen(c, listLength(keys));
while ((node = listFirst(keys)) != NULL) {
robj *kobj = listNodeValue(node);
addReplyBulk(c, kobj);
decrRefCount(kobj);
listDelNode(keys, node);
}
cleanup:
listSetFreeMethod(keys,decrRefCountVoid);
listRelease(keys);
}
命令 作用 intset编码时间复杂度 hashtable编码时间复杂度
SADD 将一个或多个 member 元素加入到集合 key 当中 O(N) O(1)
SCARD 返回指定集合所包含的元素数量 O(1) O(1)
SISMEMBER 判断元素是否是指定集合中的成员 O(log(n)) O(1)
SMEMBERS 返回指定集合的所有成员 O(n) O(n)
SRANDMEMBER 返回指定集合的count个随机成员 O(1) O(1)
SPOP 移除并返回指定集合中的一个随机元素 O(N) O(1)
SREM 移除指定集合中一个或者多个元素 O(N) O(1)
1