操作list类型value的redis协议

操作list类型value的redis命令主要包括rpush,lpush,llen,lrange,ltrim,lindex,lpop,和rpop.

redis rpush命令
redis rpush命令的格式为rpush key value. 该命令将value添加到key对应的链表尾部.telnet模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
rpush mykey 8
myvalue1
+OK
rpush mykey 8
myvalue2
+OK

redis的rpush命令对应的处理函数为rpushCommand,该函数的实现为(redis.c):

1663 static void rpushCommand(redisClient *c) {
1664     pushGenericCommand(c,REDIS_TAIL);
1665 }

函数pushGenericCommand的实现为(redis.c):

1620 static void pushGenericCommand(redisClient *c, int where) {
1621     robj *ele, *lobj;
1622     dictEntry *de;
1623     list *list;
1624    
1625     ele = createObject(REDIS_STRING,c->argv[2]);
1626     c->argv[2] = NULL;
1627 
1628     de = dictFind(c->dict,c->argv[1]);
1629     if (de == NULL) {
1630         lobj = createListObject();
1631         list = lobj->ptr;
1632         if (where == REDIS_HEAD) {
1633             if (!listAddNodeHead(list,ele)) oom("listAddNodeHead");
1634         } else {
1635             if (!listAddNodeTail(list,ele)) oom("listAddNodeTail");
1636         }
1637         dictAdd(c->dict,c->argv[1],lobj);
1638 
1639         /* Now the key is in the hash entry, don't free it */
1640         c->argv[1] = NULL;
1641     } else {
1642         lobj = dictGetEntryVal(de);
1643         if (lobj->type != REDIS_LIST) {
1644             decrRefCount(ele);
1645             addReplySds(c,sdsnew("-ERR push against existing key not holding a list\r\n"));
1646             return;
1647         }
1648         list = lobj->ptr;
1649         if (where == REDIS_HEAD) {
1650             if (!listAddNodeHead(list,ele)) oom("listAddNodeHead");
1651         } else {
1652             if (!listAddNodeTail(list,ele)) oom("listAddNodeTail");
1653         }
1654     }
1655     server.dirty++;
1656     addReply(c,shared.ok);
1657 }

Line1625:1626创建一个robj对象来封装value值,同时将c->argv[2]设置为NULL, 即不再指向value.Line1628在数据库中查找包含该key的节点.Line1629:1641如果没有找到该节点,调用函数createListObject创建一个类型REDIS_LISTrobj对象,该函数的实现为(redis.c):

1009 static robj *createListObject(void) {
1010     list *l = listCreate();
1011 
1012     if (!l) oom("createListObject");
1013     listSetFreeMethod(l,decrRefCount);
1014     return createObject(REDIS_LIST,l);
1015 }

首先创建一个类型list对象,然后再创建一个类型robj对象,封装list对象.

回到函数pushGenericCommand, Line1635将封装value的robj对象添加到链表尾部,因为链表是新创建的,所以这个添加的链接节点,既是尾部节点又是头部节点.Line1637调用函数dictAdd将key和类型list的robj对象存入数据库.Line1640将指向key的sds指针设置为NULL, 因为key已经由哈希节点对象的管理.Line1642:1653说明该key对应的哈希节点已经存在,判断哈希节点中的value是否是REDIS_LIST类型,如果不是,释放要添加的value,并向客户端发送错误提示响应.如果类型正确,将要添加的value插入到链表的尾部.rpush命令导致的数据库更新操作结束,Line1656向客户端发送成功字符串.

redis lpush命令
redis lpush命令的格式为lpush key value. 该命令将value添加到key对应的链表首部.telnet模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush lkey 6
value1
+OK
lpush lkey 6
value2
+OK

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

   1659 static void lpushCommand(redisClient *c) {
   1660     pushGenericCommand(c,REDIS_HEAD);
   1661 }

和rpush命令的唯一区别在于,函数pushGenericCommand调用listAddNodeHead将value添加到key对应的链表首部.

redis lpop命令
redis lpop命令的格式为lpop key, 该命令从key对应的链表首部移除一个value节点,并返回该value.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
lpop mykey
8
myvalue3

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

1785 static void lpopCommand(redisClient *c) {
1786     popGenericCommand(c,REDIS_HEAD);
1787 }

函数popGenericCommand的实现为(redis.c):

1749 static void popGenericCommand(redisClient *c, int where) {
1750     dictEntry *de;
1751    
1752     de = dictFind(c->dict,c->argv[1]);
1753     if (de == NULL) {
1754         addReply(c,shared.nil);
1755     } else {
1756         robj *o = dictGetEntryVal(de);
1757     
1758         if (o->type != REDIS_LIST) {
1759             char *err = "POP against key not holding a list value";
1760             addReplySds(c,
1761                 sdscatprintf(sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
1762         } else {
1763             list *list = o->ptr;
1764             listNode *ln;
1765 
1766             if (where == REDIS_HEAD)
1767                 ln = listFirst(list);
1768             else
1769                 ln = listLast(list);
1770 
1771             if (ln == NULL) {
1772                 addReply(c,shared.nil);
1773             } else {
1774                 robj *ele = listNodeValue(ln);
1775                 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr)));
1776                 addReply(c,ele);
1777                 addReply(c,shared.crlf);
1778                 listDelNode(list,ln);
1779                 server.dirty++;
1780             }
1781         }
1782     }
1783 }

Line1752在数据库中查找包含该key的哈希节点.Line1753:1755如果没有找到该哈希节点,则向客户端返回空结果字符串.
如果找到对应的哈希节点,获得其类型robj对象,如果对象类型错误,向客户端返回错误字符串.Line1766:1767获得value链表的头节点,Line1771:1772如果value链表为空(可能已经pop操作了全部节点),则向客户端返回空结果字符串.Line1774:1777向客户端发送value信息(起始信息是value字节数).Line1778:1779将该value从所在链表删除,并记录数据库更新次数.

redis rpop命令
redis rpop命令的格式为rpop key, 该命令从key对应的链表尾部移除一个value节点,并返回该value.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
rpop mykey
8
myvalue1

rpop命令对应的处理函数为rpopCommand.其实现为(redis.c):

1789 static void rpopCommand(redisClient *c) {
1790     popGenericCommand(c,REDIS_TAIL);
1791 }

rpop和lpop命令的唯一区别在于,函数popGenericCommand中, Line1769调用函数listLast从链表尾部获得要返回的value节点.

redis llen命令
redis llen命令的格式为llen key, 该命令返回key对应的链表中value节点数.telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
llen mykey
2

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

1667 static void llenCommand(redisClient *c) {
1668     dictEntry *de;
1669     list *l;
1670    
1671     de = dictFind(c->dict,c->argv[1]);
1672     if (de == NULL) {
1673         addReply(c,shared.zero);
1674         return;
1675     } else {
1676         robj *o = dictGetEntryVal(de);
1677         if (o->type != REDIS_LIST) {
1678             addReplySds(c,sdsnew("-1\r\n"));
1679         } else {
1680             l = o->ptr;
1681             addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",listLength(l)));
1682         }
1683     }
1684 }

Line1671在数据库中查找包括该key的哈希节点.Line1672:1674如果没有指定的哈希节点,向客户端返回0.Line1677:1678如果哈希节点包含的robj对象类型错误,向客户端返回-1,否则,Line1680:1681向客户端返回value链表节点数.

redis lrange命令
redis lrange命令的格式为lrange key start end, 该命令返回key对应的链表中指定的value节点,是一个子序列, 从链表索引start开始到索引end结束,注意是包含索引end的value.索引可以为负数, -1代表最后一个value节点,-2代表-1前面的value节点,依次类推. telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
lpush mykey 8
myvalue4
+OK
lrange mykey 1 2
2
8
myvalue3
8
myvalue2

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

1793 static void lrangeCommand(redisClient *c) {
1794     dictEntry *de;
1795     int start = atoi(c->argv[2]);
1796     int end = atoi(c->argv[3]);
1797    
1798     de = dictFind(c->dict,c->argv[1]);
1799     if (de == NULL) {
1800         addReply(c,shared.nil);
1801     } else {
1802         robj *o = dictGetEntryVal(de);
1803     
1804         if (o->type != REDIS_LIST) {
1805             char *err = "LRANGE against key not holding a list value";
1806             addReplySds(c,
1807                 sdscatprintf(sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
1808         } else {
1809             list *list = o->ptr;
1810             listNode *ln;
1811             int llen = listLength(list);
1812             int rangelen, j;
1813             robj *ele;
1814 
1815             /* convert negative indexes */
1816             if (start < 0) start = llen+start;
1817             if (end < 0) end = llen+end;
1818             if (start < 0) start = 0;
1819             if (end < 0) end = 0;
1820 
1821             /* indexes sanity checks */
1822             if (start > end || start >= llen) {
1823                 /* Out of range start or start > end result in empty list */
1824                 addReply(c,shared.zero);
1825                 return;
1826             }
1827             if (end >= llen) end = llen-1;
1828             rangelen = (end-start)+1;
1829 
1830             /* Return the result in form of a multi-bulk reply */
1831             ln = listIndex(list, start);
1832             addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",rangelen));
1833             for (j = 0; j < rangelen; j++) {
1834                 ele = listNodeValue(ln);
1835                 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr)));
1836                 addReply(c,ele);
1837                 addReply(c,shared.crlf);
1838                 ln = ln->next;
1839             }
1840         }
1841     }
1842 }

Line1798在数据库中查找包含该key的哈希节点. Line1799:1800如果没有指定的节点,向客户端返回空字符串提示.Line1804:1807如果哈希节点包含的robj对象类型错误,向客户端返回错误提示字符串.Line1811获得value链表中节点数.Line1815:1828根据传入的开始索引和结束索引,计算要返回的子序列节点数.Line1838调用函数listIndex返回value链表中指定索引的value节点.函数listIndex的实现为(adlist.c):

240 /* Return the element at the specified zero-based index
241  * where 0 is the head, 1 is the element next to head
242  * and so on. Negative integers are used in order to count
243  * from the tail, -1 is the last element, -2 the penultimante
244  * and so on. If the index is out of range NULL is returned. */
245 listNode *listIndex(list *list, int index) {
246     listNode *n;
247 
248     if (index < 0) {
249         index = (-index)-1;
250         n = list->tail;
251         while(index-- && n) n = n->prev;
252     } else {
253         n = list->head;
254         while(index-- && n) n = n->next;
255     }
256     return n;
257 }

索引值为负数时,从链表尾部遍历,索引值为正数时,从链表头部遍历.
回到函数lrangeCommand,Line1832:1839向客户端返回结果,包含子序列的节点个数,及其value节点.

redis lindex命令
redis lindex命令的格式为lindex key index, 该命令返回key对应的链表中指定的value节点, 索引可以为负数, -1代表最后一个value节点,-2代表-1前面的value节点,依次类推. telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lindex mykey 1
8
myvalue1

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

1686 static void lindexCommand(redisClient *c) {
1687     dictEntry *de;
1688     int index = atoi(c->argv[2]);
1689 
1690     de = dictFind(c->dict,c->argv[1]);
1691     if (de == NULL) {
1692         addReply(c,shared.nil);
1693     } else {
1694         robj *o = dictGetEntryVal(de);
1695 
1696         if (o->type != REDIS_LIST) {
1697             char *err = "LINDEX against key not holding a list value";
1698             addReplySds(c,
1699                 sdscatprintf(sdsempty(),"%d\r\n%s\r\n",-((int)strlen(err)),err));
1700         } else {
1701             list *list = o->ptr;
1702             listNode *ln;
1703 
1704             ln = listIndex(list, index);
1705             if (ln == NULL) {
1706                 addReply(c,shared.nil);
1707             } else {
1708                 robj *ele = listNodeValue(ln);
1709                 addReplySds(c,sdscatprintf(sdsempty(),"%d\r\n",(int)sdslen(ele->ptr)));
1710                 addReply(c,ele);
1711                 addReply(c,shared.crlf);
1712             }
1713         }
1714     }
1715 }

Line1690在数据库中查找包含该key的哈希节点.Line1691:1692如果没有指定的哈希节点,则向客户端返回空字符串.Line1696:1699如果哈希节点的robj对象的类型错误,则向客户端返回错误提示字符串.Line1704:1712返回value链表中指定索引的value节点,如果索引超出链表长度,向客户端返回空字符串,否则想客户端返回该索引对应的value.

redis ltrim命令
redis ltrim命令的格式为ltrim key start end, 该命令返回key对应的链表进行trim操作,之保留指定索引表示的子序列. 索引可以为负数, -1代表最后一个value节点,-2代表-1前面的value节点,依次类推. telnet的模拟操作为:

telnet 10.7.7.132 6379
Trying 10.7.7.132...
Connected to 10.7.7.132.
Escape character is '^]'.
lpush mykey 8 
myvalue1
+OK
lpush mykey 8
myvalue2
+OK
lpush mykey 8
myvalue3
+OK
lpush mykey 8
myvalue4
+OK
ltrim mykey 1 2
+OK
lrange mykey 0 -1
2
8
myvalue3
8
myvalue2

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

1844 static void ltrimCommand(redisClient *c) {
1845     dictEntry *de;
1846     int start = atoi(c->argv[2]);
1847     int end = atoi(c->argv[3]);
1848    
1849     de = dictFind(c->dict,c->argv[1]);
1850     if (de == NULL) {
1851         addReplySds(c,sdsnew("-ERR no such key\r\n"));
1852     } else {
1853         robj *o = dictGetEntryVal(de);
1854 
1855         if (o->type != REDIS_LIST) {
1856             addReplySds(c,
1857                 sdsnew("-ERR LTRIM against key not holding a list value"));
1858         } else {
1859             list *list = o->ptr;
1860             listNode *ln;
1861             int llen = listLength(list);
1862             int j, ltrim, rtrim;
1863 
1864             /* convert negative indexes */
1865             if (start < 0) start = llen+start;
1866             if (end < 0) end = llen+end;
1867             if (start < 0) start = 0;
1868             if (end < 0) end = 0;
1869 
1870             /* indexes sanity checks */
1871             if (start > end || start >= llen) {
1872                 /* Out of range start or start > end result in empty list */
1873                 ltrim = llen;
1874                 rtrim = 0;
1875             } else {
1876                 if (end >= llen) end = llen-1;
1877                 ltrim = start;
1878                 rtrim = llen-end-1;
1879             }
1880 
1881             /* Remove list elements to perform the trim */
1882             for (j = 0; j < ltrim; j++) {
1883                 ln = listFirst(list);
1884                 listDelNode(list,ln);
1885             }
1886             for (j = 0; j < rtrim; j++) {
1887                 ln = listLast(list);
1888                 listDelNode(list,ln);
1889             }
1890             addReply(c,shared.ok);
1891             server.dirty++;
1892         }
1893     }
1894 }

Line1849在数据库中查找包含该key的哈希节点.Line1850:1851如果没有指定的节点,向客户端返回错误提示字符串.Line1855:1857如果哈希节点包含的robj对象的类型错误,向客户端返回错误提示字符串.
Line1861获得value链表中节点个数.trim操作的思想是只保留指定索引区间表示的子序列,所以原来链表中该子序列前面部分和后面部分的value节点将不再需要.Line1864:1879计算子序列前面部分和后面部分分别要删除的节点数.Line1881:1889分别从value链表头部和尾部删除指定数目的节点.Line1890:1891标记内存数据库更新次数,并向客户端发送操作成功提示字符串.对于trim操作后的结果,可以使用lrange命令进行验证.

猜你喜欢

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