分布式事务最终一致性-CAP框架轻松搞定
前言
对于分布式事务,常用的解决方案根据一致性的程度可以进行如下划分:
- 强一致性(2PC、3PC):数据库层面的实现,通过锁定资源,牺牲可用性,保证数据的强一致性,效率相对比较低。
- 弱一致性(TCC):业务层面的实现,通过预留或锁定部分资源,最后通过确认或取消操作完成事务的处理。比如A向B转款500元,A账号会冻结500元,其他操作正常,B接收转款时,也不能直接入账,而是将500元放到预留空间,只有经过确认之后,A才正式扣钱,B才正式入账; 如果取消把A的500块解冻,B也不会入账。
- 最终一致性(本地消息表):不管经过多少个服务节点,最终数据一致就行。比如下单成功之后,需要库存服务扣减库存,如果库存扣减失败,不管是重试,还是最后人工处理,最后确保订单和库存数据能对上就行;为保证用户体验,及时通过中间状态的形式反馈给用户,比如常见的出票中、数据处理中等。
对于强一致性和弱一致性的解决方案一般针对数据一致性和时效性要求特别高的业务场景,通常会牺牲暂时的可用性来满足一致性的要求;由于为保证一致性,会锁定资源,在高并发的业务场景不是最佳选择,所以很多系统在业务需求允许的情况下,基本上都会采用最终一致性方案。
正文
1.1 最终一致性简述
顾名思义就是保证数据最后的一致性就行了。如果中间节点发生失败,系统为了减少代价,一般不会自动回滚,而是通过重试机制和人工参与的方式对失败数据进行处理,从而保证系统高并发场景下高可用的数据一致性需求。
1.2 解决方案
目前用得最多的方案是结合本地消息表进行实现,再加上后台任务、消息队列中间件就可以更好的实现分布式事务的处理。
本地消息表:就是在对应业务数据库中增加的一张消息表;这张表存储业务产生的消息,通过本地事务保证业务数据和消息数据的一致性。在消息表中通过一个状态来标识业务是否执行成功,如果失败,后台任务就进行重试。
1.2.2 CAP框架简介
CAP 是一个EventBus(事件总线),同时也是一个在微服务或者SOA系统中解决分布式事务问题的一个框架,基于CAP理论思想进行封装的。采用模块化设计,具有高度的可扩展性,可靠并且易于更改。
对于分布式事务的处理,CAP 框架采用的是“异步确保”这种方案,即本地消息表。官方支持的数据存储方式有SQL Server、MySQL、PostgreSql、MongoDB、In-Memory(内存),由于是开源项目,社区大佬也提供了其他数据存储支持,如:Oracle、SQLite、SmartSql等。
在分布式系统,各节点需要进行消息传输,CAP框架提供以下几种方式RabbitMQ、Kafka、Redis Streams(Redis 5.0支持)、Azure Service Bus、Amazon SQS、In-Memory Queue,使用方式都差不多。
CAP的架构图如下:
上图简要说明:
- 有两个微服务,服务A和服务B;
- 服务A中通过本地事务的方式,将事件消息和业务逻辑进行事务保存(事件消息保存在本地消息表中),保证业务逻辑和消息的一致性和可靠性;关于消息的处理和保存CAP已经封装在内部;
- CAP内部定时调度任务将消息发布到消息队列中;
- 服务B订阅到消息,将其保存到服务B的本地消息表中,CAP已经封装好,只需按照说明使用即可;
- 如果业务处理失败,服务B中集成的CAP会根据配置的定时任务策略进行重试,直到处理成功为止;
主要的理论就说那么多,更多详细内容,请进下方传送门:
接下来就到撸码时刻,CAP由于封装比较好,所以使用起来比较简单。
1.3 撸码实践
以下的业务场景是为了案例演示,目的是体现CAP的实践,所以业务逻辑都只是模拟,切勿当真。
1.3.1 环境准备
演示中要用到RabbitMQ,为了安装方便,这里使用Docker的方式,直接通过镜像运行,简单,快速方便。关于Docker的实践,后续会专门出系列文章。这里就先总结一下Docker的安装和RabbitMQ在Docker中的运行步骤,采用的主机环境是我之前买的阿里云服务器(CentOS 7);演示用的数据库是SqlServer。
Docker安装
1、移除移动旧版本
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine
2、安装需要的依赖包
sudo yum install -y yum-utils
3、设置镜像仓库
sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
4、更新Yum软件包索引
sudo yum makecache fast # 提高安装速度
5、开始安装Docker
sudo yum install docker-ce docker-ce-cli containerd.io
6、启动Docker
sudo systemctl start docker
7、测试Docker
sudo docker run hello-world # 运行Hello-world
RabbitMQ在Docker中安装和运行
1、一行命令直接指定镜像运行,如果本地找不到镜像,会去远程仓储里去找。
docker run -d --hostname my-rabbit --name cap-rabbit -p 8888:15672 -p 5672:5672 -p 5671:5671 -p 1883:1883 rabbitmq:3-management
这里先不细说命令了,后续聊Docker的时候好好说说。命令需要注意的是主机端口和容器端口的映射。
2、运行成功后就可以访问啦,默认用户名和密码:guest/guest;
这里访问的地址端口是8888,那是在启动容器的时候将主机端口8888和容器端口15672进行了映射。
这就是选择Dokcer安装的原因,超级快;如果用传统的方式,还得安装语言环境,还得配置,最后才能安装;Docker通过镜像的方式直接运行即可。
如果小伙伴新增用户之后不能访问,或者程序连接报错,可以排查是否有权限访问,如下:
注:如果小伙伴用的是云服务器,需要配置安全组,允许端口访问;另外如果程序和RabbitMq所在的主机不是同一台机器,主机防火墙也需要放开对应的端口。
1.3.2 开始撸码
项目准备
这里模拟两个服务,一个是订单服务,一个是库存服务,两都用到EF(Code First),如果小伙伴对EF入门还不熟,<<跟我一起学.NetCore之EF Core 实战入门,一看就会>>这篇文章超详细,肯定能帮到你; 所以接下来就上几张关键的图就行啦。
项目结构:
OrderDbContext:
Startup中注册服务:
库存服务的代码和这个类似。
通过迁移并更新到数据库时,会生成如下数据库和表:
集成CAP
这里因为用的是RabbitMQ、SqlServer,所以需要引入以下几个包;如果用其他消息队列或数据库,可以引入对应的包。
因为订单服务是在Respository层使用CAP,所以对应的包就在这层引用;
库存服务是直接在Controller那层引用,这里就不重复截图啦。
订单服务和库存服务都是在各自项目的Startup文件中注册CAP相关服务,并配置相关信息,如下图:
集成完毕之后,启动项目(不需要手动自己迁移),在各自业务数据库中就自动生成两个消息表,用于后续消息的存储,如下:
编写业务代码
订单服务,在订单生成成功之后,向库存服务发送消息,业务逻辑如下:
图中用到的_capPublisher是通过构造函数注入的。订单服务其他层的代码就不用截图了,就是简单调用,源码地址在文末。
库存服务直接订阅就行,演示案例中是直接在StockController中进行订阅,如下:
// 标记为不实Action
[NonAction]
// 订阅消息,参数和发布时指定的参数一致
[CapSubscribe("Order.Create.Success")]
public void UpdateStock(OrderEntity order)
{
//throw new Exception("扣减库存异常了~~~");
// 为了测试,库存里面没有数据的话,先模拟一条数据
bool bHaveData = _stockDbContext.Stock.Any();
if(!bHaveData)
{
StockEntity stock = new StockEntity
{
Id = Guid.NewGuid(),
ProductNo = "Product001",
StockCount = 100,
UpdateDate = DateTime.Now
};
_stockDbContext.Stock.Add(stock);
_stockDbContext.SaveChanges();
}
// 模拟扣减库存
using var trans = _stockDbContext.Database.BeginTransaction(_capPublisher, autoCommit: false);
try
{
// 根据产品编号找到产品
var product = _stockDbContext.Stock.Where(s => s.ProductNo == order.ProductNo).FirstOrDefault();
// 扣减库存之后保存
product.StockCount = product.StockCount - order.Count;
_stockDbContext.Update(product);
_stockDbContext.SaveChanges();
// 可以继续向下发布流程,比如库存扣减成功,下一步到物流服务进行相关处理,可以继续发布消息
// _capPublisher.Publish();
trans.Commit();
Console.WriteLine(order.OrderNo);
}
catch (Exception ex)
{
trans.Rollback();
}
}
可以看到,订阅很简单,直接标上[CapSubscribe("Order.Create.Success")]这个Attribute就行了,如果消息状态为失败,后续CAP的定时任务会根据定时策略调用此方法。
1.3.3 运行看效果
正常流程,下单成功,扣减库存成功
将订单服务(端口5000)和库存服务(端口6000)都启动起来。
订单服务中增加了OrderController,里面有一个GenerateOrder的接口,直接调用即可:
这里使用Postman工具进行测试,如下:
库存服务就会订阅到信息,如下:
业务流程完成之后,订单和库存数据整体一致了,回过头来看看消息表,看看里面有什么消息,如下:
异常流程模拟,下单成功,扣减库存失败
在扣减服务逻辑方法中手动抛出异常,代码如下:
然后启动项目重新测试,再下一个订单试试; 操作后,先来看看消息表,如下:
注:CAP在默认情况下,发送和消费消息的过程中失败会立即重试 3 次,在 3 次以后将进入重试轮询;重试将在发送和消费消息失败的 4分钟后 开始,这是为了避免设置消息状态延迟导致可能出现的问题;后续就会每隔1分钟之后重试一次,默认的最高重试次数为50次,当达到50次时,就不会重试了。
现在知道问题了,优化代码,重新启动,即把抛异常的代码注释掉,看看会不会自动处理,如下:
如上图,稍等一会,消息就自动处理了,业务数据符合预期,保证一致性。 这个是CAP内部定时读取消息表,根据状态不断重试业务逻辑,直到成功为止。 CAP的全自动是不是感觉比较便捷,写最少的代码,解决了最难搞的分布式事务。
修改默认的配置
在实际业务场景中,默认配置可能不太实用,可以在注册服务时进行默认配置更改,如下:
配置修改之后的测试这里就不截图了,留给小伙伴们动手试试吧。
案例代码地址:https://gitee.com/CodeZoe/microservies-demo/tree/main/CapDemo
总结
关于分布式事务的实操,把最常用的最终一致性方案简单分享了一下,小伙伴可以根据自己的业务场景,赶紧动手试试吧;
其他方案会在后续的文章中加上,主要还是以实用为主,已经不咋用的就没必要再说啦。
文章中提及到Docker和RabbitMQ,我已经在着手准备这块的文章了,关注“Code综艺圈”,和我一起学习吧;
分布式事务最终一致性-CAP框架轻松搞定的更多相关文章
- 【Shashlik.EventBus】.NET 事件总线,分布式事务最终一致性
[Shashlik.EventBus].NET 事件总线,分布式事务最终一致性 简介 github https://github.com/dotnet-shashlik/shashlik.eventb ...
- 一文教你迅速解决分布式事务 XA 一致性问题
欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:腾讯云数据库团队 近日,腾讯云发布了分布式数据库解决方案(DCDB),其最明显的特性之一就是提供了高于开源分布式事务XA的性能.大型 ...
- 【转】.NET Core 事件总线,分布式事务解决方案:CAP
[转].NET Core 事件总线,分布式事务解决方案:CAP 背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用 ...
- 【转】分布式事务,EventBus 解决方案:CAP【中文文档】
[转]分布式事务,EventBus 解决方案:CAP[中文文档] 最新文档地址:https://github.com/dotnetcore/CAP/wiki 前言 很多同学想对CAP的机制以及用法等想 ...
- 【微服务】之三:从零开始,轻松搞定SpringCloud微服务-配置中心
在整个微服务体系中,除了注册中心具有非常重要的意义之外,还有一个注册中心.注册中心作为管理在整个项目群的配置文件及动态参数的重要载体服务.Spring Cloud体系的子项目中,Spring Clou ...
- 春节过后就是金三银四求职季,分享几个Java面试妙招,轻松搞定HR!
春节过后就是金三银四,分享几个Java面试妙招,轻松搞定HR! 2020年了,先祝大家新年快乐! 今年IT职位依然相当热门,特别是Java开发岗位.软件开发人才在今年将有大量的就业机会.春节过后,金三 ...
- 盘它!基于CANN的辅助驾驶AI实战案例,轻松搞定车辆检测和车距计算!
摘要:基于昇腾AI异构计算架构CANN(Compute Architecture for Neural Networks)的简易版辅助驾驶AI应用,具备车辆检测.车距计算等基本功能,作为辅助驾驶入门级 ...
- 【转】轻松搞定FTP之FlashFxp全攻略
转载网址:http://www.newhua.com/2008/0603/39163.shtml 轻松搞定FTP之FlashFxp全攻略 导读: FlashFXP是一款功能强大的FXP/FTP软件,融 ...
- 【微服务】之六:轻松搞定SpringCloud微服务-API网关zuul
通过前面几篇文章的介绍,我们可以轻松搭建起来微服务体系中比较重要的几个基础构建服务.那么,在本篇博文中,我们重点讲解一下,如何将所有微服务的API同意对外暴露,这个就设计API网关的概念. 本系列教程 ...
随机推荐
- 【带你手撸Spring】没有哪个框架开发,能离开 Spring 的 FactoryBean!
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 老司机,你的砖怎么搬的那么快? 是有劲?是技巧?是后门?总之,那个老司机的代码总是可 ...
- Vsftpd虚拟用户登陆配置(Centos7)
1 安装Vsftpd服务 # yum install vsftpd -y vsftp虚拟用户是为了保证FTP服务器的安全性,由vsftpd服务器提供的非系统用户账号, 相对于FTP的本地用户来说,虚拟 ...
- 4.13、nfs挂载优化及优缺点
1.硬盘:sas/ssd磁盘,买多块,硬件raid5/raid0,网卡吞吐量要大,至少千兆(多网卡bond0) 2.nfs客户端挂载说明: 文件系统有自己的权限,挂载是建立在文件系统之上的,然后更改挂 ...
- Layui 关闭自己刷新父页面
var index = parent.layer.getFrameIndex(window.name); parent.layer.close(index); window.parent.locati ...
- 如何跟领导解释为什么选择SpringCloud alibaba作为微服务开发框架
什么是微服务 提到微服务不得不提Martin Fowler在2014年3月25日发表的文章 Microservices,里面给出了微服务的定义.后续国内所有关于微服务的介绍都是基于这篇文章的翻译,或加 ...
- Binding(五):多路绑定
Binding不止能绑定一个源,它还能绑定多个源,这就是我们这节要讲的多路绑定:MultiBinding. 使用多路绑定跟一般的绑定还是有区别的,首先它并不能很好的在标记扩展中使用,另外,使用多路绑定 ...
- 【spring源码系列】之【Bean的属性赋值】
每次进入源码的世界,就像完成一场奇妙的旅行! 1. 属性赋值概述 上一篇讲述了bean实例化中的创建实例过程,实例化后就需要对类中的属性进行依赖注入操作,本篇将重点分析属性赋值相关流程.其中属性赋值, ...
- 26 bash shell中的信号
当没有任何捕获时,一个交互式 Bash Shell 会忽略 SIGTERM(发送到进程的 TERM 信号用于要求进程终止) 和 SIGQUIT(当用户要求进程执行 core dump 时,QUIT 信 ...
- 《TCP/IP详解 卷1:协议》第4章 ARP:地址解析协议
4.1 引言 本章我们要讨论的问题是只对TCP/IP协议簇有意义的IP地址.数据链路如以太网或令牌环网都有自己的寻址机制(常常为48 bit地址),这是使用数据链路的任何网络层都必须遵从的.一个网络如 ...
- 《基于JQuery和CSS的特效整理》系列分享专栏
<基于JQuery和CSS的特效整理>已整理成PDF文档,点击可直接下载至本地查阅https://www.webfalse.com/read/201724.html 文章 一款基于jQue ...