大家好,我是jobleap.cn的小九。
你想要了解Python的redis组件如何使用Redis Streams能力,以及它能解决的问题和具体使用教程,这份教程会从核心概念、API使用到实战案例,全面讲解Redis Streams在Python中的落地方式。
一、Redis Streams 核心定位:解决什么问题?
Redis Streams是Redis 5.0版本引入的持久化、有序、可重放的消息队列,专门解决传统Redis消息方案的痛点:
- 传统List做队列:POP后消息丢失、无消费确认、无法分组消费、不能回溯历史消息;
- Pub/Sub发布订阅:消息不持久化、消费者离线则消息丢失、无法重复消费;
- 核心优势:支持分组消费、消息确认(ACK)、消息持久化、历史消息回溯、死信处理,是Redis生态中最完善的消息队列方案。
适用场景:
- 异步任务队列(如订单处理、短信发送);
- 日志/事件收集(如用户行为追踪、系统监控);
- 微服务间可靠通信(如跨服务数据同步);
- 数据流处理(如实时数据统计)。
二、环境准备
1. 基础依赖
- Redis服务:版本≥5.0(推荐6.x+,稳定性更好);
- Python客户端:安装官方推荐的
redis库(本文使用4.5.5稳定版):
pip install redis==4.5.5
2. 连接验证
先编写基础连接代码,确保Redis服务可访问:
import redis
# 初始化Redis连接(根据你的Redis配置调整参数)
r = redis.Redis(
host="localhost", # Redis服务地址
port=6379, # 端口
db=0, # 数据库编号
password=None, # 若有密码则填写
decode_responses=True # 自动将返回值解码为字符串(避免bytes类型)
)
# 验证连接
try:
r.ping()
print("Redis连接成功!")
except Exception as e:
print(f"连接失败:{e}")
三、核心概念速览
在使用API前,先明确关键概念:
| 概念 | 说明 |
|---|---|
| Stream | 消息流容器,每个Stream有唯一名称(如order_stream),消息按ID有序存储 |
| 消息ID | 格式时间戳-序列号(如1735689600000-0),Redis自动生成或自定义 |
| 消费者组(Group) | 为Stream创建的消费分组,组内消费者共同消费,同一条消息仅被组内一个消费者处理 |
| 消费者(Consumer) | 组内的具体消费实例(如consumer_1),有唯一名称 |
| PEL(未确认列表) | 已分发给消费者但未ACK的消息列表,防止消息丢失 |
| XACK | 确认消息处理完成,从PEL中移除消息 |
四、常用API实战(Python版)
以下所有示例均基于上文的r(Redis连接对象)展开。
4.1 发送消息(XADD)—— 生产者核心API
XADD用于向Stream中添加消息(生产者逻辑),支持自动生成消息ID、自定义ID、限制Stream长度(防止内存溢出)。
示例1:基础发送(自动生成ID)
# 向名为"order_stream"的Stream添加消息
# *表示让Redis自动生成消息ID,{...}是消息体(键值对格式)
msg_id = r.xadd(
name="order_stream", # Stream名称
fields={ # 消息内容(可自定义键值对)
"order_id": "OD20260127001",
"user_id": "U1001",
"amount": "99.9",
"status": "paid"
},
id="*" # 自动生成ID(推荐)
)
print(f"发送消息成功,消息ID:{msg_id}")
# 输出示例:发送消息成功,消息ID:1735689600000-0
示例2:限制Stream最大长度(避免内存溢出)
# maxlen:保留最新的1000条消息,~表示近似修剪(性能更高)
r.xadd(
name="order_stream",
fields={"order_id": "OD20260127002", "user_id": "U1002", "amount": "199.9"},
id="*",
maxlen=1000,
approximate=True # 近似修剪(推荐,提升性能)
)
4.2 基础读取消息(XREAD)—— 无分组消费
XREAD用于无分组的消息读取(适合单消费者、临时消费场景),支持阻塞/非阻塞模式。
示例1:非阻塞读取(读取最新1条消息)
# 从ID 0-0开始读取(读取所有历史消息),count限制读取数量
messages = r.xread(
streams={"order_stream": "0-0"}, # {Stream名称: 起始ID}
count=1, # 最多读取1条
block=0 # 0=非阻塞(立即返回),>0=阻塞毫秒数
)
print(f"读取到的消息:{messages}")
# 输出示例:[['order_stream', [('1735689600000-0', {'order_id': 'OD20260127001', ...})]]]
示例2:阻塞读取(实时监听新消息)
# block=5000 表示阻塞5秒,若5秒内有新消息则立即返回,否则返回空
# 起始ID设为"$"表示只读取当前时间之后的新消息(不读历史)
while True:
messages = r.xread(
streams={"order_stream": "$"},
count=2,
block=5000
)
if messages:
print(f"监听到新消息:{messages}")
# 处理消息逻辑...
else:
print("5秒内无新消息,继续监听...")
4.3 消费者组操作(核心)—— 生产级消费方案
生产环境中,几乎都使用消费者组(保证消息不重复消费、支持负载均衡),核心流程:创建组 → 组内消费 → 确认消息。
步骤1:创建消费者组(XGROUP CREATE)
# 为order_stream创建名为"logistics_group"的消费者组(物流组)
# id="$"表示组内消费者从最新消息开始消费;id="0-0"表示从第一条历史消息开始
try:
r.xgroup_create(
name="order_stream", # Stream名称
groupname="logistics_group", # 消费者组名称
id="$", # 起始消费位置
mkstream=True # 若Stream不存在则自动创建
)
print("消费者组创建成功!")
except redis.exceptions.ResponseError as e:
if "BUSYGROUP" in str(e):
print("消费者组已存在,无需重复创建")
else:
raise e
# 再创建一个短信通知组(演示多组消费)
try:
r.xgroup_create("order_stream", "sms_group", "$", mkstream=True)
except redis.exceptions.ResponseError as e:
if "BUSYGROUP" in str(e):
print("sms_group已存在")
else:
raise e
步骤2:组内消费消息(XREADGROUP)
def consume_from_group(stream_name, group_name, consumer_name):
"""
从指定消费者组消费消息
:param stream_name: Stream名称
:param group_name: 消费者组名称
:param consumer_name: 消费者名称(组内唯一)
"""
while True:
# 阻塞读取组内消息,block=3000表示阻塞3秒
messages = r.xreadgroup(
groupname=group_name,
consumername=consumer_name,
streams={stream_name: ">"}, # ">"表示读取组内未消费过的消息
count=1,
block=3000
)
if not messages:
print(f"{consumer_name}:3秒内无新消息,继续监听...")
continue
# 解析消息(messages格式:[[stream名, [(消息ID, 消息体), ...]]])
for stream, msg_list in messages:
for msg_id, msg_data in msg_list:
print(f"{consumer_name} 消费消息:ID={msg_id},内容={msg_data}")
# 模拟业务处理(如调用物流接口、发送短信)
print(f"{consumer_name} 处理消息 {msg_id} 完成")
# 确认消息(ACK):处理完成后必须ACK,否则消息会留在PEL中
r.xack(stream_name, group_name, msg_id)
print(f"{consumer_name} 确认消息 {msg_id} 成功\n")
# 启动物流组的消费者1
# 注:实际生产中可通过多线程/多进程启动多个消费者
import threading
t1 = threading.Thread(target=consume_from_group, args=("order_stream", "logistics_group", "logistics_consumer_1"))
t1.daemon = True
t1.start()
# 启动短信组的消费者1
t2 = threading.Thread(target=consume_from_group, args=("order_stream", "sms_group", "sms_consumer_1"))
t2.daemon = True
t2.start()
# 让主线程保持运行
t1.join()
t2.join()
步骤3:查看未确认消息(XPENDING)
若消费者处理消息后未ACK,可通过XPENDING查看PEL中的消息:
# 查看logistics_group的未确认消息
pending_msgs = r.xpending(
name="order_stream",
groupname="logistics_group",
min="-", # 最小消息ID(-表示所有)
max="+", # 最大消息ID(+表示所有)
count=10 # 最多返回10条
)
print(f"logistics_group 未确认消息:{pending_msgs}")
# 输出示例:[('1735689600000-0', 'logistics_consumer_1', 12345, 1)]
# 格式:(消息ID, 消费者名称, 空闲时间(ms), 被投递次数)
4.4 消息管理API
1. 修剪Stream(XTRIM):删除旧消息
# 保留order_stream最新的500条消息
trimmed_count = r.xtrim("order_stream", maxlen=500, approximate=True)
print(f"修剪掉的消息数量:{trimmed_count}")
2. 删除指定消息(XDEL)
# 根据消息ID删除消息
msg_id = "1735689600000-0"
deleted_count = r.xdel("order_stream", msg_id)
print(f"删除消息数量:{deleted_count}") # 1表示删除成功
3. 查询消息总数(XLEN)
stream_len = r.xlen("order_stream")
print(f"order_stream 消息总数:{stream_len}")
4. 按ID范围查询消息(XRANGE/XREVRANGE)
# XRANGE:从旧到新查询
history_msgs = r.xrange(
name="order_stream",
min="0-0", # 起始ID
max="+", # 结束ID
count=5 # 最多返回5条
)
print(f"历史消息(正序):{history_msgs}")
# XREVRANGE:从新到旧查询
latest_msgs = r.xrevrange("order_stream", min="-", max="+", count=5)
print(f"最新消息(倒序):{latest_msgs}")
五、实战案例:订单支付消息处理
场景描述
用户支付订单后,系统需要:
- 生产者:将支付成功的订单消息写入Stream;
- 消费者组1(物流组):处理订单发货逻辑;
- 消费者组2(短信组):给用户发送支付成功短信;
- 消费者组3(库存组):扣减商品库存。
完整代码
import redis
import threading
import time
# 1. 初始化Redis连接
r = redis.Redis(
host="localhost",
port=6379,
db=0,
decode_responses=True
)
# 2. 初始化消费者组(确保组存在)
def init_consumer_groups(stream_name):
groups = ["logistics_group", "sms_group", "inventory_group"]
for group in groups:
try:
r.xgroup_create(stream_name, group, "$", mkstream=True)
print(f"创建消费者组 {group} 成功")
except redis.exceptions.ResponseError as e:
if "BUSYGROUP" in str(e):
print(f"消费者组 {group} 已存在")
else:
raise e
# 3. 生产者:模拟生成订单支付消息
def order_producer(stream_name):
"""模拟订单支付,每秒生成1条订单消息"""
order_id = 1000
while True:
order_id += 1
msg = {
"order_id": f"OD{int(time.time())}{order_id}",
"user_id": f"U{order_id % 100}",
"amount": f"{(order_id % 10) * 10 + 9.9}",
"product_id": f"P{order_id % 50}",
"status": "paid"
}
msg_id = r.xadd(stream_name, msg, id="*", maxlen=1000, approximate=True)
print(f"\n生产者:发送订单消息 ID={msg_id},内容={msg}")
time.sleep(1) # 每秒1条
# 4. 消费者:通用消费函数
def order_consumer(stream_name, group_name, consumer_name, handle_func):
"""
通用消费者函数
:param handle_func: 消息处理函数
"""
while True:
messages = r.xreadgroup(
groupname=group_name,
consumername=consumer_name,
streams={stream_name: ">"},
count=1,
block=3000
)
if not messages:
continue
for stream, msg_list in messages:
for msg_id, msg_data in msg_list:
print(f"\n{consumer_name} 收到消息:ID={msg_id}")
# 调用自定义处理函数
handle_func(msg_data)
# 确认消息
r.xack(stream_name, group_name, msg_id)
print(f"{consumer_name} 确认消息 {msg_id} 完成")
# 5. 自定义业务处理函数
def handle_logistics(msg):
"""处理物流逻辑"""
print(f"物流处理:订单 {msg['order_id']} 已生成发货单")
def handle_sms(msg):
"""处理短信发送"""
print(f"短信发送:给用户 {msg['user_id']} 发送订单 {msg['order_id']} 支付成功短信")
def handle_inventory(msg):
"""处理库存扣减"""
print(f"库存扣减:商品 {msg['product_id']} 扣减1件,订单 {msg['order_id']}")
# 6. 启动程序
if __name__ == "__main__":
STREAM_NAME = "order_pay_stream"
# 初始化消费者组
init_consumer_groups(STREAM_NAME)
# 启动生产者线程
producer_thread = threading.Thread(target=order_producer, args=(STREAM_NAME,))
producer_thread.daemon = True
producer_thread.start()
# 启动物流消费者
logistics_thread = threading.Thread(
target=order_consumer,
args=(STREAM_NAME, "logistics_group", "logistics_1", handle_logistics)
)
logistics_thread.daemon = True
logistics_thread.start()
# 启动短信消费者
sms_thread = threading.Thread(
target=order_consumer,
args=(STREAM_NAME, "sms_group", "sms_1", handle_sms)
)
sms_thread.daemon = True
sms_thread.start()
# 启动库存消费者
inventory_thread = threading.Thread(
target=order_consumer,
args=(STREAM_NAME, "inventory_group", "inventory_1", handle_inventory)
)
inventory_thread.daemon = True
inventory_thread.start()
# 主线程阻塞(防止程序退出)
producer_thread.join()
logistics_thread.join()
sms_thread.join()
inventory_thread.join()
运行效果
创建消费者组 logistics_group 成功
创建消费者组 sms_group 成功
创建消费者组 inventory_group 成功
生产者:发送订单消息 ID=1735690000000-0,内容={'order_id': 'OD17356900001001', 'user_id': 'U1', 'amount': '9.9', 'product_id': 'P1', 'status': 'paid'}
logistics_1 收到消息:ID=1735690000000-0
物流处理:订单 OD17356900001001 已生成发货单
logistics_1 确认消息 1735690000000-0 完成
sms_1 收到消息:ID=1735690000000-0
短信发送:给用户 U1 发送订单 OD17356900001001 支付成功短信
sms_1 确认消息 1735690000000-0 完成
inventory_1 收到消息:ID=1735690000000-0
库存扣减:商品 P1 扣减1件,订单 OD17356900001001
inventory_1 确认消息 1735690000000-0 完成
六、总结
核心要点回顾
- Redis Streams核心价值:解决传统Redis消息方案(List/Pub/Sub)的持久化、消费确认、分组消费痛点,是生产级的消息队列方案;
- Python核心API:
- 生产消息:
xadd(指定Stream、消息体、最大长度); - 消费消息:组内消费用
xreadgroup(推荐),无分组用xread; - 消息确认:
xack(必须调用,否则消息留在PEL中); - 消息管理:
xpending(查未确认)、xtrim(修剪)、xdel(删除);
- 生产消息:
- 生产最佳实践:
- 始终使用消费者组(避免重复消费);
- 消息处理后必须ACK;
- 限制Stream长度(防止内存溢出);
- 通过多线程/多进程实现多消费者负载均衡。
通过以上API和实战案例,你可以快速落地Redis Streams作为Python项目的消息队列,满足异步通信、任务处理等核心场景需求。