当前位置: 首页 > news >正文

分布式环境下的一致性与幂等性

一、从一个“资金不翼而飞”的案例说起

想象一个令人心惊的场景:用户通过支付平台向商家转账100元。流程如下:

  1. 支付系统调用银行A的接口,从用户账户扣款100元。
  2. 然后,支付系统调用银行B的接口,向商家账户增加100元。

然而,在步骤1成功后,步骤2由于网络抖动失败了。结果:用户的100元已被扣除,但商家并未收到。数据不一致 发生了,用户资金受损。

在单机数据库中,我们可以用数据库事务(BEGIN; ... COMMIT;)轻松保证“扣款”和“加款”的原子性。但在分布式系统中,“用户账户”和“商家账户”是独立的服务,分属不同的数据库,传统的事务模型失效了。

这就是分布式系统面临的核心挑战:如何在网络不可靠、服务可能故障的前提下,保证跨服务的数据最终一致? 同时,在重试机制下,如何避免因重复请求导致的数据错误?

二、基石一:分布式事务与最终一致性

分布式事务的解决方案有多种,其核心是在性能、复杂度和一致性强度之间做权衡。

1. 强一致性方案(难度高,性能代价大)
  • 两阶段提交(2PC)
    • 角色:一个协调者(Coordinator)和多个参与者(Participant,即各资源管理器)。
    • 阶段一(准备阶段):协调者询问所有参与者:“是否可以提交?” 参与者执行事务所有操作(写redo/undo日志),锁定资源,但不提交,然后返回“就绪”或“失败”。
    • 阶段二(提交/回滚阶段):如果所有参与者都返回“就绪”,协调者发送“提交”命令,所有参与者正式提交;如果任一参与者返回“失败”或超时,协调者发送“回滚”命令,所有参与者回滚。
    • 缺点同步阻塞:在准备阶段,所有参与者的资源都被锁定。单点问题:协调者宕机可能导致参与者一直锁定资源。数据不一致:在阶段二,如果协调者发送完部分提交指令后宕机,会导致部分参与者提交,部分未提交。

2PC追求的是ACIDA(原子性),但在分布式环境下代价高昂,通常用于内部数据库集群(如MySQL Cluster),较少用于微服务间调用。

2. 最终一致性方案(主流,更实用)

其核心思想是接受暂时的中间状态,但通过一系列措施确保数据最终一致。这是互联网公司更常用的模式。

  • TCC(Try-Confirm-Cancel)

    • 本质:一个业务层面的2PC。
    • 三个阶段
      1. Try:尝试执行。完成所有业务的检查,并预留必要的业务资源。例如,检查账户状态是否正常,并将转账金额100元在用户账户中“冻结”起来(而非直接扣除)。
      2. Confirm:确认执行。真正执行业务操作,使用Try阶段预留的资源。本例中,将用户冻结的100元扣除,并增加到商家账户。Confirm操作需满足幂等性
      3. Cancel:取消执行。释放Try阶段预留的资源。例如,将用户冻结的100元解冻。Cancel操作也需满足幂等性
    • 优势:由业务逻辑控制,灵活性高,避免了数据库层长时间锁表。
    • 挑战:对业务有侵入性,需要为每个操作实现Try、Confirm、Cancel三个接口。
  • 基于消息队列的最终一致性(最常用)

    • 核心思路:将分布式事务拆解为一系列本地事务,通过消息队列的可靠性来驱动后续操作。
    • 流程(以转账为例)
      1. 本地事务1:在支付系统的数据库中,执行INSERT INTO local_transaction ...记录转账流水,状态为“进行中”。同时,向消息队列发送一条“扣款成功,待加款”的消息。这两个操作必须在同一个数据库事务中,保证要么都成功,要么都失败。
      2. 投递消息:消息队列保证将消息可靠地投递给商家账户服务。
      3. 本地事务2:商家账户服务消费消息,执行给商家加款的操作。成功后,更新本地流水状态。
      4. 补偿机制:如果步骤3失败,或商家服务始终没有消费消息,则需要一个对账作业定期扫描流水表,发现状态为“进行中”但已超时的流水,进行人工或自动干预(如冲正交易)。

三、基石二:幂等性 —— “一次”与“多次”的哲学

幂等性是指:同一个操作,执行一次与执行多次,对系统状态的影响是完全相同的

  • 为什么需要幂等? 因为网络是不可靠的。客户端调用超时后可能会重试;消息队列可能因网络抖动而重复投递。如果没有幂等设计,一次支付可能被重复扣款,造成资金损失。
如何实现幂等性?
  1. 数据库唯一索引:最直接的方式。如创建订单时,传入订单号作为唯一键。重复插入会失败,从而保证订单不会重复创建。
  2. 状态机:业务数据带有状态。如订单状态为“已支付”时,任何再次支付的请求都会被忽略。
  3. Token机制(防重令牌)
    • 客户端先向服务端申请一个全局唯一的Token。
    • 客户端执行业务请求时带上此Token。
    • 服务端在处理前,先检查该Token是否已被使用(如写入Redis)。如果已使用,则拒绝请求;如果未使用,则标记为已使用,然后执行业务逻辑。
  4. 悲观锁/乐观锁:通过版本号等机制,确保更新操作是基于最新数据的,避免重复更新。

幂等性是实现最终一致性的重要保障。在TCC的Confirm/Cancel阶段,在消息队列的重试机制中,都必须保证幂等,否则重试会导致严重的数据错误。

四、实战案例剖析:秒杀系统中的库存扣减

秒杀是检验一致性和幂等性设计的终极考场。

  • 挑战:秒杀100件商品,不能超卖(卖超过100件),也尽量不能少卖。
  • 方案
    1. 缓存原子操作保证一致性:库存数量预热到Redis中,利用Redis的 DECR(递减)或 LUA 脚本的原子性执行扣减。这保证了在单点上的并发安全。
    2. 数据库最终兜底:Redis扣减后,发送异步消息到MQ,由消费者将扣减结果持久化到数据库。此处采用最终一致性
    3. 幂等性防止重复扣减:每个秒杀请求携带一个唯一的“秒杀令牌”(Token)。在Redis扣减前,先检查该令牌是否已处理过,防止用户重复提交或客户端重试导致库存多扣。
    4. 事务补偿:部署一个定时对账任务,核对Redis中的库存与数据库中的最终记录是否一致,发现不一致时进行告警和修复。

五、技术是手段,业务是目的

通过本系列的五篇文章,我们系统地构建了一个高并发、高可用系统的技术体系:

  1. 第一篇(削峰异步) 和 第二篇(缓存) 解决了性能问题。
  2. 第三篇(分片) 解决了容量问题。
  3. 第四篇(弹性设计) 解决了可用性问题。
  4. 本篇(一致性与幂等性) 解决了正确性问题。

最终的心法是:所有技术方案的选择,都必须回归业务本质。

  • 对于读取场景,通常采用最终一致性,通过缓存等手段提升性能。
  • 对于核心的写入场景(如资金、库存),则需结合强一致性(如数据库事务、Redis原子操作)和幂等性设计,确保万无一失。
  • 对于复杂的跨服务写入,TCC消息队列+最终一致性是更务实的选择。

在分布式的复杂世界里,没有银弹。真正的架构能力,在于深刻理解业务需求,在一致性、可用性、性能之间做出最合理的权衡。

http://www.dtcms.com/a/606652.html

相关文章:

  • 【OpenCV + VS】 使用 OpenCV 实现实时人脸检测
  • 聊城手机网站建设电话达内网站开发视频教程
  • MySQL JOIN 机制与多表查询优化:驱动表选择、连接算法与执行计划解析
  • AI代码开发宝库系列:特征工程
  • WordPress全站广告巩义市建设局网站
  • 信誉好的顺德网站建设如何用百度平台营销
  • Apache Cloudberry 孵化报告(202508-202510)
  • 【2025 JAVA面试题】 常见几个具体问题
  • dedecms大气金融企业网站模板八年级信息所用软件做网站
  • C语言编译器手机| 如何选择适合的C语言编译器应用
  • Java 并发踩坑:高并发库存扣减丢失更新,从悲观锁到分布式锁的终极方案
  • 杭州建设网站职称人才工作专题wordpress 插件手机
  • lancedb create_scalar_index 创建索引
  • Python 网络编程
  • Java后端常用技术选型 |(四)微服务篇
  • Vue3 + Vite项目=》babel转义兼容低版本实现+vite 分包处理方案
  • 【GIS入门】GeoTIFF栅格地理数据格式介绍和基础概念详解
  • 网站开发与设计模板百度seo网站排名
  • 校园网站建设总结flash网站系统
  • openlayer省市县json
  • 计算机视觉11-相机模型与多视几何
  • 网站开发公司 经营范围eclipse做网站表格
  • 建立网站 域名 服务器7一12岁手工简单又实用
  • kotlin 集成 unity
  • 麻辣烫配方教授网站怎么做前端做的好的网站
  • [Unity Shader Base] RayMarching in Cloud Rendering
  • Java后端常用技术选型 |(六)避坑手册
  • 教育网站平台建设对网站做维护
  • RUST实现简易随机密码生成器
  • 个人主页网站网站正在建设中源码