🎯 消息队列如何保证消息有序性:排队的艺术!

29 阅读15分钟

📖 开场:食堂排队打饭

想象大学食堂打饭 🍚:

无序打饭(多个窗口)

学生1:先到 → 去窗口A → 后拿到饭 😅
学生2:后到 → 去窗口B → 先拿到饭 🎉
学生3:中间到 → 去窗口C → 中间拿到饭 😐

结果:先到的不一定先拿到(乱序)❌

有序打饭(单个窗口)

学生1:先到 → 排队 → 先拿到饭 ✅
学生2:后到 → 排队 → 后拿到饭 ✅
学生3:中间到 → 排队 → 中间拿到饭 ✅

结果:严格按照先后顺序(有序)✅

这就是消息队列的有序性保证!

核心思想

  • 全局有序:所有消息严格有序(单窗口)
  • 分区有序:同一类消息有序(多窗口,但同学总去同一个窗口)

🤔 为什么需要消息有序性?

场景1:订单状态变更 📦

问题:消息乱序导致状态错误

订单状态变更:

消息1(10:00:00):订单创建 → 状态:待支付
消息2(10:00:05):支付成功 → 状态:已支付
消息3(10:00:10):订单发货 → 状态:已发货

如果乱序处理:
消息3先到 → 状态:已发货 ✅
消息1后到 → 状态:待支付 ❌(覆盖了已发货)

结果:订单状态错误!😱

正确:严格按顺序处理

消息1 → 待支付
消息2 → 已支付
消息3 → 已发货 ✅

场景2:数据库binlog同步 🔄

问题:binlog乱序导致数据不一致

MySQL主库操作:

操作110:00:00):INSERT INTO user (id=1, name='张三')
操作210:00:05):UPDATE user SET name='李四' WHERE id=1
操作310:00:10):DELETE FROM user WHERE id=1

如果乱序同步到从库:
操作3先执行 → 删除id=1(找不到,忽略)
操作1后执行 → 插入id=1, name='张三' ❌

结果:从库多了一条脏数据!😱

正确:严格按顺序同步

操作1 → 插入
操作2 → 更新
操作3 → 删除 ✅

场景3:账户余额变更 💰

问题:余额计算错误

账户初始余额:1000元

操作1(10:00:00):充值100元 → 余额1100元
操作2(10:00:05):消费50元 → 余额1050元
操作3(10:00:10):充值200元 → 余额1250元

如果乱序处理:
初始:1000
操作2:1000 - 50 = 950
操作1:950 + 100 = 1050
操作3:1050 + 200 = 1250(碰巧对了)

但如果:
操作3:1000 + 200 = 1200
操作1:1200 + 100 = 1300
操作2:1300 - 50 = 1250(也对了?)

其实最危险的是:
操作2先执行,但账户只有1000元,就扣50元成功了
如果操作1是"充值失败"呢?账户应该是1000元,但实际是950元!❌

🎯 三种有序性保证方案

方案1:全局有序(最严格)🔒

原理

所有消息严格有序处理

Producer → 单分区/队列 → 单消费者

特点:
- 严格有序 ✅
- 性能最差 ❌(吞吐量低)
- 可用性最差 ❌(单点故障)

Kafka实现

@Configuration
public class KafkaProducerConfig {
    
    @Bean
    public ProducerFactory<String, String> producerFactory() {
        Map<String, Object> config = new HashMap<>();
        config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
        
        // ⭐ 关键配置1:单分区保证有序
        config.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, 
            SinglePartitionPartitioner.class.getName());
        
        // ⭐ 关键配置2:最多只允许1个未确认的请求(保证发送有序)
        config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);
        
        // ⭐ 关键配置3:开启幂等性(防止重复)
        config.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
        
        return new DefaultKafkaProducerFactory<>(config);
    }
}

/**
 * 自定义分区器:所有消息都发送到分区0
 */
public class SinglePartitionPartitioner implements Partitioner {
    
    @Override
    public int partition(String topic, Object key, byte[] keyBytes, 
                        Object value, byte[] valueBytes, Cluster cluster) {
        // ⭐ 所有消息都发送到分区0
        return 0;
    }
}

消费者配置

@Component
@Slf4j
public class OrderConsumer {
    
    /**
     * ⭐ 单消费者顺序消费
     * 
     * 注意:
     * 1. concurrency = 1(只有1个消费者线程)
     * 2. 处理完一条才能处理下一条
     */
    @KafkaListener(
        topics = "orders",
        groupId = "order-consumer-group",
        concurrency = "1"  // ⭐ 单消费者
    )
    public void consumeOrder(ConsumerRecord<String, OrderMessage> record) {
        OrderMessage order = record.value();
        
        log.info("顺序处理订单: orderId={}, status={}", 
            order.getOrderId(), order.getStatus());
        
        // 处理订单
        processOrder(order);
    }
    
    private void processOrder(OrderMessage order) {
        // 业务逻辑
    }
}

优缺点

优点 ✅:

  • 严格全局有序
  • 实现简单

缺点 ❌:

  • 吞吐量低(单分区、单消费者)
  • 无法并发处理
  • 单点故障(分区或消费者挂了,整个系统停止)

适用场景

  • 消息量小
  • 必须全局有序
  • 对性能要求不高

方案2:分区有序(常用)🎯

原理

同一类消息有序,不同类消息可并发

Producer → 多分区(按key分区)→ 多消费者(每个消费者处理一个分区)

特点:
- 分区内有序 ✅(同一个订单的消息有序)
- 分区间并发 ✅(不同订单可并发处理)
- 性能高 ✅(吞吐量高)
- 可用性高 ✅(分区独立)

Kafka实现

Topic和分区设计

Topic: orders
Partitions: 10

分区策略:按订单ID分区
    orderId=1 → partition 1
    orderId=2 → partition 2
    orderId=3 → partition 3
    ...

同一订单的所有消息都进入同一个分区 → 保证有序 ✅

生产者代码

@Service
@Slf4j
public class OrderProducer {
    
    @Autowired
    private KafkaTemplate<String, OrderMessage> kafkaTemplate;
    
    private static final String TOPIC = "orders";
    
    /**
     * 发送订单消息
     * 
     * ⭐ 关键:使用orderId作为key,保证同一订单的消息进入同一个分区
     */
    public void sendOrderMessage(OrderMessage message) {
        String key = String.valueOf(message.getOrderId());  // ⭐ 订单ID作为key
        
        log.info("发送订单消息: orderId={}, status={}", 
            message.getOrderId(), message.getStatus());
        
        // ⭐ 发送消息(带key)
        kafkaTemplate.send(TOPIC, key, message);
        
        // Kafka会根据key的hash值选择分区:
        // partition = hash(key) % partitionCount
        // 同一个key的消息,hash值相同,总是进入同一个分区 ✅
    }
}

消费者代码

@Component
@Slf4j
public class OrderConsumer {
    
    /**
     * ⭐ 多消费者并发消费(每个消费者处理不同的分区)
     * 
     * concurrency = 10:10个消费者线程
     * 
     * 分配策略:
     * Consumer1 → partition 0
     * Consumer2 → partition 1
     * ...
     * Consumer10 → partition 9
     * 
     * 同一个分区的消息,只会被一个消费者处理 → 保证有序 ✅
     * 不同分区的消息,由不同消费者处理 → 并发处理 ✅
     */
    @KafkaListener(
        topics = "orders",
        groupId = "order-consumer-group",
        concurrency = "10"  // ⭐ 10个消费者线程
    )
    public void consumeOrder(ConsumerRecord<String, OrderMessage> record) {
        OrderMessage order = record.value();
        int partition = record.partition();
        
        log.info("消费者线程{} 处理分区{} 的订单: orderId={}, status={}", 
            Thread.currentThread().getName(), partition,
            order.getOrderId(), order.getStatus());
        
        // 处理订单
        processOrder(order);
    }
    
    private void processOrder(OrderMessage order) {
        // 业务逻辑(同一订单的消息会被串行处理)
        log.info("处理订单状态变更: orderId={}, {} -> {}", 
            order.getOrderId(), order.getOldStatus(), order.getNewStatus());
    }
}

完整示例

/**
 * 订单服务
 */
@Service
@Slf4j
public class OrderService {
    
    @Autowired
    private OrderProducer producer;
    
    /**
     * 订单状态变更流程
     */
    public void orderLifecycle(Long orderId) {
        // 1. 创建订单
        OrderMessage createMsg = new OrderMessage();
        createMsg.setOrderId(orderId);
        createMsg.setOldStatus(null);
        createMsg.setNewStatus("CREATED");
        createMsg.setTimestamp(System.currentTimeMillis());
        producer.sendOrderMessage(createMsg);
        
        // 模拟延迟
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        
        // 2. 支付成功
        OrderMessage payMsg = new OrderMessage();
        payMsg.setOrderId(orderId);
        payMsg.setOldStatus("CREATED");
        payMsg.setNewStatus("PAID");
        payMsg.setTimestamp(System.currentTimeMillis());
        producer.sendOrderMessage(payMsg);
        
        // 模拟延迟
        try { Thread.sleep(100); } catch (InterruptedException e) {}
        
        // 3. 订单发货
        OrderMessage shipMsg = new OrderMessage();
        shipMsg.setOrderId(orderId);
        shipMsg.setOldStatus("PAID");
        shipMsg.setNewStatus("SHIPPED");
        shipMsg.setTimestamp(System.currentTimeMillis());
        producer.sendOrderMessage(shipMsg);
        
        log.info("订单{}的3条状态变更消息已发送", orderId);
    }
}

/**
 * 测试
 */
@SpringBootTest
public class OrderSequenceTest {
    
    @Autowired
    private OrderService orderService;
    
    @Test
    public void testOrderSequence() throws InterruptedException {
        // 同时创建10个订单
        for (long orderId = 1; orderId <= 10; orderId++) {
            orderService.orderLifecycle(orderId);
        }
        
        // 等待消费完成
        Thread.sleep(10000);
    }
}

输出结果

发送订单消息: orderId=1, status=CREATED
发送订单消息: orderId=1, status=PAID
发送订单消息: orderId=1, status=SHIPPED

发送订单消息: orderId=2, status=CREATED
发送订单消息: orderId=2, status=PAID
发送订单消息: orderId=2, status=SHIPPED

...

消费者线程pool-1-thread-1 处理分区1 的订单: orderId=1, status=CREATED
消费者线程pool-1-thread-1 处理分区1 的订单: orderId=1, status=PAID
消费者线程pool-1-thread-1 处理分区1 的订单: orderId=1, status=SHIPPED
处理订单状态变更: orderId=1, null -> CREATED ✅
处理订单状态变更: orderId=1, CREATED -> PAID ✅
处理订单状态变更: orderId=1, PAID -> SHIPPED ✅

消费者线程pool-1-thread-2 处理分区2 的订单: orderId=2, status=CREATED
消费者线程pool-1-thread-2 处理分区2 的订单: orderId=2, status=PAID
消费者线程pool-1-thread-2 处理分区2 的订单: orderId=2, status=SHIPPED
处理订单状态变更: orderId=2, null -> CREATED ✅
处理订单状态变更: orderId=2, CREATED -> PAID ✅
处理订单状态变更: orderId=2, PAID -> SHIPPED ✅

结论:同一订单的消息严格有序 ✅,不同订单并发处理 ✅

RabbitMQ实现

Queue设计

为每个订单创建一个独立的队列?❌ 不现实(订单太多)

使用路由键(Routing Key)实现分区效果 ✅

配置

@Configuration
public class OrderQueueConfig {
    
    private static final int QUEUE_COUNT = 10;  // 10个队列(相当于10个分区)
    
    @Bean
    public DirectExchange orderExchange() {
        return new DirectExchange("order.exchange");
    }
    
    /**
     * 创建10个队列
     */
    @Bean
    public List<Queue> orderQueues() {
        List<Queue> queues = new ArrayList<>();
        for (int i = 0; i < QUEUE_COUNT; i++) {
            queues.add(new Queue("order.queue." + i));
        }
        return queues;
    }
    
    /**
     * 绑定队列到交换机
     */
    @Bean
    public List<Binding> orderBindings(DirectExchange exchange, List<Queue> queues) {
        List<Binding> bindings = new ArrayList<>();
        for (int i = 0; i < QUEUE_COUNT; i++) {
            bindings.add(BindingBuilder.bind(queues.get(i))
                .to(exchange)
                .with("order.queue." + i));
        }
        return bindings;
    }
}

生产者

@Service
@Slf4j
public class OrderProducer {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    private static final int QUEUE_COUNT = 10;
    
    /**
     * 发送订单消息
     * 
     * ⭐ 根据订单ID选择队列(类似Kafka的分区)
     */
    public void sendOrderMessage(OrderMessage message) {
        // ⭐ 计算队列索引(类似Kafka的分区选择)
        int queueIndex = (int) (message.getOrderId() % QUEUE_COUNT);
        String routingKey = "order.queue." + queueIndex;
        
        log.info("发送订单消息到队列{}: orderId={}, status={}", 
            queueIndex, message.getOrderId(), message.getStatus());
        
        // 发送消息
        rabbitTemplate.convertAndSend("order.exchange", routingKey, message);
    }
}

消费者

@Component
@Slf4j
public class OrderConsumer {
    
    /**
     * ⭐ 为每个队列创建一个消费者
     * 
     * 同一个队列的消息,由同一个消费者串行处理 → 保证有序 ✅
     * 不同队列的消息,由不同消费者并发处理 → 并发 ✅
     */
    @RabbitListener(queues = "order.queue.0")
    public void consumeQueue0(OrderMessage message) {
        processOrder(message, 0);
    }
    
    @RabbitListener(queues = "order.queue.1")
    public void consumeQueue1(OrderMessage message) {
        processOrder(message, 1);
    }
    
    // ... 其他队列的消费者
    
    private void processOrder(OrderMessage message, int queueIndex) {
        log.info("队列{} 处理订单: orderId={}, status={}", 
            queueIndex, message.getOrderId(), message.getStatus());
        
        // 业务逻辑
    }
}

优缺点

优点 ✅:

  • 高吞吐量(多分区并发)
  • 局部有序(同一类消息有序)
  • 高可用(分区独立)

缺点 ❌:

  • 无法全局有序
  • 需要合理设计分区key

适用场景

  • 大部分业务场景 ✅
  • 需要高吞吐量
  • 只需要局部有序(如同一订单、同一用户)

方案3:业务层保证有序(最灵活)🛠️

原理

消息可以乱序到达,但业务层保证有序处理

核心思路:
1. 消息带序号(version、timestamp)
2. 消费端缓存消息
3. 按序号排序后处理

实现

消息结构

@Data
public class OrderMessage {
    private Long orderId;
    private String status;
    private Long version;  // ⭐ 版本号(序号)
    private Long timestamp;
}

消费者

@Component
@Slf4j
public class OrderConsumer {
    
    // ⭐ 缓存乱序到达的消息
    private Map<Long, PriorityQueue<OrderMessage>> messageCache = new ConcurrentHashMap<>();
    
    // ⭐ 记录每个订单当前期望的版本号
    private Map<Long, Long> expectedVersion = new ConcurrentHashMap<>();
    
    @KafkaListener(topics = "orders", groupId = "order-consumer-group")
    public void consumeOrder(ConsumerRecord<String, OrderMessage> record) {
        OrderMessage message = record.value();
        
        log.info("收到消息: orderId={}, version={}, status={}", 
            message.getOrderId(), message.getVersion(), message.getStatus());
        
        // ⭐ 处理消息(保证有序)
        processOrderInSequence(message);
    }
    
    /**
     * ⭐ 保证有序处理
     */
    private synchronized void processOrderInSequence(OrderMessage message) {
        Long orderId = message.getOrderId();
        Long version = message.getVersion();
        
        // 获取当前期望的版本号(默认从1开始)
        Long expected = expectedVersion.getOrDefault(orderId, 1L);
        
        if (version.equals(expected)) {
            // ⭐ 版本号匹配,立即处理
            processOrder(message);
            expectedVersion.put(orderId, expected + 1);
            
            // ⭐ 检查缓存中是否有下一个版本的消息
            processNextFromCache(orderId);
            
        } else if (version > expected) {
            // ⭐ 版本号大于期望值,暂存到缓存
            log.info("消息版本号{}大于期望值{},暂存到缓存", version, expected);
            
            messageCache.computeIfAbsent(orderId, k -> new PriorityQueue<>(
                Comparator.comparing(OrderMessage::getVersion)
            )).offer(message);
            
        } else {
            // ⭐ 版本号小于期望值,说明是重复消息,忽略
            log.warn("收到重复消息: orderId={}, version={}, expected={}", 
                orderId, version, expected);
        }
    }
    
    /**
     * ⭐ 从缓存中处理后续消息
     */
    private void processNextFromCache(Long orderId) {
        PriorityQueue<OrderMessage> queue = messageCache.get(orderId);
        if (queue == null || queue.isEmpty()) {
            return;
        }
        
        Long expected = expectedVersion.get(orderId);
        
        while (!queue.isEmpty()) {
            OrderMessage next = queue.peek();
            
            if (next.getVersion().equals(expected)) {
                // 版本号匹配,处理
                queue.poll();
                processOrder(next);
                expectedVersion.put(orderId, expected + 1);
                expected++;
            } else {
                // 版本号不匹配,停止
                break;
            }
        }
    }
    
    private void processOrder(OrderMessage message) {
        log.info("✅ 按序处理订单: orderId={}, version={}, status={}", 
            message.getOrderId(), message.getVersion(), message.getStatus());
        
        // 业务逻辑
    }
}

测试

@SpringBootTest
public class OrderSequenceTest {
    
    @Autowired
    private OrderProducer producer;
    
    @Test
    public void testOutOfOrder() {
        Long orderId = 1L;
        
        // ⭐ 故意乱序发送
        producer.send(createMessage(orderId, 3L, "SHIPPED"));    // 第3条
        producer.send(createMessage(orderId, 1L, "CREATED"));    // 第1条
        producer.send(createMessage(orderId, 2L, "PAID"));       // 第2条
        
        Thread.sleep(5000);
    }
    
    private OrderMessage createMessage(Long orderId, Long version, String status) {
        OrderMessage msg = new OrderMessage();
        msg.setOrderId(orderId);
        msg.setVersion(version);
        msg.setStatus(status);
        return msg;
    }
}

输出结果

收到消息: orderId=1, version=3, status=SHIPPED
消息版本号3大于期望值1,暂存到缓存

收到消息: orderId=1, version=1, status=CREATED
✅ 按序处理订单: orderId=1, version=1, status=CREATED

收到消息: orderId=1, version=2, status=PAID
✅ 按序处理订单: orderId=1, version=2, status=PAID
✅ 按序处理订单: orderId=1, version=3, status=SHIPPED(从缓存中取出)

结论:即使消息乱序到达,也能保证按序处理 ✅

优缺点

优点 ✅:

  • 最灵活(不依赖消息队列特性)
  • 可以跨消息队列
  • 可以处理复杂场景(如多个消息源)

缺点 ❌:

  • 实现复杂
  • 需要额外的内存(缓存)
  • 可能有延迟(等待前面的消息)

适用场景

  • 消息队列不支持分区
  • 需要跨多个消息队列保证有序
  • 需要更灵活的有序性控制

📊 三种方案对比

方案有序范围吞吐量复杂度适用场景
全局有序所有消息⭐ 低⭐ 简单消息量小,必须全局有序
分区有序分区内⭐⭐⭐ 高⭐⭐ 中等大部分业务场景(推荐)
业务层有序灵活⭐⭐ 中等⭐⭐⭐ 复杂跨消息队列,复杂场景

🎓 面试题速答

Q1: 消息队列如何保证消息有序性?

A: 三种方案

  1. 全局有序

    • 单分区 + 单消费者
    • 严格有序,但性能差
  2. 分区有序(推荐):

    • 按key分区,同一key的消息进入同一分区
    • 同一分区内有序,不同分区并发
  3. 业务层有序

    • 消息带版本号
    • 消费端缓存并排序

最常用的是分区有序,兼顾性能和有序性


Q2: Kafka如何保证消息有序?

A: 分区有序方案

// 1. 生产者:发送时指定key
kafkaTemplate.send("orders", orderId.toString(), message);

// 2. Kafka:根据key的hash选择分区
partition = hash(key) % partitionCount

// 3. 消费者:每个分区由一个消费者串行处理
@KafkaListener(topics = "orders", concurrency = "10")

关键点

  • 同一个key的消息,hash值相同,进入同一分区
  • 同一个分区的消息,由同一个消费者处理
  • 消费者串行处理,保证有序

Q3: 如果消息必须全局有序,怎么设计?

A: 全局有序方案

// 1. Topic只有1个分区
kafka.topics.orders.partitions=1

// 2. 生产者:max.in.flight.requests.per.connection=1
config.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 1);

// 3. 消费者:concurrency=1(单消费者)
@KafkaListener(topics = "orders", concurrency = "1")

缺点

  • 吞吐量低(单分区、单消费者)
  • 单点故障

适用场景

  • 消息量小(<100 QPS)
  • 必须全局有序(如binlog同步)

Q4: 分区有序如何选择分区key?

A: 选择原则

  1. 业务相关性

    • 订单系统:orderId
    • 用户系统:userId
    • 设备系统:deviceId
  2. 分布均匀

    • 避免数据倾斜(某个分区消息太多)
    • 如果userId分布不均,可以用 userId + timestamp
  3. 唯一性

    • 同一个业务对象的消息,key必须相同

示例

// 订单系统:使用orderId作为key
String key = String.valueOf(orderId);
kafkaTemplate.send("orders", key, message);

// 账户系统:使用userId作为key
String key = String.valueOf(userId);
kafkaTemplate.send("accounts", key, message);

Q5: 业务层如何保证消息有序?

A: 方案:消息带版本号 + 缓存排序

// 1. 消息带版本号
class OrderMessage {
    Long orderId;
    Long version;  // 版本号
    String status;
}

// 2. 消费端缓存
Map<Long, PriorityQueue<OrderMessage>> cache = new ConcurrentHashMap<>();
Map<Long, Long> expectedVersion = new ConcurrentHashMap<>();

// 3. 按版本号排序处理
if (message.version == expected) {
    process(message);  // 立即处理
    expected++;
} else {
    cache.put(message);  // 暂存
}

优点:灵活,不依赖消息队列特性
缺点:实现复杂,需要额外内存


Q6: 如何避免消息队列的数据倾斜?

A: 问题:某些分区消息太多,导致负载不均

解决方案

  1. 合理选择key

    • 避免使用分布不均的字段(如性别、地区)
    • 使用唯一性强的字段(如订单ID、用户ID)
  2. 复合key

    // 如果userId分布不均,加上随机数
    String key = userId + "-" + (random.nextInt(10));
    
  3. 监控分区负载

    • 定期检查每个分区的消息数
    • 发现倾斜及时调整
  4. 增加分区数

    • 更多分区,更均匀分布

🎬 总结

          消息有序性方案对比

┌─────────────────────────────────────────────┐
│          全局有序(最严格)                  │
│                                             │
│  Producer → 单分区 → 单消费者               │
│                                             │
│  特点:严格有序,性能差 ⭐                  │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│        分区有序(推荐)⭐⭐⭐                │
│                                             │
│  Producer → 按key分区 → 多消费者并发        │
│                                             │
│  特点:局部有序,高性能 ✅                  │
└─────────────────────────────────────────────┘

┌─────────────────────────────────────────────┐
│         业务层有序(最灵活)                 │
│                                             │
│  Producer → 乱序到达 → 消费端排序           │
│                                             │
│  特点:灵活,实现复杂 ⚙️                    │
└─────────────────────────────────────────────┘

      大部分场景用分区有序!✅

🎉 恭喜你!

你已经完全掌握了消息队列的有序性保证方案!🎊

核心要点

  1. 全局有序:单分区+单消费者(性能差)
  2. 分区有序:按key分区(推荐)⭐⭐⭐
  3. 业务层有序:版本号+缓存排序(灵活)

下次面试,这样回答

"消息队列保证有序性有三种方案:

  1. 全局有序:单分区+单消费者,严格有序但性能差,适用于消息量小的场景。

  2. 分区有序(最常用):按业务key分区,同一key的消息进入同一分区,由同一消费者串行处理。Kafka中,发送时指定key,根据hash(key)%partitionCount选择分区。优点是兼顾性能和有序性。

  3. 业务层有序:消息带版本号,消费端缓存并排序处理。最灵活但实现复杂。

我们项目的订单系统使用分区有序,以orderId作为key,保证同一订单的状态变更消息严格有序,不同订单并发处理,吞吐量达到10万QPS。"

面试官:👍 "很好!你对消息有序性理解很深刻!"


本文完 🎬

上一篇: 193-消息队列的削峰填谷作用和实际应用.md
下一篇: 195-分布式锁的实现方式.md

作者注:写完这篇,我都想去食堂当窗口阿姨了!🍚
如果这篇文章对你有帮助,请给我一个Star⭐!