redis 命令不区分大小写,这里均以小写格式说明.
操作string类型value的redis命令主要包括set,get,setnx,incr和decr.
redis set命令
redis set命令的格式为set key value
命令字符串,key和value之间以空格分割, set命令是bulk类型的命令,其value部分由四部分组成,其二进制格式为bytes\r\nxxxxxx\r\n
其中bytes代表value字节数,xxxxxx代表实际的value字节,二者之间以\r\n分割,value最后部分以\r\n结束.
假设客户端已经通过如下telnet命令连接到了redis服务器:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
接着在控制终端继续输入redis set命令:
set mykey 7
myvalue
+OK
用telnet模拟向redis服务器发送set命令及其参数时, 回车会导致输入,因此value部分将以两部分(两行)输入,字节数和字节数据. “+OK”是redis服务器返回给客户端的响应字符串.当输入myvalue这个字符窜时, 服务端完成了set命令及其参数的所有解析,函数processCommand
中将调用set命令处理函数setCommand
,该函数的实现为(redis.c):
1337 static void setCommand(redisClient *c) {
1338 return setGenericCommand(c,0);
1339 }
调用函数setGenericCommand
,第二个参数为0,表示如果数据库中已经存在该key,则对该key对应的value进行更新. redis还有一个setnx
命令, 相当于SET if Not eXists
,即数据库中不存在该key时,才进行设置,否则不进行任何操作.
函数setGenericCommand
的实现为(redis.c):
1317 static void setGenericCommand(redisClient *c, int nx) {
1318 int retval;
1319 robj *o;
1320
1321 o = createObject(REDIS_STRING,c->argv[2]);
1322 c->argv[2] = NULL;
1323 retval = dictAdd(c->dict,c->argv[1],o);
1324 if (retval == DICT_ERR) {
1325 if (!nx)
1326 dictReplace(c->dict,c->argv[1],o);
1327 else
1328 decrRefCount(o);
1329 } else {
1330 /* Now the key is in the hash entry, don't free it */
1331 c->argv[1] = NULL;
1332 }
1333 server.dirty++;
1334 addReply(c,shared.ok);
1335 }
客户端对象的argv数组中,argv[0]记录set命令字符串,argv[1]记录key的string,argv[2]记录value的string.Line1321调用函数createObject
创建一个类型robj
的对象, 来封装value的值.因为set命令的value是string类型,所以创建的robj
对象的类型是REDIS_STRING
. 函数createObject
的实现为(redis.c):
992 static robj *createObject(int type, void *ptr) {
993 robj *o;
994
995 if (listLength(server.objfreelist)) {
996 listNode *head = listFirst(server.objfreelist);
997 o = listNodeValue(head);
998 listDelNode(server.objfreelist,head);
999 } else {
1000 o = malloc(sizeof(*o));
1001 }
1002 if (!o) oom("createObject");
1003 o->type = type;
1004 o->ptr = ptr;
1005 o->refcount = 1;
1006 return o;
1007 }
Line995:1001判断全局的空闲robj
链表是否为空,如果非空,则从链表头删除该对象,否则,动态创建一个robj
对象.Line1003:1005设置该对象的类型,引用的value,初始的应用计数.Line1322将sds对象设置为NULL, 即不再指向value, 因为value已经被robj
对象所管理.调用函数dictAdd
将key/value存储在内存数据库中.默认情况下,客户端对象的字段dict
指向16个全局数据库中的第一个(索引为0).函数dictAdd
的实现为(dict.c):
175 /* Add an element to the target hash table */
176 int dictAdd(dict *ht, void *key, void *val)
177 {
178 int index;
179 dictEntry *entry;
180
181 /* Get the index of the new element, or -1 if
182 * the element already exists. */
183 if ((index = _dictKeyIndex(ht, key)) == -1)
184 return DICT_ERR;
185
186 /* Allocates the memory and stores key */
187 entry = _dictAlloc(sizeof(*entry));
188 entry->next = ht->table[index];
189 ht->table[index] = entry;
190
191 /* Set the hash entry fields. */
192 dictSetHashKey(ht, entry, key);
193 dictSetHashVal(ht, entry, val);
194 ht->used++;
195 return DICT_OK;
196 }
Line183调用函数_dictKeyIndex
判断key在该内存数据库中是否存在,如果存在,返回-1,如果不存在则返回该key经哈希算法映射后对应的该数据库的BUCKET的索引.函数_dictKeyIndex
的实现为(dict.c):
399 /* Returns the index of a free slot that can be populated with
400 * an hash entry for the given 'key'.
401 * If the key already exists, -1 is returned. */
402 static int _dictKeyIndex(dict *ht, const void *key)
403 {
404 unsigned int h;
405 dictEntry *he;
406
407 /* Expand the hashtable if needed */
408 if (_dictExpandIfNeeded(ht) == DICT_ERR)
409 return -1;
410 /* Compute the key hash value */
411 h = dictHashKey(ht, key) & ht->sizemask;
412 /* Search if this slot does not already contain the given key */
413 he = ht->table[h];
414 while(he) {
415 if (dictCompareHashKeys(ht, key, he->key))
416 return -1;
417 he = he->next;
418 }
419 return h;
420 }
Line408调用函数_dictExpandIfNeeded
尝试扩展内存数据库的容量, 因为redis的内存数据库在逻辑上设计为哈希表结构, 因此这里的容量指的是哈希表的BUCKET的大小.函数_dictExpandIfNeeded
的实现为(dict.c):
373 /* Expand the hash table if needed */
374 static int _dictExpandIfNeeded(dict *ht)
375 {
376 /* If the hash table is empty expand it to the intial size,
377 * if the table is "full" dobule its size. */
378 if (ht->size == 0)
379 return dictExpand(ht, DICT_HT_INITIAL_SIZE);
380 if (ht->used == ht->size)
381 return dictExpand(ht, ht->size*2);
382 return DICT_OK;
383 }
如果数据库容量为0或者已经存储的节点个数达到了数据库容量,均需要扩展其容量.对于全局的16个数据库,在redis服务器实例启动后,如果没有从dump.rdb中装载key/value节点,则其数据库容量均为0.数据库容量为0时,将其容量扩展为DICT_HT_INITIAL_SIZE
, 否则扩展为当前容量的2倍.需要注意的是函数dictExpand
不一定只是扩展容量, 也可能是将容量调整小, 请参考之前定时事件逻辑的分析.
函数dictExpand
的实现为(dict.c):
124 /* Expand or create the hashtable */
125 int dictExpand(dict *ht, unsigned int size)
126 {
127 dict n; /* the new hashtable */
128 unsigned int realsize = _dictNextPower(size), i;
129
130 /* the size is invalid if it is smaller than the number of
131 * elements already inside the hashtable */
132 if (ht->used > size)
133 return DICT_ERR;
134
135 _dictInit(&n, ht->type, ht->privdata);
136 n.size = realsize;
137 n.sizemask = realsize-1;
138 n.table = _dictAlloc(realsize*sizeof(dictEntry*));
139
140 /* Initialize all the pointers to NULL */
141 memset(n.table, 0, realsize*sizeof(dictEntry*));
142
143 /* Copy all the elements from the old to the new table:
144 * note that if the old hash table is empty ht->size is zero,
145 * so dictExpand just creates an hash table. */
146 n.used = ht->used;
147 for (i = 0; i < ht->size && ht->used > 0; i++) {
148 dictEntry *he, *nextHe;
149
150 if (ht->table[i] == NULL) continue;
151
152 /* For each hash entry on this slot... */
153 he = ht->table[i];
154 while(he) {
155 unsigned int h;
156
157 nextHe = he->next;
158 /* Get the new element index */
159 h = dictHashKey(ht, he->key) & n.sizemask;
160 he->next = n.table[h];
161 n.table[h] = he;
162 ht->used--;
163 /* Pass to the next element */
164 he = nextHe;
165 }
166 }
167 assert(ht->used == 0);
168 _dictFree(ht->table);
169
170 /* Remap the new hashtable in the old */
171 *ht = n;
172 return DICT_OK;
173 }
Line128调用函数_dictNextPower
计算实际要扩展的容量,其值是2的幂,大于或者等于输入参数值.因为这个值是哈希表的BUCKET数目,所以存在一个最大值.函数_dictNextPower
的实现为(dict.c):
385 /* Our hash table capability is a power of two */
386 static unsigned int _dictNextPower(unsigned int size)
387 {
388 unsigned int i = DICT_HT_INITIAL_SIZE;
389
390 if (size >= 2147483648U)
391 return 2147483648U;
392 while(1) {
393 if (i >= size)
394 return i;
395 i *= 2;
396 }
397 }
回到函数dictExpand
, Line132判断如果希望调整的容量小于当前存储的节点数,则直接返回,不做任何操作.Line135初始化类型dict
的一个局部对象,相关调用函数实现包括.
函数_dictInit
的实现为(dict.c):
103 /* Initialize the hash table */
104 int _dictInit(dict *ht, dictType *type,
105 void *privDataPtr)
106 {
107 _dictReset(ht);
108 ht->type = type;
109 ht->privdata = privDataPtr;
110 return DICT_OK;
111 }
函数_dictReset
的实现为(dict.c):
85 static void _dictReset(dict *ht)
86 {
87 ht->table = NULL;
88 ht->size = 0;
89 ht->sizemask = 0;
90 ht->used = 0;
91 }
92
Line136为该局部dict对象设置正确的BUCKET容量.Line137设置的掩码值,任意数值与该掩码值进行’&’操作,相当于对哈希表容量取模操作, 也就是获得一个BUCKET索引.Line138的逻辑是为字段table分配指针数组,注意这里的数组元素是指针.Line141初始化指针数组元素,使所有元素均指向NULL.Line147:166如果哈希表数据库之前存储有节点,需要将节点拷贝到新扩容的哈希表数据库中,因此需要遍历之前的哈希表数据库.Line150如果该BUCKET为空,则检查下一个索引BUCKET.Line159:161对节点重新计算哈希并映射到新的哈希表BUCKET索引上.函数dictHashKey
就是类型dictType
对象中的函数指针hashFunction
,对应的函数实现为函数dictGenHashFunction
(dict.c):
71 /* Generic hash function (a popular one from Bernstein).
72 * I tested a few and this was the best. */
73 unsigned int dictGenHashFunction(const unsigned char *buf, int len) {
74 unsigned int hash = 5381;
75
76 while (len--)
77 hash = ((hash << 5) + hash) + (*buf++); /* hash * 33 + c */
78 return hash;
79 }
Line168将之前哈希表的指针数组的内存释放掉,因为这个数组是在堆上动态创建的.Line171将新扩展的哈希表赋值给客户端对象的dict
字段,因为该字段是指向的全局的哈希表数据库,所以全局的哈希表相应的得到了更新.
回到函数_dictKeyIndex
, Line411计算key在哈希表中的BUCKET索引,函数dictHashKey
已经分析过.Line413:419如果该BUCKET中存在节点,则比较输入参数key和节点的key是否一致,如果找到一致key的节点,返回-1,否则返回BUCKET索引.
回到函数dictAdd
, Line183如果返回-1,说明该节点已经存储在数据库中,否则,Line187:193将创建一个类型dictEntry
的节点对象,将对节点对象插入到该BUCKET的链表头.
回到函数setGenericCommand
,Line1324:1329说明该节点之前已经存在于数据库中了, 所以如果是set命令,调用dictReplace
替换该节点的value值,如果是setnx
命令,调用函数decrRefCount
释放创建的robj
对象.
函数dictReplace
的实现为(dict.c):
198 /* Add an element, discarding the old if the key already exists */
199 int dictReplace(dict *ht, void *key, void *val)
200 {
201 dictEntry *entry;
202
203 /* Try to add the element. If the key
204 * does not exists dictAdd will suceed. */
205 if (dictAdd(ht, key, val) == DICT_OK)
206 return DICT_OK;
207 /* It already exists, get the entry */
208 entry = dictFind(ht, key);
209 /* Free the old value and set the new one */
210 dictFreeEntryVal(ht, entry);
211 dictSetHashVal(ht, entry, val);
212 return DICT_OK;
213 }
Line205:206调用函数dictAdd将key/value存储到数据库,如果存储成功,说明该key对应的之前节点不存在,不需要替换其value,直接返回.Line207:211为value的替换处理, 首先通过key找到该dictEntry
节点对象,然后释放旧的value值,并为该节点设置新的value值.
函数dictFind
的实现为(dict.c):
289 dictEntry *dictFind(dict *ht, const void *key)
290 {
291 dictEntry *he;
292 unsigned int h;
293
294 if (ht->size == 0) return NULL;
295 h = dictHashKey(ht, key) & ht->sizemask;
296 he = ht->table[h];
297 while(he) {
298 if (dictCompareHashKeys(ht, key, he->key))
299 return he;
300 he = he->next;
301 }
302 return NULL;
303 }
搜索节点的逻辑是, 通过哈希函数将key映射到数据库的BUCKET索引,在该BUCKET指向的链表中依次比较节点的key和待查找key,如果相等,返回该节点.如果没有找到匹配的节点,返回NULL.
函数dictFreeEntryVal
释放value值,该函数(其实是个宏定义)对应类型dictType
中的valDestructor
指针,其指向的是函数sdsDictValDestructor
, 该函数的实现为(redis.c):
395 static void sdsDictValDestructor(void *privdata, void *val)
396 {
397 DICT_NOTUSED(privdata);
398
399 decrRefCount(val);
400 }
函数decrRefCount
的实现为(redis.c):
1039 static void decrRefCount(void *obj) {
1040 robj *o = obj;
1041 if (--(o->refcount) == 0) {
1042 switch(o->type) {
1043 case REDIS_STRING: freeStringObject(o); break;
1044 case REDIS_LIST: freeListObject(o); break;
1045 case REDIS_SET: freeSetObject(o); break;
1046 default: assert(0 != 0); break;
1047 }
1048 if (!listAddNodeHead(server.objfreelist,o))
1049 free(o);
1050 }
1051 }
首先将类型robj
对象引用计数减少一次,如果其值为0, 根据该对象的类型,释放该对象引用的value(由其字段ptr
指向).这里先分析REDIS_STRING
类型的robj
对象,其他两种类型,在后续相应命令中再分析.调用函数freeStringObject
将释放string类型的value,其实现为(redis.c):
1023 static void freeStringObject(robj *o) {
1024 sdsfree(o->ptr);
1025 }
string类型的value由类型sds
管理,所以需要释放sds
对象.
回到函数decrRefCount
, 释放完robj
对象管理的value后,Line1048将robj
对象插入到全局的空闲链表中.
回到函数dictReplace
, Line211调用函数dictSetHashVal
将新的value值赋值给节点对象.
回到函数setGenericCommand
, set命令操作完成以后,更新全局的字段dirty
,以反映内存数据更新次数.需要注意的是如果是setnx命令,可能内存数据并没有更新,所以这个操作并不严密,需要判断是否是setnx命令.Line1334调用函数addReply
设置向客户端发送的响应内容.该函数的实现为(redis.c):
957 static void addReply(redisClient *c, robj *obj) {
958 if (listLength(c->reply) == 0 &&
959 aeCreateFileEvent(server.el, c->fd, AE_WRITABLE,
960 sendReplyToClient, c, NULL) == AE_ERR) return;
961 if (!listAddNodeTail(c->reply,obj)) oom("listAddNodeTail");
962 incrRefCount(obj);
963 }
如果客户端对象的响应链表为空,需要为客户端创建一个文件事件对象,监听其可写状态,其触发回调函数为sendReplyToClient
.如果客户端对象的响应链表非空,说明已经创建了监听可写状态的文件事件对象.Line961将类型robj
对象添加到响应链表尾部.函数setGenericCommand
中的逻辑已经全部处理完毕,将返回到事件主循环,系统调用select将监听包括为该客户端创建的监听可写状态的文件事件对象,可写状态的文件事件对象在select系统调用时将立即触发,不会阻塞.函数sendReplyToClient
被调用,想客户端发送命令响应.该函数的实现为(redis.c):
716 static void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
717 redisClient *c = privdata;
718 int nwritten = 0, totwritten = 0, objlen;
719 robj *o;
720 REDIS_NOTUSED(el);
721 REDIS_NOTUSED(mask);
722
723 while(listLength(c->reply)) {
724 o = listNodeValue(listFirst(c->reply));
725 objlen = sdslen(o->ptr);
726
727 if (objlen == 0) {
728 listDelNode(c->reply,listFirst(c->reply));
729 continue;
730 }
731
732 nwritten = write(fd, o->ptr+c->sentlen, objlen - c->sentlen);
733 if (nwritten <= 0) break;
734 c->sentlen += nwritten;
735 totwritten += nwritten;
736 /* If we fully sent the object on head go to the next one */
737 if (c->sentlen == objlen) {
738 listDelNode(c->reply,listFirst(c->reply));
739 c->sentlen = 0;
740 }
741 }
742 if (nwritten == -1) {
743 if (errno == EAGAIN) {
744 nwritten = 0;
745 } else {
746 redisLog(REDIS_DEBUG,
747 "Error writing to client: %s", strerror(errno));
748 freeClient(c);
749 return;
750 }
751 }
752 if (totwritten > 0) c->lastinteraction = time(NULL);
753 if (listLength(c->reply) == 0) {
754 c->sentlen = 0;
755 aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE);
756 }
757 }
当redis服务器向客户端发送命令响应时, 会把客户端对象的响应链表中的所有robj
对象都发送给客户端.即一个响应内容可能是由多个robj
对象组成的.注意响应链表上的robj
对象都是string类型的,其内容都是存储在sds
对象里面的.Line724:725从响应链表头获得robj
对象,并获得sds
中的数据长度.Line732:740系统调用write
将数据写入socket,对于robj
对象中的数据,可能需要多次write
写入,因此客户端对象的字段sentlen
记录累计写入的数据量,当一个robj
中的数据全部写入socket,则将该robj
对象从响应链表中删除,并将客户端对象的字段sentlen
清零.接着发送响应链表中下一个robj
对象中的数据.Line742:751系统调用write返回错误,如果错误码是EAGAIN
, 该函数返回后, 重新进入主事件循环,客户端对象的监听可写状态的文件事件对象再次被触发,重新进入该函数,重试write
操作.如果是其他不可重试错误,则释放客户端对象资源, 终止同客户端的连接.Line752如果系统调用write
成功,更新交互时间戳.Line753:756如果客户端对象中响应链表中的内容全部写入,即链表为空,调用函数aeDeleteFileEvent
删除该文件事件对象,即不再需要监听可写状态,只需继续监听可读状态,继续等待该客户端发送过来的redis命令.
函数aeDeleteFileEvent
的实现为(ae.c):
56 void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
57 {
58 aeFileEvent *fe, *prev = NULL;
59
60 fe = eventLoop->fileEventHead;
61 while(fe) {
62 if (fe->fd == fd && fe->mask == mask) {
63 if (prev == NULL)
64 eventLoop->fileEventHead = fe->next;
65 else
66 prev->next = fe->next;
67 if (fe->finalizerProc)
68 fe->finalizerProc(eventLoop, fe->clientData);
69 free(fe);
70 return;
71 }
72 prev = fe;
73 fe = fe->next;
74 }
75 }
在全局的文件事件对象链表中, 根据文件描述符和掩码查找指定的对象,找到之后,将其从链表中删除,并释放该文件事件对象的内存.
至此, 整个set命令的处理逻辑全部结束.其中主要步骤包括,监听客户端可读状态的文件事件被触发,从客户端socket中读取数据,解析命令及其参数,调用响应命令的处理函数,将set命令中的key/value写入内存数据库,为该客户端创建一个监听可写状态的文件事件,并把封装有响应数据的robj
对象插入客户端对象的响应链表.返回主事件循环,监听可写状态的文件事件对象被触发,向客户端发送响应数据.其中有写处理步骤在处理redis其他命令时都是类似的.
redis setnx命令
setnx命令的格式为setnx key value
, setnx和set的区别在于,当key已经存在于数据库中时, setnx命令什么也不做.代码中的处理差异在于函数setGenericCommand
中,Line1325:1328如果是setnx命令,调用函数decrRefCount
释放value中的数据,并不替换之前存在的value.
redis get命令
redis的get命令返回指定key的value值,其命令格式为get key
. telnet模拟操作如下:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
set mykey 7
myvalue
+OK
get mykey
7
myvalue
get nonkey
nil
redis的get命令相应的处理函数为getCommand
, 其实现为(redis.c):
1345 static void getCommand(redisClient *c) {
1346 dictEntry *de;
1347
1348 de = dictFind(c->dict,c->argv[1]);
1349 if (de == NULL) {
1350 addReply(c,shared.nil);
1351 } else {
1352 robj *o = dictGetEntryVal(de);
1353
1354 if (o->type != REDIS_STRING) {
1355 char *err = "GET against key not holding a string value";
1356 addReplySds(c,
1357 sdscatprintf(sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
1358 } else {
1359 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(o->ptr)));
1360 addReply(c,o);
1361 addReply(c,shared.crlf);
1362 }
1363 }
1364 }
Line1348在数据库中查找key对应的节点,如果没有找到包含该key的节点,则向客户端返回shared.nil
对象,该robj
对象包含字符串nil
,表示不存在的节点.如果存在包含该key的节点,因为redis的get命令操作的value必须是string类型,Line1354:1358对此进行判断,如果类型错误,向客户端发送错误提示字符串,该字符串已一个负整数起始,其绝对值是Line1355错误提示字符串的字符数.Line1359:1361想客户端发送get命令的结果,即查询key对应的value.可以看到响应由三个robj
对象构成,第一个robj
对象包含value的字节数和\r\n两个字节,第二个robj
对象是value数据,第三个robj
对象是shared.crlf
,也就是\r\n.
redis incr命令
incr命令将key所在节点的value值增加一次, 也说是说incr命令是计数用的,其value是个数值.incr命令的格式为incr key
.如果是对指定key第一次执行incr命令,则将value数值设置为1.telnet的模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
incr mykey
1
incr mykey
2
incr命令的相应处理函数为incrCommand
(redis.c):
1419 static void incrCommand(redisClient *c) {
1420 return incrDecrCommand(c,1);
1421 }
函数incrDecrCommand
的实现为(redis.c):
1382 static void incrDecrCommand(redisClient *c, int incr) {
1383 dictEntry *de;
1384 sds newval;
1385 long long value;
1386 int retval;
1387 robj *o;
1388
1389 de = dictFind(c->dict,c->argv[1]);
1390 if (de == NULL) {
1391 value = 0;
1392 } else {
1393 robj *o = dictGetEntryVal(de);
1394
1395 if (o->type != REDIS_STRING) {
1396 value = 0;
1397 } else {
1398 char *eptr;
1399
1400 value = strtoll(o->ptr, &eptr, 10);
1401 }
1402 }
1403
1404 value += incr;
1405 newval = sdscatprintf(sdsempty(),"%lld",value);
1406 o = createObject(REDIS_STRING,newval);
1407 retval = dictAdd(c->dict,c->argv[1],o);
1408 if (retval == DICT_ERR) {
1409 dictReplace(c->dict,c->argv[1],o);
1410 } else {
1411 /* Now the key is in the hash entry, don't free it */
1412 c->argv[1] = NULL;
1413 }
1414 server.dirty++;
1415 addReply(c,o);
1416 addReply(c,shared.crlf);
1417 }
Line1389:1402查找包括该key的节点,如果找到该节点,获得该节点中存储的value数值,value在节点中是以字符串的形式存储的,因此需要调用strtoll
转换为数值.如果没有找到该节点或者节点类型错误(非string类型),则将计数初始化为0.Line1404:1405更新计数值,并将数值以字符串的格式存储在sds
对象中.对于incr命令,该函数的第二个参数传入的是1,是步长为1的递增.Line1407:1413创建一个robj
对象封装sds
对象,并存储在数据库中,如果对该key是第一次调用incr命令,函数dictAdd
将返回DICT_OK
, 否则DICT_ERR
, 如果对该key不是第一次调用incr命令,通过调用函数dictReplace
更新节点中value的数值.Line1415:1416向客户端返回更新后的value值.
redis decr命令
decr命令将key所在节点的value值减少一次, 也说是说decr命令是计数用的,其value是个数值.decr命令的格式为decr key
.如果是对指定key第一次执行decr命令,则将value数值设置为-1用telnet模拟操作为:
telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
incr mykey
1
decr mykey
0
redis decr命令的相应处理函数为decrCommand
,其实现为(redis.c):
1423 static void decrCommand(redisClient *c) {
1424 return incrDecrCommand(c,-1);
1425 }
其实现和incr命令的逻辑相同,只是以步长为-1,增加其value值.