Redis Stream是Redis 5.0引入的日志型数据结构,专门为消息队列场景设计。它支持持久化、消费者组、消息确认、阻塞读取等特性,功能上比List完善得多。往Stream里添加消息只有一个入口命令,就是XADD。理解XADD的参数和行为,是使用Stream的第一步,也是最容易踩坑的一步,因为消息ID的生成规则、长度裁剪策略都藏在这条命令里。

XADD的基本语法与消息ID生成规则
XADD的完整语法是XADD key [NOMKSTREAM] [< MAXLEN | MINID > [~] threshold] [LIMIT count] *|ID field value [field value ...]。最简单的用法只需要指定键名、一个星号和若干字段值对:
XADD mystream * sensor-id 1 temperature 26.5 "1718000000000-0"
命令返回的就是这条消息的ID。Stream的消息ID由两部分组成,中间用短横线连接,格式为毫秒时间戳-序列号。当ID位置传入星号时,Redis会自动生成:毫秒部分取服务器当前时间,序列部分在同一毫秒内自增,从0开始。这样即使在同一毫秒内写入大量消息,ID也不会重复,并且ID天然按写入时间有序,这也是Stream能保证消息顺序的基础。
也可以手工指定ID,例如XADD mystream 5-1 field1 value1。但手工指定有严格限制:新ID必须大于流中已有的最大ID,否则会报错。如果手工指定的毫秒部分与当前最大ID相同,序列号必须更大;如果小于已有最大ID,命令直接失败。需要注意一个特殊约定,手工指定的ID毫秒部分为0时,序列号可以是任意值,这在从外部系统导入历史数据时比较有用,但实践中更推荐让Redis自动生成,避免时钟回拨或并发场景下手动分配ID冲突。
消息体结构与常见写入错误
XADD支持一条消息携带多个字段值对,字段名和值都是字符串类型,数量不限。例如同时写入设备号、温度、湿度三个字段是完全合法的。但要注意字段值对必须成对出现,写成奇数个参数时Redis会直接报参数个数错误。消息内容不支持嵌套结构,如果需要存复杂对象,通常先序列化成JSON字符串再作为一个字段写入,消费端再反序列化。
XADD order-stream * orderId 10001 status created amount 199.00
XADD order-stream * payload '{"orderId":10001,"items":[1,2,3]}'
XLEN order-stream常见的错误还有两类。一类是往不存在的流写入时会自动创建流对象,这本身是预期行为,但如果写错了键名,就会凭空多出一个流,造成数据污染。Redis 7.4之后提供了NOMKSTREAM选项,加上它以后,键不存在时命令直接返回nil而不创建新键,适合在消费确认类场景里防止误建。另一类是把消息ID和字段搞混,例如误以为ID可以作为业务字段使用。虽然ID可以提取出时间戳,但建议业务标识始终放在消息体字段里,不要依赖ID做业务逻辑,ID只作为定位和删除的游标。
MAXLEN裁剪与Stream的内存控制
Stream是持久写入的日志,只增不减,不主动控制就会无限膨胀。XADD提供了MAXLEN参数,写入的同时裁剪掉最旧的消息,保持流长度不超过指定值:
XADD mystream MAXLEN 10000 * field1 value1 XADD mystream MAXLEN ~ 10000 * field1 value1 XADD mystream MINID ~ 1718000000000 * field1 value1
MAXLEN的取值可以是精确值也可以是近似值。精确裁剪(不带波浪号)需要遍历整个宏节点逐条计数,性能开销大;加上波浪号变成近似裁剪,Redis按宏节点整体删除,速度快,最终长度可能略小于设定值,误差在几千条以内,绝大多数场景推荐近似方式。MINID则是另一种裁剪思路,删除所有ID小于给定值的消息,适合按时间清理而不是按条数清理的场景。
裁剪还有一个细节值得注意:被裁剪的消息如果还没被消费者组确认,也会直接删除,消费者组的相关状态由Redis自动修正,但未消费的数据就丢了。所以如果是可靠性要求高的队列,要么把MAXLEN设置得足够大,覆盖业务允许的最大积压量,要么采用外部定期归档加XTRIM的组合方案,把冷数据落到数据库后再裁剪。
Stream与List做消息队列的取舍
用List做消息队列是传统做法,生产端LPUSH,消费端BRPOP,实现简单。但List有两个硬伤:一是消费即删除,消息弹出后如果消费者崩溃,数据就丢了;二是没有消费者组概念,多个消费者只能靠业务代码自己分发。Stream解决了这两点,消息写入后一直保留,消费者组内每个消费者独立维护自己的待处理列表,处理完通过XACK确认,没确认的消息可以被XPENDING查询并重新投递,这就是典型的至少一次投递语义。
XADD对应List场景的写入操作,成本上略高于LPUSH,因为要生成ID、维护宏节点链表,但换来了可追溯性。实际选型时可以这么判断:简单的任务分发、允许丢消息、追求极限吞吐,用List加Pub/Sub就够了;需要消息堆积、消费确认、按组消费、历史回溯,就上Stream。另外Spring Data Redis、Redisson等客户端都对Stream和XADD做了封装,集成成本不高,生产环境记得关注流长度监控,避免内存被无限增长的Stream吃掉。
Redis XADDRedis Stream消息队列修改时间:2026-09-03 18:58:55