深入剖析Redis系列(七) - Redis数据结构之列表

栏目: 数据库 · 发布时间: 7年前

内容简介:列表(列表是一种比较如图所示,

列表( list )类型是用来存储多个 有序字符串 。在 Redis 中,可以对列表的 两端 进行 插入push )和 弹出pop )操作,还可以获取 指定范围元素列表 、获取 指定索引下标元素 等。

深入剖析 <a href='https://www.codercto.com/topics/18994.html'>Redis</a> 系列(七) - Redis数据结构之列表

列表是一种比较 灵活数据结构 ,它可以充当 队列 的角色,在实际开发上有很多应用场景。

如图所示, abcde 五个元素 从左到右 组成了一个 有序的列表 ,列表中的每个字符串称为 元素element ),一个列表最多可以存储 2 ^ 32 - 1 个元素。

  • 列表的 插入弹出 操作
深入剖析Redis系列(七) - Redis数据结构之列表
  • 列表的 获取截取删除 操作
深入剖析Redis系列(七) - Redis数据结构之列表

正文

1. 相关命令

下面将按照对 列表5操作类型 对命令进行介绍:

深入剖析Redis系列(七) - Redis数据结构之列表

1.1. 添加命令

1.1.1. 从右边插入元素

rpush key value [value ...]

下面代码 从右向左 插入元素 cba

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 操作会获取列表 指定索引 范围所有的元素。

索引下标有两个特点:

  • 其一,索引下标 从左到右 分别是 0N-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 弹出,弹出后 列表 变为 redisba

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, 删除所有

例如向列表 从左向右 插入 5a ,那么当前 列表 变为 “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

blpopbrpoplpoprpop阻塞版本 ,它们除了 弹出方向 不同, 使用方法 基本相同,所以下面以 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:2list: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"
复制代码

有关 列表基础命令 已经介绍完了,下表是相关命令的 时间复杂度

深入剖析Redis系列(七) - Redis数据结构之列表

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 ,它结合了 ziplistlinkedlist 两者的优势,为 列表类型 提供了一种更为优秀的 内部编码 实现,它的设计原理可以参考 Redis 的另一个作者 Matt Stancliff 的博客redis-quicklist。

3. 应用场景

3.1. 消息队列

通过 Redislpush + brpop 命令组合 ,即可实现 阻塞队列 。如图所示:

深入剖析Redis系列(七) - Redis数据结构之列表

生产者客户端使用 lrpush 从列表 左侧插入元素多个消费者客户端 使用 brpop 命令 阻塞式“抢” 列表 尾部 的元素, 多个客户端 保证了消费的 负载均衡高可用性

3.2. 文章列表

每个 用户 有属于自己的 文章列表 ,现需要 分页 展示文章列表。此时可以考虑使用 列表 ,因为列表不但是 有序的 ,同时支持 按照索引范围 获取元素。

  • 每篇文章使用 哈希结构 存储,例如每篇文章有 3 个属性 titletimestampcontent
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.2quicklist 内部编码实现,它结合 ziplistlinkedlist 的特点,获取列表 中间范围 的元素时也可以 高效完成

3.3. 其他场景

实际上列表的使用场景很多,具体可以参考如下:

命令组合 对应数据结构
lpush + lpop Stack(栈)
lpush + rpop Queue(队列)
lpush + ltrim Capped Collection(有限集合)
lpush + brpop Message Queue(消息队列)

小结

本文介绍了 Redis 中的 列表 的 一些 基本命令内部编码适用场景 。通过组合不同 命令 ,可以把 列表 转换为不同的 数据结构 使用。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Information

The Information

James Gleick / Vintage / 2012-3-6 / USD 16.95

James Gleick, the author of the best sellers Chaos and Genius, now brings us a work just as astonishing and masterly: a revelatory chronicle and meditation that shows how information has become th......一起来看看 《The Information》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具