文章目录
前言
redis有一个数据类型"set",它的特性是"无序,不重复"。遇到一些需要去重的场景可以使用redis的"set"进行存储。它的底层实现有两种,一个是"intset整数集合"是一种比较节省内存的结构,另一个是"ht哈希表"。本篇文章先介绍"intset","ht"在之后的文章介绍。
在读源码的过程中,发现在redis7.2.2版本中set的底层多了一种实现"listpack",我又去看了之前的版本发现之前的版本set的实现只有两种"intset"和"ht"。对于上一篇文章的出现的错误已改正。
saddCommand
先从"sadd(向集合中添加元素)"命令开始分析,
typedef struct client {
//此处省略
..........
int argc;//Num of arguments of current command.
robj **argv;//Arguments of current command.
.....................
//此处省略
}client;
void saddCommand(client *c) {
robj *set;
int j, added = 0;
/*
要操作的key已经被创建但是这个key的类型却不是"OBJ_SET"报错之后直接返回;
比方说,先使用"set m a"创建了一个string Object;
如果此时再使用"sadd m 2"命令欲创建一个set Object会报错如下图所示。
因为redis有一个全局的数据字典,所有的key都存储在里面,所以不同类型的key不能够重名。
*/
set = lookupKeyWrite(c->db,c->argv[1]);
if (checkType(c,set,OBJ_SET)) return;
/*
int checkType(client *c, robj *o, int type) {
if (o && o->type != type) {
addReplyErrorObject(c,shared.wrongtypeerr);
return 1;
}
return 0;
}
*/
if (set == NULL) {
//根据第一个value新建一个set Object,并选择合适的编码,
//"setTypeCreate",第一个参数表示命令中第一个加入集合中的value值,第二个参数表示加入集合的元素总数,具体分析在下面
set = setTypeCreate(c->argv[2]->ptr, c->argc - 2);
//将key-value的映射加入到全局dict中
dbAdd(c->db,c->argv[1],set);
} else {
//依据要加入集合中元素的个数判断是否需要转换编码
//需要的话则转换
setTypeMaybeConvert(set, c->argc - 2);
}
//将value值全部加入到set中
//"setTypeAdd"的分析在下面
for (j = 2; j < c->argc; j++) {
if (setTypeAdd(set,c->argv[j]->ptr)) added++;
}
if (added) {
signalModifiedKey(c,c->db,c->argv[1]);
notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[1],c->db->id);
- }
server.dirty += added;
addReplyLongLong(c,added);
}
setTypeCreate
首先看一下如何根据value创建合适的set Object
[redis-7.2.2\src\t_set.c]
/*
Factory method to return a set that *can* hold "value".
When the object has an integer-encodable value, an intset will be returned.
Otherwise a listpack or a regular hash table.
The size hint indicates approximately how many items will be added which is used to determine the initial representation.
*/
/*
setTypeCreate方法返回一个可以存储元素的集合.
如果可以整形编码,则会返回一个intset.
否则的话,返回一个listpack或者常规的ht.
size_hint表示大约有多少元素将被加入"set"
*/
robj *setTypeCreate(sds value, size_t size_hint) {
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK && size_hint <= server.set_max_intset_entries)
return createIntsetObject();
if (size_hint <= server.set_max_listpack_entries)
return createSetListpackObject();
/* We may oversize the set by using the hint if the hint is not accurate, but we will assume this is acceptable to maximize performance. */
robj *o = createSetObject();
dictExpand(o->ptr, size_hint);
return o;
}
- isSdsRepresentableAsLongLong(sds s,long long *llval);函数的作用是判断能否将sds表示的字符串转化为"long long"型的整数。
int isSdsRepresentableAsLongLong(sds s, long long *llval) {
return string2ll(s,sdslen(s),llval) ? C_OK : C_ERR;
}
/*
Convert a string into a long long.
Returns 1 if the string could be parsed into a (non-overflowing) long long, 0 otherwise.
The value will be set to the parsed value when appropriate.
*/
/*
将字符串转换为long long类型。如果字符串可以在无溢出的情况下转化为long long类型返回1,反之返回0。
传输参数value会在适当的时候被设置成解析后的整数值。
*/
int string2ll(const char *s, size_t slen, long long *value) {
//此处省略具体的代码
........................
}
setTypeCreate函数的整体思想:
- 如果加入到集合中的元素能转换为"long long"类型并且元素总数小于等于"server.set_max_intset_entries",set的底层编码是intset。
- 如果加入到集合中的元素不能转化为"long long"类型,但是元素总数小于等于"server.set_max_listpack_entries",set底层编码实现为"listpack".
- 如果加入到集合中的元素能转化为"long long"类型,但是元素总数大于"server.set_max_intset_entries" 并且小于等于"server.set_max_listpack_entries",set底层实现为"listpack",但是事实上这种情况不会发生。。因为"server.set_max_listpack_entries"的数值小于"server.set_max_intset_entries"的数值。
- 如果加入到集合中的元素能转化为"long long"类型,但是元素总数大于"server.set_max_intset_entries" ,set底层编码为"ht"。
总结
- 对于全是整数的情况,选用"intset"编码节省内存,至于为什么不选择"ziplist"而是选择一种新的结构"intset"看完下面的分析就会理解。
- 对于含有非整数的情况,如果元素总数在一定范围内,选用"listpack"节省内存。
接下来看看"server.set_max_intset_entries"和"server.set_max_listpack_entries"的具体默认数值是多少,以下是在"redis.conf"文件中找到的
redis.conf[7.2.2]
Sets have a special encoding when a set is composed
of just strings that happen to be integers in radix 10 in the range
of 64 bit signed integers.
The following configuration setting sets the limit in the size of the
set in order to use this special memory saving encoding.
set-max-intset-entries 512
Sets containing non-integer values are also encoded using a memory efficient data structure when they have a small number of entries, and the biggest entry does not exceed a given threshold. These thresholds can be configured using the following directives.
set-max-listpack-entries 128
set-max-listpack-value 64
对比看看之前的版本,"setTypeCreate"的实现。
redis6.2.14\redis5.0.13\3.2.100版本
robj *setTypeCreate(sds value) {
if (isSdsRepresentableAsLongLong(value,NULL) == C_OK)
return createIntsetObject();
return createSetObject();
}
Sets have a special encoding in just one case: when a set is composed
of just strings that happen to be integers in radix 10 in the range
of 64 bit signed integers.
The following configuration setting sets the limit in the size of the
set in order to use this special memory saving encoding.
set-max-intset-entries 512
- 之前版本set的底层编码只有两种"intset"和"ht"
createIntsetObject
[redis-7.2.2\src\object.c]
分析完了什么情况下选择"intset"作为set Object的编码,接下来看看如何创建"intset"对象。
robj *createIntsetObject(void) {
//创建intset Object,具体分析在下面