redis6.0.5之t_set.c阅读笔记--集合
2021/7/16 19:06:06
本文主要是介绍redis6.0.5之t_set.c阅读笔记--集合,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
********************************************************************************************************************* /*----------------------------------------------------------------------------- * Set Commands 集合相关命令 *----------------------------------------------------------------------------*/ void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum, robj *dstkey, int op); /* 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 regular * hash table. */ 工厂模式返回一个能够保持值的集合。当对象有一个整数编码的值,一个整数集合将会返回。否则返回一个正常的hash表 robj *setTypeCreate(sds value) { if (isSdsRepresentableAsLongLong(value,NULL) == C_OK) 是否可以用整数表示,可以就创建整数集合对象 return createIntsetObject(); return createSetObject(); 创建hash表形式的对象 } ********************************************************************************************************************* /* Add the specified value into a set. 增加特定值到一个集合 * If the value was already member of the set, nothing is done and 0 is * returned, otherwise the new element is added and 1 is returned. */ 如果值已经是集合的元素了,不用做任何事情,返回0,否则添加新元素到集合,返回1 int setTypeAdd(robj *subject, sds value) { long long llval; if (subject->encoding == OBJ_ENCODING_HT) { hash表形式 dict *ht = subject->ptr; dictEntry *de = dictAddRaw(ht,value,NULL); 添加一个新元素 if (de) { 如果非空,说明添加元素成功 dictSetKey(ht,de,sdsdup(value)); dictSetVal(ht,de,NULL); return 1; } } else if (subject->encoding == OBJ_ENCODING_INTSET) { 整数集合编码 if (isSdsRepresentableAsLongLong(value,&llval) == C_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. */ 当整数集包含太多的实体元素时需要转化为一般的hash表形式 // createSizeTConfig("set-max-intset-entries", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, // server.set_max_intset_entries, 512, INTEGER_CONFIG, NULL, NULL), if (intsetLen(subject->ptr) > server.set_max_intset_entries) 大于默认值512 setTypeConvert(subject,OBJ_ENCODING_HT); 转化集合到特定编码 return 1; } } else { /* Failed to get integer from object, convert to regular set. */ 新元素不能转化为整型,那只能把原集合转化为一般的hash集合了 setTypeConvert(subject,OBJ_ENCODING_HT); /* The set *was* an intset and this value is not integer * encodable, so dictAdd should always work. */ 原集合是整数集合,但是这个新元素不是整数,所以需要用dictAdd添加新元素 serverAssert(dictAdd(subject->ptr,sdsdup(value),NULL) == DICT_OK); return 1; } } else { serverPanic("Unknown set encoding"); } return 0; } ********************************************************************************************************************* 移除元素 int setTypeRemove(robj *setobj, sds value) { long long llval; if (setobj->encoding == OBJ_ENCODING_HT) { 正常hash表格式 if (dictDelete(setobj->ptr,value) == DICT_OK) { 移除元素 if (htNeedsResize(setobj->ptr)) dictResize(setobj->ptr); 需要进行hash表迁移,就进行迁移 return 1; } } else if (setobj->encoding == OBJ_ENCODING_INTSET) { 编码是整数集合 if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) { 待删除元素是整型 int success; setobj->ptr = intsetRemove(setobj->ptr,llval,&success); 移除元素 if (success) return 1; 移除成功success为1 } } else { serverPanic("Unknown set encoding"); } return 0; } ********************************************************************************************************************* 集合中是否存在传入元素 int setTypeIsMember(robj *subject, sds value) { long long llval; if (subject->encoding == OBJ_ENCODING_HT) { return dictFind((dict*)subject->ptr,value) != NULL; 找到就返回1 } else if (subject->encoding == OBJ_ENCODING_INTSET) { if (isSdsRepresentableAsLongLong(value,&llval) == C_OK) { 确认可以转化为整数 return intsetFind((intset*)subject->ptr,llval); 找到返回1 } } else { serverPanic("Unknown set encoding"); } return 0; } ********************************************************************************************************************* 设置元素迭代器 /* Structure to hold set iteration abstraction. */ 用来保持集合迭代器的数据结构 typedef struct { robj *subject; 对象 int encoding; 编码方式 int ii; /* intset iterator */ 整数迭代器 dictIterator *di; hash迭代器 } setTypeIterator; 初始化迭代器 setTypeIterator *setTypeInitIterator(robj *subject) { setTypeIterator *si = zmalloc(sizeof(setTypeIterator)); 分配空间 si->subject = subject; si->encoding = subject->encoding; if (si->encoding == OBJ_ENCODING_HT) { si->di = dictGetIterator(subject->ptr); 获取hash迭代器 } else if (si->encoding == OBJ_ENCODING_INTSET) { si->ii = 0; 从数组第一个元素开始 } else { serverPanic("Unknown set encoding"); } return si; } ********************************************************************************************************************* 释放迭代器,hash有额外的空间需要释放,整型没有额外分配空间,删除迭代器本身即可 void setTypeReleaseIterator(setTypeIterator *si) { if (si->encoding == OBJ_ENCODING_HT) dictReleaseIterator(si->di); zfree(si); } ********************************************************************************************************************* /* Move to the next entry in the set. Returns the object at the current * position. 移动到集合中的下一个实体。返回在当前位置的对象 * Since set elements can be internally be stored as SDS strings or * simple arrays of integers, setTypeNext returns the encoding of the * set object you are iterating, and will populate the appropriate pointer * (sdsele) or (llele) accordingly. 由于集合元素可以在内部存储为SDS字符串或简单的整数数组,因此setTypeNext返回要迭代的集合对象的编码, 并相应地填充相应的指针(sdsele)或(llele) * Note that both the sdsele and llele pointers should be passed and cannot * be NULL since the function will try to defensively populate the non * used field with values which are easy to trap if misused. 注意,sdsele和llele指针都应该被传递,并且不能为NULL,因为函数将用值来防御性地填充未使用的字段, 因为这些值很容易被误用陷阱 * When there are no longer elements -1 is returned. */ 当没有元素时,返回-1 int setTypeNext(setTypeIterator *si, sds *sdsele, int64_t *llele) { if (si->encoding == OBJ_ENCODING_HT) { hash表形式 dictEntry *de = dictNext(si->di); if (de == NULL) return -1; 返回-1 *sdsele = dictGetKey(de); 获取键 *llele = -123456789; /* Not needed. Defensive. */ 不需要的情况下,防御性赋值 } else if (si->encoding == OBJ_ENCODING_INTSET) { if (!intsetGet(si->subject->ptr,si->ii++,llele)) 查找下一个元素 return -1; 找不到返回-1 *sdsele = NULL; /* Not needed. Defensive. */防御性赋值,可以不处理,防止误用 } else { serverPanic("Wrong set encoding in setTypeNext"); } return si->encoding; 返回编码类型 } ********************************************************************************************************************* /* The not copy on write friendly version but easy to use version * of setTypeNext() is setTypeNextObject(), returning new SDS * strings. So if you don't retain a pointer to this object you should call * sdsfree() against it. setTypeNext的不支持写时复制但易于使用的版本是setTypeNextObject,它返回新的SDS字符串。 因此,如果不保留指向此对象的指针,则应该对其调用sdsfree * This function is the way to go for write operations where COW is not * an issue. */此函数用于不存在COW问题的写操作 sds setTypeNextObject(setTypeIterator *si) { int64_t intele; sds sdsele; int encoding; encoding = setTypeNext(si,&sdsele,&intele); 获取一个元素 switch(encoding) { case -1: return NULL; 找不到元素 case OBJ_ENCODING_INTSET: 整型值 return sdsfromlonglong(intele); case OBJ_ENCODING_HT: hash表中的值 return sdsdup(sdsele); default: serverPanic("Unsupported encoding"); } return NULL; /* just to suppress warnings */ 只是为了防止编译警告 } ********************************************************************************************************************* /* Return random element from a non empty set. 从一个非空的列表返回一个随机的元素 * The returned element can be a int64_t value if the set is encoded * as an "intset" blob of integers, or an SDS string if the set * is a regular set. 返回元素可能是一个64位的整数,如果集合被编码为二进制表示的整数集合, 或者一个SDS字符串,如果是一个hash集合 * The caller provides both pointers to be populated with the right * object. The return value of the function is the object->encoding * field of the object and is used by the caller to check if the * int64_t pointer or the redis object pointer was populated. 调用者提供两个指针来填充正确的对象。函数的返回值是对象的object->encoding字段, 调用者使用它来检查是否填充了64位整数指针或字符串指针 * Note that both the sdsele and llele pointers should be passed and cannot * be NULL since the function will try to defensively populate the non * used field with values which are easy to trap if misused. */ 注意,sdsele和llele指针都应该被传递,并且不能为NULL,因为函数将用值来防御性地填充未使用的字段, 因为这些值很容易被误用陷阱 获取一个随机的元素 int setTypeRandomElement(robj *setobj, sds *sdsele, int64_t *llele) { if (setobj->encoding == OBJ_ENCODING_HT) { dictEntry *de = dictGetFairRandomKey(setobj->ptr); 从hash表中随机获取一个元素 *sdsele = dictGetKey(de); *llele = -123456789; /* Not needed. Defensive. */ } else if (setobj->encoding == OBJ_ENCODING_INTSET) { *llele = intsetRandom(setobj->ptr);从整型数组中随机获取一个元素 *sdsele = NULL; /* Not needed. Defensive. */ } else { serverPanic("Unknown set encoding"); } return setobj->encoding; } ********************************************************************************************************************* 返回集合元素个数 unsigned long setTypeSize(const robj *subject) { if (subject->encoding == OBJ_ENCODING_HT) { return dictSize((const dict*)subject->ptr); } else if (subject->encoding == OBJ_ENCODING_INTSET) { return intsetLen((const intset*)subject->ptr); } else { serverPanic("Unknown set encoding"); } } ********************************************************************************************************************* /* Convert the set to specified encoding. The resulting dict (when converting * to a hash table) is presized to hold the number of elements in the original * set. */ 转化集合到指定编码。结果dict(当转换为哈希表时)用来保存原始集合中的元素 void setTypeConvert(robj *setobj, int enc) { setTypeIterator *si; serverAssertWithInfo(NULL,setobj,setobj->type == OBJ_SET && setobj->encoding == OBJ_ENCODING_INTSET); 确认原对象是整型集合 if (enc == OBJ_ENCODING_HT) { 目标编码是hash表 int64_t intele; dict *d = dictCreate(&setDictType,NULL); sds element; /* Presize the dict to avoid rehashing */预先设置字典的带下,避免做迁移动作 dictExpand(d,intsetLen(setobj->ptr)); /* To add the elements we extract integers and create redis objects */ 为了添加元素,我们提取整数并创建redis对象 si = setTypeInitIterator(setobj); while (setTypeNext(si,&element,&intele) != -1) { 挨个获取元素然后添加到新的hash表 element = sdsfromlonglong(intele); serverAssert(dictAdd(d,element,NULL) == DICT_OK); } setTypeReleaseIterator(si); 遍历完原整型集合数据,释放迭代器 setobj->encoding = OBJ_ENCODING_HT; 修改编码类型 zfree(setobj->ptr); 释放原整型集合数据 setobj->ptr = d; 指向新的hash表 } else { serverPanic("Unsupported set conversion"); } } ********************************************************************************************************************* 添加命令,键不存在就创建 void saddCommand(client *c) { robj *set; int j, added = 0; set = lookupKeyWrite(c->db,c->argv[1]); 库中查找键是否存在 if (set == NULL) { 不存在就添加集合 set = setTypeCreate(c->argv[2]->ptr); dbAdd(c->db,c->argv[1],set); } else { 存在就判断类型是否符合 if (set->type != OBJ_SET) { addReply(c,shared.wrongtypeerr); return; } } 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); } ********************************************************************************************************************* 移除集合中的指定元素 void sremCommand(client *c) { robj *set; int j, deleted = 0, keyremoved = 0; if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || 库中查找对象 并且 检查对象类型是否为集合 checkType(c,set,OBJ_SET)) return; for (j = 2; j < c->argc; j++) { 挨个删除指定的键值 if (setTypeRemove(set,c->argv[j]->ptr)) { deleted++; if (setTypeSize(set) == 0) { 如果该对象被删除空了,将该对象删除 dbDelete(c->db,c->argv[1]); keyremoved = 1; break; } } } if (deleted) { 存在被删除的键值 signalModifiedKey(c,c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_SET,"srem",c->argv[1],c->db->id); if (keyremoved) 如果还有删除对象事件 notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1], c->db->id); server.dirty += deleted; } addReplyLongLong(c,deleted); } ********************************************************************************************************************* 将目标元素 从源集合 移动 到目标集合 void smoveCommand(client *c) { robj *srcset, *dstset, *ele; srcset = lookupKeyWrite(c->db,c->argv[1]); 库中查找源集合 dstset = lookupKeyWrite(c->db,c->argv[2]); 库中查找目标集合 ele = 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,OBJ_SET) || 源对象不是集合 (dstset && checkType(c,dstset,OBJ_SET))) return; 目标对象不是集合 /* If srcset and dstset are equal, SMOVE is a no-op */ 如果源集合和目标集合是同样的,则无需任何操作 if (srcset == dstset) { addReply(c,setTypeIsMember(srcset,ele->ptr) ? shared.cone : shared.czero); return; } /* If the element cannot be removed from the src set, return 0. */ 如果元素不能从源集合移走,返回0 if (!setTypeRemove(srcset,ele->ptr)) { 移不走 addReply(c,shared.czero); return; } notifyKeyspaceEvent(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(NOTIFY_GENERIC,"del",c->argv[1],c->db->id); } /* Create the destination set when it doesn't exist */ 如果目标集合不存在,库中新建集合 if (!dstset) { dstset = setTypeCreate(ele->ptr); dbAdd(c->db,c->argv[2],dstset); } signalModifiedKey(c,c->db,c->argv[1]); signalModifiedKey(c,c->db,c->argv[2]); server.dirty++; /* An extra key has changed when ele was successfully added to dstset */ 如果在目标集合中添加元素成功,发出添加成功消息 if (setTypeAdd(dstset,ele->ptr)) { server.dirty++; notifyKeyspaceEvent(NOTIFY_SET,"sadd",c->argv[2],c->db->id); } addReply(c,shared.cone); } ********************************************************************************************************************* 判断元素是否是集合的成员 void sismemberCommand(client *c) { robj *set; if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || 库中是否存在这个对象 对象是否为集合 checkType(c,set,OBJ_SET)) return; if (setTypeIsMember(set,c->argv[2]->ptr)) addReply(c,shared.cone); else addReply(c,shared.czero); } ********************************************************************************************************************* 获取集合中元素的数量 void scardCommand(client *c) { robj *o; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,OBJ_SET)) return; addReplyLongLong(c,setTypeSize(o)); 返回集合中元素的数量 } ********************************************************************************************************************* /* Handle the "SPOP key <count>" variant. The normal version of the * command is handled by the spopCommand() function itself. */ 处理"SPOP key <count>"变量。这个命令一般的版本通过函数spopCommand处理 /* How many times bigger should be the set compared to the remaining size * for us to use the "create new set" strategy? Read later in the * implementation for more info. */ 如果要使用“创建新的集合”策略,集合应该比剩余大小大多少倍? 想深入了解,请阅读后面的实现 #define SPOP_MOVE_STRATEGY_MUL 5 从集合中弹出指定数量的元素 void spopWithCountCommand(client *c) { long l; unsigned long count, size; robj *set; /* Get the count argument */ 获取输入参数个数 if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return; if (l >= 0) { count = (unsigned long) l; } else { addReply(c,shared.outofrangeerr); return; } /* Make sure a key with the name inputted exists, and that it's type is * indeed a set. Otherwise, return nil */ 确认输入的参数在库中存在对象,并且对象是集合。否则返回空 if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.emptyset[c->resp])) == NULL || checkType(c,set,OBJ_SET)) return; /* If count is zero, serve an empty set ASAP to avoid special * cases later. */ 如果输入的参数计数是0,尽快提供一个空的集合 来避免出现特殊情况 if (count == 0) { addReply(c,shared.emptyset[c->resp]); return; } size = setTypeSize(set); 获取集合中元素个数 /* Generate an SPOP keyspace notification */创建一个SPOP键空间消息通知 notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id); server.dirty += count; /* CASE 1: * The number of requested elements is greater than or equal to * the number of elements inside the set: simply return the whole set. */ 情况1:请求弹出的元素数大于或等于集合中的元素数:只需返回整个集合 if (count >= size) { /* We just return the entire set */ 我们只需要返回整个集合 sunionDiffGenericCommand(c,c->argv+1,1,NULL,SET_OP_UNION); /* Delete the set as it is now empty */ 如果集合是空的,在库中删除它 dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id); /* Propagate this command as an DEL operation */ 像del操作一样传播这个命令 rewriteClientCommandVector(c,2,shared.del,c->argv[1]); signalModifiedKey(c,c->db,c->argv[1]); server.dirty++; return; } /* Case 2 and 3 require to replicate SPOP as a set of SREM commands. * Prepare our replication argument vector. Also send the array length * which is common to both the code paths. */ 情况2和3需要将SPOP复制为一组SREM命令。准备复制参数向量。同时发送两个代码路径共用的数组长度 robj *propargv[3]; propargv[0] = createStringObject("SREM",4); propargv[1] = c->argv[1]; addReplySetLen(c,count); /* Common iteration vars. */ 公共迭代变量 sds sdsele; robj *objele; int encoding; int64_t llele; unsigned long remaining = size-count; /* Elements left after SPOP. */ 经过弹出操作遗留的元素 /* If we are here, the number of requested elements is less than the * number of elements inside the set. Also we are sure that count < size. * Use two different strategies. 如果执行到这类,表示请求的元素数量小于集合中的元素数量。我们确认 请求数量 小于 集合数量。 使用如下两种不同的策略。 * CASE 2: The number of elements to return is small compared to the * set size. We can just extract random elements and return them to * the set. */ 情况2:返回元素的数量小于集合的大小。我们值需要抽取随机的元素然后返回给输出集合 if (remaining*SPOP_MOVE_STRATEGY_MUL > count) { 如果集合中留下来的元素数量 的 5倍 大于 请求数量 while(count--) { /* Emit and remove. */ 弹出和删除 encoding = setTypeRandomElement(set,&sdsele,&llele);弹出元素 if (encoding == OBJ_ENCODING_INTSET) { 是整型编码 addReplyBulkLongLong(c,llele); objele = createStringObjectFromLongLong(llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { hash表编码 addReplyBulkCBuffer(c,sdsele,sdslen(sdsele)); objele = createStringObject(sdsele,sdslen(sdsele)); setTypeRemove(set,sdsele); } /* Replicate/AOF this command as an SREM operation */ 赋值或者AOF 的时候 如SREM命令一样传播 propargv[2] = objele; alsoPropagate(server.sremCommand,c->db->id,propargv,3, PROPAGATE_AOF|PROPAGATE_REPL); decrRefCount(objele); } } else { /* CASE 3: The number of elements to return is very big, approaching * the size of the set itself. After some time extracting random elements * from such a set becomes computationally expensive, so we use * a different strategy, we extract random elements that we don't * want to return (the elements that will remain part of the set), * creating a new set as we do this (that will be stored as the original * set). Then we return the elements left in the original set and * release it. */ 情况3:返回元素的数量非常大,接近集合本身的大小。经过多次从集合随机抽取元素,计算的代价越来越大, 所以我们使用不一样的策略,我们抽取不需要返回的随机元素(就是保留在集合中的元素), 我们用创建一个新的集合来处理这个问题(这将如原始集合一般保存).然后我们返回原始集合中剩下的元素,释放掉原始集合 robj *newset = NULL; /* Create a new set with just the remaining elements. */ 创建一个新集合保存遗留的元素 while(remaining--) { encoding = setTypeRandomElement(set,&sdsele,&llele); if (encoding == OBJ_ENCODING_INTSET) { sdsele = sdsfromlonglong(llele); } else { sdsele = sdsdup(sdsele); } if (!newset) newset = setTypeCreate(sdsele); 第一次需要创建新的集合 setTypeAdd(newset,sdsele); 在新集合中增加元素 setTypeRemove(set,sdsele); 从原集合中删除元素 sdsfree(sdsele); 释放这个新建的元素 } /* Transfer the old set to the client. */ 返回原来的集合给客户端 setTypeIterator *si; si = setTypeInitIterator(set); while((encoding = setTypeNext(si,&sdsele,&llele)) != -1) { if (encoding == OBJ_ENCODING_INTSET) { 整型编码 addReplyBulkLongLong(c,llele); objele = createStringObjectFromLongLong(llele); } else { hash表 addReplyBulkCBuffer(c,sdsele,sdslen(sdsele)); objele = createStringObject(sdsele,sdslen(sdsele)); } /* Replicate/AOF this command as an SREM operation */ 赋值或者AOF 的时候 如SREM命令一样传播 propargv[2] = objele; alsoPropagate(server.sremCommand,c->db->id,propargv,3, PROPAGATE_AOF|PROPAGATE_REPL); decrRefCount(objele); } setTypeReleaseIterator(si); 释放迭代器 /* Assign the new set as the key value. */ 覆盖在库中的原来集合 dbOverwrite(c->db,c->argv[1],newset); } /* Don't propagate the command itself even if we incremented the * dirty counter. We don't want to propagate an SPOP command since * we propagated the command as a set of SREMs operations using * the alsoPropagate() API. */ 即使我们增加了变动键计数器,也不传播命令本身。我们不想传播SPOP命令, 因为我们使用alsoPropagate这个API,将该命令传播为一组SREMs操作 decrRefCount(propargv[0]); 减少引用 preventCommandPropagation(c); signalModifiedKey(c,c->db,c->argv[1]); server.dirty++; } ********************************************************************************************************************* 从集合弹出元素命令 void spopCommand(client *c) { robj *set, *ele, *aux; sds sdsele; int64_t llele; int encoding; if (c->argc == 3) { 三个参数的时候,直接调用函数spopWithCountCommand spopWithCountCommand(c); return; } else if (c->argc > 3) { 大于3个参数,格式不对 addReply(c,shared.syntaxerr); return; } /* Make sure a key with the name inputted exists, and that it's type is * indeed a set */ 确认传入参数的对象在数据库中,并且其类型是集合 if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.null[c->resp])) == NULL || checkType(c,set,OBJ_SET)) return; /* Get a random element from the set */ 从集合中获取一个随机元素 encoding = setTypeRandomElement(set,&sdsele,&llele); /* Remove the element from the set */ 从集合中移除一个元素 if (encoding == OBJ_ENCODING_INTSET) { ele = createStringObjectFromLongLong(llele); set->ptr = intsetRemove(set->ptr,llele,NULL); } else { ele = createStringObject(sdsele,sdslen(sdsele)); setTypeRemove(set,ele->ptr); } notifyKeyspaceEvent(NOTIFY_SET,"spop",c->argv[1],c->db->id); 发出弹出消息 /* Replicate/AOF this command as an SREM operation */ 赋值或者AOF 的时候 如SREM命令一样传播 aux = createStringObject("SREM",4); rewriteClientCommandVector(c,3,aux,c->argv[1],ele); decrRefCount(aux); 引用计数减1 /* Add the element to the reply */ 增加元素到回复 addReplyBulk(c,ele); decrRefCount(ele); /* Delete the set if it's empty */ 如果集合为空,删除之 if (setTypeSize(set) == 0) { dbDelete(c->db,c->argv[1]); notifyKeyspaceEvent(NOTIFY_GENERIC,"del",c->argv[1],c->db->id); } /* Set has been modified */ 集合被修改,需要将这个信息通知到各方 signalModifiedKey(c,c->db,c->argv[1]); server.dirty++; } ********************************************************************************************************************* /* handle the "SRANDMEMBER key <count>" variant. The normal version of the * command is handled by the srandmemberCommand() function itself. */ 处理"SRANDMEMBER key <count>"变量。这个命令一般的版本通过函数srandmemberCommand处理 /* How many times bigger should be the set compared to the requested size * for us to don't use the "remove elements" strategy? Read later in the * implementation for more info. */ 如果我们不使用“移除元素”策略,集合应该比请求元素数量大多少倍? 想深入了解,请阅读后面的实现 #define SRANDMEMBER_SUB_STRATEGY_MUL 3 根据传入参数的返回数量 随机获取集合的返回数量 void srandmemberWithCountCommand(client *c) { long l; unsigned long count, size; int uniq = 1; robj *set; sds ele; int64_t llele; int encoding; dict *d; if (getLongFromObjectOrReply(c,c->argv[2],&l,NULL) != C_OK) return; 传入的获取元素个数 if (l >= 0) { count = (unsigned long) l; } else { /* A negative count means: return the same elements multiple times * (i.e. don't remove the extracted element after every extraction). */ 负数意味着,多次返回同样的元素(举例如下:每次提出元素后不要移除提取的元素,这样下次可以直接获得,不用再提取) count = -l; uniq = 0; } if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyset[c->resp])) 库中存在名字所指对象 对象类型是集合 == NULL || checkType(c,set,OBJ_SET)) return; size = setTypeSize(set); 集合元素个数 /* If count is zero, serve it ASAP to avoid special cases later. */ 集合元素个数为0,为了避免后面特殊情况,尽快返回一个空集合 if (count == 0) { addReply(c,shared.emptyset[c->resp]); return; } /* CASE 1: The count was negative, so the extraction method is just: * "return N random elements" sampling the whole set every time. * This case is trivial and can be served without auxiliary data * structures. */ 情况1:数量是负的,所以抽取方式如下:返回N个随机元素, "每次对整个集合抽取元素,这种情况是平凡的并且不用辅助数据结构就可以实现" if (!uniq) { addReplySetLen(c,count); while(count--) { encoding = setTypeRandomElement(set,&ele,&llele);随机获取一个元素 if (encoding == OBJ_ENCODING_INTSET) { addReplyBulkLongLong(c,llele); } else { addReplyBulkCBuffer(c,ele,sdslen(ele)); } } return; } /* CASE 2: * The number of requested elements is greater than the number of * elements inside the set: simply return the whole set. */ 情况2: 请求的元素数量大于集合中的元素数量,只需要返回整个集合即可 if (count >= size) { sunionDiffGenericCommand(c,c->argv+1,1,NULL,SET_OP_UNION); return; } /* For CASE 3 and CASE 4 we need an auxiliary dictionary. */ 对情况3和情况4 我们需要一个辅助的字典结构 d = dictCreate(&objectKeyPointerValueDictType,NULL); /* CASE 3: * The number of elements inside the set is not greater than * SRANDMEMBER_SUB_STRATEGY_MUL times the number of requested elements. * In this case we create a set from scratch with all the elements, and * subtract random elements to reach the requested number of elements. 情况3: 集合内的元素数量 不大于 SRANDMEMBER_SUB_STRATEGY_MUL 乘以 请求的元素数量 。 在本例中,我们从头开始创建一个包含所有元素的集合,然后减去随机元素以达到请求的元素数量。 * This is done because if the number of requsted elements is just * a bit less than the number of elements in the set, the natural approach * used into CASE 3 is highly inefficient. */ 我们这样做事因为 如果请求的元素数量 只是比集合的数量小一点点,那么使用情况3的处理效率就很低 if (count*SRANDMEMBER_SUB_STRATEGY_MUL > size) { 如果请求元素的数量乘以3 大于 集合中元素的数量 setTypeIterator *si; /* Add all the elements into the temporary dictionary. */ 添加所有的元素到一个临时的字典中 si = setTypeInitIterator(set); 初始化迭代器 while((encoding = setTypeNext(si,&ele,&llele)) != -1) { 遍历原集合 int retval = DICT_ERR; if (encoding == OBJ_ENCODING_INTSET) { retval = dictAdd(d,createStringObjectFromLongLong(llele),NULL); } else { retval = dictAdd(d,createStringObject(ele,sdslen(ele)),NULL); } serverAssert(retval == DICT_OK); } setTypeReleaseIterator(si); 释放迭代器 serverAssert(dictSize(d) == size); 确认临时字典的数量同原集合一致 /* Remove random elements to reach the right count. */ 随机移除集合元素,让其元素数量 刚好到达 传入的参数值 while(size > count) { 当集合中的元素数量大于传入参数时,需要逐一删除元素 dictEntry *de; de = dictGetRandomKey(d); dictDelete(d,dictGetKey(de)); size--; } } /* CASE 4: We have a big set compared to the requested number of elements. * In this case we can simply get random elements from the set and add * to the temporary set, trying to eventually get enough unique elements * to reach the specified count. */ 情况4:对比请求元素的数量,我们拥有一个非常大数量的集合。 在这种情况下,我们只需要随机从集合中获取元素,并且添加到临时集合中, 尝试最终获取足够的不一样元素到达指定的数量 else { unsigned long added = 0; robj *objele; while(added < count) { encoding = setTypeRandomElement(set,&ele,&llele); if (encoding == OBJ_ENCODING_INTSET) { objele = createStringObjectFromLongLong(llele); } else { objele = createStringObject(ele,sdslen(ele)); } /* Try to add the object to the dictionary. If it already exists * free it, otherwise increment the number of objects we have * in the result dictionary. */ 尝试添加元素到临时字典。如果已经存在,那么就释放它。否则增加我们在结果字典中的元素数量 if (dictAdd(d,objele,NULL) == DICT_OK) added++; else decrRefCount(objele); 添加失败情况下,需要减少引用值 } } /* CASE 3 & 4: send the result to the user. */ 情况3和4:发送结果给用户 { dictIterator *di; dictEntry *de; addReplySetLen(c,count); di = dictGetIterator(d); while((de = dictNext(di)) != NULL) 遍历临时字典,将结果数据发送给客户 addReplyBulk(c,dictGetKey(de)); dictReleaseIterator(di); dictRelease(d); } } ********************************************************************************************************************* 返回随机元素 void srandmemberCommand(client *c) { robj *set; sds ele; int64_t llele; int encoding; if (c->argc == 3) { 参数为3,直接调用函数srandmemberWithCountCommand srandmemberWithCountCommand(c); return; } else if (c->argc > 3) { 参数个数大于3,参数格式错误 addReply(c,shared.syntaxerr); return; } if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.null[c->resp])) 对象存在 对象类型为集合 == NULL || checkType(c,set,OBJ_SET)) return; encoding = setTypeRandomElement(set,&ele,&llele); 随机获取一个元素 if (encoding == OBJ_ENCODING_INTSET) { addReplyBulkLongLong(c,llele); } else { addReplyBulkCBuffer(c,ele,sdslen(ele)); } } ********************************************************************************************************************* 通过节后元素数量大小 来对比集合大小 int qsortCompareSetsByCardinality(const void *s1, const void *s2) { if (setTypeSize(*(robj**)s1) > setTypeSize(*(robj**)s2)) return 1; if (setTypeSize(*(robj**)s1) < setTypeSize(*(robj**)s2)) return -1; return 0; } ********************************************************************************************************************* /* This is used by SDIFF and in this case we can receive NULL that should * be handled as empty sets. */ 这是由函数SDIFF使用的,在这种情况下,我们可以接收应该作为空集处理的NULL int qsortCompareSetsByRevCardinality(const void *s1, const void *s2) { robj *o1 = *(robj**)s1, *o2 = *(robj**)s2; unsigned long first = o1 ? setTypeSize(o1) : 0; unsigned long second = o2 ? setTypeSize(o2) : 0; if (first < second) return 1; if (first > second) return -1; return 0; } ********************************************************************************************************************* 交集一般化操作 void sinterGenericCommand(client *c, robj **setkeys, unsigned long setnum, robj *dstkey) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *dstset = NULL; sds elesds; int64_t intobj; void *replylen = NULL; unsigned long j, cardinality = 0; int encoding; for (j = 0; j < setnum; j++) { robj *setobj = dstkey ? lookupKeyWrite(c->db,setkeys[j]) : lookupKeyRead(c->db,setkeys[j]); if (!setobj) { setobj为空 zfree(sets); if (dstkey) { 目标集合非空 if (dbDelete(c->db,dstkey)) { 从库中删除目标集合 signalModifiedKey(c,c->db,dstkey); server.dirty++; } addReply(c,shared.czero); } else { addReply(c,shared.emptyset[c->resp]); } return; } if (checkType(c,setobj,OBJ_SET)) { 不是集合类型 zfree(sets); return; } sets[j] = setobj; } /* 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 */ 第一件事情我们要做的是输出总的元素数量。。。 因为这是多组写,但是在这个时候我们还不在交集的数量,所以我们使用了一个技巧, 添加一个空的对象到输出列表,保存它的指针为为后面使用正确的长度修改留了一手。 if (!dstkey) { 目标为空 replylen = addReplyDeferredLen(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,&elesds,&intobj)) != -1) { 遍历集合每个元素 for (j = 1; j < setnum; j++) { 对每个元素遍历所有集合 if (sets[j] == sets[0]) continue; if (encoding == OBJ_ENCODING_INTSET) { /* intset with intset is simple... and fast */ 整型集合和整型集合 简单且快 if (sets[j]->encoding == OBJ_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 == OBJ_ENCODING_HT) { 要比较的集合是hash类型 elesds = sdsfromlonglong(intobj); if (!setTypeIsMember(sets[j],elesds)) { sdsfree(elesds); break; } sdsfree(elesds); } } else if (encoding == OBJ_ENCODING_HT) { if (!setTypeIsMember(sets[j],elesds)) { break; } } } /* Only take action when all sets contain the member */ 只有所有集合用于这个元素 才采取行动 if (j == setnum) { 表示所有集合都有这个元素 if (!dstkey) { 目标集合为空 if (encoding == OBJ_ENCODING_HT) addReplyBulkCBuffer(c,elesds,sdslen(elesds)); else addReplyBulkLongLong(c,intobj); cardinality++; 数量加1 } else { if (encoding == OBJ_ENCODING_INTSET) { elesds = sdsfromlonglong(intobj); setTypeAdd(dstset,elesds); 不空情况下就加入到目标集合 sdsfree(elesds); } else { setTypeAdd(dstset,elesds); } } } } setTypeReleaseIterator(si); if (dstkey) { 目标集合在库中 /* Store the resulting set into the target, if the intersection * is not an empty set. */ 如果交集非空,保存结果集到目标集合 int deleted = dbDelete(c->db,dstkey);删除库中原指向集合 if (setTypeSize(dstset) > 0) { 新集合不为空 dbAdd(c->db,dstkey,dstset); 添加新集合到库中 addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(NOTIFY_SET,"sinterstore", dstkey,c->db->id); } else { decrRefCount(dstset); 减少引用计数 addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c,c->db,dstkey); server.dirty++; } else { 设置交集长度即可 setDeferredSetLen(c,replylen,cardinality); } zfree(sets); } ********************************************************************************************************************* 求交集 void sinterCommand(client *c) { sinterGenericCommand(c,c->argv+1,c->argc-1,NULL); } ********************************************************************************************************************* 求交集并且保存 void sinterstoreCommand(client *c) { sinterGenericCommand(c,c->argv+2,c->argc-2,c->argv[1]); } ********************************************************************************************************************* #define SET_OP_UNION 0 #define SET_OP_DIFF 1 #define SET_OP_INTER 2 求并集 void sunionDiffGenericCommand(client *c, robj **setkeys, int setnum, robj *dstkey, int op) { robj **sets = zmalloc(sizeof(robj*)*setnum); setTypeIterator *si; robj *dstset = NULL; sds ele; 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]); if (!setobj) { sets[j] = NULL; continue; } if (checkType(c,setobj,OBJ_SET)) { zfree(sets); return; } sets[j] = setobj; } /* Select what DIFF algorithm to use. 选择使用什么样的DIFF算法 * 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 == SET_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; algo_one_work += setTypeSize(sets[0]); 计算算法1的工作量 algo_two_work += setTypeSize(sets[j]); 计算算法2的工作量 } /* Algorithm 1 has better constant times and performs less operations * if there are elements in common. Give it some advantage. */ 在同样元素个数情况下,算法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中,最好通过减小大小来排序集合相减,这样我们就更有可能尽快找到重复的元素 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*/ 我们需要一个临时集合对象来存储我们的并集。如果dstkey不为空(也就是说,我们在SUNIONSTORE操作中), 那么这个集合对象将是要设置到目标键中的结果对象 dstset = createIntsetObject(); if (op == SET_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) { 非空集,遍历集合每个元素 if (setTypeAdd(dstset,ele)) cardinality++; sdsfree(ele); } setTypeReleaseIterator(si); } } else if (op == SET_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. 我们通过迭代第一个集合的所有元素来执行差操作, 并且仅当元素不存在于所有其他集合中时才将其添加到目标集合中 * 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++; } sdsfree(ele); } setTypeReleaseIterator(si); } else if (op == SET_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. 将第一个集合的所有元素添加到辅助集合中,然后从中删除下一个集合的所有元素 * 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) { if (j == 0) { 先把第一个集合中的元素全部添加进 目标集合 if (setTypeAdd(dstset,ele)) cardinality++; } else { 根据剩余集合的元素 从目标集合中慢慢删除 if (setTypeRemove(dstset,ele)) cardinality--; } sdsfree(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 */ 如果不是在保存模式下,则输出结果集的内容 if (!dstkey) { 非保存模式 addReplySetLen(c,cardinality); si = setTypeInitIterator(dstset); while((ele = setTypeNextObject(si)) != NULL) { addReplyBulkCBuffer(c,ele,sdslen(ele)); sdsfree(ele); } setTypeReleaseIterator(si); createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, server.lazyfree_lazy_server_del, 0, NULL, NULL), 系统模式是同步释放 server.lazyfree_lazy_server_del ? freeObjAsync(dstset) : 如果释放的对象过大,异步释放 decrRefCount(dstset); } else { /* If we have a target key where to store the resulting set * create this key with the result set inside */ 如果我们有一个存储结果集的目标键,那么就创建这个键,并在其中包含结果集 int deleted = dbDelete(c->db,dstkey); if (setTypeSize(dstset) > 0) { 结果集大于0 dbAdd(c->db,dstkey,dstset); 添加新结果集 addReplyLongLong(c,setTypeSize(dstset)); notifyKeyspaceEvent(NOTIFY_SET, op == SET_OP_UNION ? "sunionstore" : "sdiffstore", dstkey,c->db->id); } else { decrRefCount(dstset); addReply(c,shared.czero); if (deleted) notifyKeyspaceEvent(NOTIFY_GENERIC,"del", dstkey,c->db->id); } signalModifiedKey(c,c->db,dstkey); server.dirty++; } zfree(sets); } ********************************************************************************************************************* 求并集不保存结果 void sunionCommand(client *c) { sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_UNION); } ********************************************************************************************************************* 求并集保存结果 void sunionstoreCommand(client *c) { sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_UNION); } ********************************************************************************************************************* 求差集不保存结果 void sdiffCommand(client *c) { sunionDiffGenericCommand(c,c->argv+1,c->argc-1,NULL,SET_OP_DIFF); } ********************************************************************************************************************* 求差集保存结果 void sdiffstoreCommand(client *c) { sunionDiffGenericCommand(c,c->argv+2,c->argc-2,c->argv[1],SET_OP_DIFF); } ********************************************************************************************************************* 迭代集合中键的元素 void sscanCommand(client *c) { robj *set; unsigned long cursor; if (parseScanCursorOrReply(c,c->argv[2],&cursor) == C_ERR) return; 解析传入的游标参数 if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.emptyscan)) == NULL || checkType(c,set,OBJ_SET)) return; scanGenericCommand(c,set,cursor); 根据游标参数获取元素 } *********************************************************************************************************************
这篇关于redis6.0.5之t_set.c阅读笔记--集合的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-18Redis安装入门:新手必读指南
- 2024-11-08阿里云Redis项目实战入门教程
- 2024-11-08阿里云Redis资料:新手入门与初级使用指南
- 2024-11-08阿里云Redis教程:新手入门及实用指南
- 2024-11-07阿里云Redis学习入门:新手必读指南
- 2024-11-07阿里云Redis学习入门:从零开始的操作指南
- 2024-11-07阿里云Redis学习:初学者指南
- 2024-11-06阿里云Redis入门教程:轻松搭建与使用指南
- 2024-11-02Redis项目实战:新手入门教程
- 2024-10-22Redis入门教程:轻松掌握数据存储与操作