菜鸟AI - 让提示词生成更简单! 全站导航 全站导航
AI工具安装 新手教程 进阶教程 辅助资源 AI提示词 热点资讯 技术资讯 产业资讯 内容生成 模型技术 AI信息库

已有账号?

首页 > AI教程 > 分布式与高并发进阶指南:实战排行榜精选
进阶教程 分布式 分布式与高并发进阶

分布式与高并发进阶指南:实战排行榜精选

2026-06-09
阅读 0
热度 0
作者 菜鸟AI编辑部
摘要

摘要

消息队列通过异步通信实现系统解耦、流量削峰与最终一致性,主流场景包括异步处理、应

第五部分:消息队列 —— 异步削峰的解耦神器

说到分布式系统,消息队列绝对是绕不开的基础设施。它的核心价值在于,通过异步通信这种“延迟满足”的方式,完美地解决了应用之间的解耦、流量的削峰填谷,以及最终一致性的问题。

5.1 典型使用场景

先来聊聊它最常用的几个场景,你一定会觉得“原来如此”:

  • 异步处理: 比如用户注册成功后,系统需要发邮件和信息通知。如果同步执行,用户注册的响应时间会变长,体验很差。正确做法是,把这些耗时的操作丢进消息队列,主流程直接返回“注册成功”,用户感受就快多了。
  • 应用解耦: 想象一下,订单系统创建订单后,需要通知库存系统扣减库存、积分系统增加积分。如果直接通过RPC调用,任何一个下游系统出问题,都会导致订单失败。而通过消息队列,订单系统只需要发布一个“订单创建”事件,下游系统自行订阅处理,互不干扰。
  • 流量削峰: 这是消息队列的“王牌”场景。比如秒杀系统在瞬间涌入海量请求,数据库根本扛不住。解决方案就是先把请求一股脑丢进队列,后端服务再根据自己的处理能力慢慢地从队列里取任务执行。这就像水库,在暴雨时蓄水,在干旱时放水,从而保护了下游的“农田”(数据库)。
  • 日志收集: 各个服务产生的日志,以往都是直接写到本地文件,管理和查询很头疼。现在,每个服务都把日志异步发送到MQ,统一由Logstash等工具消费并写入Elasticsearch,实现集中化管理和分析。

5.2 主流MQ对比

市面上的消息队列选择不少,每个都有自己的脾气秉性。

image.png

5.3 可靠消息传递的关键概念

用好消息队列,必须理解几个核心概念,否则容易丢消息:

  • 生产端确认: 生产者发送消息后,必须等Broker(消息服务器)返回一个确认信号(RabbitMQ的Publisher Confirm,Kafka的acks),才算是真的发送成功了。如果没有收到确认,生产者就要考虑重发。
  • 消费端确认: 消费者从队列里拿到消息并处理完毕后,需要手动告诉Broker:“我搞定了,你可以删除了”(也就是手动ack)。如果因为某种原因(如消费者宕机)没有发送ack,Broker会认为处理失败,这条消息会重新排队,被其他消费者处理——这也就是“至少一次投递”的保证。
  • 消息持久化: 消息默认存储在内存中,Broker重启就会丢失。必须配置将其写入磁盘,才能在Broker挂掉后恢复。
  • 死信队列(DLQ): 对于那些因为各种原因(如业务报错)无法被正常消费的消息,队列会将其丢入一个“死信队列”。这相当于一个回收站,方便开发人员发现、排查和重试问题消息。

5.4 使用RocketMQ实现削峰(Ja va示例)

理论讲完,我们来看个实战例子——用RocketMQ实现秒杀请求的削峰。

生产者:秒杀请求入队

@PostMapping("/seckill")
public String seckill(Long userId, Long productId) {
    // 简单校验库存(此处可先用Redis预减库存)
    String orderId = UUID.randomUUID().toString();
    // 发送延迟消息(例如10秒后处理支付超时)
    Message msg = new Message("SEC_KILL_TOPIC", "order", (userId + ":" + productId).getBytes());
    // 同步发送
    SendResult result = producer.send(msg);
    return "排队中";
}

消费者:处理订单创建

@RocketMQMessageListener(topic = "SEC_KILL_TOPIC", consumerGroup = "order_consumer")
public class OrderConsumer implements RocketMQListener {
    @Override
    public void onMessage(String message) {
        // 1. 校验库存
        // 2. 创建订单
        // 3. 扣减数据库库存
        // 注意:必须实现幂等性,防止重复消费
    }
}

5.5 消息幂等性

既然MQ提供了“至少一次投递”的保证,就意味着消息可能会被重复投递。所以,消费者必须自己处理重复消息。实现幂等(同一个消息被消费多次,结果一样)的常见方式:

  • 数据库唯一键约束: 将订单号作为数据库表的唯一索引,重复插入会失败,天然保证了幂等。
  • Redis记录已处理的消息ID: 消费前先检查这个ID是否已在Redis中,用`SETNX`命令,如果成功,说明是第一次处理;如果失败,说明已经处理过了,直接跳过。
  • 业务状态机: 处理前先检查业务状态。比如订单已经处于“已处理”状态,那么重复的消息就不再做任何操作。

第六部分:分布式事务 —— 跨数据的一致性难题

当业务跨越多个独立的数据源(比如数据库、Redis、消息队列)时,单机数据库提供的事务就无能为力了。分布式事务,就是用来解决这个全局一致性难题的,但它需要在性能和一致性之间做出艰难的取舍。

6.1 两阶段提交(2PC)与三阶段提交(3PC)

这两种是最经典的分布式事务协议,但各有明显缺陷。

  • 2PC (Two-Phase Commit): 有个协调者,先问所有参与者“准备好了没”(Prepare阶段)。如果所有人都说“准备好了”,协调者就下令“提交”(Commit);如果有人说不,或者超时,就下令“回滚”。看起来很完美,但问题在于:Prepare阶段后,协调者如果崩溃,所有参与者都处于一种“悬空”状态,必须锁定资源等待,造成同步阻塞。而且协调者本身也是单点故障,一旦出问题,整个系统就瘫痪了。
  • 3PC (Three-Phase Commit): 为了解决2PC的阻塞问题,3PC引入了超时机制和一个“预提交”阶段。虽然减少了阻塞范围,但它的设计复杂得多,实际生产环境中应用很少。

典型的实现是XA协议(MySQL、Oracle支持),但它性能较差,不适合高并发场景。

6.2 TCC(Try-Confirm-Cancel)

TCC是一种补偿型事务方案,更轻量级,也更灵活。它将一个完整的业务操作拆分为三个阶段:

  • Try: 预留资源。比如冻结用户的100元库存。
  • Confirm: 确认执行。实际扣减冻结的库存,完成业务。
  • Cancel: 取消。如果Confirm失败,就释放Try阶段预留的资源。

优点: 性能高,由业务层控制粒度,非常灵活。
缺点: 对业务代码侵入性强,需要为每个操作编写Try、Confirm、Cancel三个接口,而且还要处理幂等和“悬挂”等复杂问题。

示例:转账服务 (A转给B 100元)

// Try:冻结A的100元,增加B的预收资金
void tryTransfer(String from, String to, int amount);
// Confirm:扣减A的冻结资金,将B的预收变为可用余额
void confirmTransfer(String from, String to, int amount);
// Cancel:解冻A的资金,回退B的预收
void cancelTransfer(String from, String to, int amount);

实现TCC时,Confirm和Cancel方法都必须保证幂等性。

6.3 本地消息表 + MQ (最终一致性)

这是目前最常用的最终一致性方案,因为它简单、不依赖复杂的分布式事务框架。以创建一个订单并扣减库存为例:

  1. 订单服务在自己的本地事务中做两件事:插入一条订单记录,同时插入一条消息记录(状态为“待发送”)。这两步操作在同一个数据库事务中,要么同时成功,要么同时失败。
  2. 后台有一个定时任务,不停地扫描消息表,把“待发送”的消息发送到MQ。
  3. 库存服务消费MQ消息,执行扣减库存。
  4. 库存处理成功后,通过一个回调接口通知订单服务,将对应的消息状态更新为“已处理”。
  5. 如果库存服务处理失败,或者MQ丢消息了,订单服务的定时任务会不断重试,直到成功或转人工处理。

这个方案的精妙之处在于,它利用本地事务保证了消息不会丢,同时又通过MQ实现了服务间的解耦。缺点是维护了一张消息表,且至少要保证MQ是“至少一次投递”。

6.4 Seata框架

Seata是阿里开源的分布式事务解决方案,它集成了AT(自动补偿)、TCC、Saga等多种模式。其中,AT模式最吸引人,因为它基本没有业务侵入。它通过袋里数据源,自动生成反向的回滚SQL(Undo Log),当全局事务失败时,自动回滚所有分支事务。

// Spring Boot中使用Seata AT模式
@GlobalTransactional
public void purchase(Long userId, Long productId, int count) {
    orderService.createOrder(userId, productId, count); // 本地事务
    stockService.deductStock(productId, count); // 远程调用
    accountService.debitBalance(userId, count * price); // 远程调用
}

Seata会记录每个分支事务的Undo Log,一旦`purchase`方法中任何一个步骤失败,所有已经执行成功的步骤都会被自动反方向补偿。它本质上是对2PC的改进,但在性能和易用性上做了巨大提升。

来源:互联网

免责声明

本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。

同类文章推荐

相关文章推荐

更多