如何保证MQ消息顺序消费

如何保证MQ消息顺序消费

🔍 问题本质:为什么会出现消息乱序?

消息乱序的根本原因是生产有序→传输 / 存储无序→消费无序,具体体现在三个环节:

核心乱序点:

  • ✅ 生产端:单生产者单线程发送是有序的
  • ❌ 存储端:消息被分发到不同的 Queue/Partition
  • ❌ 消费端:多个消费者并发消费不同 Queue

🧩 保证顺序消费的核心原则

  • 全局有序:所有消息严格按照发送顺序消费(性能极差,几乎不用)
  • 局部有序:同一业务标识的消息保证顺序(业界主流方案)

💡 核心公式:相同业务 Key 的消息 → 同一个队列 → 同一个消费者线程

🚀 主流 MQ 的具体实现方案

1. RocketMQ(最完善的顺序消费支持)

实现步骤:

  1. 生产端:使用MessageQueueSelector,按业务 Key(如订单 ID)哈希选择队列
  2. 消费端:使用MessageListenerOrderly(自动加锁,单线程消费每个队列)
  3. 注意:RocketMQ 会自动处理 Broker 宕机时的队列重新分配,保证顺序不被破坏

2. Kafka(基于 Partition 的顺序消费)

实现步骤

  1. 生产端:指定key,Kafka 默认按 key 哈希分配到同一个 Partition
  2. 消费端:一个 Partition 只能被一个消费者线程消费(消费者组内)
  3. 限制:Kafka 只保证 Partition 内有序,不保证全局有序

3. RabbitMQ(相对较弱的顺序支持)

实现步骤

  1. 生产端:按业务 Key 将消息发送到同一个 Queue
  2. 消费端:设置prefetchCount=1,关闭自动 ack,单线程消费
  3. 缺点:并发度极低,不适合高吞吐量场景

📊 三种 MQ 顺序消费方案对比

特性 RocketMQ Kafka RabbitMQ
顺序粒度 队列级 Partition 级 Queue 级
并发度 高(队列数 = 并发数) 高(Partition 数 = 并发数) 极低
异常处理 自动重试,不影响其他队列 手动处理,失败会阻塞 手动处理,失败会阻塞
推荐指数 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐

🛡️ 业务层面的兜底方案

即使 MQ 层面做了保证,实际生产中还需要业务层兜底:

1.消息状态机校验 📝

  • 每条消息携带版本号或状态
  • 消费时先检查状态是否合法(如订单已支付就不能再处理创建消息)
  • 示例:if (order.getStatus() >= PAID && message.getStatus() == CREATED) { 丢弃消息 }

2.幂等性设计 ✅

  • 即使消息重复消费或乱序,最终结果一致
  • 使用唯一业务 ID 作为幂等键(如订单 ID + 操作类型)

3.延迟重试机制 ⏱️

  • 对于乱序的消息,不要立即失败,而是延迟一段时间重试
  • 示例:收到 "支付完成" 但没收到 "订单创建",延迟 5 秒重试

⚠️ 常见的坑和注意事项

  1. 不要使用全局有序,除非业务必须(吞吐量会下降 100 倍以上)
  2. ❌ 不要在消费端使用多线程消费同一个队列
  3. ✅ 队列数量要提前规划,避免后期扩容导致顺序被破坏
  4. ✅ 处理好消息重试和死信队列,避免单个失败消息阻塞整个队列

📝 总结

保证 MQ 消息顺序消费的最佳实践是:

RocketMQ + MessageQueueSelector + MessageListenerOrderly + 业务状态机校验

这样既保证了局部顺序,又能获得较高的吞吐量,同时通过业务层兜底解决极端情况下的乱序问题。

💻 核心代码实现(RocketMQ + Kafka)

1. RocketMQ 顺序消费核心代码(业界首选)

生产者端(按业务 Key 哈希路由到同一队列)

java 复制代码
@Service
public class OrderProducer {
    @Autowired
    private DefaultMQProducer producer;

    /**
     * 发送顺序消息
     * @param orderId 业务唯一标识(订单ID)
     * @param messageBody 消息体
     */
    public void sendOrderlyMessage(Long orderId, String messageBody) throws Exception {
        Message message = new Message(
            "ORDER_TOPIC",
            "ORDER_TAG",
            orderId.toString(), // 业务Key,用于哈希路由
            messageBody.getBytes(StandardCharsets.UTF_8)
        );

        // ✅ 技术亮点:自定义队列选择器,保证相同订单ID的消息进入同一个队列
        SendResult result = producer.send(message, new MessageQueueSelector() {
            @Override
            public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
                Long orderId = (Long) arg;
                // 取模运算,保证相同订单ID永远路由到同一个队列
                int index = (int) (orderId % mqs.size());
                return mqs.get(index);
            }
        }, orderId); // 传入业务Key作为参数

        log.info("顺序消息发送成功,订单ID:{},队列ID:{}", orderId, result.getMessageQueue().getQueueId());
    }
}

消费者端(单线程消费每个队列,自动加锁)

java 复制代码
@Component
public class OrderConsumer {
    @PostConstruct
    public void start() throws Exception {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("ORDER_CONSUMER_GROUP");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.subscribe("ORDER_TOPIC", "ORDER_TAG");

        // ✅ 技术亮点:使用顺序消息监听器,自动保证单线程消费每个队列
        consumer.registerMessageListener(new MessageListenerOrderly() {
            @Override
            public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext context) {
                // 自动设置队列锁,防止其他消费者消费该队列
                context.setAutoCommit(true);

                for (MessageExt msg : msgs) {
                    try {
                        String messageBody = new String(msg.getBody(), StandardCharsets.UTF_8);
                        Long orderId = Long.parseLong(msg.getKeys());
                        log.info("顺序消费消息,订单ID:{},消息内容:{}", orderId, messageBody);

                        // 业务逻辑处理(订单创建→支付→发货→完成)
                        orderService.processOrderMessage(orderId, messageBody);

                    } catch (Exception e) {
                        log.error("消息消费失败,重试次数:{}", msg.getReconsumeTimes(), e);
                        // 消费失败,稍后重试(不会影响其他队列的消费)
                        return ConsumeOrderlyStatus.SUSPEND_CURRENT_QUEUE_A_MOMENT;
                    }
                }
                return ConsumeOrderlyStatus.SUCCESS;
            }
        });

        consumer.start();
        log.info("RocketMQ顺序消费者启动成功");
    }
}

2. Kafka 顺序消费核心代码

生产者端(指定 Key 路由到同一 Partition)

java 复制代码
@Service
public class KafkaOrderProducer {
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;

    public void sendOrderlyMessage(Long orderId, String messageBody) {
        // ✅ 技术亮点:指定Key,Kafka默认按Key哈希分配到同一个Partition
        kafkaTemplate.send("ORDER_TOPIC", orderId.toString(), messageBody);
    }
}

消费者端(线程数 = Partition 数,保证一个 Partition 只被一个线程消费)

java 复制代码
@Component
public class KafkaOrderConsumer {
    // ✅ 技术亮点:消费者线程数必须等于Topic的Partition数
    @KafkaListener(topics = "ORDER_TOPIC", groupId = "ORDER_CONSUMER_GROUP", concurrency = "8")
    public void consumeMessage(ConsumerRecord<String, String> record) {
        Long orderId = Long.parseLong(record.key());
        String messageBody = record.value();
        log.info("Kafka顺序消费消息,订单ID:{},Partition:{}", orderId, record.partition());

        // 业务逻辑处理
        orderService.processOrderMessage(orderId, messageBody);
    }
}

3. 业务层兜底核心代码(状态机校验 + 幂等性)

java 复制代码
@Service
public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    /**
     * 处理订单消息
     */
    @Transactional
    public void processOrderMessage(Long orderId, String messageBody) {
        OrderMessage message = JSON.parseObject(messageBody, OrderMessage.class);
        String operationType = message.getOperationType();

        // 1. ✅ 技术亮点:状态机校验,从根本上防止乱序
        Order order = orderMapper.selectById(orderId);
        if (order == null && !"CREATE".equals(operationType)) {
            // 收到非创建消息但订单不存在,延迟重试
            throw new RuntimeException("订单不存在,延迟重试");
        }

        if (order != null && order.getStatus().getCode() >= message.getTargetStatus().getCode()) {
            // 当前状态 >= 目标状态,说明消息已处理或乱序,直接丢弃
            log.warn("消息乱序或重复,订单ID:{},当前状态:{},目标状态:{}",
                    orderId, order.getStatus(), message.getTargetStatus());
            return;
        }

        // 2. ✅ 技术亮点:幂等性保证,使用唯一业务ID作为幂等键
        String idempotentKey = orderId + "_" + operationType;
        if (idempotentService.checkIdempotent(idempotentKey)) {
            log.warn("消息重复消费,幂等键:{}", idempotentKey);
            return;
        }

        // 3. 执行业务逻辑
        switch (operationType) {
            case "CREATE":
                createOrder(message);
                break;
            case "PAY":
                payOrder(orderId, message.getPayAmount());
                break;
            case "DELIVER":
                deliverOrder(orderId, message.getLogisticsNo());
                break;
            case "COMPLETE":
                completeOrder(orderId);
                break;
        }

        // 4. 记录幂等性
        idempotentService.recordIdempotent(idempotentKey);
    }
}

🚩 技术难点与解决方案(面试官必问)

技术难点 问题影响 解决方案
Broker 宕机导致队列重分配,顺序被破坏 当某个 Broker 宕机时,其负责的队列会被分配给其他 Broker,导致同一业务 Key 的消息被路由到不同队列,出现乱序 1. RocketMQ:使用MessageListenerOrderly,会自动在 Broker 端和消费端加分布式锁,保证队列重分配时只有一个消费者能消费2. Kafka:开启幂等生产者和事务,结合业务层状态机校验兜底3. 避免单 Broker 部署,采用主从架构
消息重试导致的乱序问题 消费失败的消息会被发送到重试队列,与原队列的消息分离,导致同一业务 Key 的消息在不同队列中,出现乱序 1. RocketMQ:顺序消息的重试消息会被发送回原队列的末尾,保证顺序2. Kafka:自定义重试逻辑,将重试消息发送回原 Partition3. 控制重试次数,超过次数进入死信队列
队列 / Partition 扩容导致的顺序破坏 当队列数量增加时,取模运算的结果会发生变化,导致同一业务 Key 的消息被路由到新的队列,出现乱序 1. 提前规划队列数量,尽量避免后期扩容2. 扩容时采用 "双倍扩容法",保证原有的哈希映射关系不变3. 扩容期间暂停写入,或使用双写方案过渡
单个失败消息阻塞整个队列 顺序消费时,一个消息消费失败会导致整个队列被阻塞,影响其他消息的消费 1. 区分可重试异常和不可重试异常,不可重试异常直接进入死信队列2. 设置最大重试次数(建议 16 次),超过次数进入死信队列3. 使用SUSPEND_CURRENT_QUEUE_A_MOMENT,只阻塞当前队列一段时间
高并发下的吞吐量瓶颈 顺序消费的并发度等于队列数量,队列数量有限会导致吞吐量不足 1. 合理设置队列数量(建议与 CPU 核心数相当)2. 拆分业务,将不同业务的消息发送到不同的 Topic3. 对于非严格顺序的业务,采用局部有序 + 最终一致的方案
跨 Broker 的消息顺序问题 当 Topic 的队列分布在多个 Broker 上时,同一业务 Key 的消息可能被路由到不同 Broker 的队列,出现乱序 1. 生产端使用自定义队列选择器,保证同一业务 Key 的消息永远路由到同一个 Broker 的同一个队列2. 避免使用自动创建 Topic 功能,手动指定队列分布
消费者重启导致的重复消费和乱序 消费者重启时,未提交的 offset 会被重新消费,导致重复消费和乱序 1. 开启手动提交 offset,消费成功后再提交2. 业务层实现幂等性,保证重复消费结果一致3. 消费者优雅停机,避免强制 kill

✨ 技术亮点总结

  1. RocketMQ 原生支持MessageQueueSelector + MessageListenerOrderly 组合,从 MQ 层面保证局部顺序
  2. 哈希路由算法:通过业务 Key 取模,保证相同业务标识的消息进入同一个队列
  3. 分布式锁机制:自动在 Broker 端和消费端加锁,防止队列重分配导致的乱序
  4. 业务层兜底:状态机校验 + 幂等性设计,从根本上解决极端情况下的乱序和重复消费问题
  5. 优雅的异常处理:区分可重试和不可重试异常,避免单个失败消息阻塞整个队列

面试现场实录

真实面试模拟

面试官 👨‍💼:

"行,我看你简历上写了擅长 RocketMQ 和 Kafka,那咱们聊一个经典场景题:如何保证 MQ 消息的顺序消费? 先别急着说方案,你先讲一个你遇到过的、非得顺序消费不可的业务场景。"

候选人 🙋:

"好的。我印象最深的就是订单状态流转 。比如:创建 → 支付 → 发货 → 完成,如果下游消费的顺序颠倒了,先收到支付消息,但因为订单还没创建,数据库里查不到这条订单,就会直接报错 😅。

但这里有个关键认知:我不需要所有订单全局有序,只需要保证同一个订单号的消息绝对有序。不同订单之间完全可以并行处理,这就决定了我们不会采用全局有序这种极端方案。"

面试官 👨‍💼:

"说得好,区分了全局和局部。那你给我画一下,这两种有序模型的区别,顺便分析一下代价。"

候选人 🙋:

"我画个草图(打开画板),大概是这样的结构:"

"然后这两种方式的取舍,我整理成了一个表:"

模型 特点 代价
全局有序 整个 Topic 只用一个队列/分区 生产消费全单线程,并发能力几乎为0 🐌
分区有序 同一分区内严格有序,分区之间并行 需要设计好分区键(Sharding Key),业务最常用 ✅

"所以我们项目选的是分区有序,用订单ID做哈希,保证同一订单进同一分区,既保证了顺序,又保留了水平扩展能力。"

面试官 👨‍💼:

"模型清楚了,那落地到代码层面,RocketMQ 你怎么写? 生产者、消费者两端分别怎么处理?"

候选人 🙋:

"RocketMQ 天然支持顺序消息,我们分两步走。

发送端 ,用 MessageQueueSelector 把同一个订单ID的消息固定发送到同一个队列:"

java 复制代码
producer.send(msg, new MessageQueueSelector() {
    @Override
    public MessageQueue select(List<MessageQueue> mqs, Message msg, Object arg) {
        long orderId = (long) arg;
        int index = (int) (orderId % mqs.size());
        return mqs.get(index);
    }
}, orderId);

"消费端 ,必须用 MessageListenerOrderly,它会为每个队列加本地锁,保证同一队列内单线程消费。即使消费者集群有多台机器,同一个队列也只会被一个线程拉取:"

java 复制代码
consumer.registerMessageListener(new MessageListenerOrderly() {
    @Override
    public ConsumeOrderlyStatus consumeMessage(List<MessageExt> msgs, ConsumeOrderlyContext ctx) {
        for (MessageExt msg : msgs) {
            // 处理业务,天然有序 ✅
        }
        return ConsumeOrderlyStatus.SUCCESS;
    }
});

"这里锁的粒度是队列级别,不是整个 Topic,所以不同订单号分散在不同队列,照样高并发。"

面试官 👨‍💼:

"那 Kafka 呢? 也有类似机制吗?有什么坑要注意?"

候选人 🙋:

"Kafka 原理一样:同一分区内有序

  • 生产者:指定 key(如订单ID),相同 key 进入同一分区。
  • 消费者:天然一个线程拉取一个分区,我们关闭自动提交,处理完再手动提交 offset。

但 Kafka 有个坑:消费者组发生 rebalance 时,分区可能被重新分配,导致短暂乱序或重复消费。我们的解法是加大 session.timeout.ms 并配置静态成员ID,减少不必要的重平衡,同时配合幂等来兜底。"

面试官 👨‍💼:

"嗯,接下来是灵魂拷问:如果消费的时候,某条消息处理失败了怎么办? 比如第3条消息业务异常,后面的第4、5条全部卡住,这问题你怎么解?"

候选人 🙋:

"这正是队头阻塞问题,面试官您问到点上了 🤓。我们有几套策略,根据业务严重性选择:"

策略 做法 利弊
阻塞+预警 当前消息反复重试,直到成功,后续等待 强一致,但影响延迟,必须配上监控告警 🚨
跳死信+人工介入 失败N次后转入死信队列,人工修复数据后回调 允许暂时跳过,但要保证最终一致,适合多数非致命业务
业务状态机校验 消息体里带前置状态,消费时做乐观锁校验,状态不对直接忽略或丢弃 更灵活,但业务代码有侵入性

"我们订单流转场景选择状态机校验 + 死信兜底

比如支付消息里带上期望前置状态 待支付,消费者执行 update order set status='已支付' where id=xxx and status='待支付'。如果状态已是 已取消,那就直接 ack 或移到死信。这样就算历史消息重放,也不会打乱最终结果。"

面试官 👨‍💼:

"还有一个关联点:消息重复,网络重试下,顺序消费能不能防重复?你怎么处理幂等性?"

候选人 🙋:

"顺序消费不能解决重复问题,它俩是好基友必须配合 👯。即使分区内有序,生产端因超时重发也可能产生重复消息。

我们做法很常规但有效:业务流水号 + 数据库唯一键Redis 分布式锁。消费前先判重,做过就跳过,保证最终数据准确。"

面试官 👨‍💼:

"好,最后用一段话给我总结一下你的完整方案。"

候选人 🙋:

"核心是:同一业务标识进入同一分区/队列,且该分区内单线程消费

我们线上用 RocketMQ 的 MessageListenerOrderly,配合订单号哈希选择队列,既保证顺序又保留横向扩展。消费失败引入状态机乐观锁校验 + 死信兜底 ,同时用业务流水号做幂等,防止重复消息造成错乱。全局有序只在极端特殊场景才考虑,一般业务不建议。"

面试官 👨‍💼:

"好,回到顺序消费这个题。刚才你说落地 RocketMQ 用了 MessageListenerOrderly,能不能把你们项目里真实的核心代码写出来?要体现技术亮点,别写伪代码。"

候选人 🙋:

"没问题。我直接写三段,分别是生产者、消费者基础版、消费者进阶版(带状态机校验+死信兜底)。"

1️⃣ 生产者:订单消息顺序发送

java 复制代码
@Service
public class OrderMessageProducer {

    @Resource
    private RocketMQTemplate rocketMQTemplate;

    /**
     * 发送订单状态变更消息,保证同一订单发送到同一队列
     * @param orderEvent 订单事件
     */
    public void sendOrderMessage(OrderEvent orderEvent) {
        String topic = "order-status-topic";
        String orderId = orderEvent.getOrderId();

        Message<String> message = MessageBuilder
                .withPayload(JSON.toJSONString(orderEvent))
                .setHeader(MessageConst.PROPERTY_KEYS, orderId) // 业务标识
                .build();

        // 核心:通过 MessageQueueSelector 按订单ID哈希选队列
        rocketMQTemplate.syncSendOrderly(topic, message, orderId);
    }
}

🧠 亮点syncSendOrderly 内部封装了 MessageQueueSelector,相同 orderId 经哈希取模固定映射到同一队列,天然保证分区有序。

2️⃣ 消费者基础版:顺序监听

java 复制代码
@Component
@RocketMQMessageListener(
    topic = "order-status-topic",
    consumerGroup = "order-consumer-group",
    consumeMode = ConsumeMode.ORDERLY  // 核心:启用顺序消费模式
)
public class OrderMessageConsumer implements RocketMQListener<String> {

    @Override
    public void onMessage(String message) {
        OrderEvent event = JSON.parseObject(message, OrderEvent.class);
        // 处理业务逻辑
        processOrderEvent(event);
    }

    private void processOrderEvent(OrderEvent event) {
        // 状态流转处理
    }
}

🧠 亮点ConsumeMode.ORDERLY 让 Spring Boot 自动配置为 MessageListenerOrderly,同一队列内单线程拉取,避免并发乱序。

3️⃣ 消费者进阶版:状态机校验 + 死信兜底(核心亮点)

java 复制代码
@Component
@RocketMQMessageListener(
    topic = "order-status-topic",
    consumerGroup = "order-consumer-group",
    consumeMode = ConsumeMode.ORDERLY
)
public class OrderMessageConsumerAdv implements RocketMQListener<MessageExt> {

    @Resource
    private OrderService orderService;
    @Resource
    private DeadLetterHandler deadLetterHandler;

    @Override
    public void onMessage(MessageExt msg) {
        String body = new String(msg.getBody(), StandardCharsets.UTF_8);
        OrderEvent event = JSON.parseObject(body, OrderEvent.class);

        try {
            // 1. 业务状态机校验(乐观锁)
            boolean success = orderService.updateWithStatusCheck(event);
            if (success) {
                // 正常处理成功,返回 CONSUME_SUCCESS
                return;
            }

            // 2. 状态不符合预期(比如已取消的订单收到支付消息)
            if (event.getRetryTimes() >= MAX_RETRY) {
                // 重试耗尽,投入死信
                deadLetterHandler.sendToDeadLetter(event);
                log.warn("消息状态不匹配且重试耗尽,转入死信: {}", event);
                return; // 消费成功,不阻塞队列
            }

            // 3. 状态不匹配但未达最大重试次数,抛异常触发重试
            throw new IllegalStateException("订单状态不符合预期: " + event);

        } catch (Exception e) {
            // 其他异常,让 RocketMQ 自动重试
            throw new RuntimeException("消费异常,触发重试", e);
        }
    }
}

对应的状态机 SQL(核心中的核心):

java 复制代码
// OrderService.updateWithStatusCheck
@Update("UPDATE t_order SET status = #{newStatus}, update_time = NOW() " +
        "WHERE order_id = #{orderId} AND status = #{expectedPreStatus}")
int updateWithStatusCheck(OrderEvent event);

🎯 技术亮点:

  • 乐观锁 + 状态机:用前置状态作为 WHERE 条件,无效消息自动过滤,避免业务数据错乱。
  • 重试耗尽转死信 :超过最大次数直接 ACK 进入死信,绝对不卡队列,解决了队头阻塞。
  • 异常分类处理:状态不匹配与系统异常分开对待,策略更细致。

面试官 👨‍💼:

"代码写得很扎实。那你在这个场景里,遇到过哪些技术难点?又是怎么解决的?整理一下。"

候选人 🙋:

"我梳理了四个核心难点,一一对应解决方案:"

技术难点 描述 解决方案 对应代码/配置
分区选择与负载均衡 如何保证同一订单进同一队列,同时不同订单均匀分布? 订单ID哈希取模选队列;消费者端采用集群模式,队列均匀分配 syncSendOrderly 内部哈希
消费失败队头阻塞 某条消息失败,后续消息全部等待,队列堵塞 状态机乐观锁校验 + 最大重试次数 + 死信兜底,非预期消息直接跳过 消费者进阶版代码
Rebalance 导致短暂乱序 Kafka 消费者组重平衡时分区漂移,短时间可能乱序 增大 session.timeout.ms;使用静态组ID;配合业务幂等容忍短暂重复 session.timeout.ms=30000group.instance.id 固定
重复消息破坏幂等性 顺序消费不能防止生产者重试造成的重复消息 消息体带业务流水号,消费前通过 DB 唯一键 / Redis 锁去重 唯一索引:uk_order_event_id

外补充一个生产级细节:

RocketMQ 顺序模式下,如果消息消费失败需要重试 ,注意队列的锁不会释放 ,其他消息必须等待。因此我们设定了 retryTimes 上限,到达上限必须强行跳出,避免死锁整个队列。这个点很多候选人不知道,说出来很加分 📈。

面试官 👨‍💼:

"非常好,从模型选择到代码落地,再到难点攻破,完整性很高。尤其是状态机校验 + 死信跳出这套组合,在保证顺序的前提下完美规避了队头阻塞,确实是工程里最优雅的解法 👍。这个题你过了。"

候选人 🙋:

"谢谢面试官,我也从这次交流里把一些细节重新理了一遍,收获很大。"


公众号"Rain的Java大神之路"

个人博客"www.javadashen.com"

相关推荐
Rain的Java大神实战圈1 天前
线上MQ消息积压了怎么处理
场景设计题
Rain的Java大神实战圈4 天前
介绍一下Redis的高可用方案
场景设计题
Rain的Java大神实战圈5 天前
如何保证Mysql和Redis双写一致性
场景设计题
Rain的Java大神实战圈6 天前
介绍一下Redis的缓存穿透击穿雪崩
场景设计题
Rain的Java大神实战圈7 天前
对千万级单表进行分页查询
场景设计题
Rain的Java大神实战圈8 天前
MySQL建表时需要注意什么
场景设计题
Rain的Java大神实战圈11 天前
千万级大表如何新增字段
场景设计题
Rain的Java大神实战圈13 天前
日增量500万的表结构如何设计
场景设计题
Rain的Java大神实战圈15 天前
数据库主从有延迟怎么处理
架构设计·场景设计题