分布式数据一致性场景与方案处理分析|得物技术
一、引言
在经典的CAP理论中一致性是指分布式或多副本系统中数据在任一时刻均保持逻辑与物理状态的统一,这是确保业务逻辑正确性和系统可靠性的核心要素。在单体应用单一数据库中可以直接通过本地事务(ACID)保证数据的强一致性。
然而随着微服务架构的普及和业务场景的复杂化,原来的原子性操作会随着系统拆分而无法保障原子性从而产生一致性问题,但业务实际又需要保障一致性,为此BASE理论提出了最终一致性来解决这类问题。那么如何在跨服务、跨数据库的事务中保证数据最终一致性。
二、CAP理论与BASE理论
在经典的CAP理论中提到一个分布式系统中,一致性(C)、可用性(A)、分区容错性(P)最多只能同时实现两点,不可能三者兼顾。实际上这是一个伪命题,必须从 A 和 C 选择一个和 P 组合,更进一步基本上都会选择 A,相比一致性,系统一旦不可用或不可靠都可能会造成整个站点崩溃,所以一般都会选择 AP。

BASE理论源于对大规模互联网分布式系统实践的总结,作为CAP定理中一致性与可用性矛盾的实践性补充逐步演化形成。该理论主张在无法保证强一致性的场景下,系统可基于业务特性灵活调整架构设计,通过基本可用性保障、允许短暂中间状态等机制,确保数据最终达成一致性状态,从而在分布式环境中实现可靠服务能力与业务需求的平衡。
三、一致性失效场景及其解决方案
这里有一个简化的仓库上架的流程(在实际业务中可能还会涉及到履约,仓储库存等等),体现分布式系统中可能出现的一致性问题,在分布式系统中的处理流程可能如下所示:
1.操作员操作商品仓库上架 商品在仓储系统(WMS)中上架,写入仓储数据库 仓储系统通知中央库存系统(SCI)添加可用库存 仓储系统通知交易该商品可以进行售卖

简化代码示例:
@Transactional
public void upper(upperRequest request) {// 1. 写入仓储数据库UpperDo upperDo = buildUpperDo(request);wmsService.upper(upperDo);// 2. 调用rpc添加中央库存系统库存SciAInventoryRequest sciInventoryRequest = buildSciAInventoryRequest(request);sciRpcService.addInventory(sciInventoryRequest)// 3. 发送商品可以售卖的消息TradeMessageRequest tradeMessage = buildTradeMessageRequest(request);sendMessageToDealings(tradeMessage);// 4. 其他处理recordLog(buildLogRequest(request))return;
}
整个时序逻辑拆解到事务层面执行流程如下:

在第5步添加sci库存之前任意一步出现问题,事务都会回滚,对其他系统的影响为0,所以不存在一致性问题。
但是,在此之后出现问题都有可能会出现事务问题。
3.1 调用写RPC
在分布式系统中,调用RPC一般可以分为着两类: 1.读RPC:当前数据结构不完整,需要通过其他服务补充数据,对其他服务无影响。 2.写RPC:当前业务操作、数据变更需要通知其他服务,对其他服务有影响。
调用写RPC添加sci可用库存可能出现的问题:
- 调用处理成功,返回成功。【数据一致】
- 调用处理成功,返回失败。【数据不一致】
对于这种情况,最简单的做法是直接操作重试,但是需要下游幂等处理,保证同样的请求效果一致。这里重试的方式,即重新操作上架,此外也可以直接在rpc方法中异步重试机制(这种方式不会阻塞整体流程,但是增大了数据不一致的风险)。如果重试失败可能需要研发介入排查具体失败的原因(对于写RPC的接口超时问题,需要研发关注,配置告警或抛出特定异常等)。
针对RPC方法重试,可以考虑采用本地消息表的方式实现,具体参考3.3.本地消息表。
3.2消息发送
写RPC调用成功后,会给trade服务发送消息,而后提交事务,整个流程结束。
Rocket消息发送有多种方式,不同的方式适用场景不一,一般业务逻辑使用同步发送消息配合重试机制即可,对于一致性要求高的场景,可以考虑事务消息确保消息与本地事务的原子性。

同步消息+重试
同步消息比异步消息更可靠,比事务消息性能更高是一种广泛采用的方式。
同步消息通过confirm机制能保证消息发送成功:生产者发送同步消息后,等待Broker返回确认结果(SendResult)。如果 Broker 成功接收并存储消息,返回成功状态;否则返回失败状态。消息发送失败时,Rocket默认自动重试2次,支持手动设置,提高消息发送的可靠性。
DefaultMQProducer producer = new DefaultMQProducer("ProducerGroup");
producer.setRetryTimesWhenSendFailed(3); // 设置重试次数为 3 次
producer.start();
Message msg = new Message("TopicTest", "TagA", "Hello RocketMQ".getBytes());
SendResult sendResult = producer.send(msg); // 同步发送
if (sendResult.getSendStatus() == SendStatus.SEND_OK) {log.info("Send Success: " + sendResult);
} else {log.warn("Send Failed: " + sendResult);
}
同步消息+重试机制能尽可能的保证消息成功发送,但是在这种情况下仍可能出现一致性问题:消息成功发送,在提交事务之前,依然可能出现问题(第8步出现问题),导致事务回滚,但是下游的消息是无法回滚的。
为此在RocketMQ中提供了事务消息作为一种解决方案。
RocketMQ事务消息
RocketMQ 的分布式事务消息功能,在普通消息基础上,支持二阶段的提交能力。将二阶段提交和本地事务绑定,实现全局提交结果的一致性。

Rocket的事务消息可以确保消息和本地事务的原子性,但是实现起来很复杂,性能也比较低,特别是需要实现回查本地事务状态,这是一个比较复杂的问题,需要case by case,每一个消息都需要单独写逻辑,还必须确保消息体中的数据支持回查本地事务状态,对代码入侵度较高。
在笔者的了解中我司事务消息的使用情况不多,对于低并发且强一致性的场景可以考虑使用这种方式。在这个业务场景中使用事务消息可以解决3.2.1中出现的消息发送成功但事务回滚的问题,但是这个场景使用这种方式并不太合适。最终结果可能是整体数据一致性提升2%-3%,但是业务性能下降20%-30%。
spring提供给了一种事件发布-订阅机制可以解决事务回滚但消息依然发送成功的问题,并且性能损失几乎可以忽略。
事务事件+同步消息
事务事件是指在事务执行的不同阶段触发的事件。这些事件通常用于处理次要逻辑,例如发送领域事件、消息或者邮件等。
spring通过事务管理@Transactional和事件发布机制ApplicationEventPublisher,可以实现类似事务事件的功能。事件发布后事件广播器(SimpleApplicationEventMulticaster)接收事件,根据事件类型匹配所有的监听者(getApplicationListeners)。
@Service
public class wmsService {@Autowiredprivate ApplicationEventPublisher eventPublisher;@Transactionalpublic void upper(upperRequest request) {// 1. 写入仓储数据库UpperDo upperDo = buildUpperDo(request);wmsService.upper(upperDo);// 3. 发布上架事件UpperFinishEvent upperFinishEvent = buildUpperFinishEvent(request)eventPublisher.publishEvent(upperFinishEvent);return;}
}@Component
public class upperFinishEventListener {@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)public void handleUpperFinishEvent(UpperFinishEvent event) {// 处理事件// 1. 调用rpc添加中央库存系统库存SciAInventoryRequest sciInventoryRequest = buildSciAInventoryRequest(event);sciRpcService.addInventory(sciInventoryRequest)// 2. 发送商品可以售卖的消息TradeMessageRequest tradeMessage = buildTradeMessageRequest(event);sendMessageToDealings(tradeMessage);// 2. 其他处理recordLog(buildLogRequest(event))}
}
上述流程在写完DB,调用写RPC之后,发布上架完成的事件并提交事务。upperFinishEventListener订阅上架完成的事件,并发送可以售卖的消息。
通过这种方式可以在事务提交之后再发送消息。通过事务事件保证事务提交,通过重试机制和confirm机制确保生产者发送消息成功。
3.3 本地消息表
在上述过程中我们选择使用事务事件+同步消息可以来替代事务消息,但是事务事件对RPC调用并不太友好,本地事务提交之后,调用写RPC就一定要成功,不然一致性问题就无法保证。
为此可以考虑使用本地消息表这个方案:将需要分布式处理的事件通过本地消息日志存储的方式来异步执行,通过异步线程或者自动Job发起重试,确保上下游一致。

将上述流程抽象为代码可以实现一个一致性框架,通过注解实现无侵入、策略化、通用性和高复用性的能力。然后本地消息表的方式仍然存在一些问题:
- 高并发场景不适用,写本地消息会带来延迟可能出现数据积压,影响系统的吞吐量。
- 业务逻辑过程会长时间的占用事务,造成大事务问题。
- 本地消息报文巨大,难以存储等。
四、总结
本文分析的场景都是解决生产者端的一致性问题。结合部分场景探讨不同方式的优缺点。
- 事务事件+普通消息&重试 :适合对实时一致性要求不高、需要异步处理的场景、适合高并发场景,可靠性一般,实现简单但需手动处理重试和幂等性。
- 事务消息 :适合一致性要求较高的场景(如金融交易),性能较低,实现复杂但能确保消息与事务的原子性。
- 本地消息表 :适合跨服务事务、异步任务处理和最终一致性场景,高并发场景可能出现数据积压,实现简单且可靠性高,但存在延迟性和资源占用问题。
在分布式系统中,很难有能100%保证一致性的方案,正如《人月神话》中说的“没有不存在缺陷的软件,只是尚未发现缺陷”。
在上面提到的各种方案中,笔者所在团队高并发场景很少,所以一般都采用本地详细表的方式来处理一致性问题,这既可以处理写RPC的调用问题,也能通过消息状态显示的统一失败情况,统一进行重试。
往期回顾
1.从对话到自主行动:AI应用如何从 Chat 进化为 Agent?开源项目源码深度揭秘|得物技术
2.得物技术部算法项目管理实践分享
3.商家域稳定性建设之原理探索|得物技术
4.得物 Android Crash 治理实践
5.基于ANTLR4的大数据SQL编辑器解析引擎实践|得物技术
文 / 勇者
关注得物技术,每周一、三更新技术干货
要是觉得文章对你有帮助的话,欢迎评论转发点赞~
未经得物技术许可严禁转载,否则依法追究法律责任。
相关文章:
分布式数据一致性场景与方案处理分析|得物技术
一、引言 在经典的CAP理论中一致性是指分布式或多副本系统中数据在任一时刻均保持逻辑与物理状态的统一,这是确保业务逻辑正确性和系统可靠性的核心要素。在单体应用单一数据库中可以直接通过本地事务(ACID)保证数据的强一致性。 然而随着微服务架构的普及和业务场…...
JAVA:使用 Curator 进行 ZooKeeper 操作的技术指南
1、简述 Apache Curator 是一个基于 ZooKeeper 的 Java 客户端库,它极大地简化了使用 ZooKeeper 的开发工作。Curator 提供了高层次的 API,封装了很多复杂的 ZooKeeper 操作,例如连接管理、分布式锁、Leader 选举等。 在分布式系统中&#…...
C++ - 宏基础(简单常量替换宏、函数样式的宏、多行宏、预定义宏、字符串化宏、连接宏、可变参数日志宏)
宏概述 在编程中,宏(Macro)是一种预处理器指令 宏可以让程序员在源代码中定义一段值或代码的别名,在编译程序之前,预处理器会查找这些宏,并将其替换为相应的值或代码 C 宏 在 C 中,宏可以通过…...
Linux中的调试器gdb与冯·诺伊曼体系
一、Linux中的调试器:gdb 1.1安装与版本查看 可以使用yum进行安装: yum install -y gdb 版本查看:使用指令 gdb --version 1.2调试的先决条件:release版本与debug版本的切换 debug版本:含有调试信息 release版本…...
STM32 + keil5 跑马灯
硬件清单 1. STM32F407VET6 2. STLINK V2下载器(带线) 环境配置 1. 安装ST-LINK 2. 安装并配置 keil5 https://blog.csdn.net/qq_36535414/article/details/108947292 https://blog.csdn.net/weixin_43732386/article/details/117375266 3. 接线并下载 点击"LOAD“&a…...
Ruby语言的代码重构
Ruby语言的代码重构:探索清晰、可维护与高效的代码 引言 在软件开发的过程中,代码的质量直接影响到项目的可维护性、扩展性和整体性能。随着时间的推移,系统的需求变化,代码可能会变得混乱和难以理解,因此࿰…...
leetcode 数组总结篇
基础理论 数组:下标时从 0 开始的,地址是连续的,不能删除,只能覆盖;数组的实现:vector动态数组 常用操作 头文件 #include <iostream> #include <vector> #include <cstdint> // IN…...
盲盒小程序开发平台搭建:打造个性化、高互动性的娱乐消费新体验
在数字化浪潮席卷消费市场的今天,盲盒小程序以其独特的趣味性和互动性,迅速成为了年轻人追捧的娱乐消费新宠。盲盒小程序不仅为用户带来了拆盒的惊喜和刺激,更为商家提供了创新的营销手段。为了满足市场对盲盒小程序日益增长的需求࿰…...
DuckDB系列教程:如何分析Parquet文件
Parquet 是一种强大的、基于列的存储格式,适用于实现更快捷和更高效的数据分析。您可以使用 DuckDB 这种内存型分析数据库来处理 Parquet 文件并运行查询以对其进行分析。 在这篇文章中,我们将逐步介绍如何使用 DuckDB 对存储在 Parquet 文件中的餐厅订单…...
深入解析:使用Python爬取Bilibili视频
深入解析:使用Python爬取Bilibili视频 引言 Bilibili,作为中国领先的年轻人文化社区,拥有海量的视频资源。对于想要下载Bilibili视频的用户来说,手动下载不仅费时费力,而且效率低下。本文将介绍如何使用Python编写一…...
GRE,MGRE
GRE:静态过程,有局限性 R1 : [r1]interface Tunnel 0/0/0 --- 创建一个虚拟的隧道接口 [r1-Tunnel0/0/0]ip address 192.168.3.1 24 --- 给隧道接口分配一个 IP 地址 [r1-Tunnel0/0/0]tunnel-protocol gre --- 定义接口的封装方式 [r1-Tun…...
【linux学习】linux系统调用编程
目录 一、任务、进程和线程 1.1任务 1.2进程 1.3线程 1.4线程和进程的关系 1.5 在linux系统下进程操作 二、Linux虚拟内存管理与stm32的真实物理内存区别 2.1 Linux虚拟内存管理 2.2 STM32的真实物理内存映射 2.3区别 三、 Linux系统调用函数 fork()、wait()、exec(…...
Azure Speech 赋能,为智能硬件注入 AI 语音 “新灵魂”
在人工智能技术飞速发展的今天,智能硬件正逐步渗透到人们生活的方方面面。AI玩具、AI眼镜、AI鼠标等创新产品不仅提升了用户体验,更带来了前所未有的交互方式。领驭科技凭借微软Azure Speech的强大技术能力,为硬件厂商提供一站式AI语音解决方…...
力扣DAY35 | 热100 | LRU缓存
前言 中等 ⚪ 这个题原本打算用双链表最小堆做,发现无解。没想到双向链表。 题目 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int …...
Python 助力人工智能与机器学习的深度融合
技术革新的 “源动力” 在当今数字化时代,人工智能(AI)与机器学习(ML)无疑是最具影响力的技术领域,它们如同强大的引擎,推动着各个行业的变革与发展。Python 凭借其简洁易读的语法、丰富的库和…...
ARXML文件解析-1
目录 1 摘要2 ARXML文件2.1 作用及典型应用场景2.2 ARXML文件的结构树2.3 TAG(XML元素)2.4 ARXML文件关键元素解析2.4.1 XML声明与处理指令2.4.2 XML注释2.4.3 XML声明与根元素4.4.3.1 xmlns(默认命名空间)4.4.3.2 xmlns:xsi&…...
SignalR给特定User发送消息
1、背景 官网上SignalR的demo很详细,但是有个特别的问题,就是没有详细阐述如何给指定的用户发送消息。 2、解决思路 网上整体解决思路有三个: 1、最简单的方案,客户端连接SignalR的Hub时,只是简单的连接,…...
React: hook相当于函数吗?
一、Hook 是一个函数,但不仅仅是函数 函数的本质 Hook 确实是一个 JavaScript 函数,例如 useState、useEffect 或自定义 Hook 都是函数。它们可以接受参数(如初始状态值或依赖项数组),并返回结果(如状态值和…...
Ubuntu 安装 VLC
最近项目中需要用VLC查看NVR下子设备的RTSP流,特此记录,便于日后查阅。 1、安装snap $ sudo apt update $ sudo apt install snapd 2、安装vlc $ sudo snap install vlc 3、可能遇到的问题 snap beta install on ubuntu 22.04 failing to start Qt: Se…...
【数据分享】2002-2023中国湖泊水位变化数据集(免费获取)
湖泊水位变化是研究水资源动态、生态系统演变和气候变化影响的重要指标。湖泊水位的升降不仅反映了降水、蒸发和入流水量的变化,还与人类活动、气候波动及地质过程密切相关。因此,高精度、长时间序列的湖泊水位数据对于水资源管理、洪水预测以及生态环境…...
UBUNTU编译datalink
参考文档 datalink 语雀 下载 git clone https://gitee.com/liyang9512/datalink 源码打包 mvn -Prelease-datalink -Dmaven.test.skiptrue clean install -U 启动准备 # unzip ./distribution/target/datalink-server-1.0.0.tar.gz tar -xvf ./distribution/target/da…...
免费送源码:Java+SSM+Android Studio 基于Android Studio游戏搜索app的设计与实现 计算机毕业设计原创定制
摘要 本文旨在探讨基于SSM框架和Android Studio的游戏搜索App的设计与实现。首先,我们详细介绍了SSM框架,这是一种经典的Java Web开发框架,由Spring、SpringMVC和MyBatis三个开源项目整合而成,为开发企业级应用提供了高效、灵活、…...
STM32单片机入门学习——第14节: [6-2] 定时器定时中断定时器外部时钟
写这个文章是用来学习的,记录一下我的学习过程。希望我能一直坚持下去,我只是一个小白,只是想好好学习,我知道这会很难,但我还是想去做! 本文写于:2025.04.04 STM32开发板学习——第14节: [6-2] 定时器定时中断&定时器外部时钟 前言开发…...
2025-04-03 Latex学习1——本地配置Latex + VScode环境
文章目录 1 安装 Latex2 安装 VScode3 配置环境3.1 汉化 VScode3.2 安装 latex 插件3.3 配置解释 4 编译示例5 加快你的编译5.1 取消压缩5.2 使用 PDF 代替图片 6 参考文章 1 安装 Latex 本文配置环境: Windows11 打开清华大学开源软件镜像站:https://mi…...
【CF】Day24——Codeforces Round 994 (Div. 2) D
D. Shift Esc 题目: 思路: 典DP的变种 如果这一题没有这个变换操作,那么是一个很典型的二维dp,每一个格子我们都选择上面和左边中的最小值即可 而这题由于可以变换,那我们就要考虑变换操作,首先一个显然…...
【Java集合】LinkedList源码深度分析
参考笔记:java LinkedList 源码分析(通俗易懂)_linkedlist源码分析-CSDN博客 目录 1.前言 2.LinkedList简介 3.LinkedList的底层实现 4.LinkedList 与 ArrayList 的对比 4.1 如何选择 4.2 对比图 5.LinkedList 源码Debug 5.1 add(E e) ÿ…...
第十五届蓝桥杯大赛软件赛省赛Python 大学 C 组:5.回文数组
题目1 回文数组 小蓝在无聊时随机生成了一个长度为 n 的整数数组,数组中的第 i 个数为 ai,他觉得随机生成的数组不太美观,想把它变成回文数组,也是就对于任意 i∈[1,n] 满足 a i a n − i 1 a_ia_{n−i}1 aian−i1。 小蓝…...
高并发系统架构设计的深度解析与实施指南【大模型总结】
以下是对高并发系统架构设计的深度解析与实施指南,通过技术分层拆解和场景化案例说明,呈现完整的系统设计方法论: 一、容错优先思维的系统级实现 1. 混沌工程落地框架 # 混沌实验设计模板 class ChaosExperiment:def __init__(self, scope,…...
Python办公自动化(2)对wordpdf的操作
一、操作word文档 终端下载操作word文件的工具库: pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple python-docx 1.遍历文档中内容 paragraphs:段落属性,返回列表类型的段落地址,遍历每一个段落地址,通过…...
pip安装第三方库,但PyCharm中却无法识别
点击菜单栏File,选择Settings 系统默认的是PyCharm安装目录下的python.exe 解释器,不要用。 选择你的PYTHON的安装目录下的python.exe 解释器。如果不存在的话,增加进去 如果文件》设置打不开,需移除法化包。 打开 pycharm 安装目…...
