分布式事务解决方案:保障淘宝客APP资金结算的数据强一致性
分布式事务解决方案保障淘宝客APP资金结算的数据强一致性又见面了我是高佣返利省赚客APP研发者微赚在淘客返利业务中资金结算的准确性是平台的生命线。当用户订单确认收货后系统需要同时完成“订单状态更新”、“佣金计算入账”和“用户余额增加”三个操作。在微服务架构下这三个操作分散在不同的数据库实例中传统的本地Transactional注解失效。一旦某个环节失败极易导致“订单已结算但钱没到账”或“钱扣了但订单未更新”的严重资损事故。为了保障资金数据的强一致性或在极高并发下的最终一致性省赚客APP研发团队深入实践了TCCTry-Confirm-Cancel模式与可靠消息最终一致性方案。TCC模式的三阶段提交实战对于核心资金链路我们放弃了性能较差的XA协议转而采用TCC模式。TCC将事务拆分为Try尝试、Confirm确认、Cancel取消三个阶段。Try阶段进行资源预留和检查Confirm阶段执行真实业务Cancel阶段回滚预留资源。这种模式避免了长事务锁表显著提升了并发性能。packagejuwatech.cn.provinceearn.finance.tcc.service;importjuwatech.cn.provinceearn.finance.entity.UserWallet;importjuwatech.cn.provinceearn.finance.repository.WalletRepository;importjuwatech.cn.provinceearn.common.exception.InsufficientBalanceException;importorg.springframework.stereotype.Service;importorg.springframework.transaction.annotation.Transactional;importlombok.extern.slf4j.Slf4j;importjava.math.BigDecimal;/** * 用户钱包服务的TCC实现 * 负责在分布式事务中管理余额的冻结与扣减 */Slf4jServicepublicclassWalletTccService{privatefinalWalletRepositorywalletRepository;publicWalletTccService(WalletRepositorywalletRepository){this.walletRepositorywalletRepository;}/** * Try阶段检查余额并冻结资金 * 此时资金并未真正扣除只是标记为“冻结”状态防止被其他事务使用 */TransactionalpublicbooleantryDeduct(StringuserId,BigDecimalamount,StringbizId){UserWalletwalletwalletRepository.findByUserId(userId);if(walletnull||wallet.getAvailableBalance().compareTo(amount)0){thrownewInsufficientBalanceException(Insufficient balance for user: userId);}// 记录冻结流水幂等性检查if(walletRepository.existsFreezeRecord(bizId)){returntrue;// 已经冻结过直接返回成功}// 执行冻结可用余额减少冻结余额增加wallet.freezeAmount(amount);walletRepository.save(wallet);// 插入冻结记录表用于Confirm/Cancel时查找walletRepository.saveFreezeRecord(bizId,userId,amount);log.info(Try phase success: frozen {} for user {},amount,userId);returntrue;}/** * Confirm阶段真正扣减资金 * 此阶段必须保证成功若失败需人工介入或无限重试 */TransactionalpublicvoidconfirmDeduct(StringbizId){varrecordwalletRepository.findFreezeRecord(bizId);if(recordnull){thrownewIllegalStateException(Freeze record not found for bizId: bizId);}// 将冻结余额真正扣除清除冻结标记walletRepository.deductFrozenAmount(record.getUserId(),record.getAmount());walletRepository.markFreezeRecordAsConfirmed(bizId);log.info(Confirm phase success: deducted for bizId {},bizId);}/** * Cancel阶段回滚冻结资金 * 将冻结的余额解冻恢复到可用余额 */TransactionalpublicvoidcancelDeduct(StringbizId){varrecordwalletRepository.findFreezeRecord(bizId);if(recordnull){return;// 幂等处理}// 解冻冻结余额减少可用余额增加walletRepository.unfreezeAmount(record.getUserId(),record.getAmount());walletRepository.markFreezeRecordAsCancelled(bizId);log.info(Cancel phase success: unfrozen for bizId {},bizId);}}基于本地消息表的最终一致性方案对于非实时强一致性的场景如积分赠送、通知发送我们采用了“本地消息表定时任务”的方案。该方案的核心思想是将“业务数据写入”与“消息发送”放在同一个本地数据库事务中确保消息一定被持久化。随后由独立的后台线程轮询消息表将消息投递到MQ并在投递成功后更新消息状态。packagejuwatech.cn.provinceearn.finance.message.handler;importjuwatech.cn.provinceearn.finance.entity.DomainEventLog;importjuwatech.cn.provinceearn.finance.repository.EventLogRepository;importjuwatech.cn.provinceearn.common.constant.EventStatus;importorg.apache.rocketmq.spring.core.RocketMQTemplate;importorg.springframework.scheduling.annotation.Scheduled;importorg.springframework.stereotype.Component;importorg.springframework.transaction.annotation.Transactional;importlombok.RequiredArgsConstructor;importjava.time.LocalDateTime;importjava.util.List;/** * 可靠消息投递处理器 * 解决本地事务与MQ发送的原子性问题 */ComponentRequiredArgsConstructorpublicclassReliableMessageSender{privatefinalEventLogRepositoryeventLogRepository;privatefinalRocketMQTemplaterocketMQTemplate;/** * 业务服务调用此方法保存事件必须在业务事务内执行 */TransactionalpublicvoidsaveEvent(StringeventType,StringbusinessId,Stringpayload){DomainEventLogeventLognewDomainEventLog();eventLog.setEventType(eventType);eventLog.setBusinessId(businessId);eventLog.setPayload(payload);eventLog.setStatus(EventStatus.PENDING);eventLog.setCreateTime(LocalDateTime.now());eventLog.setRetryCount(0);// 与业务数据同库同事务保存eventLogRepository.save(eventLog);}/** * 定时任务扫描待发送的消息并投递 * 频率不宜过高避免对数据库造成压力通常设置为每秒或每5秒一次 */Scheduled(fixedRate2000)publicvoidscanAndSend(){// 只查询超过3秒未发送的消息避免干扰刚写入的事务ListDomainEventLogpendingEventseventLogRepository.findPendingEvents(LocalDateTime.now().minusSeconds(3));for(DomainEventLogevent:pendingEvents){try{// 发送消息到MQrocketMQTemplate.syncSend(TOPIC_FINANCE_SETTLEMENT,event.getPayload());// 发送成功更新状态event.setStatus(EventStatus.SENT);event.setUpdateTime(LocalDateTime.now());eventLogRepository.save(event);}catch(Exceptione){// 发送失败增加重试次数event.setRetryCount(event.getRetryCount()1);if(event.getRetryCount()10){event.setStatus(EventStatus.FAILED);log.error(Message failed after 10 retries: {},event.getBusinessId(),e);}else{event.setUpdateTime(LocalDateTime.now());}eventLogRepository.save(event);}}}}防重与补偿机制分布式环境下网络抖动可能导致Confirm或消息重复投递。因此所有下游服务必须具备幂等性。我们在数据库中建立了唯一的业务流水号索引利用数据库的唯一约束来防止重复入账。同时针对TCC模式中Confirm阶段可能出现的长期阻塞我们设计了自动对账补偿任务每日凌晨扫描异常状态的分布式事务自动触发修复或报警人工处理。packagejuwatech.cn.provinceearn.finance.compensation.job;importjuwatech.cn.provinceearn.finance.repository.SettlementOrderRepository;importorg.springframework.batch.core.Job;importorg.springframework.batch.core.Step;importorg.springframework.batch.core.configuration.annotation.JobBuilderFactory;importorg.springframework.batch.core.configuration.annotation.StepBuilderFactory;importorg.springframework.batch.item.database.JdbcPagingItemReader;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importjavax.sql.DataSource;importjava.util.HashMap;importjava.util.Map;/** * 每日对账补偿任务配置 * 扫描状态不一致的结算单进行修复 */ConfigurationpublicclassCompensationJobConfig{privatefinalJobBuilderFactoryjobBuilderFactory;privatefinalStepBuilderFactorystepBuilderFactory;privatefinalDataSourcedataSource;publicCompensationJobConfig(JobBuilderFactoryjobBuilderFactory,StepBuilderFactorystepBuilderFactory,DataSourcedataSource){this.jobBuilderFactoryjobBuilderFactory;this.stepBuilderFactorystepBuilderFactory;this.dataSourcedataSource;}BeanpublicJobreconciliationJob(){returnjobBuilderFactory.get(reconciliationJob).start(reconciliationStep()).build();}BeanpublicStepreconciliationStep(){returnstepBuilderFactory.get(reconciliationStep).MapString,Object,Voidchunk(100).reader(abnormalOrderReader()).processor(item-{// 执行补偿逻辑根据订单状态强制修正资金流// 例如订单已结算但钱包未入账则手动触发入账compensateSettlement(item);returnnull;}).writer(list-{})// 无需写入补偿逻辑在processor中完成.build();}privateJdbcPagingItemReaderMapString,ObjectabnormalOrderReader(){JdbcPagingItemReaderMapString,ObjectreadernewJdbcPagingItemReader();reader.setDataSource(dataSource);reader.setFetchSize(100);reader.setRowMapper((rs,rowNum)-{MapString,ObjectmapnewHashMap();map.put(orderId,rs.getString(order_id));map.put(status,rs.getString(status));returnmap;});// 查询SQL查找状态不匹配的异常数据reader.setSelectStatement(SELECT order_id, status FROM t_settlement_order WHERE status PARTIAL_SUCCESS);returnreader;}privatevoidcompensateSettlement(MapString,Objectitem){// 具体的补偿业务逻辑实现System.out.println(Compensating order: item.get(orderId));}}结语资金安全无小事。通过TCC模式处理核心链路的强一致性需求配合本地消息表实现高吞吐场景下的最终一致性再辅以严密的幂等设计与自动对账补偿省赚客APP构建了一套坚不可摧的资金结算体系。这套架构不仅经受住了双11流量洪峰的考验更确保了每一分返利都准确无误地到达用户手中。本文著作权归 省赚客app 研发团队转载请注明出处

相关新闻

智谱AI:从多模态预训练到商业化落地的技术演进

智谱AI:从多模态预训练到商业化落地的技术演进

1. 从实验室到商业化的技术演进路径 2018年,一支来自清华大学的科研团队在自然语言处理领域取得突破性进展,这个后来被称为"智谱AI"的项目最初只是实验室里的几行代码。团队基于Transformer架构,创新性地提出了多模态预训练框架&am…

2026/9/22 10:03:40 阅读更多 →
AI智能体开发实战:Nexent平台电商客服搭建指南

AI智能体开发实战:Nexent平台电商客服搭建指南

1. 项目概述:AI智能体开发的新范式最近半年,AI智能体开发正在经历从实验室走向产业化的关键转折。不同于传统的单任务AI模型,智能体具备环境感知、自主决策和持续学习的能力,这使其在客服自动化、流程优化等场景展现出惊人潜力。N…

2026/9/19 10:22:47 阅读更多 →
Unreal Eangie 5:蓝图编辑

Unreal Eangie 5:蓝图编辑

实现玩家移动功能首先打开内容侧滑菜单右键打开后新建名为charrater(可以自定档案名)的档案先点击运行,我们发现我们什么都没做人物就可以跑动,这是因为项目模板中人物角色有默认的移动模板,今天我要自己来实现移动功能首先要把默认的移动模板…

2026/9/22 16:26:11 阅读更多 →

最新新闻

基于Arduino的自制乒乓球发球机:从机械设计到PID控制

基于Arduino的自制乒乓球发球机:从机械设计到PID控制

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 8:44:00 阅读更多 →
M型Czerny-Turner光谱仪设计:用非对称光路压制彗差的光学仿真实践

M型Czerny-Turner光谱仪设计:用非对称光路压制彗差的光学仿真实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 8:44:00 阅读更多 →
PowerShell与CMD深度对比:从脚本迁移到编码乱码的避坑指南

PowerShell与CMD深度对比:从脚本迁移到编码乱码的避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 8:44:00 阅读更多 →
从零实现入侵检测系统:规则引擎、误报率调优与避坑指南

从零实现入侵检测系统:规则引擎、误报率调优与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 8:44:00 阅读更多 →
国产MCU替代STM32实战:从选型到移植的完整指南

国产MCU替代STM32实战:从选型到移植的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/24 8:44:00 阅读更多 →
MySQL基础入门:从表结构到Python连接

MySQL基础入门:从表结构到Python连接

MySQL 是目前最流行的开源关系型数据库管理系统之一,凭借高性能、高可靠性和易用性,被广泛应用于各类 Web 应用、电商平台、内容管理系统以及数据分析场景。它支持标准的 SQL 语言,能够高效地存储、查询和管理结构化数据,同时提供…

2026/9/24 8:42:59 阅读更多 →

日新闻

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为…

2026/9/24 0:00:19 阅读更多 →
单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

单细胞注释实战:基于Scanpy的标记基因与参考映射流程解析

简介:一份基于单细胞RNA测序数据的细胞类型注释算法研究Python毕业设计源码,针对计算机相关专业正在做毕设或需要项目实战的学习者,可用于课程设计与期末大作业。项目代码完整、经导师指导评审通过,可直接运行,覆盖数据…

2026/9/24 0:00:19 阅读更多 →
C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

C#源生成器实战:用增量生成器替代反射,告别AOT崩溃

第一次在项目里被反射卡住,是在一个老旧的WinForms模块里:几十个类依赖PropertyChanged通知,运行时反射读属性、发通知,每次启动慢半拍不说,一上.NET Native/AOT裁剪模式几乎全面崩盘。后来我把这段逻辑全部改成C#源生…

2026/9/24 0:00:19 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/23 4:55:02 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/23 4:49:06 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/23 9:53:41 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/23 9:53:40 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/23 9:53:40 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/23 9:53:40 阅读更多 →