深入剖析Redis系列(七) - Redis数据结构之列表
前言
列表(list
)类型是用来存储多个 有序 的 字符串。在 Redis
中,可以对列表的 两端 进行 插入(push
)和 弹出(pop
)操作,还可以获取 指定范围 的 元素列表、获取 指定索引下标 的 元素 等。
列表 是一种比较 灵活 的 数据结构,它可以充当 栈 和 队列 的角色,在实际开发上有很多应用场景。
如图所示,a
、b
、c
、d
、e
五个元素 从左到右 组成了一个 有序的列表,列表中的每个字符串称为 元素(element
),一个列表最多可以存储 2 ^ 32 - 1
个元素。
- 列表的 插入 和 弹出 操作
- 列表的 获取、截取 和 删除 操作
其他文章
深入剖析Redis系列(一) - Redis入门简介与主从搭建
深入剖析Redis系列(二) - Redis哨兵模式与高可用集群
深入剖析Redis系列(三) - Redis集群模式搭建与原理详解
深入剖析Redis系列(四) - Redis数据结构与全局命令概述
深入剖析Redis系列(五) - Redis数据结构之字符串
深入剖析Redis系列(六) - Redis数据结构之哈希
深入剖析Redis系列(七) - Redis数据结构之列表
深入剖析Redis系列(八) - Redis数据结构之集合
正文
1. 相关命令
下面将按照对 列表 的 5
种 操作类型 对命令进行介绍:
1.1. 添加命令
1.1.1. 从右边插入元素
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"
复制代码
1.1.2. 从左边插入元素
lpush key value [value ...]
使用方法和 rpush
相同,只不过从 左侧插入,这里不再赘述。
1.1.3. 向某个元素前或者后插入元素
linsert key before|after pivot value
linsert
命令会从 列表 中找到 第一个 等于 pivot
的元素,在其 前(before
)或者 后(after
)插入一个新的元素 value
,例如下面操作会在列表的 元素 b
前插入 redis
:
127.0.0.1:6379> linsert listkey before b redis
(integer) 4
复制代码
返回结果为 4
,代表当前 列表 的 长度,当前列表变为:
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "redis"
3) "b"
4) "a"
复制代码
1.2. 查询命令
1.2.1. 获取指定范围内的元素列表
lrange key start stop
lrange
操作会获取列表 指定索引 范围所有的元素。
索引下标 有两个特点:
其一,索引下标 从左到右 分别是
0
到N-1
,但是 从右到左 分别是-1
到-N
。其二,
lrange
中的end
选项包含了 自身,这个和很多编程语言不包含end
不太相同。
从左到右 获取列表的第 2
到第 4
个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey 1 3
1) "redis"
2) "b"
3) "a"
复制代码
从右到左 获取列表的第 1
到第 3
个元素,可以执行如下操作:
127.0.0.1:6379> lrange listkey -3 -1
1) "redis"
2) "b"
3) "a"
复制代码
1.2.2. 获取列表指定索引下标的元素
lindex key index
例如当前列表 最后一个 元素为 a
:
127.0.0.1:6379> lindex listkey -1
"a"
复制代码
1.2.3. 获取列表长度
llen key
例如,下面示例 当前列表长度 为 4
:
127.0.0.1:6379> llen listkey
(integer) 4
复制代码
1.3. 删除命令
1.3.1. 从列表左侧弹出元素
lpop key
如下操作将 列表 最左侧的元素 c
弹出,弹出后 列表 变为 redis
、b
、a
。
127.0.0.1:6379> lpop listkey
"c"
127.0.0.1:6379> lrange listkey 0 -1
1) "redis"
2) "b"
3) "a"
复制代码
1.3.2. 从列表右侧弹出元素
rpop key
它的使用方法和 lpop
是一样的,只不过从列表 右侧 弹出元元素。
127.0.0.1:6379> lpop listkey
"a"
127.0.0.1:6379> lrange listkey 0 -1
1) "c"
2) "redis"
3) "b"
复制代码
1.3.3. 删除指定元素
lrem key count value
lrem
命令会从 列表 中找到 等于 value
的元素进行 删除,根据 count
的不同分为三种情况:
count > 0:从左到右,删除最多
count
个元素。count < 0:从右到左,删除最多
count
绝对值 个元素。count = 0,删除所有。
例如向列表 从左向右 插入 5
个 a
,那么当前 列表 变为 “a a a a a redis 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) "redis"
3) "b"
4) "a"
复制代码
1.3.4. 按照索引范围修剪列表
127.0.0.1:6379> ltrim listkey 1 3
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "redis"
2) "b"
3) "a"
复制代码
1.4. 修改命令
1.4.1. 修改指定索引下标的元素
修改 指定索引下标 的元素:
lset key index newValue
下面操作会将列表 listkey
中的第 3
个元素设置为 mysql
:
127.0.0.1:6379> lset listkey 2 mysql
OK
127.0.0.1:6379> lrange listkey 0 -1
1) "redis"
2) "b"
3) "mysql"
复制代码
1.5. 阻塞操作命令
阻塞式弹出 操作的命令如下:
blpop key [key ...] timeout brpop key [key ...] timeout
blpop
和 brpop
是 lpop
和 rpop
的 阻塞版本,它们除了 弹出方向 不同,使用方法 基本相同,所以下面以 brpop
命令进行说明, brpop
命令包含两个参数:
key[key...]:一个列表的 多个键。
timeout:阻塞 时间(单位:秒)。
对于 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"
复制代码
- 其二,如果 多个客户端 对 同一个键 执行
brpop
,那么 最先执行brpop
命令的 客户端 可以 获取 到弹出的值。
按先后顺序在 3
个客户端执行 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"
复制代码
有关 列表 的 基础命令 已经介绍完了,下表是相关命令的 时间复杂度:
2. 内部编码
列表类型的 内部编码 有两种:
2.1. ziplist(压缩列表)
当列表的元素个数 小于 list-max-ziplist-entries
配置(默认 512
个),同时列表中 每个元素 的值都 小于 list-max-ziplist-value
配置时(默认 64
字节),Redis
会选用 ziplist
来作为 列表 的 内部实现 来减少内存的使用。
2.2. linkedlist(链表)
当 列表类型 无法满足 ziplist
的条件时, Redis
会使用 linkedlist
作为 列表 的 内部实现。
2.3. 编码转换
下面的示例演示了 列表类型 的 内部编码,以及相应的变化。
- 当元素 个数较少 且 没有大元素 时,内部编码 为
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
两者的优势,为 列表类型 提供了一种更为优秀的 内部编码 实现,它的设计原理可以参考 Redis
的另一个作者 Matt Stancliff
的博客 redis-quicklist。
3. 应用场景
3.1. 消息队列
通过 Redis
的 lpush + brpop
命令组合,即可实现 阻塞队列。如图所示:
生产者客户端 使用 lrpush
从列表 左侧插入元素,多个消费者客户端 使用 brpop
命令 阻塞式 的 “抢” 列表 尾部 的元素,多个客户端 保证了消费的 负载均衡 和 高可用性。
3.2. 文章列表
每个 用户 有属于自己的 文章列表,现需要 分页 展示文章列表。此时可以考虑使用 列表,因为列表不但是 有序的,同时支持 按照索引范围 获取元素。
- 每篇文章使用 哈希结构 存储,例如每篇文章有
3
个属性title
、timestamp
、content
:
hmset acticle:1 title xx timestamp 1476536196 content xxxx
hmset acticle:2 title yy timestamp 1476536196 content yyyy
...
hmset acticle:k title kk timestamp 1476512536 content kkkk
复制代码
- 向用户文章列表 添加文章,
user:{id}:articles
作为用户文章列表的 键:
lpush user:1:acticles article:1 article:3 article:5
lpush user:2:acticles article:2 article:4 article:6
...
lpush user:k:acticles article:7 article:8
复制代码
- 分页 获取 用户文章列表,例如下面 伪代码 获取用户
id=1
的前10
篇文章:
articles = lrange user:1:articles 0 9
for article in {articles}hgetall {article}
复制代码
使用 列表 类型 保存 和 获取 文章列表会存在两个问题:
第一:如果每次 分页 获取的 文章个数较多,需要执行多次
hgetall
操作,此时可以考虑使用Pipeline
进行 批量获取,或者考虑将文章数据 序列化为字符串 类型,使用mget
批量获取。第二:分页 获取 文章列表 时,
lrange
命令在列表 两端性能较好,但是如果 列表较大,获取列表 中间范围 的元素 性能会变差。此时可以考虑将列表做 二级拆分,或者使用Redis 3.2
的quicklist
内部编码实现,它结合ziplist
和linkedlist
的特点,获取列表 中间范围 的元素时也可以 高效完成。
3.3. 其他场景
实际上列表的使用场景很多,具体可以参考如下:
命令组合 | 对应数据结构 |
---|---|
lpush + lpop | Stack(栈) |
lpush + rpop | Queue(队列) |
lpush + ltrim | Capped Collection(有限集合) |
lpush + brpop | Message Queue(消息队列) |
小结
本文介绍了 Redis
中的 列表 的 一些 基本命令、内部编码 和 适用场景。通过组合不同 命令,可以把 列表 转换为不同的 数据结构 使用。
参考
《Redis 开发与运维》
欢迎关注技术公众号: 零壹技术栈
本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。
深入剖析Redis系列(七) - Redis数据结构之列表相关推荐
- 深入剖析Redis系列(五) - Redis数据结构之字符串
前言 字符串类型 是 Redis 最基础的数据结构.字符串类型 的值实际可以是 字符串(简单 和 复杂 的字符串,例如 JSON.XML).数字(整数.浮点数),甚至是 二进制(图片.音频.视频),但 ...
- 深入剖析Redis系列(三) - Redis集群模式搭建与原理详解
前言 在 Redis 3.0 之前,使用 哨兵(sentinel)机制来监控各个节点之间的状态.Redis Cluster 是 Redis 的 分布式解决方案,在 3.0 版本正式推出,有效地解决了 ...
- NoSql之Redis系列一: Redis的数据类型和基本使用
NoSql之Redis系列一: Redis的数据类型和基本使用 Redis简介及特点 Redis常用数据结构及使用 启动redis-server (win) 使用redis-cli操作redis St ...
- 深入剖析Redis系列(四) - Redis数据结构与全局命令概述
前言 Redis 提供了 5 种数据结构.理解每种数据结构的特点,对于 Redis 的 开发运维 非常重要,同时掌握 Redis 的 单线程命令处理 机制,会使 数据结构 和 命令 的选择事半功倍. ...
- redis的七种数据结构
[IT168 技术]长生剑.孔雀翎.碧玉刀.多情环.离别钩.霸王枪.拳头是古龙笔下的 七种武器,而本文打算将 Redis的几种使用方式 Strings.Hashs.Lists.Sets.Sort ...
- redis 启动无输出_深入剖析Redis系列: Redis入门简介与主从搭建
前言 Redis 是一种基于 键值对 的 NoSQL 数据库.与很多键值对数据库不同,Redis 提供了丰富的 值数据存储结构,包括 string(字符串).hash(哈希).list(列表).set ...
- redis db0 到 db15_深入剖析Redis系列: Redis集群模式搭建与原理详解
前言 在 Redis 3.0 之前,使用 哨兵(sentinel)机制来监控各个节点之间的状态.Redis Cluster 是 Redis 的 分布式解决方案,在 3.0 版本正式推出,有效地解决了 ...
- Redis系列:Redis的数据结构
Redis 的基本数据类型包括:二进制安全字符串 String.Hashes(哈希).Lists 列表.Sets 集合 和 Sorted sets 有序集合: Redis 的特殊数据类型还包括:geo ...
- redis系列:redis介绍与安装
前言 这个redis系列的文章将会记录博主学习redis的过程.基本上现在的互联网公司都会用到redis,所以学习这门技术于你于我都是有帮助的. 博主在写这个系列是用的是目前最新版本4.0.10,虚拟 ...
最新文章
- Linux7-常用文件管理命令及系统变量基础
- 大家好,给大家介绍一下,我们的主题歌 @虾米音乐
- linux内存之buff/cache
- win mysql 最大连接_常用的三种修改mysql最大连接数的方法
- python urllib2下载文件 是否成功,Python urllib2未完成下载fi
- 前端入门--解决问题的一些方法
- salt远程执行python脚本_SaltStack远程执行Windows job程序(黑窗口)填坑经过
- 【渝粤教育】国家开放大学2018年秋季 0107-21T现代货币金融学 参考试题
- 安装WordPress图解
- 便利蜂发布《白领早餐报告》:仅5成白领每天吃早餐
- idea的maven调用本地仓库的jar报错
- sql语句php不执行mysql执行_为什么我写的php代码不执行sql语句呢,请大侠指点。...
- 「本地搭建」worpress博客系统
- 学习笔记(3):SAP S4 HANA财务模块入门到精通-供应商自动付款的操作-Fiori
- matlab绘制二元一次函数图像_基于MATLAB的数学图像绘制
- 支持javascript的ppt软件_有哪些辅助工具,可以让PowerPoint软件更强大?
- word中插入pdf图片(矢量图、高清图)
- python def是什么意思-python里面def是什么意思
- OpenCv视频读与存
- 量子侦探社之“寻猫记”