核心概念:列表的方向
在深入命令之前,必须明确 redis 列表的“方向”概念:
- 头部 (left/head): 列表的起始端。
- 尾部 (right/tail): 列表的结束端。
想象一个列表 ["a", "b", "c"]:
- 头部是
"a"。 - 尾部是
"c"。
1.rpop: 从尾部弹出 (非阻塞)
命令:rpop key
功能:移除并返回列表 key 的最后一个元素(即尾部元素)。
阻塞:否。这是一个立即返回的命令。
返回值:
- 如果列表存在且非空:返回被弹出的元素。
- 如果列表不存在或为空:返回
nil。
使用场景:当你需要一个简单的栈 (stack) 数据结构时(后进先出,lifo)。
示例:
# 创建一个列表 127.0.0.1:6379> rpush mylist "a" "b" "c" (integer) 3 # 查看列表内容 127.0.0.1:6379> lrange mylist 0 -1 1) "a" 2) "b" 3) "c" # 执行 rpop,弹出尾部元素 "c" 127.0.0.1:6379> rpop mylist "c" # 再次查看,"c" 已被移除 127.0.0.1:6379> lrange mylist 0 -1 1) "a" 2) "b" # 再次 rpop,弹出 "b" 127.0.0.1:6379> rpop mylist "b" # 列表为空时,返回 nil 127.0.0.1:6379> rpop mylist (nil)
2.brpop: 从尾部弹出 (阻塞)
命令:brpop key [key ...] timeout
功能:rpop 的阻塞版本。它会移除并返回列表的最后一个元素。如果所有给定的列表都为空,它会阻塞连接,直到有元素可用或超时。
阻塞:是。这是其核心特性。
返回值:
- 成功弹出:返回一个包含两个元素的数组
[key, value]。 - 超时:返回
nil。
使用场景:实现消费者,用于任务队列或消息队列,避免轮询。
示例:
终端 a (消费者):
# 尝试从 mylist 弹出元素,最多阻塞 5 秒 127.0.0.1:6379> brpop mylist 5 (等待中...)
终端 b (生产者):
# 向 mylist 推送一个新元素 127.0.0.1:6379> lpush mylist "new task" (integer) 1
终端 a (消费者):
# 立即收到响应 1) "mylist" # 元素来自哪个键 2) "new task" # 被弹出的元素值
超时示例:
# 5秒内没有任何元素被推入 127.0.0.1:6379> brpop emptylist 5 (nil) (5.04s)
3.lpop: 从头部弹出 (非阻塞)
命令:lpop key
功能:移除并返回列表 key 的第一个元素(即头部元素)。
阻塞:否。
返回值:
- 如果列表存在且非空:返回被弹出的元素。
- 如果列表不存在或为空:返回
nil。
使用场景:与 rpop 相反,如果你将列表用作栈,并希望从另一端操作。
示例:
# 使用之前的列表 ["a", "b"] 127.0.0.1:6379> lrange mylist 0 -1 1) "a" 2) "b" # 执行 lpop,弹出头部元素 "a" 127.0.0.1:6379> lpop mylist "a" # 列表现在只剩下 ["b"] 127.0.0.1:6379> lrange mylist 0 -1 1) "b"
4.blpop: 从头部弹出 (阻塞)
命令:blpop key [key ...] timeout
功能:lpop 的阻塞版本。它会移除并返回列表的第一个元素。如果所有给定的列表都为空,它会阻塞连接,直到有元素可用或超时。
阻塞:是。
返回值:
- 成功弹出:返回一个包含两个元素的数组
[key, value]。 - 超时:返回
nil。
使用场景:这是最常用的队列消费模式。配合 rpush(从尾部推入),可以完美实现先进先出 (fifo) 的队列。
示例 (fifo 队列):
生产者:
# 依次将任务推入队列尾部 127.0.0.1:6379> rpush task_queue "task-1" (integer) 1 127.0.0.1:6379> rpush task_queue "task-2" (integer) 2
消费者:
# 从队列头部阻塞式地获取任务 127.0.0.1:6379> blpop task_queue 0 1) "task_queue" 2) "task-1" # 最先推入的任务最先被消费 # 再次执行,获取下一个任务 127.0.0.1:6379> blpop task_queue 0 1) "task_queue" 2) "task-2"
python代码示例
首先,请确保你已经安装了 redis 库:
pip install redis
1. 基础设置
我们先创建一个 redis 连接,并定义一个辅助函数来清理测试用的 key。
import redis
import time
import threading
# 创建 redis 连接
r = redis.redis(host='localhost', port=6379, decode_responses=true)
def cleanup_keys(*keys):
"""清理测试用的 keys"""
r.delete(*keys)
2.rpop和lpop示例 (非阻塞)
这两个命令会立即返回结果,无论列表是否为空。
# 清理环境
cleanup_keys('test_list')
# 向列表中添加元素
r.rpush('test_list', 'a', 'b', 'c')
print("初始列表:", r.lrange('test_list', 0, -1)) # ['a', 'b', 'c']
# lpop: 从头部弹出
head_element = r.lpop('test_list')
print("lpop 弹出:", head_element) # a
print("lpop 后列表:", r.lrange('test_list', 0, -1)) # ['b', 'c']
# rpop: 从尾部弹出
tail_element = r.rpop('test_list')
print("rpop 弹出:", tail_element) # c
print("rpop 后列表:", r.lrange('test_list', 0, -1)) # ['b']
# 对空列表操作
empty_lpop = r.lpop('empty_list')
empty_rpop = r.rpop('empty_list')
print("空列表 lpop:", empty_lpop) # none
print("空列表 rpop:", empty_rpop) # none
输出:
初始列表: ['a', 'b', 'c'] lpop 弹出: a lpop 后列表: ['b', 'c'] rpop 弹出: c rpop 后列表: ['b'] 空列表 lpop: none 空列表 rpop: none
3.blpop示例 (阻塞式队列消费者)
blpop 是实现 fifo(先进先出)队列的标准方式。生产者使用 rpush,消费者使用 blpop。
# 清理环境
cleanup_keys('task_queue')
def producer():
"""模拟生产者,每隔2秒推送一个任务"""
for i in range(3):
task = f"task-{i+1}"
r.rpush('task_queue', task)
print(f"[生产者] 推送任务: {task}")
time.sleep(2)
def blpop_consumer():
"""blpop 消费者,阻塞等待任务"""
print("[blpop消费者] 开始等待任务...")
while true:
# 阻塞等待,超时时间设为5秒
result = r.blpop('task_queue', timeout=5)
if result is none:
print("[blpop消费者] 超时,没有收到新任务,退出。")
break
key, value = result
print(f"[blpop消费者] 处理任务: {value} (来自 {key})")
# 启动生产者线程
producer_thread = threading.thread(target=producer)
producer_thread.start()
# 运行消费者
blpop_consumer()
# 等待生产者线程结束
producer_thread.join()
输出:
[blpop消费者] 开始等待任务...[生产者] 推送任务: task-1 [blpop消费者] 处理任务: task-1 (来自 task_queue) [生产者] 推送任务: task-2 [blpop消费者] 处理任务: task-2 (来自 task_queue) [生产者] 推送任务: task-3[blpop消费者] 处理任务: task-3 (来自 task_queue) [blpop消费者] 超时,没有收到新任务,退出。
4.brpop示例 (阻塞式栈或反向队列)
brpop 通常与 lpush 配合,也可以实现 fifo 队列,但更常用于实现 lifo(后进先出)的栈。
# 清理环境
cleanup_keys('stack')
def brpop_consumer():
"""brpop 消费者,模拟一个栈的弹出操作"""
print("[brpop消费者] 开始从栈中弹出元素...")
# 先手动压入一些元素
r.lpush('stack', 'x', 'y', 'z') # 栈: ['z', 'y', 'x'] (z 在顶部/尾部)
print("当前栈内容:", r.lrange('stack', 0, -1))
# 弹出3次
for _ in range(3):
result = r.brpop('stack', timeout=1)
if result:
key, value = result
print(f"[brpop消费者] 弹出: {value}")
# 尝试在空栈上弹出,会超时
result = r.brpop('stack', timeout=2)
print("[brpop消费者] 空栈弹出结果:", result) # none
brpop_consumer()
输出:
[brpop消费者] 开始从栈中弹出元素... 当前栈内容: ['z', 'y', 'x'] [brpop消费者] 弹出: x [brpop消费者] 弹出: y [brpop消费者] 弹出: z [brpop消费者] 空栈弹出结果: none
5.blpop/brpop监听多个列表 (优先级队列)
这是阻塞命令的一个强大特性,可以用于实现简单的优先级队列。
# 清理环境
cleanup_keys('high_priority', 'low_priority')
def multi_list_producer():
"""向高优先级和低优先级队列推送任务"""
time.sleep(1) # 稍等一下,让消费者先启动
r.rpush('low_priority', 'low-priority-task')
print("[生产者] 推送低优先级任务")
time.sleep(1)
r.rpush('high_priority', 'high-priority-task')
print("[生产者] 推送高优先级任务")
def priority_consumer():
"""消费者优先监听高优先级队列"""
print("[优先级消费者] 启动,优先监听: high_priority, low_priority")
# blpop 会按顺序检查列表
for _ in range(2):
result = r.blpop(['high_priority', 'low_priority'], timeout=5)
if result:
key, value = result
print(f"[优先级消费者] 处理任务: {value} (来自 {key})")
# 启动生产者线程
prod_thread = threading.thread(target=multi_list_producer)
prod_thread.start()
# 运行消费者
priority_consumer()
prod_thread.join()
输出:
[优先级消费者] 启动,优先监听: high_priority, low_priority [生产者] 推送低优先级任务 [优先级消费者] 处理任务: low-priority-task (来自 low_priority) [生产者] 推送高优先级任务 [优先级消费者] 处理任务: high-priority-task (来自 high_priority)
总结对比表
| 命令 | 功能 | 弹出方向 | 阻塞 | 典型用途 |
|---|---|---|---|---|
rpop | 移除并返回最后一个元素 | 尾部 (right) | 否 | 栈 (lifo) |
brpop | 阻塞式移除并返回最后一个元素 | 尾部 (right) | 是 | 消费者(配合 lpush 实现 fifo) |
lpop | 移除并返回第一个元素 | 头部 (left) | 否 | 栈 (lifo) (另一端) |
blpop | 阻塞式移除并返回第一个元素 | 头部 (left) | 是 | 标准队列消费者 (fifo) (配合 rpush) |
关键记忆点:
- l 开头的命令操作头部。
- r 开头的命令操作尾部。
- b 开头的命令是阻塞版本。
- 标准 fifo 队列 = rpush (生产者) + blpop (消费者)。
到此这篇关于redis中rpop、brpop、lpop和blpop使用示例代码的文章就介绍到这了,更多相关redis中rpop、brpop、lpop和blpop内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论