操作set类型value的redis协议

操作set类型value的redis命令主要包括sadd,srem,sismember,sintersect和smembers.
对于set类型的value, 从宏观上来看,存在2层哈希表结构,第一层哈希表是客户端对象的字段dict指向的哈希表,第二层哈希表是第一层哈希表中的节点,该节点管理的robj对象的类型是类型REDIS_SET.

redis sadd命令
sadd命令格式为sadd key member, 其含义是将member存入哈希表中,这里的哈希表即前文所说的第二层哈希表.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
sadd mykey 7
member1
+OK

sadd命令对应的处理函数为saddCommand, 其实现为(redis.c):

1896 static void saddCommand(redisClient *c) {
1897     dictEntry *de;
1898     robj *set, *ele;
1899 
1900     de = dictFind(c->dict,c->argv[1]);
1901     if (de == NULL) {
1902         set = createSetObject();
1903         dictAdd(c->dict,c->argv[1],set);
1904         c->argv[1] = 0;
1905     } else {
1906         set = dictGetEntryVal(de);
1907         if (set->type != REDIS_SET) {
1908             addReplySds(c,
1909                 sdsnew("-ERR SADD against key not holding a set value\r\n"));
1910             return;
1911         }
1912     }
1913     ele = createObject(REDIS_STRING, c->argv[2]);
1914     if (dictAdd(set->ptr,ele,NULL) == DICT_OK) {
1915         server.dirty++;
1916     } else {
1917         decrRefCount(ele);
1918     }
1919     c->argv[2] = NULL;
1920     addReply(c,shared.ok);
1921 }

Line1900在数据库中查找包含指定key的哈希节点.Line1901:1904如果没有找到指定的节点,调用函数createSetObject创建一个类型robj对象,其类型为REDIS_SET.函数的实现为(redis.c):

1017 static robj *createSetObject(void) {
1018     dict *d = dictCreate(&setDictType,NULL);
1019     if (!d) oom("dictCreate");
1020     return createObject(REDIS_SET,d);
1021 }

该函数先创建类型dict对象,再创建类型robj对象,封装创建的dict对象.
回到函数saddCommand中,Line1903将添加这个新节点到哈希表数据库中.这个哈希节点的value又是个dict类型,并且其中是空的.Line1913:1918将member封装为REDIS_STRING类型的robj对象,并且添加到第二层哈希表结构中,注意对于第二层哈希表结构,只需关注其key即可,也就是对应命令格式中的member,所以Line1914调用函数dictAdd时,第三个参数可以传入NULL.如果Line1900查询的哈希节点存在,Line1906:1911只需获得该哈希节点的value即可,然后将member添加到第二层哈希表中.Line1920向客户端返回操作成功提示字符串.

redis scard命令
redis scard命令的格式为scard key, 该命令返回该key对应的哈希表中的member个数.telnet模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
sadd mykey 7
member1
+OK
scard mykey
1

scard命令对应的处理函数为scardCommand,其实现为(redis.c):

1971 static void scardCommand(redisClient *c) {
1972     dictEntry *de;
1973     dict *s;
1974    
1975     de = dictFind(c->dict,c->argv[1]);
1976     if (de == NULL) {
1977         addReply(c,shared.zero);
1978         return;
1979     } else {
1980         robj *o = dictGetEntryVal(de);
1981         if (o->type != REDIS_SET) {
1982             addReplySds(c,sdsnew("-1\r\n"));
1983         } else {
1984             s = o->ptr;
1985             addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",
1986                 dictGetHashTableUsed(s)));
1987         }
1988     }
1989 }

Line1975在数据库中查找包含key的哈希节点.Line1976:1978如果没有找到该节点,向客户端返回0.如果找到该哈希节点,但是robj对象类型错误,向客户端返回-1,否则向客户端返回第二层哈希表中存在的节点数量.

redis srem命令
redis srem命令的格式为srem key member, 该命令的含义是将member从key对应的哈希表中删除.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
sadd mykey 7
member1
+OK
srem mykey 7
member1
+OK
scard mykey
0

srem命令的对应处理函数为sremCommand,其实现为(redis.c):

1923 static void sremCommand(redisClient *c) {
1924     dictEntry *de;
1925 
1926     de = dictFind(c->dict,c->argv[1]);
1927     if (de == NULL) {
1928         addReplySds(c,sdsnew("-ERR no such key\r\n"));
1929     } else {
1930         robj *set, *ele;
1931 
1932         set = dictGetEntryVal(de);
1933         if (set->type != REDIS_SET) {
1934             addReplySds(c,
1935                 sdsnew("-ERR SDEL against key not holding a set value\r\n"));
1936             return;
1937         }
1938         ele = createObject(REDIS_STRING,c->argv[2]);
1939         if (dictDelete(set->ptr,ele) == DICT_OK)
1940             server.dirty++;
1941         ele->ptr = NULL;
1942         decrRefCount(ele);
1943         addReply(c,shared.ok);
1944     }
1945 }

Line1926在数据库中查找指定key的哈希节点.Line1927:1928如果指定的哈希节点不存在,向客户端返回错误提示字符串.Line1938创建一个robj对象封装string类型的member,Line1939调用函数dictDelete删除member,该函数的实现为(dict.c):

248 int dictDelete(dict *ht, const void *key) {
249     return dictGenericDelete(ht,key,0);
250 }

真正删除的逻辑在函数dictGenericDelete中, 其的实现为(dict.c):

215 /* Search and remove an element */
216 static int dictGenericDelete(dict *ht, const void *key, int nofree)
217 {
218     unsigned int h;
219     dictEntry *he, *prevHe;
220 
221     if (ht->size == 0)
222         return DICT_ERR;
223     h = dictHashKey(ht, key) & ht->sizemask;
224     he = ht->table[h];
225 
226     prevHe = NULL;
227     while(he) {
228         if (dictCompareHashKeys(ht, key, he->key)) {
229             /* Unlink the element from the list */
230             if (prevHe)
231                 prevHe->next = he->next;
232             else
233                 ht->table[h] = he->next;
234             if (!nofree) {
235                 dictFreeEntryKey(ht, he);
236                 dictFreeEntryVal(ht, he);
237             }
238             _dictFree(he);
239             ht->used--;
240             return DICT_OK;
241         }
242         prevHe = he;
243         he = he->next;
244     }
245     return DICT_ERR; /* not found */
246 }

要删除的member其实是第二层哈希表结构中的key.Line221:222如果哈希表是空的,该函数直接返回.如果哈希表非空,Line223:224获得该key映射的哈希表的BUCKET索引,如果该索引指向的链表为空,说明待删除的member不在哈希表中,Line245返回未找到member.否则Line227:244便利该链表,找到member并删除.注意如果传入的参数nofree为0,表示哈希节点中的key和value需要在释放,Line235调用函数dictFreeEntryKey释放哈希节点的key,这里的key就是member,哈希节点的key是类型REDIS_STRINGrobj对象, 即类型sds的对象,函数dictFreeEntryKey其实调用的是sdsDictValDestructor,该函数根据robj的类型处理释放操作.Line236调用函数dictFreeEntryVal释放哈希节点的value,之前分析过,对于第二层的哈希表结构, 在添加节点时,其value是NULL, 所以value不需要释放,全局对象setDictType是控制第二层哈希表节点操作的,其value destructor为NULL, 这是和添加value为NULL想对应的.

回到函数dictGenericDelete, Line238:240释放哈希节点本身,并更新哈希表中节点的个数,然后返回.
回到函数sremCommand, Line1940更新数据库更新次数,Line1941设置robj对象的类型sds字段指向NULL, 因为member已经在函数dictDelete中释放.Line1942调用函数decrRefCountrobj对象记录到全局的空闲链表中,并释放其封装的对象,这里将导致调用库函数free释放NULL, C语言中向free函数传入NULL是可以的. 需要注意的是,Line1941的设置是有益的,否则会导致2次释放同一地址上的内存,反而可能有问题.最后Line1943向客户端返回操作成功提示字符串.

redis sismember命令
redis sismember命令的格式为sismember key member,该命令的含义是判断么member是否在key所对应的哈希表中,如果存在返回1,如果不存在返回0,如果包含key的哈希表不存在或者其他错误,则返回-1.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
sadd mykey 7
member1
+OK
sismember mykey 7
member1
1

sismember命令对应的处理函数为sismemberCommand,其实现为(redis.c):

1947 static void sismemberCommand(redisClient *c) {
1948     dictEntry *de;
1949 
1950     de = dictFind(c->dict,c->argv[1]);
1951     if (de == NULL) {
1952         addReplySds(c,sdsnew("-1\r\n"));
1953     } else {
1954         robj *set, *ele;
1955 
1956         set = dictGetEntryVal(de);
1957         if (set->type != REDIS_SET) {
1958             addReplySds(c,sdsnew("-1\r\n"));
1959             return;
1960         }
1961         ele = createObject(REDIS_STRING,c->argv[2]);
1962         if (dictFind(set->ptr,ele))
1963             addReply(c,shared.one);
1964         else
1965             addReply(c,shared.zero);
1966         ele->ptr = NULL;
1967         decrRefCount(ele);
1968     }
1969 }

Line1950在数据库中查找包含指定key的哈希节点.Line1951:1952如果没有指定的节点,向客户端返回-1.Line1961:1965在哈希表中查找member,如果找到,则返回1, 如果没有找到则返回0.Line1967将动态创建的robj对象记录到全局空闲链表中.

redis sinter命令
redis sinter命令的格式为sinter key1 key2 ... keyN, 其含义是返回多个哈希表内容的交集.如果只指定了一个哈希表,则返回该哈希表的全部内容.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
sadd key1 7
member1
+OK
sadd key1 7
member2
+OK
sadd key2 7
member2
+OK
sadd key2 7
member3
+OK
sinter key1 key2
1
7
member2

sinter命令对应的处理函数为sinterCommand,其实现为(redis.c):

1997 static void sinterCommand(redisClient *c) {
1998     dict **dv = malloc(sizeof(dict*)*(c->argc-1));
1999     dictIterator *di;
2000     dictEntry *de;
2001     robj *lenobj;
2002     int j, cardinality = 0;
2003 
2004     if (!dv) oom("sinterCommand");
2005     for (j = 0; j < c->argc-1; j++) {
2006         robj *setobj;
2007         dictEntry *de;
2008 
2009         de = dictFind(c->dict,c->argv[j+1]);
2010         if (!de) {
2011             free(dv);
2012             char *err = "No such key";
2013             addReplySds(c, sdscatprintf(
2014                 sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
2015             return;
2016         }
2017         setobj = dictGetEntryVal(de);
2018         if (setobj->type != REDIS_SET) {
2019             free(dv);
2020             char *err = "LINTER against key not holding a set value";
2021             addReplySds(c, sdscatprintf(
2022                 sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
2023             return;
2024         }
2025         dv[j] = setobj->ptr;
2026     }
2027     /* Sort sets from the smallest to largest, this will improve our
2028      * algorithm's performace */
2029     qsort(dv,c->argc-1,sizeof(dict*),qsortCompareSetsByCardinality);
2030 
2031     /* The first thing we should output is the total number of elements...
2032      * since this is a multi-bulk write, but at this stage we don't know
2033      * the intersection set size, so we use a trick, append an empty object
2034      * to the output list and save the pointer to later modify it with the
2035      * right length */
2036     lenobj = createObject(REDIS_STRING,NULL);
2037     addReply(c,lenobj);
2038     decrRefCount(lenobj);
2039 
2040     /* Iterate all the elements of the first (smallest) set, and test
2041      * the element against all the other sets, if at least one set does
2042      * not include the element it is discarded */
2043     di = dictGetIterator(dv[0]);
2044     if (!di) oom("dictGetIterator");
2045 
2046     while((de = dictNext(di)) != NULL) {
2047         robj *ele;
2048 
2049         for (j = 1; j < c->argc-1; j++)
2050             if (dictFind(dv[j],dictGetEntryKey(de)) == NULL) break;
2051         if (j != c->argc-1)
2052             continue; /* at least one set don't contain the member */
2053         ele = dictGetEntryKey(de);
2054         addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",sdslen(ele->ptr)));
2055         addReply(c,ele);
2056         addReply(c,shared.crlf);
2057         cardinality++;
2058     }
2059     lenobj->ptr = sdscatprintf(sdsempty(),"%d\r\n",cardinality);
2060     dictReleaseIterator(di);
2061     free(dv);
2062 }

Line1998动态创建了一个类型dict的指针数组,数组元素的个数是传入的哈希表的个数.Line2005:2026判断传入的取交集的哈希表是否存在,如果有一个不存在,就可以直接返回了.并向客户端发送错误提示字符串.因为取交集操作,必须是操作的所有集合都存在才有意义.Line2017:2024如果类型错误,也直接返回,并向客户端发送错误提示字符串.sinter命令必须操作的是哈希表,不能是其他类型.Line2025将数组指针指向各个哈希表.Line2029将各个哈希表进行排序,排序的标准是以哈希表中的节点数量,排序函数qsortCompareSetsByCardinality的实现为(redis.c):

   1991 static int qsortCompareSetsByCardinality(const void *s1, const void *s2) {
   1992     dict **d1 = (void*) s1, **d2 = (void*) s2;
   1993 
   1994     return dictGetHashTableUsed(*d1)-dictGetHashTableUsed(*d2);
   1995 }

之所以按照哈希表节点数量排序,是因为集合取交集后的结果,肯定小于等于其中最小的集合.
回到函数sinterCommand, Line2031:2038的意思是说,sinter命令的结果是个bulk类型,结果的第一部分是个整数,表示交集中的节点个数,所以先向客户端响应链表中添加这个响应对象,但是这时候还没有计算出交集,不知道交集中节点个数,所以这时候响应对象中包含的节点个数为空,使用了一个局部对象lenobj来引用这个响应对象,等到交集计算完毕,再更新这个响应对象.Line2040:2058计算哈希表交集,Line2043基于最小的哈希表(经过排序后,索引为0)创建一个dict类型的遍历器.依次遍历该哈希表中的节点元素, 如果该节点在其他哈希表中也存在,则该节点属于交集中的节点,同时把该节点记录到客户端对象的响应链表中.Line2059更新交集中节点数量.Line2060:2061释放动态创建的遍历器和指针数组.

redis smembers命令
redis smembers命令的格式为smembers key, 该命令返回指定key对应的哈希表中的所有元素.telnet模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
sadd mykey 7
member1
+OK
sadd mykey 7
member2
+OK
smembers mykey
2
7
member1
7
member2

smembers命令对应的处理函数为sinterCommand,和sinter命令是相同的处理函数,因为smembers只传入一个哈希表参数,所以函数sinterCommand会返回该哈希表中的所有元素.

猜你喜欢

转载自blog.csdn.net/azurelaker/article/details/81545807