Redis 笔记(04)— list类型(作为消息队列使用、在列表头部添加元素、尾部删除元素、查看列表长度、遍历指定列表区间元素、获取指定区间列表元素、阻塞式获取列表元素)

Redis 的列表是链表而不是数组。这意味着 list 的插入和删除操作非常快,时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)

当列表弹出了最后一个元素之后,该数据结构自动被删除,内存被回收。

Redis 的列表结构常用来做异步队列使用。将需要延后处理的任务结构体序列化成字符串塞进 Redis 的列表,另一个线程从这个列表中轮询数据进行处理。

Redis 在列表元素较少的情况下会使用一块连续的内存来存储列表,这个结构是 ziplist ,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。当数据量比较多的时候才会改成 quicklist

因为普通的链表需要的附加指针空间太大,会比较浪费空间,而且会加重内存的碎片化。比如这个列表里存的只是 int 类型的数据,结构上还需要两个额外的指针 prevnext 。所以 Redis 将链表和 ziplist 结合起来组成了 quicklist。也就是将多个 ziplist 使用双向指针串起来使用。这样既满足了快速的插入删除性能,又不会出现太大的空间冗余。

1. list 类型相关命令

命令说明
lpush key string在key对应list的头部添加字符串元素
rpop key在list的尾部删除元素,并返回删除元素
rpush key string在key对应list的尾部添加字符串元素
lpop key在list的头部删除元素,并返回删除元素
lset key index value将列表key下标为index的元素值设置为value
llen key返回对应list的长度
lrange key start end返回指定区间内的元素,从下标 0 开始
ltrim key start end截取list, 保留指定区间内元素
lindex key 下标获取列表下标对应的指定元素
blpop key[key…] time out删除,并获得该列的第一元素, 或阻塞,直到有一个可用
brpop key[key…] time out删除, 并获得该列的最后一个元素, 或阻塞,直到有一个可用
rpoplpush source destination删除列表中的最后一个元素,将其追加到另一个列表
brpoplpush source destination timeout弹出一个列表的值,将他推到另一个列表,并返回他,直到有一个可用

2. 使用示例

127.0.0.1:6379> lpush list 1
(integer) 1
127.0.0.1:6379> lpush list 2
(integer) 2
127.0.0.1:6379> lpush list 3
(integer) 3
127.0.0.1:6379> lpush list 4
(integer) 4
127.0.0.1:6379> lpush list 5
(integer) 5
127.0.0.1:6379> lrange list 0 -1
1) "5"
2) "4"
3) "3"
4) "2"
5) "1"
127.0.0.1:6379> lpop list
"5"
127.0.0.1:6379> rpop list
"1"
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> rpush list 1
(integer) 4
127.0.0.1:6379> rpush list -1
(integer) 5
127.0.0.1:6379> rpush list -2
(integer) 6
127.0.0.1:6379> lrange list 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "-1"
6) "-2"
127.0.0.1:6379> lset list 0 00
OK
127.0.0.1:6379> lrange list 0 -1
1) "00"
2) "3"
3) "2"
4) "1"
5) "-1"
6) "-2"
127.0.0.1:6379> llen list
(integer) 6

lindex 需要对链表进行遍历,性能随着参数 index 增大而变差。

127.0.0.1:6379> lindex -2
(error) ERR wrong number of arguments for 'lindex' command
127.0.0.1:6379> lindex list -2
"-1"
127.0.0.1:6379> lindex list 0
"00"

ltrim 和字面上的含义不太一样,个人觉得它叫 lretain (保留) 更合适一些,因为 ltrim 跟的两个参数start_indexend_index 定义了一个区间,在这个区间内的值,ltrim 要保留,区间之外统统砍掉。我们可以通过 ltrim 来实现一个定长的链表,这一点非常有用。

index 可以为负数,index=-1 表示倒数第一个元素,同样 index=-2 表示倒数第二个元素。

127.0.0.1:6379> ltrim list 1 4
OK
127.0.0.1:6379> lrange list 0 -1
1) "3"
2) "2"
3) "1"
4) "-1"
127.0.0.1:6379> blpop list 3
1) "list"
2) "3"
127.0.0.1:6379> brpop list 2
1) "list"
2) "-1"
127.0.0.1:6379> 

3. 消息队列

Redislist(列表) 数据结构常用来作为异步消息队列使用,使用 rpush/lpush 操作入队列,使用 lpoprpop 来出队列。

lpushrpop 命令可以实现队列功能,只需要生产者将任务使用 lpush 命令加入到某个键中,而消费者使用 rpop 命令将任务从对应的键中取出来即可。

brpop 命令和 rpop 命令相似,区别在于 brpop 是阻塞式的,当队列中没有任务时,brpop 命令会一直阻塞住连接,直到队列中有任务。

brpop 命令格式

brpop key seconds

seconds 为 0 表示不限制等待时间,即永远阻塞下去。

3.1 队列空情况

客户端是通过队列的 pop 操作来获取消息,然后进行处理。处理完了再接着获取消息,再进行处理。如此循环往复,这便是作为队列消费者的客户端的生命周期。

如果队列空了,客户端就会陷入 pop 的死循环,不停地 pop,没有数据,接着再 pop,又没有数据。这就是浪费生命的空轮询。空轮询不但拉高了客户端的 CPURedisQPS 也会被拉高。

通常我们使用 sleep 来解决这个问题,让线程睡一会,睡个 1s 钟就可以了。不但客户端的 CPU 能降下来,RedisQPS 也降下来了。

time.sleep(1)  # python 睡 1s

3.2 阻塞队列

用上面睡眠的办法可以解决问题。但是有个小问题,那就是睡眠会导致消息的延迟增大。更好的解决方法是使用 blpop/brpop,这两个指令的前缀字符 b 代表的是 blocking ,也就是阻塞读。

阻塞读在队列没有数据的时候,会立即进入休眠状态,一旦数据到来,则立刻醒过来。消息的延迟几乎为零。用 blpop/brpop 替代前面的 lpop/rpop 可以完美解决队列延迟问题。

3.3 空闲连接自动断开

如果线程一直阻塞在哪里,Redis 的客户端连接就成了闲置连接,闲置过久,服务器一般会主动断开连接,减少闲置资源占用。这个时候 blpop/brpop 会抛出异常来。

所以编写客户端消费者的时候要小心,注意捕获异常,还要重试。

代码参考:
玩转 Redis:简单消息队列

玩转 Redis — 延时消息队列

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值