完整教程:Redis 是如何实现消息队列的?
一、为什么选择 Redis 做消息队列?
1.1 Redis 消息队列的核心优势
轻量级部署:无需单独部署 RabbitMQ、Kafka 等消息队列服务,可以直接复用现有 Redis 集群。例如一个电商系统可能已经使用 Redis 做缓存,现在只需增加消息队列功能,无需额外维护其他中间件,显著降低运维成本;
高性能:基于内存操作,单节点 QPS 可达 10 万级,满足高吞吐场景。实测表明,在标准服务器配置下,Redis 处理简单消息的延迟可低至 0.1ms,远优于传统磁盘存储的消息队列;
API 简洁:依托 Redis 原生命令即可实现完整队列功能:
- LPUSH/RPUSH 用于生产者推送消息
- BLPOP/BRPOP 实现消费者阻塞式拉取
- PUBLISH/SUBSCRIBE 支持发布订阅模式
- XADD/XREAD 提供 Stream 类型支持 开发人员无需学习复杂的新 API,显著降低开发成本;
支持多语言:所有主流语言的 Redis 客户端(Java/Jedis、Python/redis-py、Go/redigo 等)均原生支持消息队列相关命令。例如 Java 开发者可以直接使用 Jedis 的 lpush() 方法发送消息,无需额外依赖;
可扩展性:通过 Redis Cluster 可以轻松实现消息队列的横向扩展。例如可以将不同业务的消息分配到不同分片,同时利用 Redis Sentinel 实现高可用,确保消息服务不间断。
1.2 适用场景与不适用场景
适用场景
- 轻量级异步通信:如电商系统中的订单状态变更通知、APP 的日志上报等。例如用户下单后,系统可以通过 Redis 队列异步通知库存系统扣减库存,而不影响主流程响应速度; 
- 高吞吐但允许少量重复的场景:如用户行为数据同步、监控数据采集等。例如一个短视频平台需要将用户的观看记录同步到推荐系统,即使偶尔出现重复消息也不影响业务逻辑; 
- 中小型系统的解耦需求:当系统规模尚未达到需要引入 Kafka 等重量级组件时。例如一个初创公司的支付系统与通知系统之间使用 Redis 队列解耦,避免系统间直接依赖。 
不适用场景
- 金融级事务消息:如银行转账、证券交易等需要强一致性和零丢失的场景。Redis 的持久化机制(RDB/AOF)无法保证 100% 不丢失消息,且缺乏事务消息的回查机制; 
- 复杂路由需求:如需要死信队列、优先级队列、延迟队列等高级特性时。虽然 Redis 可以通过 Sorted Set 实现简单延迟队列,但相比 RabbitMQ 的专业实现功能有限; 
- 海量消息存储:如需要保存数月历史消息的聊天系统。Redis 作为内存数据库,存储容量受服务器内存限制,且长期存储成本过高。例如一个日均百万消息的客服系统,使用 Redis 存储一周消息就可能需要上百 GB 内存。 
二、Redis 实现消息队列的 3 种核心方案
方案一、基于 Redis List 的简单消息队列实现
1. 方案概述
Redis 的 List 数据结构是一个双向链表,具有以下特性使其非常适合实现消息队列:
- 支持从两端(O(1)时间复杂度)插入和删除元素
- 天然支持"生产者-消费者"模型
- 提供阻塞式获取消息的命令
- 内存存储,性能极高(每秒可处理数万次操作)
1.1 核心命令详解
| 角色 | 核心命令 | 作用说明 | 时间复杂度 | 
|---|---|---|---|
| 生产者 | LPUSH key value1 value2 | 从 List 左侧插入消息(头部插入),支持批量插入,返回插入后 List 的长度 | O(1) | 
| 生产者 | RPUSH key value1 value2 | 从 List 右侧插入消息(尾部插入),支持批量插入 | O(1) | 
| 消费者 | BLPOP key timeout | 从 List 左侧阻塞获取消息(头部取出),若 List 为空则等待timeout秒 | O(1) | 
| 消费者 | BRPOP key timeout | 从 List 右侧阻塞获取消息(尾部取出),若 List 为空则等待timeout秒 | O(1) | 
| 监控 | LLEN key | 获取当前队列的消息数量 | O(1) | 
| 监控 | LRANGE key start end | 查看队列中从start到end的消息(如LRANGE queue 0 9查看前10条) | O(S+N) | 
2. 代码实战(Java + Jedis)
2.1 环境准备
首先引入 Jedis 依赖(Maven):
    redis.clients 
    jedis 
    4.4.3  
 2.2 生产者实现
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class ListMQProducer {
    // 队列key命名规范:业务域:组件类型:数据结构:具体业务
    private static final String QUEUE_KEY = "redis:mq:list:order";
    // 使用连接池提高性能
    private static final JedisPool jedisPool = new JedisPool(
        new JedisPoolConfig(),
        "localhost",
        6379,
        2000,  // 连接超时时间
        null   // 密码
    );
    public static void main(String[] args) throws InterruptedException {
        try (Jedis jedis = jedisPool.getResource()) {
            // 模拟发送10条订单消息
            for (int i = 1; i <= 10; i++) {
                // 消息内容格式:业务标识_序号_时间戳
                String message = String.format("order_%d_%d", i, System.currentTimeMillis());
                // LPUSH命令将消息放入队列头部
                long queueLength = jedis.lpush(QUEUE_KEY, message);
                System.out.printf("生产者发送消息:%s,当前队列长度:%d%n", message, queueLength);
                // 模拟业务处理间隔
                Thread.sleep(500);
            }
        } catch (Exception e) {
            System.err.println("生产者异常:" + e.getMessage());
        } finally {
            jedisPool.close();
        }
    }
}2.3 消费者实现
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.List;
public class ListMQConsumer {
    private static final String QUEUE_KEY = "redis:mq:list:order";
    private static final JedisPool jedisPool = new JedisPool(
        new JedisPoolConfig(),
        "localhost",
        6379
    );
    public static void main(String[] args) {
        System.out.println("消费者启动,等待接收消息...");
        while (true) {
            try (Jedis jedis = jedisPool.getResource()) {
                // BRPOP命令参数:
                // 1. 超时时间3秒(避免空轮询消耗CPU)
                // 2. 可以监听多个队列
                List messages = jedis.brpop(3, QUEUE_KEY);
                if (messages != null) {
                    // BRPOP返回结果格式:
                    // 第一个元素是队列key
                    // 第二个元素是消息内容
                    String message = messages.get(1);
                    System.out.println("消费者接收消息:" + message);
                    // 业务处理逻辑示例
                    processMessage(message);
                } else {
                    System.out.println("队列暂无消息,继续等待...");
                }
            } catch (Exception e) {
                System.err.println("消费者处理消息异常:" + e.getMessage());
                // 异常处理策略:
                // 1. 记录错误日志
                // 2. 重试机制
                // 3. 告警通知
                try {
                    Thread.sleep(5000); // 出错后暂停5秒
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    private static void processMessage(String message) throws InterruptedException {
        // 模拟业务处理
        System.out.println("处理消息:" + message);
        // 解析消息内容
        String[] parts = message.split("_");
        String orderId = parts[1];
        // 模拟业务处理耗时
        Thread.sleep(1000);
        System.out.println("订单" + orderId + "处理完成");
    }
} 3. 方案优化与问题解决
3.1 标准方案的局限性
- 消息丢失风险: - 消费者获取消息后,如果处理过程中崩溃,消息将永久丢失
- 无消息确认机制
 
- 功能限制: - 不支持广播模式(多个消费者同时消费同一条消息)
- 无优先级队列
- 无延迟队列功能
 
- 监控缺失: - 缺乏消息处理状态跟踪
- 无死信队列处理机制
 
3.2 消息可靠性优化方案
3.2.1 消息确认机制实现
private static final String CONFIRM_QUEUE_KEY = "redis:mq:list:order:confirm";
private static final String DEAD_QUEUE_KEY = "redis:mq:list:order:dead";
private static final int MAX_RETRY = 3;
// 优化后的消费者处理逻辑
List messages = jedis.brpop(3, QUEUE_KEY);
if (messages != null) {
    String message = messages.get(1);
    // 1. 将消息移到待确认队列(使用RPUSH保持顺序)
    jedis.rpush(CONFIRM_QUEUE_KEY, message);
    try {
        // 2. 处理业务逻辑
        processMessage(message);
        // 3. 处理成功,从待确认队列删除
        jedis.lrem(CONFIRM_QUEUE_KEY, 1, message);
    } catch (Exception e) {
        System.err.println("处理消息失败:" + message);
        // 4. 检查重试次数
        long retryCount = jedis.incr("retry:" + message);
        if (retryCount <= MAX_RETRY) {
            // 放回主队列重试
            jedis.lpush(QUEUE_KEY, message);
        } else {
            // 超过重试次数,放入死信队列
            jedis.rpush(DEAD_QUEUE_KEY, message);
        }
        // 无论重试还是加入死信队列,都要从待确认队列删除
        jedis.lrem(CONFIRM_QUEUE_KEY, 1, message);
    }
} 3.2.2 定时补偿任务
// 定时检查待确认队列(每分钟执行)
public void checkConfirmQueue() {
    try (Jedis jedis = jedisPool.getResource()) {
        // 获取待确认队列所有消息
        List pendingMessages = jedis.lrange(CONFIRM_QUEUE_KEY, 0, -1);
        for (String message : pendingMessages) {
            // 检查消息滞留时间
            long createTime = Long.parseLong(message.split("_")[2]);
            long currentTime = System.currentTimeMillis();
            long delay = currentTime - createTime;
            // 超过30秒未处理则重试
            if (delay > 30000) {
                jedis.lrem(CONFIRM_QUEUE_KEY, 1, message);
                jedis.lpush(QUEUE_KEY, message);
                System.out.println("消息超时重试:" + message);
            }
        }
    }
} 3.3 性能优化策略
- 横向扩展: - 增加消费者实例数量,利用 List 的 BRPOP 命令天然支持多消费者竞争
- 可采用消费者组模式,每个组独立消费
 
- 批量处理: - // 生产者批量发送 jedis.lpush(QUEUE_KEY, "msg1", "msg2", "msg3"); // 消费者批量获取(非阻塞) List- batch = jedis.rpop(QUEUE_KEY, 10); // 获取最多10条 
- 管道(Pipeline)优化: - try (Pipeline p = jedis.pipelined()) { p.lpush(QUEUE_KEY, "msg1"); p.lpush(QUEUE_KEY, "msg2"); p.sync(); // 批量提交 }
- 监控指标: - 队列长度监控:LLEN key
- 消费者积压:比较生产和消费速率
- 异常告警:死信队列增长监控
 
- 队列长度监控:
4. 适用场景分析
4.1 推荐使用场景
- 异步任务处理: - 订单创建后的后续处理(如发送通知、更新库存)
- 日志收集和分析
 
- 削峰填谷: - 秒杀系统请求缓冲
- 突发流量处理
 
- 系统解耦: - 微服务间通信
- 事件驱动架构
 
4.2 不适用场景
- 严格顺序要求:List虽然有序,但在多消费者场景下不能保证全局顺序
- 广播模式需求:需要所有消费者收到相同消息
- 持久化要求高:Redis是内存数据库,虽然支持持久化但不保证100%可靠
- 复杂路由需求:需要根据消息内容路由到不同队列
5. 生产环境建议
- Redis配置: - 启用AOF持久化:appendonly yes
- 合理设置内存淘汰策略:maxmemory-policy volatile-lru
- 设置合理超时:timeout 300(秒)
 
- 启用AOF持久化:
- 高可用: - 使用Redis Sentinel或Cluster
- 客户端实现故障转移
 
- 监控指标: - # 监控队列长度 redis-cli llen redis:mq:list:order # 监控Redis内存 redis-cli info memory
- 命名规范: - 业务域:组件类型:数据结构:具体业务
- 示例:payment:mq:list:refund
 
方案二、基于 Pub/Sub 的广播式消息队列方案详解
Redis Pub/Sub 模型介绍
Redis 的 Pub/Sub(发布 - 订阅)模型是一种高效的"一对多"消息通信机制,它允许生产者将消息发布到特定的频道(Channel),而所有订阅该频道的消费者都能即时接收到这些消息。这种模式特别适合需要实时广播的场景,如新闻推送、实时聊天系统等。
核心命令及功能详解
| 角色 | 核心命令 | 作用说明 | 
|---|---|---|
| 生产者 | PUBLISH channel message | 向指定频道发布消息,返回接收消息的消费者数量 | 
| 消费者 | SUBSCRIBE channel1 channel2 | 订阅一个或多个频道,阻塞等待消息(订阅状态下只能接收消息,无法执行其他命令) | 
| 消费者 | PSUBSCRIBE pattern | 使用模式匹配订阅频道(如 PSUBSCRIBE redis:mq:pubsub:*订阅所有匹配前缀的频道) | 
2.1 代码实战(Java + Jedis)
生产者实现(发布消息)
import redis.clients.jedis.Jedis;
public class PubSubProducer {
    // 定义频道名称,采用命名空间方式避免冲突
    private static final String CHANNEL_KEY = "redis:mq:pubsub:news";
    // 创建Redis连接实例
    private static final Jedis jedis = new Jedis("localhost", 6379);
    public static void main(String[] args) throws InterruptedException {
        // 模拟发布3条新闻消息,实际应用中可接入实时数据源
        String[] news = {
            "Redis 7.2版本发布,新增Stream增强功能",
            "基于Redis的消息队列在电商场景的实践",
            "Redis Cluster集群部署最佳实践"
        };
        // 循环发布消息
        for (String msg : news) {
            // 发布消息并获取接收者数量
            long receiverCount = jedis.publish(CHANNEL_KEY, msg);
            System.out.println(String.format(
                "【生产者】发布消息:%s,当前订阅者数量:%d",
                msg, receiverCount));
            // 模拟消息间隔
            Thread.sleep(1000);
        }
        // 关闭连接
        jedis.close();
    }
}消费者实现(订阅消息)
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPubSub;
public class PubSubConsumer {
    private static final String CHANNEL_KEY = "redis:mq:pubsub:news";
    private static final Jedis jedis = new Jedis("localhost", 6379);
    public static void main(String[] args) {
        // 创建自定义的消息处理器
        JedisPubSub pubSub = new JedisPubSub() {
            // 接收到消息时的回调方法
            @Override
            public void onMessage(String channel, String message) {
                System.out.println(String.format(
                    "【消费者1】接收到新消息(频道:%s):%s",
                    channel, message));
                // 此处可添加业务处理逻辑
                // 例如:解析消息内容、写入数据库、触发其他操作等
            }
            // 成功订阅频道时的回调
            @Override
            public void onSubscribe(String channel, int subscribedChannels) {
                System.out.println(String.format(
                    "【消费者1】成功订阅频道:%s,当前订阅总数:%d",
                    channel, subscribedChannels));
            }
            // 可添加其他回调方法如onUnsubscribe、onPSubscribe等
        };
        System.out.println("【消费者1】启动并开始监听...");
        // 开始订阅(该方法会阻塞当前线程)
        jedis.subscribe(pubSub, CHANNEL_KEY);
        // 注意:在实际应用中,通常会将订阅逻辑放在独立线程中
        // 以避免阻塞主线程
    }
}2.2 方案深度分析与应用场景
优点详解
- 实时广播能力:天然支持一对多的消息分发,一条消息可以同时被多个消费者接收
- 实现简单:无需额外中间件,使用Redis原生命令即可实现
- 低延迟:消息发布后立即推送给所有订阅者,延迟通常在毫秒级
- 动态扩展:消费者可以随时加入或退出订阅,系统自动处理连接管理
缺点与限制
- 消息持久化问题: - Redis重启后所有未消费的消息都会丢失
- 消费者离线期间的消息无法恢复
 
- 可靠性限制: - 缺乏消息确认机制,无法保证消息必达
- 网络中断可能导致消息丢失
 
- 流量控制缺失: - 没有背压机制,生产者可能压垮消费者
- 无法限制消息堆积(因为根本不堆积)
 
适用场景分析
- 实时通知系统: - 网站全局公告推送
- 在线聊天室消息分发
- 游戏服务器中的全服通知
 
- 日志收集与监控: - 多个监控系统同时接收相同的日志流
- 实时统计系统指标
- 分布式系统的调试信息广播
 
- 临时性事件广播: - 系统配置变更通知
- 缓存失效广播
- 服务注册中心的服务变更通知
 
- 不要求可靠性的场景: - 实时数据统计(允许少量数据丢失)
- 非关键业务的实时通知
- 辅助性的系统状态更新
 
不适用场景
- 金融交易等要求消息100%可靠的系统
- 需要保证消息顺序的场景
- 需要消息重放或回溯的业务
- 消费者处理能力远低于生产者速率的场景
使用建议
- 频道命名规范:建议采用 - 业务域:子系统:消息类型的层次结构,如- trade:order:create
- 消费者实现: - 为每个订阅者创建独立连接
- 将订阅逻辑放在独立线程中
- 实现重连机制处理网络中断
 
- 监控指标: - 跟踪每个频道的订阅者数量
- 监控消息发布速率
- 记录消息丢失情况(需应用层实现)
 
- 性能优化: - 对于高频消息,考虑消息聚合
- 大消息可考虑只发送引用ID
- 合理设置Redis的TCP-Keepalive参数
 
方案 3:基于 Stream 的可靠消息队列(Redis 5.0+)
Redis 5.0 推出的 Stream 数据结构是专门为消息队列场景设计的,它完美解决了传统 List 和 Pub/Sub 模式的诸多缺陷。Stream 支持消息持久化存储、消息确认机制、消费者组管理、死信队列等企业级特性,是目前 Redis 实现可靠消息队列的最佳方案。在实际应用中,如电商订单处理、支付流水记录、日志收集等场景都能发挥重要作用。
3.1 Stream 核心概念
Stream:消息队列的主体,每个 Stream 有唯一的 key(如"order:stream")。消息以"条目(Entry)"形式存储,每个条目包含:
- 唯一 ID:自动生成的格式为"时间戳-序列号"(如1680000000000-0)
- 多个字段值对:如{"order_id":"1001","amount":"199.00"}
消费者组(Consumer Group):通过将多个消费者归为一组,实现:
- 组内消费者共享消息,避免重复消费
- 自动负载均衡,消息均匀分配给各消费者
- 支持水平扩展,可随时增加消费者
消息确认(ACK)机制:
- 消费者获取消息后,消息进入"Pending"状态
- 处理完成后需显式发送ACK命令
- 未确认的消息会在消费者断开后重新分配
Pending 列表:
- 存储所有已获取但未确认的消息
- 记录每个消息的消费者名称、获取时间、重试次数
- 支持通过XPENDING命令查看待处理消息
死信队列:
- 当消息超过最大重试次数(如3次)仍未处理成功
- 可自动/手动转移到专门设计的死信Stream
- 便于后续人工干预或特殊处理
3.2 核心命令详解
基本操作命令
| 操作类型 | 命令格式 | 说明 | 
|---|---|---|
| 添加消息 | XADD key * field1 value1 [field2 value2...] | *表示自动生成ID,可指定ID保证顺序 | 
| 创建消费者组 | XGROUP CREATE key groupname id [MKSTREAM] | MKSTREAM选项在Stream不存在时自动创建 | 
| 消费消息 | XREADGROUP GROUP group consumer [COUNT n] [BLOCK ms] STREAMS key [id] | id通常为>表示新消息,0表示Pending消息 | 
| 消息确认 | XACK key groupname id [id...] | 支持批量确认多个消息 | 
| 查看Pending消息 | XPENDING key groupname [start end count] [consumer] | 可查看指定消费者的未确认消息 | 
| 消息所有权转移 | XCLAIM key groupname consumer min-idle-time id [id...] | 将空闲超时的消息转给其他消费者处理 | 
高级管理命令
- 消息回溯:XREAD STREAMS key 0-0从最早消息开始读取
- 范围查询:XRANGE key start end [COUNT n]按ID范围查询
- 监控命令:XINFO GROUPS key查看消费者组信息
3.3 代码实战(Java + Jedis)
1. 环境准备
// Maven依赖
    redis.clients 
    jedis 
    4.3.1 
 
// 连接配置
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(10);
try (JedisPool pool = new JedisPool(config, "localhost", 6379)) {
    Jedis jedis = pool.getResource();
    // 业务代码...
}2. 生产消费完整流程
生产者增强版:
public class EnhancedProducer {
    private static final String[] ORDER_STATUS = {"PENDING", "PAID", "SHIPPED", "COMPLETED"};
    public void sendOrderEvent(Order order) {
        try (Jedis jedis = pool.getResource()) {
            Map fields = new HashMap<>();
            fields.put("order_id", order.getId());
            fields.put("user_id", order.getUserId());
            fields.put("amount", order.getAmount().toString());
            fields.put("status", ORDER_STATUS[0]);
            fields.put("create_time", Instant.now().toString());
            // 使用事务保证原子性
            Transaction t = jedis.multi();
            t.xadd(STREAM_KEY, StreamEntryID.NEW_ENTRY, fields);
            t.sadd("order:ids", order.getId()); // 记录订单ID集合
            t.exec();
            // 添加监控埋点
            Metrics.counter("mq.produce.count").increment();
        }
    }
} 消费者增强版:
public class ReliableConsumer implements Runnable {
    private static final int MAX_RETRY = 3;
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                Map> messages = jedis.xreadGroup(
                    GROUP_NAME, consumerName,
                    new StreamParams().count(1).block(2000),
                    new StreamOffset(STREAM_KEY, ">")
                );
                if (messages != null) {
                    messages.forEach((stream, entries) -> {
                        entries.forEach(entry -> {
                            processWithRetry(entry);
                        });
                    });
                }
            } catch (Exception e) {
                logger.error("消费异常", e);
                sleep(1000);
            }
        }
    }
    private void processWithRetry(StreamEntry entry) {
        int retryCount = getRetryCount(entry.getID());
        if (retryCount >= MAX_RETRY) {
            moveToDeadLetter(entry);
            return;
        }
        try {
            Order order = parseOrder(entry.getFields());
            orderService.process(order);
            jedis.xack(STREAM_KEY, GROUP_NAME, entry.getID());
        } catch (Exception e) {
            logger.warn("处理失败准备重试", e);
            sleep(1000 * (retryCount + 1));
        }
    }
} 3. 死信队列管理
public class DeadLetterMonitor {
    public void checkPendingMessages() {
        // 获取所有超时未确认的消息
        List pending = getPendingMessages(TIMEOUT_MS);
        pending.forEach(entry -> {
            // 检查重试次数
            if (getRetryCount(entry.getID()) > MAX_RETRY) {
                // 转移到死信队列
                jedis.xadd(DEAD_STREAM_KEY, StreamEntryID.NEW_ENTRY, entry.getFields());
                jedis.xack(STREAM_KEY, GROUP_NAME, entry.getID());
                logger.warn("消息转入死信队列: {}", entry.getID());
                // 发送告警通知
                alertService.notifyAdmin(entry);
            }
        });
    }
    public void reprocessDeadLetters() {
        // 从死信队列重新处理
        List deadMessages = jedis.xrange(DEAD_STREAM_KEY, "-", "+");
        deadMessages.forEach(entry -> {
            try {
                manualProcess(entry.getFields());
                jedis.xdel(DEAD_STREAM_KEY, entry.getID());
            } catch (Exception e) {
                logger.error("死信处理失败", e);
            }
        });
    }
}  3.4 最佳实践建议
- 消费者设计原则: - 每个消费者设置唯一标识
- 实现幂等性处理逻辑
- 添加合理的阻塞超时时间(通常1-5秒)
 
- 性能优化: - // 批量消费提高吞吐量 jedis.xreadGroup(GROUP_NAME, consumerName, new StreamParams().count(100).block(1000), new StreamOffset(STREAM_KEY, ">")); // 批量确认减少网络开销 jedis.xack(STREAM_KEY, GROUP_NAME, id1, id2, id3);
- 监控指标: - 待处理消息数(XPENDING)
- 消费者延迟(当前时间 - 消息创建时间)
- 死信队列大小
- 消费成功率
 
- 异常处理: - // 消费者崩溃后的恢复处理 public void recoverConsumer(String failedConsumer) { List- pendings = jedis.xpending( STREAM_KEY, GROUP_NAME, "-", "+", 100, failedConsumer); pendings.forEach(pending -> { jedis.xclaim(STREAM_KEY, GROUP_NAME, currentConsumer, TIMEOUT_MS, pending.getIdAsString()); }); } 
通过以上实现,基于Redis Stream的消息队列可以达到:
- 99.9%的消息可靠性
- 每秒万级的吞吐量
- 秒级的端到端延迟
- 完善的故障恢复机制
三、三种方案的选型对比与最佳实践
3.1 方案选型对比表:
| 对比维度 | List 方案 | Pub/Sub 方案 | Stream 方案(推荐) | 
|---|---|---|---|
| 消息持久化 | 支持(需手动处理) | 不支持 | 原生支持 | 
| 消息确认 | 需自定义(如RPOPLPUSH) | 不支持 | 原生支持(ACK机制) | 
| 广播能力 | 不支持 | 原生支持(全量广播) | 支持(通过多消费者组实现) | 
| 消费者负载均衡 | 支持(竞争消费模式) | 不支持(全量推送) | 支持(消费者组内自动均衡) | 
| 死信队列 | 需自定义(备份List) | 不支持 | 支持(通过XCLAIM命令) | 
| 实现复杂度 | 低(基础命令即可) | 低(订阅/发布模式) | 中(需理解消费者组概念) | 
| 内存占用 | 线性增长 | 瞬时内存 | 可控制(支持消息修剪) | 
| 历史消息回溯 | 有限支持(需保存完整List) | 不支持 | 完整支持(消息ID时间序列) | 
| 适用场景 | 简单异步通信 | 实时广播通知 | 可靠消息、企业级场景 | 
3.2 最佳实践建议
- 选型决策树: - 首要判断消息可靠性需求:
 - 必须保证不丢失 → 直接选择Stream
- 可接受偶尔丢失 → 进入下一判断
 
- 次要判断消息分发模式:
 - 需要广播 → 选择Pub/Sub
- 点对点消费 → 选择List或Stream
 
- 最后评估开发成本:
 - 快速实现 → 选择List
- 长期维护 → 选择Stream
 
 
- 首要判断消息可靠性需求:
 
- Stream方案实施细节: - 消费者组创建示例:
 XGROUP CREATE mystream mygroup $ MKSTREAM
- 典型消费代码逻辑:
 - 使用XREADGROUP阻塞读取
- 业务处理成功后发送XACK
- 处理失败时使用XCLAIM转移消息
- 设置合理的PEL(Pending Entries List)超时
 
 
- 消费者组创建示例:
 
- List方案优化建议: - 可靠消费模式实现:
 RPOPLPUSH source_list backup_list # 原子操作 # 处理成功后再LREM备份列表
- 性能提升技巧:
 - 批量生产:使用Pipeline打包多个LPUSH
- 批量消费:LUA脚本实现多消息批量获取
 
 
- 可靠消费模式实现:
 
- 集群环境特别注意事项: - 跨slot访问问题:
 - 所有相关key必须使用相同hash tag(如{msg})
- 或者采用客户端分片路由
 
- 监控重点指标:
 - Stream方案的PEL积压长度
- List方案的内存增长曲线
- Pub/Sub的客户端连接数
 
 
- 跨slot访问问题:
 
- 运维管理建议: - 容量规划:
 - 按业务峰值QPS的1.5倍预留资源
- Stream建议单分片不超过10MB/s写入
 
- 监控告警:
 - 设置消息积压阈值(如Stream的PEL>1000)
- 监控消费者延迟(XINFO GROUPS)
 
- 灾备方案:
 - 定期备份Stream的RDB快照
- 对于关键业务实现双写机制
 
 
- 容量规划:
 
四、实际应用案例:电商订单异步处理
4.1 业务流程详解
电商平台的订单处理采用异步消息队列模式,通过Redis Stream实现可靠的消息传递和消费。整个流程包含以下关键环节:
- 订单创建阶段 - 用户下单后,订单服务作为生产者将订单数据持久化到MySQL数据库
- 同时将订单关键信息(订单ID、用户ID、商品ID、数量等)封装为消息,发送到名为"order_create"的Stream中
- 消息格式示例:
 { "order_id": "ORD20231125001", "user_id": "U10086", "product_id": "P8808", "quantity": "2" }
 
- 并行消费阶段 - 通知服务(消费者1):专门处理用户通知 - 消费消息后调用短信平台API或极光推送服务
- 通知内容示例:"尊敬的会员,您的订单ORD20231125001已创建成功,我们将尽快为您处理"
- 支持重试机制:若首次发送失败,会按照指数退避策略重试3次
 
- 库存服务(消费者2):负责库存扣减 - 采用乐观锁机制更新库存:UPDATE inventory SET stock = stock - ? WHERE product_id = ? AND stock >= ?
- 实现分布式事务:若扣减失败会记录操作日志,便于后续人工核对
 
- 采用乐观锁机制更新库存:
 
- 异常处理机制 - 当库存扣减失败时,消息会进入Pending列表并设置5分钟超时
- 超时后自动转移到死信队列"DLQ:order_create"
- 运维人员通过管理后台查看死信队列,可:
 - 人工补扣库存
- 触发订单取消流程
- 联系用户协商处理
 
 
4.2 核心代码实现(生产级优化版)
订单服务(生产者)增强实现
// 订单服务(生产者)发送消息 - 增强版
public void createOrder(Order order) {
    // 1. 数据库事务确保数据一致性
    TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    try {
        // 1.1 保存主订单
        orderMapper.insert(order);
        // 1.2 保存订单明细
        order.getItems().forEach(item -> {
            item.setOrderId(order.getId());
            orderItemMapper.insert(item);
        });
        // 2. 构建消息体(添加时间戳和业务标识)
        Map message = new HashMap<>();
        message.put("order_id", order.getId());
        message.put("user_id", order.getUserId());
        message.put("product_id", order.getProductId());
        message.put("quantity", order.getQuantity() + "");
        message.put("create_time", System.currentTimeMillis() + "");
        message.put("biz_type", "NORMAL_ORDER");
        // 3. 发送消息(添加重试机制)
        int retryTimes = 0;
        while (retryTimes < 3) {
            try {
                jedis.xadd("redis:mq:stream:order_create", null, message);
                break;
            } catch (Exception e) {
                retryTimes++;
                if (retryTimes == 3) {
                    throw new RuntimeException("消息发送失败", e);
                }
                Thread.sleep(1000 * retryTimes);
            }
        }
        transactionManager.commit(status);
    } catch (Exception e) {
        transactionManager.rollback(status);
        throw new BusinessException("订单创建失败", e);
    }
} 通知服务(消费者)完整实现
// 通知服务(消费者)完整实现
public void handleNotification() {
    // 初始化消费者组(幂等操作)
    initConsumerGroup("redis:mq:stream:order_create", "order_group");
    while (!Thread.currentThread().isInterrupted()) {
        try {
            Map> messages = jedis.xreadGroup(
                "order_group",
                "notify_consumer_" + instanceId, // 使用实例ID区分消费者
                1,
                5000,
                false,
                Map.of("redis:mq:stream:order_create", StreamEntryID.UNRECEIVED_ENTRY)
            );
            if (messages != null && !messages.isEmpty()) {
                for (StreamEntry entry : messages.get("redis:mq:stream:order_create")) {
                    Map content = entry.getFields();
                    String userId = content.get("user_id");
                    String orderId = content.get("order_id");
                    // 1. 发送短信(带熔断机制)
                    boolean smsSent = circuitBreaker.execute(() ->
                        smsService.send(userId, "订单通知", "您的订单" + orderId + "已创建成功"));
                    // 2. 发送APP推送
                    boolean pushSent = pushService.send(userId, "订单创建通知",
                        Map.of("orderId", orderId, "type", "order_created"));
                    if (smsSent || pushSent) {
                        // 至少一个通知发送成功才确认消息
                        jedis.xack("redis:mq:stream:order_create", "order_group", entry.getID());
                        monitorService.recordSuccess("order_notify");
                    } else {
                        monitorService.recordFailure("order_notify");
                    }
                }
            }
        } catch (Exception e) {
            log.error("通知处理异常", e);
            monitorService.recordError("order_notify", e);
            Thread.sleep(5000); // 异常休眠避免循环异常
        }
    }
}
private void initConsumerGroup(String streamKey, String groupName) {
    try {
        jedis.xgroupCreate(streamKey, groupName, StreamEntryID.LAST_ENTRY, true);
    } catch (RedisBusyException e) {
        log.info("消费者组已存在: {}", groupName);
    }
}  库存服务(消费者)完整实现
// 库存服务(消费者)完整实现
public void handleInventory() {
    // 初始化消费者组
    initConsumerGroup("redis:mq:stream:order_create", "order_group");
    while (!Thread.currentThread().isInterrupted()) {
        try {
            Map> messages = jedis.xreadGroup(
                "order_group",
                "inventory_consumer_" + instanceId,
                1,
                5000,
                false,
                Map.of("redis:mq:stream:order_create", StreamEntryID.UNRECEIVED_ENTRY)
            );
            if (messages != null && !messages.isEmpty()) {
                for (StreamEntry entry : messages.get("redis:mq:stream:order_create")) {
                    Map content = entry.getFields();
                    String productId = content.get("product_id");
                    int quantity = Integer.parseInt(content.get("quantity"));
                    String orderId = content.get("order_id");
                    // 1. 扣减库存(带事务)
                    boolean success = inventoryService.deductWithLog(
                        productId,
                        quantity,
                        orderId,
                        "ORDER_DEDUCTION"
                    );
                    if (success) {
                        // 2. 确认消息
                        jedis.xack("redis:mq:stream:order_create", "order_group", entry.getID());
                        monitorService.recordSuccess("inventory_deduct");
                    } else {
                        // 3. 记录失败日志
                        log.warn("库存扣减失败 orderId={}, productId={}", orderId, productId);
                        monitorService.recordFailure("inventory_deduct");
                        // 不确认消息,让其进入Pending状态
                    }
                }
            }
        } catch (Exception e) {
            log.error("库存处理异常", e);
            monitorService.recordError("inventory_deduct", e);
            Thread.sleep(5000);
        }
    }
}
// 库存扣减服务方法
@Transactional
public boolean deductWithLog(String productId, int quantity, String bizId, String bizType) {
    // 1. 扣减库存
    int affected = inventoryMapper.deductWithVersion(
        productId,
        quantity,
        getCurrentVersion(productId)
    );
    if (affected == 0) {
        return false;
    }
    // 2. 记录操作流水
    InventoryLog log = new InventoryLog();
    log.setLogId(UUID.randomUUID().toString());
    log.setProductId(productId);
    log.setChangedAmount(-quantity);
    log.setBizId(bizId);
    log.setBizType(bizType);
    log.setRemarks("订单扣减");
    inventoryLogMapper.insert(log);
    return true;
}  4.3 监控与运维设计
- 监控指标 - 消息堆积量:XLEN redis:mq:stream:order_create
- Pending列表数量:XPENDING redis:mq:stream:order_create order_group
- 消费者延迟:通过消息时间戳与当前时间差值计算
 
- 消息堆积量:
- 运维命令示例 - # 查看消费者组信息 XINFO GROUPS redis:mq:stream:order_create # 处理死信消息 XRANGE DLQ:order_create - + COUNT 10 XACK DLQ:order_create manual_group
- 自动恢复方案 - 定时任务每小时检查Pending列表
- 对于超时1小时未处理的消息:
 - 尝试重新投递到原Stream
- 超过3次重试则转入死信队列
- 触发企业微信告警通知运维人员
 
 
 
                    
                
 
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号