Python Redis Streams 全面教程:常用 API 串联与实战指南

5 阅读8分钟

大家好,我是jobleap.cn的小九。

你想要了解Python的redis组件如何使用Redis Streams能力,以及它能解决的问题和具体使用教程,这份教程会从核心概念、API使用到实战案例,全面讲解Redis Streams在Python中的落地方式。

一、Redis Streams 核心定位:解决什么问题?

Redis Streams是Redis 5.0版本引入的持久化、有序、可重放的消息队列,专门解决传统Redis消息方案的痛点:

  1. 传统List做队列:POP后消息丢失、无消费确认、无法分组消费、不能回溯历史消息;
  2. Pub/Sub发布订阅:消息不持久化、消费者离线则消息丢失、无法重复消费;
  3. 核心优势:支持分组消费、消息确认(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}")

五、实战案例:订单支付消息处理

场景描述

用户支付订单后,系统需要:

  1. 生产者:将支付成功的订单消息写入Stream;
  2. 消费者组1(物流组):处理订单发货逻辑;
  3. 消费者组2(短信组):给用户发送支付成功短信;
  4. 消费者组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 完成

六、总结

核心要点回顾

  1. Redis Streams核心价值:解决传统Redis消息方案(List/Pub/Sub)的持久化、消费确认、分组消费痛点,是生产级的消息队列方案;
  2. Python核心API
    • 生产消息:xadd(指定Stream、消息体、最大长度);
    • 消费消息:组内消费用xreadgroup(推荐),无分组用xread
    • 消息确认:xack(必须调用,否则消息留在PEL中);
    • 消息管理:xpending(查未确认)、xtrim(修剪)、xdel(删除);
  3. 生产最佳实践
    • 始终使用消费者组(避免重复消费);
    • 消息处理后必须ACK;
    • 限制Stream长度(防止内存溢出);
    • 通过多线程/多进程实现多消费者负载均衡。

通过以上API和实战案例,你可以快速落地Redis Streams作为Python项目的消息队列,满足异步通信、任务处理等核心场景需求。