Skip to content

10. 异步发送:大促时不要让消息发送阻塞主链路

业务增长

大促来了。下单接口每秒请求明显上涨。同步发送消息虽然比同步调用多个服务轻很多,但它仍然会等待发送结果。

产品只关心用户尽快看到“下单成功”。技术上要继续缩短主链路耗时。

这时可以考虑异步发送。

同步发送和异步发送

方式特点适合场景
同步发送调用方等待发送结果关键事件、低中等吞吐
异步发送通过 future 或回调处理结果高吞吐、对响应时间敏感

不是所有消息都要异步。支付成功这类关键事件,优先保证处理清晰。异步发送更适合大流量下的旁路事件。

异步发送普通消息

java
public CompletableFuture<SendReceipt> publishOrderCreatedAsync(OrderCreatedEvent event) {
    CompletableFuture<SendReceipt> future = new CompletableFuture<>();

    future.whenCompleteAsync((receipt, error) -> {
        if (error != null) {
            log.error("订单创建消息发送失败, orderId={}", event.orderId(), error);
            messageOutboxRepository.markFailed(event.eventId(), error.getMessage());
            return;
        }
        log.info("订单创建消息发送成功, orderId={}, messageId={}",
                event.orderId(), receipt.getMessageId());
        messageOutboxRepository.markSent(event.eventId(), receipt.getMessageId().toString());
    });

    return rocketMQClientTemplate.asyncSendNormalMessage(
            "order-event-topic",
            event,
            future
    );
}

官方 2.3.4 RocketMQClientTemplate 提供了 asyncSendNormalMessageasyncSendFifoMessageasyncSendDelayMessage

异步发送不等于不用管结果

异步只是“不在当前线程等待”。发送结果仍然要处理:

  1. 成功时记录 messageId。
  2. 失败时记录失败原因。
  3. 必要时进入本地 outbox 或补偿任务。
  4. 不要把异常静默吞掉。

小技巧

异步回调建议使用独立线程池。大促时默认线程池可能和业务线程互相影响。

java
ExecutorService callbackExecutor = Executors.newFixedThreadPool(8);

future.whenCompleteAsync((receipt, error) -> {
    // 处理发送结果
}, callbackExecutor);

线程池大小要结合机器资源、发送量和回调逻辑复杂度,不要盲目开大。

常见坑

  1. 异步发送后立刻认为成功。
    future 还没完成,消息可能失败。

  2. 回调里做重业务。
    回调尽量只做状态记录,复杂补偿交给后台任务。

  3. 关键一致性场景滥用异步。
    支付消息优先考虑事务消息,而不是简单异步发送。

练习题

  1. 下单成功通知适合异步发送吗?
  2. 支付成功履约消息适合简单异步发送吗?
  3. 异步发送失败后,最少要记录哪些信息?

参考答案

  1. 适合,但要记录发送结果和补偿。
  2. 不适合简单异步。更适合事务消息或 outbox 方案。
  3. eventId、业务主键、topic、payload 摘要、失败原因、重试次数、发生时间。

来源

Built with VitePress. Deployed on Cloudflare Pages.