CAP定理是由加州大学伯克利分校Eric Brewer教授提出来的,他指出WEB服务无法同时满足一下3个属性:
一致性(Consistency) : 客户端知道一系列的操作都会同时发生(生效)
可用性(Availability) : 每个操作都必须以可预期的响应结束
分区容错性(Partition tolerance) : 即使出现单个组件无法可用,操作依然可以完成
具体地讲在分布式系统中,在任何数据库设计中,一个Web应用至多只能同时支持上面的两个属性。
显然,任何横向扩展策略都要依赖于数据分区。因此,设计人员必须在一致性与可用性之间做出选择。
在分布式系统中,我们往往追求的是可用性,它的重要程序比一致性要高,那么如何实现高可用性呢? 前人已经给我们提出来了另外一个理论,就是BASE理论,它是用来对CAP定理进行进一步扩充的。BASE理论指的是:
Basically Available(基本可用)
Soft state(软状态)
Eventually consistent(最终一致性)
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。
事务是由一组操作组成的一个工作单元。怎么去理解这个问题呢?
我们从现实生活中去理解
那么事务有哪些特性呢?
事务特性
原子性:事务内部的一组操作要么同时成功,要么同时失败
隔离性:不同事务之间是互相不影响的
一致性:事务内部一组操作,各自操作产生的结果数据,要能够保证都是预期的状态
持久性:事务内部一组操作,各个操作产生的数据要能够持久的效应
本地事务就是由一组sql语句操作的集合,
本地事务主要就是指sql语句的操作
分布式事务就是一组服务操作的集合
例如:在分布式系统或者微服务系统内,完成一个任何,需要涉及到多个服务来共同完成,这一组服务操作组成的集合,就是分布式事务
我们从真实的项目场景中出发,例如我们现在学习的微服务架构,那么我们现在来研究一下为什么要使用分布式事务
目前我们的项目已经集成了4个微服务架构技术,那么我们目前来看一下,他们之间的调用逻辑
现在有这样一个需求,我想通过在团队聚合微服务里面添加团队信息和成员信息
如果在添加团队信息和成员信息,如果添加团队信息成功了,成员信息失败了,那么这个时候,就会出现团队添加成功,而成员信息就没有添加成功,是吧。那么,这个问题该如何解决呢?
方案,就是分布式事务。
那么分布式事务是如何解决的呢,
优点: 尽量保证了数据的强一致,适合对数据强一致要求很高的关键领域。(其实也不能100%保证强一致)
缺点: 实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。它分为三个阶段:
Try 阶段主要是对业务系统做检测及资源预留
Confirm 阶段主要是对业务系统做确认提交,Try阶段执行成功并开始执行 Confirm阶段时,默认 Confirm阶段是不会出错的。即:只要Try成功,Confirm一定成功。
Cancel 阶段主要是在业务执行错误,需要回滚的状态下执行的业务取消,预留资源释放。
优点: 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些
缺点: 缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,耦合高
这种方案遵循BASE理论,采用的是最终一致性
优点: 一种非常经典的实现,避免了分布式事务,实现了最终一致性。
缺点: 消息表会耦合到业务系统中
该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,
然后由 Saga 工作流引擎负责协调,
如果整个流程正常结束,那么就算是业务成功完成,
如果在这过程中实现失败,那么Sagas工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。
目前方案有很多,目前事务分为两类,一种是刚性事务,一种是柔性事务
就是完全遵守事务4大特性的分布式事务 ----- 主要体现在一致性(强一致性)
cap理论
2阶段提交
3阶段提交
就是不完全遵守事务4特性的分布式事务-----主要体现在一致性(不完全一直,最终一致性)
Base理论
特性
可查询操作:服务操作具有全局唯一标识,操作唯一确定的时间
幂等操作:重复调用多次产生的业务结果与调用一次产生的结果相同,一是通过业务操作实现幂等性,二是系统缓存所有请求与处理结果,最后是检测到重复请求之后,自动返回之前的处理结果。
Tcc分布式事务
Saga分布式事务
本地消息表
1、准备阶段 prepare
2、提交阶段 commit
优点
保证了数据的强一致性,适合对数据强一致性要求很高的关键领域
缺点
1、同步阻塞 性能问题
2、数据一致性问题
3、单点故障
4、实现复杂,牺牲了可用性,对性能影响较大,不适合高并发高性能场景
场景
同服务不同数据库(单体架构)
1、确认阶段 canCommit
2、准备阶段 preCommit
3、提交阶段 do commit
优点
避免了单点问题,避免了阻塞问题
缺点
1、状态不一致
场景
同服务不同数据库(单体架构)
有3个状态, 未确认状态,确认状态,取消未确认状态
每个子事务都有各自的协调器,
最终一致性,有 人工处理和重试处理
TCC通信用rpc,
优点
1.解决了跨服务的业务操作原子性问题,例如组合支付,订单减库存等场景非常实用
2.TCC的本质原理是把数据库的二阶段提交上升到微服务来实现,从而避免了数据库2阶段中锁冲突的长事务低性能风险。
3.TCC异步高性能,它采用了try先检查,然后异步实现confirm,真正提交的是在confirm方法中。
缺点
1.对微服务的侵入性强,微服务的每个事务都必须实现try,confirm,cancel等3个方法,开发成本高,今后维护改造的成本也高。
2.为了达到事务的一致性要求,try,confirm、cancel接口必须实现等幂性操作。
(定时器+重试)
3.由于事务管理器要记录事务日志,必定会损耗一定的性能,并使得整个TCC事务时间拉长,建议采用redis的方式来记录事务日志。
场景:微服务
缺点
协调器集中太多逻辑的风险
Saga模式很难调试,特别是涉及许多微服务时。此外,如果系统变得复杂,事件消息可能变得难以维护。
Saga模式的另一个缺点是它没有读取隔离。例如,客户可以看到正在创建的订单,但在下一秒,订单将因补偿交易而被删除
场景:微服务
主要用在微服务
1、Eventuate-Tram-Saga JDBC/JPA的java微服务的Saga框架
目前c# net没有支持的客户端,不好使用
2、ServiceComb Pack
是华为开源的一个微服务框架 后进入Apache软件基金会孵化,
servicecomb是华为开源的一个微服务框架,后进入Apache软件基金会孵化,现已毕业,是apache顶级开源项目,而servicecomb-pack是servicecomb孵化的三个子项目之一,是分布式事务最终一致性解决方案,0.3.0版本之前叫saga,现改名为servicecomb-pack,支持saga和tcc两种分布式事务协议,相关理论知识请移步(传送门),本文主要介绍saga模式
ServiceComb Pack 架构是由 alpha 和 omega组成,其中:
ServiceComb Pack微服中如何解决事务问题
微服务中servicecomb-pack-csharp-omega 运行原理
1、微服务项目
2、ServiceComb Pack saga事务框架alpha,协调器
3、servicecomb-pack-csharp-omega_v0.1,saga事务框架客户端omega,客户端
4、mysql或者PostgreSQL
1、微服务项目操作
1.1 启动微服务项目,正确响应出结果
2、alpha操作
2.1 环境准备
2.2 ServiceComb Pack 启动 (连接mysql为例)
2.2.1 PostgreSQL下启动ServiceComb Pack
\alpha\apache-servicecomb-pack-distribution-0.5.0-bin 使用cmd
java -D"spring.profiles.active=prd" -D"spring.datasource.url=jdbc:postgresql://localhost:5432/saga?useSSL=false" -D"spring.datasource.username=root" -D"spring.datasource.password=root" -jar alpha-server-0.5.0-exec.jar
2.2.2 mysql配置
1、在apache-servicecomb-pack-distribution-0.5.0-bin目录下创建插件目录
plugins文件夹
2、 在plugins文件夹内部添加mysql驱动
mysql-connector-java-8.0.15.jar
3、mysql下启动ServiceComb Pack
切换到什么目录 \alpha\apache-servicecomb-pack-distribution-0.5.0-bin 使用cmd
java -D"spring.profiles.active=mysql" -D"loader.path=./plugins" -D"spring.datasource.url=jdbc:mysql://localhost:3306/saga?useSSL=false&serverTimezone=Asia/Shanghai" -D"spring.datasource.username=root" -D"spring.datasource.password=root" -jar alpha-server-0.5.0-exec.jar
4、查询mysql saga数据库
事务相关表信息
txexvent- 事件表详情
txtimeout-超时表详情
compenste-补偿表详情
3、omega操作
3.1 环境准备
servicecomb-pack-csharp-omega_v0.1
下载地址:https://github.com/OpenSagas-csharp/servicecomb-pack-csharp#servicecomb-pack-csharp
https://github.com/OpenSagas-csharp/servicecomb-pack-csharp
3.2 omega配置
1、在RuanMou.MicroService下面创建解决方案文件夹
omega文件夹
2、在omega文件下面从导入项目
3、在AggregateService,MemberService,TeamService下分别引入
4、在AggregateService,MemberService,TeamService中从Servicecomb.Saga.Omega.Core中复制AssemblyInfo.cs 到根目录下
AssemblyInfo.cs
5、在AggregateService,MemberService,TeamService项目startup.cs引入
public void ConfigureServices(IServiceCollection services)
{
// 7、注册saga分布式事务
services.AddOmegaCore(option =>
{
option.GrpcServerAddress = "localhost:8080"; // 1、协调中心地址
option.InstanceId = "AggregateService-1";// 2、服务实例Id
option.ServiceName = "AggregateService";// 3、服务名称
});
services.AddControllers();
}
6、在AggregateService项目AggregateController.cs文件中添加SagaStart
///
/// 添加团队和成员信息
///
///
[HttpPost, SagaStart]
public ActionResult Post(string value)
{
Console.WriteLine($"添加团队信息和成员信息");
// 1、添加团队
var team = new Team() { Name = "研发"};
teamServiceClient.InsertTeams(team);
// 2、添加团队成员
var member = new Member() { FirstName ="tony",NickName="tony-1", TeamId = team.Id};
memberServiceClient.InsertMembers(member);
return Ok("添加成功");
}
7、在MemberService项目中MemberServiceImpl.cs文件下添加
///
/// saga事务参与者 Compensable撤销业务 逻辑
///
///
[Compensable(nameof(DeleteMember))]
public void Create(Member member)
{
memberRepository.Create(member);
}
void DeleteMember(Member member)
{
memberRepository.Delete(member);
}
8、在TeamService项目中TeamServiceImpl.cs文件下添加
///
/// saga事务参与者 Compensable撤销业务 逻辑
///
///
[Compensable(nameof(DeleteTeam))]
public void Create(Team team)
{
teamRepository.Create(team);
}
///
/// 撤销方法
///
///
void DeleteTeam(Team team)
{
teamRepository.Delete(team);
}
全部执行成功的情况下面
1、TeamService服务异常
1.1 代码配置
///
/// saga团队提交和撤销
///
///
[Compensable(nameof(DeleteCompensable))]
public void Create(Team team)
{
teamRepository.Create(team);
// 1、异常情况测试
throw new Exception("TeamService出现异常");
}
///
/// 撤销方法
///
///
void DeleteCompensable(Team team)
{
teamRepository.Delete(team);
}
·1.2 效果展示
2、MemberServce服务异常
2.1 在 MemberServce服务MemberServceImpl.cs中添加
///
/// saga事务参与者 Compensable撤销业务逻辑
///
///
[Compensable(nameof(DeleteMember))]
public void Create(Member member)
{
memberRepository.Create(member);
// 1、异常情况测试
throw new Exception("MemberService出现异常");
}
void DeleteMember(Member member)
{
memberRepository.Delete(member);
}
2.2 效果展示
3、AggregateService服务异常
3.1 代码配置
///
/// 添加团队和成员信息
///
///
[HttpPost, SagaStart]
public ActionResult Post(string value)
{
Console.WriteLine($"添加团队信息和成员信息");
// 1、添加团队
var team = new Team() { Name = "研发"};
teamServiceClient.InsertTeams(team);
// 2、添加团队成员
var member = new Member() { FirstName ="tony",NickName="tony-1", TeamId = team.Id};
memberServiceClient.InsertMembers(member);
// 3、添加异常
throw new Exception("AggregateService服务异常");
return Ok("添加成功");
}
3.2 效果展示
1、AggregateService超时
1.1 代码配置
///
/// 添加团队和成员信息
///
///
[HttpPost, SagaStart(TimeOut =5)]
public ActionResult Post(string value)
{
Console.WriteLine($"添加团队信息和成员信息");
// 1、添加团队
var team = new Team() { Name = "研发"};
teamServiceClient.InsertTeams(team);
// 2、添加团队成员
var member = new Member() { FirstName ="tony",NickName="tony-1", TeamId = team.Id};
memberServiceClient.InsertMembers(member);
// 3、节点超时
Thread.Sleep(10);
return Ok("添加成功");
}
1.2 结果展示
步骤
1、ServiceComb Pack 实例1
切换到什么目录 \apache-servicecomb-pack-distribution-0.5.0-bin 使用cmd
java -D"spring.profiles.active=mysql" -D"loader.path=./plugins" -D"spring.datasource.url=jdbc:mysql://localhost:3306/saga?useSSL=false&serverTimezone=Asia/Shanghai" -D"spring.datasource.username=root" -D"spring.datasource.password=root" -D"alpha.server.port=8080" -D"server.port=8090" -jar alpha-server-0.5.0-exec.jar
2、ServiceComb Pack 实例2
切换到什么目录 \apache-servicecomb-pack-distribution-0.5.0-bin 使用cmd
java -D"spring.profiles.active=mysql" -D"loader.path=./plugins" -D"spring.datasource.url=jdbc:mysql://localhost:3306/saga?useSSL=false&serverTimezone=Asia/Shanghai" -D"spring.datasource.username=root" -D"spring.datasource.password=root" -D"alpha.server.port=8080" -D"server.port=8090" -jar alpha-server-0.5.0-exec.jar
3、ServiceComb Pack 实例2
切换到什么目录 \apache-servicecomb-pack-distribution-0.5.0-bin 使用cmd
java -D"spring.profiles.active=mysql" -D"loader.path=./plugins" -D"spring.datasource.url=jdbc:mysql://localhost:3306/saga?useSSL=false&serverTimezone=Asia/Shanghai" -D"spring.datasource.username=root" -D"spring.datasource.password=root" -D"alpha.server.port=8080" -D"server.port=8090" -jar alpha-server-0.5.0-exec.jar
4、分别连接到不同的集群进行操作数据库
条件
1、consul
步骤
1、配置consul
java -D"spring.profiles.active=mysql" -D"loader.path=./plugins" -D"spring.datasource.url=jdbc:mysql://localhost:3306/saga?useSSL=false&serverTimezone=Asia/Shanghai" -D"spring.datasource.username=root" -D"spring.datasource.password=root" -D"spring.cloud.consul.host=http://127.0.0.1" -D"spring.cloud.consul.port=8500" -D"spring.cloud.consul.enabled=true" -jar alpha-server-0.5.0-exec.jar
2、consul客户端查看