目录
列表
基本概念
列表特点:元素有序可重复
命令
添加操作
从右边插入元素:rpush key value [value ...]
从左边插入元素:lpush key value [value ...]
向某个元素前或者后插入元素:linsert key before|after pivot value
查找
获取指定范围内的元素列表:lrange key start end
获取列表指定索引下标的元素:lindex key index
获取列表长度:llen key
删除
从列表左侧弹出元素:lpop key
从列表右侧弹出:rpop key
删除指定元素:lrem key count value
按照索引范围修剪列表:ltrim key start end
修改
修改指定索引下标的元素:lset key index newValue
阻塞操作
阻塞式弹出如下:blpop key [key ...] timeout \ brpop key [key ...] timeout
列表命令时间复杂度
内部编码
ziplist(压缩列表)
linkedlist(链表)
当元素个数较少且没有大元素时,内部编码为ziplist
当元素个数超过512个,内部编码变为linkedlist
当某个元素超过64字节,内部编码也会变为linkedlist
使用场景
消息队列
典型应用总结
列表
基本概念
- 用来存储多个有序的字符串,如图所示,a、b、c、d、e五个元素从左到右组成了一个有序的列表
- 列表中的每个字符串称为元素(element),一个列表最多可以存储2^(32-1)个元素。
- 可以对列表两端插入(push)和弹出(pop),还可以获取指定范围的元素列表、获取指定索引下标的元素等。
- 列表是一种比较灵活的数据结构,它可以充当栈和队列的角色,在实际开发上有很多应用场景。
列表特点:元素有序可重复
- 列表中的元素是有序的,这就意味着可以通过索引下标获取某个元素或者某个范围内的元素列表
- 例如要获取上图的第5个元素,可以执行lindex user:1:message4(索引从0算起)就可以得到元素e。
- 列表中的元素可以是重复的
- 例如下图所示列表中包含了两个字符串a。
命令
添加操作
从右边插入元素:rpush key value [value ...]
- 下面代码从右向左插入元素c、b、a:
127.0.0. 1:6379> rpush listkey c b a (integer) 3
- lrange 0 -1命令可以从左到右获取列表的所有元素:
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "b" 3) "a"
从左边插入元素:lpush key value [value ...]
- 使用方法和rpush相同,只不过从左侧插入
向某个元素前或者后插入元素:linsert key before|after pivot value
- linsert命令会从列表中找到等于pivot的元素,在其前(before)或者后(after)插入一个新的元素value,例如下面操作会在列表的元素b前插入java:
127.0.0.1:6379> linsert listkey before b java (integer) 4
- 返回结果为4,代表当前命令的长度,当前列表变为:
127.0.0.1:6379> lrange listkey 0 -1 1) "c" 2) "java" 3) "b" 4) "a"
查找
获取指定范围内的元素列表:lrange key start end
- lrange操作会获取列表指定索引范围所有的元素。
- 索引下标有两个特点:
- 索引下标从左到右分别是0到N-1,但是从右到左分别是-1到-N。
- lrange中的end选项包含了自身,这个和很多编程语言不包含end不太相同,例如想获取列表的第2到第4个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey 1 3 1) "java" 2) "b" 3) "a"
获取列表指定索引下标的元素:lindex key index
- 例如当前列表最后一个元素为a:
127.0.0.1:6379> lindex listkey -1 "a"
获取列表长度:llen key
- 例如,下面示例当前列表长度为4:
127.0.0.1:6379> llen listkey (integer) 4
删除
从列表左侧弹出元素:lpop key
- 如下操作将列表最左侧的元素c会被弹出,弹出后列表变为java、b、a:
127.0.0.1:6379>t lpop listkey "c" 127.0.0.1:6379> lrange listkey 0 -1 1) "java" 2) "b" 3) "a"
从列表右侧弹出:rpop key
- 它的使用方法和lpop是一样的,只不过从列表右侧弹出
删除指定元素:lrem key count value
- lrem命令会从列表中找到等于value的元素进行删除,根据count的不同分为三种情况:
- count>0,从左到右,删除最多count个元素。
- count<0,从右到左,删除最多count绝对值个元素。
- count=0,删除所有。
- 例如向列表从左向右插入5个a,那么当前列表变为“a a a a a java b a”,下面操作将从列表左边开始删除4个为a的元素:
127.0.0.1:6379> lrem listkey 4 a (integer) 4 127.0.0.1:6379> lrange listkey 0 -1 1) "a" 2) "java" 3) "b" 4) "a"
按照索引范围修剪列表:ltrim key start end
- 例如,下面操作会只保留列表listkey第2个到第4个元素:
127.0.0.1:6379> ltrim listkey 1 3 OK 127.0.0.1:6379> lrange listkey 0 -1 1) "java" 2) "b" 3) "a"
修改
修改指定索引下标的元素:lset key index newValue
- 下面操作会将列表listkey中的第3个元素设置为python:
127.0.0.1:6379> lset listkey 2 python OK 127.0.0.1:6379> lrange listkey 0 -1 1) "java" 2) "b" 3) "python"
阻塞操作
阻塞式弹出如下:blpop key [key ...] timeout \ brpop key [key ...] timeout
- blpop和brpop是lpop和rpop的阻塞版本,除了弹出方向不同,使用方法基本相同,以brpop命令进行说明,brpop命令包含两个参数:
- key[key...]:多个列表的键。
- timeout:阻塞时间(单位:秒)。
- 列表为空:如果timeout=3,那么客户端要等到3秒后返回,如果timeout=0,那么客户端一直阻塞等下去:
127.0.0.1:6379> brpop list:test 3 (nil) (3.10s) 127.0.0.1:6379> brpop list:test 0 ...阻塞...
- 如果此期间添加了数据element1,客户端立即返回:
127.0.0.1:6379> brpop list:test 3 1) "list:test" 2) "element1" (2.06s)
- 列表不为空:客户端会立即返回。
127.0.0.1:6379> brpop list:test 0 1) "list:test" 2) "element1"
- 使用brpop时,有两点注意:
- 第一点
- 如果是多个键,那么brpop会从左至右遍历键,一旦有一个键能弹出元素,客户端立即返回:
127.0.0.1:6379> brpop list:1 list:2 list:3 0 ..阻塞..
- 此时另一个客户端分别向list:2和list:3插入元素
client-lpush> lpush list:2 element2 (integer) 1 client-lpush> lpush list:3 element3 (integer) 1
- 客户端会立即返回list:2中的element2,因为list:2最先有可以弹出的元素:
127.0.0.1:6379> brpop list:1 list:2 list:3 0 1) "list:2" 2) "element2_1"
- 第二点
- 如果多个客户端对同一个键执行brpop,那么最先执行brpop命令的客户端可以获取到弹出的值。
- 客户端1:
client-1> brpop list:test 0 ...阻塞...
- 客户端2:
client-2> brpop list:test 0 ...阻塞...
- 客户端3:
client-3> brpop list:test 0 ...阻塞...
- 此时另一个客户端lpush一个元素到list:test列表中:
client-lpush> lpush list:test element (integer) 1
- 那么客户端1最会获取到元素,因为客户端1最先执行brpop,而客户端2和客户端3继续阻塞:
client> brpop list:test 0 1) "list:test" 2) "element"
列表命令时间复杂度
内部编码
ziplist(压缩列表)
- 当列表的元素个数小于list-max-ziplist-entries配置(默认512个),同时列表中每个元素的值都小于list-max-ziplist-value配置时(默认64字节),Redis会选用ziplist来作为列表的内部实现来减少内存的使用。
linkedlist(链表)
- 当列表类型无法满足ziplist的条件时,Redis会使用linkedlist作为列表的内部实现。
当元素个数较少且没有大元素时,内部编码为ziplist
127.0.0.1:6379> rpush listkey e1 e2 e3 (integer) 3 127.0.0.1:6379> object encoding listkey "ziplist"
当元素个数超过512个,内部编码变为linkedlist
127.0.0.1:6379> rpush listkey e4 e5 ...忽略... e512 e513 (integer) 513 127.0.0.1:6379> object encoding listkey "linkedlist"
当某个元素超过64字节,内部编码也会变为linkedlist
127.0.0.1:6379> rpush listkey "one string is bigger than 64 byte............... ................." (integer) 4 127.0.0.1:6379> object encoding listkey "linkedlist"
- 注:Redis3.2版本提供了quicklist内部编码,它是以一个ziplist为节点的linkedlist,结合了ziplist和linkedlist两者的优势,为列表类型提供了更优秀的内部编码实现,其设计原理可以参考:https://matt.sh/redis-quicklist
使用场景
消息队列
- 如下图所示,lpush + brpop命令组合即可实现阻塞队列
- 生产者客户端使用lrpush从列表左侧插入元素,多个消费者客户端使用brpop命令阻塞式的“抢”列表尾部的元素,多个客户端保证了消费的负载均衡和高可用性。
- 每篇文章使用哈希结构存储,例如每篇文章有3个属性title、timestamp、content:
hmset acticle:1 title xx timestamp 1476536196 content xxxx... hmset acticle:k title yy timestamp 1476512536 content yyyy...
- 向用户文章列表添加文章,user:{id}:articles作为用户文章列表的键:
lpush user:1:acticles article:1 article3 ... lpush user:k:acticles article:5 ...
- 分页获取用户文章列表,例如下面伪代码获取用户id=1的前10篇文章:
articles = lrange user:1:articles 0 9 for article in {articles} hgetall {article}
- 使用列表类型保存和获取文章列表会存在两个问题。
- 第一,如果每次分页获取的文章个数较多,需要执行多次hgetall操作,此时可以考虑使用Pipeline批量获取,或者考虑将文章数据序列化为字符串类型,使用mget批量获取。
- 第二,分页获取文章列表时,lrange命令在列表两端性能较好,但是如果列表较大,获取列表中间范围的元素性能会变差,此时可以考虑将列表做二级拆分,或者使用Redis3.2的quicklist内部编码实现,它结合ziplist和linkedlist的特点,获取列表中间范围的元素时也可以高效完成。
典型应用总结
lpush+lpop=Stack
栈lpush+rpop=Queue队列lpsh+ltrim=Capped Collection有限集合lpush+brpop=Message Queue消息队列
redis get数据编码
转载本文章为转载内容,我们尊重原作者对文章享有的著作权。如有内容错误或侵权问题,欢迎原作者联系我们进行内容更正或删除文章。
提问和评论都可以,用心的回复会被更多人看到
评论
发布评论
相关文章