高并发三板斧-异步

高并发系统里,异步是一个很像"办公室甩锅"的技术。

区别是,普通甩锅会让同事想拉黑你;设计得好的异步,会让系统少加班、用户少等待、老板少拍桌子。

很多人一提到异步,第一反应就是:

这还不简单?丢 MQ,完事。

这句话听起来很潇洒,但真到线上,MQ 可不是垃圾桶。你随手一丢,后面可能就是消息堆积、重复消费、状态不一致、补偿任务满天飞。

所以异步不是为了把问题"挪到后面",而是为了把一次请求里不同优先级的事情拆开:必须马上干的先干,不着急的排队干,失败能补的慢慢补。

如果用一句话概括异步:

异步的本质,是把主链路从"全家桶套餐"改造成"主菜先上,配菜后厨慢慢做"。

这篇文章继续按照金字塔思想,聊聊高并发场景下异步到底怎么做,哪些地方容易翻车,以及怎么把异步从"看起来很高级"落到"线上真能扛"。

一、为什么需要异步

先看一个很常见的下单流程。

用户点一下"提交订单",系统如果很实诚,可能会在一个接口里干完这些事:

  • 创建订单;
  • 扣减库存;
  • 创建支付单;
  • 发短信;
  • 发优惠券;
  • 加积分;
  • 更新用户画像;
  • 同步搜索;
  • 推送站内信;
  • 写操作日志;
  • 通知数据仓库。

这就像用户只是点了一份盖饭,后厨非要当场完成采购、切菜、炒菜、结算、开发票、打扫卫生、写经营分析报告。

用户等得住,线程池也等不住。

这些动作从业务上看都和下单有关,但从系统设计上看,完全不是一个级别:

  • 订单创建失败,用户确实不能继续;
  • 库存没锁住,可能会超卖;
  • 支付单没创建,后面没法付款;
  • 短信晚 3 秒,用户一般不会报警;
  • 积分晚 1 分钟,大多数人也不会半夜坐起来追问;
  • 用户画像晚点同步,对当前下单没有直接影响。

所以高并发系统里,最怕的不是逻辑多,而是所有逻辑都挤在同步链路里。

同步链路太长,会带来几个很现实的问题:

  1. 接口 RT 被拉长,用户感觉系统慢;
  2. 线程长期被占用,吞吐量上不去;
  3. 一个非核心下游抖动,拖慢整个主流程;
  4. 高峰期请求堆积,服务开始排队;
  5. 系统扩容只能一锅端,成本很高。

异步要解决的,就是把主链路从"大巴车模式"改成"地铁换乘模式":核心乘客先到站,其他乘客按线路继续走。

二、用金字塔思想看异步

异步不是只引入一个 MQ 就算完成。真正成熟的异步设计,应该是分层的。

越靠近底层,越偏基础能力和削峰;越靠近上层,越靠近业务流程和一致性。

可以把它看成一个金字塔:

text 复制代码
          业务异步:流程拆分、最终一致性、补偿闭环
       应用异步:线程池、任务队列、事件监听、回调
    消息异步:MQ、延迟消息、顺序消息、事务消息
基础异步:日志、埋点、IO、批处理、缓存刷新

1. 基础层:先把"顺手但不值钱"的活拆出去

有些操作很常见,但没有必要拖着用户一起等。

比如:

  • 访问日志;
  • 埋点上报;
  • 操作流水;
  • 图片压缩;
  • 文件解析;
  • 缓存刷新;
  • 报表生成;
  • 非核心通知。

这些活不是不重要,而是不值得堵在用户面前。

用户提交一个表单,核心是数据保存成功;至于埋点、统计、日志、画像更新,可以慢一点。系统不要像一个过分热情的服务员,非要把后厨清洁记录也念给用户听完再放人走。

基础层异步的目标很简单:

不让低价值、耗时长、可延后的操作占住主请求线程。

这一步做好了,接口 RT 往往能立刻降一截。

2. 消息层:MQ 不是垃圾桶,是中转站

MQ 是异步里最常见的工具,但它经常被误用。

正确姿势是:

text 复制代码
订单服务 -> 订单创建事件 -> 库存、积分、优惠券、通知、数据同步

订单服务只负责把"订单已经创建"这件事可靠地发出去,后面的消费者各干各的。

MQ 的核心价值有两个:

  • 解耦:生产者不需要认识所有消费者;
  • 削峰:流量先进入队列,消费者按自己的能力处理。

但 MQ 也不是魔法棒。

你把 10 万个请求扔进队列,不代表这 10 万个请求消失了,只是它们换了个地方排队。消费者处理不过来,队列照样堆积;幂等没做好,重复消费照样把数据写乱;补偿没设计,消息丢了你可能半个月后才发现。

所以 MQ 的正确定位是"中转站",不是"垃圾桶"。中转站要有路由、秩序、监控和兜底。

3. 应用层:线程池别搞成公共澡堂

不是所有异步都需要 MQ。

有些场景在应用内部用线程池、事件监听、任务队列就够了:

  • 查询接口并行调用多个下游;
  • 批量任务拆分执行;
  • 非核心逻辑后台处理;
  • 本地事件通知多个监听器;
  • 定时任务扫描待处理数据。

这里最容易踩的坑是:所有异步任务共用一个线程池。

结果就是,报表导出这种慢任务占满线程池,短信通知进不来;某个下游超时,所有异步任务一起陪葬;队列越堆越长,内存也开始冒汗。

线程池要隔离,至少要按任务类型拆:

  • 通知线程池;
  • 报表线程池;
  • 下游调用线程池;
  • 数据同步线程池;
  • 高优先级任务线程池。

每个线程池都要有自己的边界:

  • 核心线程数;
  • 最大线程数;
  • 队列长度;
  • 超时时间;
  • 拒绝策略;
  • 线程命名;
  • 监控指标。

线程池不是越大越好。线程太多,CPU 会忙着上下文切换;队列太长,问题会被藏起来,等你发现时已经堆成一座小山。

4. 业务层:异步最后拼的是最终一致性

异步做到业务层,真正难的不是"怎么执行",而是"怎么算成功"。

还是以下单为例。用户看到"下单成功"的那一刻,系统到底必须保证什么?

  • 订单必须创建成功;
  • 库存必须锁定或扣减成功;
  • 支付单必须可用;
  • 短信可以晚一点;
  • 积分可以晚一点;
  • 画像可以晚一点;
  • 搜索和数仓可以晚一点。

这就是业务异步最核心的判断:

哪些事情要强一致,哪些事情可以最终一致。

强一致的部分放主链路里,不能装作看不见;最终一致的部分放异步链路里,用消息、任务表、重试、补偿慢慢收敛。

异步不是逃避一致性,而是承认一致性有不同等级。

三、哪些场景适合异步

1. 削峰填谷:别让洪水直接冲进客厅

大促、秒杀、抢券、报名,这些场景流量来得很不讲武德。

平时 1000 QPS,活动一开可能直接 10 万 QPS。你要是让所有请求直奔订单和库存系统,那基本就是让数据库现场表演极限运动。

常见设计是:

text 复制代码
用户请求 -> 资格校验 -> 排队/令牌 -> MQ -> 后台消费 -> 查询结果

前台做轻量校验和排队,后台按系统承载能力慢慢创建订单。

注意,这里一定要给用户明确反馈:

  • 请求已受理;
  • 正在排队;
  • 预计稍后返回结果;
  • 可以稍后刷新查看。

不要让用户面对一个一直转圈的页面。用户不知道你在削峰,只会觉得你在"装死"。

2. 非核心链路后置:主菜先上,餐巾纸可以晚点来

很多动作是必要的,但不必同步完成。

比如:

  • 下单后的短信通知;
  • 注册后的欢迎消息;
  • 支付后的积分发放;
  • 内容发布后的分发;
  • 商品更新后的搜索同步;
  • 用户行为后的推荐画像更新。

这些任务适合异步化。

判断标准很简单:

用户当前操作是否依赖这个结果?

依赖,就同步;不依赖,就异步。

用户付完款,最关心的是订单状态;积分晚几秒到账可以接受。用户发布内容,最关心的是提交成功;推荐系统晚点拿到数据也可以接受。

3. 慢任务后台化:别让用户陪你跑批

报表导出、文件解析、批量导入、视频处理、数据清洗,这些任务天生不适合同步接口。

好的交互方式应该是:

text 复制代码
提交任务 -> 返回任务 ID -> 后台执行 -> 查询进度 -> 下载结果

接口只负责创建任务,真正耗时的处理放到后台。

这样有几个好处:

  • 用户不用一直等;
  • 系统可以控制后台并发;
  • 任务失败可以重试;
  • 任务进度可以展示;
  • 历史结果可以追踪。

不要把一个 3 分钟的导出任务塞进 HTTP 请求里。Nginx、网关、浏览器、用户耐心,总有一个会先扛不住。

4. 下游隔离:别让一个慢服务拖全家下水

高并发系统里,下游服务不稳定是常态。

如果主链路强依赖一个慢下游,上游线程会被长期占住。这个时候异步可以把部分下游调用拆出去,降低抖动传播。

比如:

  • 支付成功后异步通知 CRM;
  • 订单完成后异步同步 ERP;
  • 用户行为异步写推荐系统;
  • 商品变更异步刷新搜索索引。

但也要注意:不是所有下游都能异步。

如果下游结果决定当前请求是否成功,就不能简单丢到异步里。比如支付确认、库存扣减、风控校验,这些关键动作该同步还是得同步,只是要配合超时、熔断、隔离来控制风险。

四、异步最容易翻车的地方

1. 异步边界切错

异步边界切得不好,就会变成两种极端。

一种是切得太少,主链路还是很胖,异步只是摆设。

另一种是切得太碎,一个简单流程被拆成十几个消息,排查问题像翻案卷:消息 A 发了吗?B 消费了吗?C 重试了吗?D 补偿了吗?最后人也异步了,精神状态延迟一致。

我的判断方式是三个问题:

  1. 这个动作是否影响主流程成功;
  2. 用户是否必须立刻看到结果;
  3. 失败后是否可以补偿。

如果不影响主流程、用户不急、失败能补,那就适合异步。

如果影响主流程、用户马上依赖、失败不可补,那就别硬异步。为了异步而异步,最后通常会得到一套很复杂但不可靠的系统。

2. 消息可靠性没兜住

异步最怕的不是慢,而是悄悄失败。

同步接口失败,至少用户能看到报错;异步消息丢了,可能系统表面风平浪静,业务结果却永远停在半路。

常见做法有:

  • 本地事务表:业务数据和消息记录同事务提交;
  • Outbox 模式:先写消息表,再由后台投递 MQ;
  • 事务消息:利用 MQ 自身能力保证半消息和确认;
  • 消费确认:处理成功后再 ack;
  • 失败重试:短期失败自动重试;
  • 死信队列:长期失败转入人工或补偿流程;
  • 补偿扫描:定时找异常状态重新推进。

核心原则就一句:

关键异步任务不能只赌一次网络调用。

网络这东西,平时像朋友,关键时刻可能像面试官,突然沉默。

3. 幂等没做好

异步系统里,重复消费不是异常,是日常。

消费者重启、ack 失败、网络抖动、MQ 重投、业务重试,都可能让同一条消息被处理多次。

所以消费者必须幂等。

常见方式包括:

  • 使用业务唯一键,比如订单号、支付单号、消息 ID;
  • 数据库唯一索引防重复写;
  • 状态机控制,只允许合法状态流转;
  • Redis setnx 做短期去重;
  • 幂等表记录处理结果;
  • 下游接口提供幂等请求号。

比如发积分,不能简单写:

sql 复制代码
update user_point set point = point + 100 where user_id = ?

否则重复消费两次,用户积分喜提翻倍,财务同事可能就不太喜悦。

更稳的方式是用订单号做唯一约束,保证同一笔订单只能发一次积分。

4. 顺序性没想清楚

很多异步问题不是消息没到,而是消息乱序。

比如订单状态应该是:

text 复制代码
已创建 -> 已支付 -> 已发货 -> 已完成

如果"已完成"消息先到了,"已支付"消息后到了,消费者不校验状态,订单状态可能被写回去。

常见处理方式:

  • 同一个业务 key 发到同一个分区;
  • 消费端按状态机校验;
  • 加版本号,旧版本消息直接丢弃;
  • 数据库乐观锁;
  • 乱序消息暂存后重试;
  • 尽量减少对全局顺序的依赖。

不要一上来就追求全局有序。全局有序听起来很美,落地时经常很贵。大多数业务只需要保证单个订单、单个用户、单个商品内有序。

5. 队列堆积没人看

异步削峰不是无限削峰。

生产速度长期大于消费速度,队列一定会堆积。堆积后不是"慢一点"这么简单,可能会出现:

  • 用户结果迟迟不返回;
  • 消息过期;
  • 重试流量放大;
  • 消费者扩容滞后;
  • 下游被集中打爆;
  • 业务状态长时间不一致。

所以队列必须有监控:

  • 队列长度;
  • 消费延迟;
  • 生产速率;
  • 消费速率;
  • 失败次数;
  • 重试次数;
  • 死信数量;
  • 单条消息处理耗时;
  • 消费者线程池水位。

队列堆积时,先别急着加消费者。先判断是生产突增、消费变慢、下游故障,还是消息本身处理异常。原因不同,解法完全不同。

五、落地实践建议

1. 先给主链路减肥

异步改造不要一上来就全系统铺开。

先画出核心链路:

text 复制代码
用户请求 -> 参数校验 -> 业务校验 -> 核心写入 -> 返回结果 -> 后置处理

然后逐个动作问:

  • 是否必须同步;
  • 是否影响用户当前结果;
  • 是否可以失败重试;
  • 是否需要强一致;
  • 是否有补偿入口;
  • 是否有明确监控。

优先拆掉那些耗时长、失败可补偿、和主结果弱相关的动作。

这一步做好了,比盲目上 MQ 更有价值。

2. 任务一定要有状态

重要的异步任务,不要只活在内存里。

最好有任务表或状态字段:

text 复制代码
INIT -> PROCESSING -> SUCCESS / FAILED -> COMPENSATED

这样系统至少能回答:

  • 任务有没有创建;
  • 消息有没有发出去;
  • 消费有没有开始;
  • 现在卡在哪一步;
  • 失败原因是什么;
  • 是否还能重试;
  • 是否需要人工处理。

没有状态的异步任务,排查起来就像在黑屋子里找键盘,还不能开灯。

3. 重试要有节制

失败重试是必要的,但无限重试就是系统版"死缠烂打"。

比较合理的策略是:

  • 短暂抖动:快速重试几次;
  • 下游故障:指数退避;
  • 参数错误:不要重试,直接失败;
  • 长期失败:进入死信队列;
  • 关键任务:补偿扫描兜底;
  • 重试次数、间隔、最大耗时都要可配置。

尤其要避免失败任务疯狂重试,把已经不舒服的下游继续按在地上摩擦。

4. 线程池和 MQ 都要限流

异步不是没有成本。

线程池满了会拒绝,队列满了会堆积,MQ 慢了会拖延,下游扛不住会失败。

所以异步链路也要限流:

  • 生产端控制发送速度;
  • 消费端控制并发数;
  • 线程池控制队列长度;
  • 下游调用设置超时;
  • 消费失败设置退避;
  • 核心任务和非核心任务隔离。

不要让异步链路变成另一个没有边界的同步链路。

5. 监控要看业务是否收敛

异步监控不能只看 MQ 是否活着。

更关键的是业务结果有没有收敛:

  • 下单后库存是否最终扣减;
  • 支付后积分是否最终发放;
  • 发布后内容是否最终审核;
  • 商品变更后搜索是否最终同步;
  • 失败任务是否最终补偿成功。

技术指标说明系统在跑,业务指标说明系统跑对了。

只看"消息消费成功",不看"业务结果正确",很容易出现一种尴尬:机器很忙,业务很慌。

六、一个简单的异步伪代码

下面是一个本地事务表加后台投递的简化例子:

java 复制代码
@Transactional
public void createOrder(CreateOrderCommand command) {
    Order order = orderRepository.save(command);

    OutboxMessage message = OutboxMessage.builder()
            .bizId(order.getOrderNo())
            .topic("order.created")
            .payload(toJson(order))
            .status("INIT")
            .build();

    outboxRepository.save(message);
}

public void publishOutboxMessage() {
    List<OutboxMessage> messages = outboxRepository.findInitMessages();

    for (OutboxMessage message : messages) {
        try {
            mqProducer.send(message.getTopic(), message.getPayload());
            message.markSuccess();
        } catch (Exception ex) {
            message.markFailed(ex.getMessage());
        }
    }
}

public void consumeOrderCreated(OrderCreatedEvent event) {
    if (pointRepository.existsByOrderNo(event.getOrderNo())) {
        return;
    }

    pointRepository.addPoint(event.getUserId(), event.getOrderNo(), 100);
}

这个例子想表达的不是代码有多高级,而是几个关键点:

  • 订单和消息记录一起提交,避免业务成功但消息没了;
  • 后台任务负责投递消息,失败后可以继续扫描;
  • 消费端按订单号做幂等,避免重复发积分;
  • 每一步都有状态,出了问题能查、能补、能追。

真实项目里可以用 RocketMQ、Kafka、RabbitMQ、Pulsar,也可以用数据库任务表、延迟队列、调度平台。工具选型很重要,但比工具更重要的是:可靠性、幂等性、顺序性、可观测性和补偿能力有没有设计进去。

七、总结

异步不是把代码扔进 MQ 就完事,也不是为了显得架构很复杂。

按照金字塔思想来看:

  • 基础层:拆掉低价值耗时操作;
  • 消息层:做解耦和削峰;
  • 应用层:做好线程池隔离和任务边界;
  • 业务层:围绕最终一致性做补偿闭环。

一个好的异步方案,应该让主链路更短,让后置任务更稳,让系统在高并发下既能快一点,也能靠谱一点。

最后再总结一句:

异步的目标不是把问题往后推,而是让正确的事情在正确的位置、用正确的节奏完成。

相关推荐
ASKED_20191 小时前
Anthropic Agent最佳实践系列二: Agent系统测试
人工智能·架构
轻刀快马1 小时前
重塑 Java 世界的两根支柱:穿透 Spring IoC 与 AOP 的架构哲学
java·spring·架构
段一凡-华北理工大学1 小时前
工业领域的Hadoop架构学习~系列文章06:Hive数据仓库
数据仓库·hadoop·架构·高炉炼铁·工业智能体·高炉智能化·hive数据仓库
AI科技星1 小时前
基于光速螺旋拓扑模型的宇宙时空特征周期研究
人工智能·线性代数·架构·概率论·学习方法
千里马学框架10 小时前
aosp新增窗口层级 Type 完整实现方案(有源码)-wms需求和面试题
android·智能手机·架构·wms·aaos·车机
搞科研的小刘选手10 小时前
【中山大学主办】第六届计算机科学与区块链国际学术会议(CCSB 2026)
分布式·神经网络·计算机视觉·区块链·计算机科学·共识算法·自然语言
AI_Auto11 小时前
【智能制造】- APS系列|14 生产计划三层架构:长期、中期、短期
架构·制造
小饼干在学嘎瓦12 小时前
本地缓存和分布式缓存如何选择?
分布式·缓存
morning_judger12 小时前
Agent系列(一) - Agent系统分层架构
人工智能·架构