思考背景
由于最近的项目有使用了腾讯的一套开源微服务框架 tars 做游戏服,该框架提供了一种 Devops 的开发理念,基于微服务的框架,我们在一开始设计游戏业务的时候,就可以将玩家业务拆得比较细,比如拆成角色基础信息、钱包、背包、房间等等,然后让其分布到不同的微服务进程上分摊计算压力,来提升整体系统的并发能力,同时该开源框架也提供完善的开发运维管理后台 tarsweb,从框架层面很好地支持快速开发、持续集成、快速部署以及扩容缩容,通过 tarsweb 后台,我们可以便捷高效地管理成千上万的微服务进程。
在其框架带来的便捷的同时,也带来了一些让我感觉不便的地方,尽管框架层提供便捷的RPC机制,让一些远程函数封装成由于本地调用,但由于调用了多个业务系统才能完成本次业务的完整操作,而这些业务系统都分布在不同的微服务进程上执行,如果在执行过程中某个业务系统出现故障或者条件不满足情况下,对于一些已执行成功的业务就需要考虑该业务数据回滚的问题,这就迫使我们需要在这种分布式的场合上思考分布式事务的问题。
微服务框架与传统游戏服框架区别?
传统游戏服框架需考虑分布式事务的问题?
在传统非分布式框架下的游戏服下,我们几乎所有的游戏业务都在一个进程内完成,并且底层网络数据包是按顺序执行,可以保证当前业务请求处理过程不被打断,让一个业务请求处理能够保证原子性,我们可以对多个业务系统预先进行try判断,多个前置条件满足后,才调用这些业务系统接口操作数据,最终完成当前这次业务处理,因为在同一个进程里面,所有的业务数据都在内存里面,同时执行没有被打断,只要前置try判断多个业务系统都成功(即条件满足),后续所有的业务系统接口操作必定会成功,所以这种使用场景下,压根无需考虑事务的问题,因为有序的网络包处理以及所需运算的各种业务数据在本地,这已经能够保障这点。
传统游戏服框架在处理异步场合需要考虑事务?
也就是说有一个业务处理进来,需要一个本地文件IO或者远程网络的IO,这些IO处理完毕后,需要回调一个callback处理,很多时候我们为了不希望这种IO堵塞后续的网络包处理(影响服务器吞吐),通常会使用异步处理,就是说触发读写IO后,就不再等待IO,而是继续处理后续网络包,当IO事件完成后,会以异步的形式回调之前注册的callback函数,这种情况下,因为一个业务处理过程就会分布在不同的时间段里面处理,确实会打断我们一个完整业务的执行,破环了业务执行的原子性,这个时候如果我们没有对某些数据进行加锁保护的话,当异步等待IO事件到来之前,数据状态也许被后续的待处理的业务被修改,也就是说数据状态已改变,因为在callback之前数据状态是符合要求的,但在callback执行的时候,数据状态已不符合条件,导致一些业务系统执行失败,当然这里有些情况是可以通过二次校验去判断数据状态是否再次符合要求,再确定是否执行该业务逻辑,但有些情况我们是无法通过简单的二次校验就可以完美解决这种问题,如下面情况:
// 有可能由于异步引起数据不一致性问题的案例
func Test() int {
// 预判 A、B、C 系统在执行 callback 之前是否满足条件
if (!IsSystemA_Enough()) {
return -1
}
if (!IsSystemB_Enough()) {
return -2
}
if (!IsSystemC_Enough()) {
return -3
}
// 消耗A资源成功(A系统前置判断是否足够,所以这里必定能够扣除A资源成功)
Cost_SystemA()
// 这里触发一次异步callback
GetRemoteData(func(data) {
if (IsSystemB_Enough()) { // 二次校验B是否满足条件
Cost_SystemB() // 异步回来: 这里假设满足
} else {
return
}
if (IsSystemC_Enough()) { // 二次校验C是否满足条件
Cost_SystemC()
} else {
return // 异步回来:这里状态被修改,这里不满足
}
HandleSomething(data) // 处理某些业务
})
}
// 通过修改逻辑的调用次序去修复异步引起的数据不一致性问题:修改Cost_SystemA()的调用位置
func Test() int {
// 预判 A、B、C系统是否满足条件
if (!IsSystemA_Enough()) {
return -1
}
if (!IsSystemB_Enough()) {
return -2
}
if (!IsSystemC_Enough()) {
return -3
}
// 这里触发一次异步callback
GetRemoteData(func(data) {
// 等待执行 callback 的时,还需要对 A、B、C 系统进行二次校验
if (!IsSystemA_Enough()) {
return
}
if (!IsSystemB_Enough()) {
return
}
if (!IsSystemC_Enough()) {
return
}
// 通过上面的二次校验,这里执行统一扣除,并且可以确定执行必定成功
// 因为在A、B、C三个业务系统都有前置的二次校验条件是否满足,并且
// 在 callback 过程中,没有其他 io 事件打断
IsSystemA_Enough()
IsSystemB_Enough()
IsSystemC_Enough()
HandleSomething(data) // 处理某些业务
})
}
从上面的伪代码中,我们可以看出异步回来后,B系统满足条件,B系统成功扣除资源,但是C系统没那么幸运,异步回来C资源在二次校验下发现不满足条件,导致无法执行C资源的消耗扣除,这里你就可以发现,这个业务请求执行是失败的,但是A资源已经被扣除,所以说这里即使有二次校验,但是还是无法规避业务执行过程中被中断带来的问题,这里会导致,部分执行成功,部分执行不成功,如果部分不成功,就需要考虑数据回滚的问题,这就不得不考虑事务的问题。
当然上述例子,业务开发人员可以通过将 Cost_SystemA() 写到 callback 里面,可以规避上述被打断带来的问题,但是前提是,需要开发人员对这种异步机制有比较深刻的理解以及去关心这种逻辑先后顺序的问题,也许对于一些对框架还不太了解的开发人员来说,可能就会踩到上述坑,所以我们有时候不得不考虑从框架层面引入事务组件,让业务开发人员少关心更少框架层面的东西。当然这里由于我们是在传统非微服务游戏服框架下,压根也无需考虑事务的问题,因为可以通过调整代码的先后次序就可以解决这个问题,就不应该再引入事务,因为引入事务会让整套服务器框架变得更加复杂。
引入上述的例子,主要想通过这些例子,从具体使用场景的角度,去区分微服务框架和非微服务传统框架的区别。
微服务框架下,我们可以很好地提前规划好业务拆分,其微服务理念是让业务拆分的更加细,让一些业务模块高内聚低耦合,而且这些微服务大部分情况下是无状态,所以它可以满足系统在线上能够快速迭代开发交付、快速持续集成以及便于后期大规模的横向拓展,以应付业务快速增长的需求。
而我们传统的非微服务框架,最多可以按公共业务(工会、排行榜等)、玩家个人业务、战斗业务等很大粒度的业务拆分,比如说玩家个人业务实际上是最重的业务,负责过重度RPG玩法的同学应该知道,玩家对象可以聚合非常多与玩家相关的业务系统,如装备、背包、坐骑、任务等等以及各种深挖付费的养成业务系统,而这些系统在这种传统的框架下是很难业务拆分,而这堆养成业务系统都聚合到一个玩家对象身上,然后在一个业务进程里面处理,这些玩家对象往往会按他们在不同的场景,分配到不同的业务进程上,来分摊场景压力,但是对于非微服务框架而言,不可无条件对等横向扩容,另外比较幸运的是,战斗业务还是比较轻松拆分到战斗进程上处理。
从我个人的开发经历来说,我是已经习惯于这种传统的非微服务框架下,进行业务开发,所以第一次去接触这种微服务框架去开发游戏逻辑业务,感觉非常别扭,主要别扭的点在于:
第一、无状态情况下,你需要每次从远程mysql或者redis拉数据回来做业务运算,再立刻更新回去;
第二、一个业务处理需要涉及多个业务系统时,你需要通过rpc调用远程方法得到运算结果后,继续本地运算;
基于上述两点,除了别扭之外,还有让业务代码看起来是面向过程编程,而不是面向对象编程,代码看起来也显得臃肿起来,这也导致我后续无法忍受,去开发一些类似对象内存可持久化的通用组件,去兼容适配这种写代码不够优雅的情况,主要会让业务开发人员写代码起来舒服很多,同时也无需关注当前业务对象如何跟本地缓存和远程数据库交互的细节,降低开发者心智压力,提高开发效率,至于如何实现这些组件可以参考我另外一篇文章:无状态游戏服框架tars2go–如何做本地缓存摆脱Redis降低IO
基于上述的分析,这种传统非微服务框架的服务器,比较适用于分区玩法的游戏,扩容的方式都是通过开新区去扩容,后期活跃玩家数量下降,通过合服来缩容;而对于做那种全球同服的游戏服来说,显然使用微服务框架有天然的优势,因为它具备大规模快速的扩容缩容的能力,对于传统的非微服务框架,对于这种场合显然有点心有余力不足。
微服务框架确实有很多天然优势,对于游戏业务来说,因为游戏业务跟一些传统的电商金融业务不太一样,但是对于电商金融这类业务,可以是相对相互独立的,比如:订单系统与库存系统是相对独立的系统,它们没有过多的交互,所以从这个角度来看这种微服务框架实际上更加适合做那种业务系统之间依赖程度比较低的业务。
但是,游戏的一些业务系统是相互依赖,联系比较紧密,比如:装备系统与角色系统,如果在非微服务传统框架下,装备系统是聚合于角色身上,装备升级和分解依赖角色等级等一些基础数据,一个完整的角色数据在同一个进程上运算不仅可提高开发效率,而且性能也高,因为它不用与其他进程交互,也不用考虑一些事务锁的问题,但是如果在微服务框架下,角色基础系统和装备系统会被拆分在不同的微服务上,装备系统执行一些装备升级或者分解的业务,就会远程调用角色基础系统相关接口、资源系统相关接口、背包相关接口,这个过程不是在一个业务进程内完成,而是横跨三四个进程去协同才能完成一个装备升级或者分解,所以需要对这几个系统进行操作,要么全部操作成功,要么你全部操作失败,这就需要考虑到分布式事务的问题。
从这里可以看出,微服务框架带来极大好处的同时,也会给我们一些额外的不便以及一些额外的复杂度。
微服务框架是否适用于游戏业务?
从上个话题来分析来说,微服务对于那种重度交互比较实时的RPG游戏来说,确实不是很好用,但不能说不适用,而这种不好用,我也相信可以通过框架层面去解决,比如说业务切割太细,考虑分布式事务的问题,比如说无状态每次需要IO访问外部存储问题,让业务开发效率低下,这些都可以通过框架层面的基础组件去解决,其实我们最终还是需要结合具体游戏类型具体业务的需求,去提前规划好业务是如何拆分,如何从框架层去制作一些基础组件来兼顾我们的系统性能与开发效率,乃至运维效率。
什么是分布式事务?
关于分布式事务的基础理论介绍,不是本文章的主要讨论内容,下面有引用另外一个作者的一片文章,它能用相对通俗易懂的方式去解释分布式事务的一些理论性的问题,个人感觉比较好理解,所以粘贴上来:分布式事务介绍
分布式事务使用场景?为什么需要分布式事务?
上述的一些描述应该也涉及到这点,但是我还不介意继续再举其他业务例子,这次举个例子是跟游戏不相关的,跟金融相关的,比如:支付宝账户钱转到余额宝,支付宝业务和余额宝业务是分布在不同的微服务上处理,它们各自有自己的数据库,现在一个用户操作一个业务,需要将1w从支付宝转到余额宝上,我们需要在调用支付宝业务扣1w操作,同时需要调用余额宝业务加1w操作,由于这2个业务是分布在不同的微服务进程上,我们如何确保要么两个操作都成功,要么两个操作都失败,不可能出现扣1w成功,但是发现余额宝加1w失败,我相信这样的情况用户会极端焦急地将支付宝客服电话打爆还有可能产生各种投诉,所以这种场景我们就需要分布式事务,它可以确保执行这一个转账业务的原子性,也就是说,要么全部成功,要么全部失败。
那全部失败又是一种什么客户体验呢?比如我支付宝扣1w成功了,发现余额宝服务由于某种原因,譬如出现不可服务的情况,导致余额宝无法加1w成功,那么在分布式事务的保驾护航情况下,支付宝这边扣的1w会回滚或者补偿,然后再客户端侧也许会提示:用户系统繁忙中,稍后重试。
这样的流程就会让客户体验好很多,而不是出现上面说那种让用户捉狂的情况。
传统分布式事务有哪些缺点?微服务框架上是否可以避开使用传统分布式事务?
首先我们要稍微了解下传统的分布式事务大概是怎样一个概念:
事务最重要的用途是能够让一组操作要么全部执行成功,要么全部执行失败,确保数据的一致性,所以事务具备ACID,即原子性、一致性、隔离性和持久性,具体解释这不展开。
本地事务与全局事务(分布式事务)有什么区别?
本地事务:也即是说事务由本地进程发起到本地进程提交的整个过程,资源管理器(远程DBMS或者消息管理)始终由本地进程操控管理。本地事务的优点是严格支持ACID特性,高效、可靠、并且状态仅在资源管理器中维护(DB中维护),而且应用编程模型简单,但是无法支持跨进程的事务处理,隔离的最小单位受限于资源管理器,对于跨DB,跨进程的业务处理,无法使用本地事务去确保整个业务执行的安全性。
全局事务:我们也可以认为这是分布式事务,这种全局事务,需要增加一个全局的事务管理器TM,它负责管理全局的事务状态和参与的资源管理器RM,会协同资源一致提交或回滚,一般业务应用会通过TX协议与全局事务管理器TM通讯交互,全局事务管理器TM又通过XA协议与资源管理器通讯交互,XA接口是一个双向的系统接口,全局事务管理器可以与多个资源管理器交互,引入TM这样一个单点进行协调,可以让管理事务跨越多个资源管理器RM和多个应用进程AP,额外说明下,全局事务管理器TM一般使用XA的二阶段协议与数据库进行交互,这样就构成了传统的分布式事务系统(这样说得有点抽象,后续通过作图再细化说明下几个角色的交互关系)
顺便补充:XA协议以及其接口:
XA协议是由X/Open组织提出分布式事务的规范,其规范主要定义全局事务管理器与多个资源管理器之间的交互接口,主流的数据库产品几乎都实现XA接口,分布式系统,从理论上来说,数据状态在同一时刻几乎无法做到一致性的,所以需要引入单点的全局事务管理器作为中介去协调与管理多个数据库资源与多个业务进程。(注:全局事务管理器一般需要通过XA协议与这些通用数据库通讯交互,完成管理和协调工作)
传统的分布式事务有什么明显缺点呢?
分布式事务一般通过两阶段2PC或三阶段3PC去实现全局事务,其很好地确保强一致性。
但是这里的2PC缺点也更加明显:
第一、阻塞等待性能低,因为强事务,每个应用进程因事务都会堵塞等待其他事务的进行;
第二、单点故障,若第一阶段完成后,事务管理器挂掉,就会导致所有相关的应用进程一直堵塞等待,事务无法完成;
第三、有可能出现事务不一致,若全局事务管理器发送第二阶段的提交指令后,由于网络抖动,A应用进程没收到该指令而B进程收到,此时会导致B应用进程可正常提交事务,而A应用进程无法提交;
而来到3PC这里,相对2PC有了一些改善:
增加超时检查机制,从一定程度解决单点故障问题;
主要解决以下场景问题:
场景一:若在第二阶段只收到第一阶段的指令后,事务管理器挂掉,这时应用进程A和B堵塞一定时间超时后,发现自己还依然无法连接上事务管理器,则会自动释放锁;
场景二:若在第二阶段的预提交指令发送后,事务管理器挂了,无法进入第三阶段,也就是说这时应用进程A和B都无法接收第三阶段的提交指令,A和B堵塞一定时间超时后,就触发自己提交事务(即:无需等待事务管理器第三阶段的提交指令通知),因为执行提交之前,预提交指令都收到,说明所有应用进程都已准备好,这时超时提交事务是相对合法的,这里的合法情况是指应用进程收到预提交指令为前提,所以这里又暴露3PC的另外一个缺点:
第四、若有网络抖动,导致事务管理器发送的预提交指令只有部分应用进程收到,部分没收到,这时刚好遇到事务管理器崩溃了,收到预提交指令的应用进程由于堵塞一段时间后触发超时提交事务的机制,但是如果没收到预提交指令的应用进程,则无法享受这种超时提交事务的机制,直接会导致事务不一致的问题。
从上述的四点来看,这种传统的分布式事务,2PC或者3PC的局限性在于协议沟通成本、准备阶段的持久成本、全局事务状态的持久成本,潜在的可能出现的故障点多,其带来的整套分布式事务系统的复杂性和脆弱性,不管怎样,都会给性能和并发带来瓶颈,以及全局事务管理器单点问题,也大大增加了数据不一致性概率。
基于传统的分布式事务,存在那么多缺点以及复杂性,那么有什么其他替代方案可以让微服务框架下,确保数据的一致性呢?
所以,
有了TCC方案:
这时一个先 try 尝试从资源扣除,如果执行业务成功则 commit 操作,如果连 try 预尝试扣资源都不成功则执行 cancel 操作,其最大的优势就是不会堵塞除自己以外的其他事务,因为它提前冻结了所需的资源,其他事务无需与它参与公共资源的竞争,其他事务就无需堵塞,所以在并发这块会比2PC或3PC要好不少,但是不代表自己内部事务不需要堵塞。
这个方案我认为是 2PC或者3PC 在并发上有一定改良的版本,重点改良的地方在于提高了其他业务的并发性,但是又带来了新的缺点,比如:增加业务的复杂度,在冻结资源、解冻/补偿资源、消费资源操作上,是需要程序员去开发这部分额外的业务逻辑去处理,所以对于一些旧改的项目非常不友好,对原有代码的入侵性非常强,对于原有庞大的代码量的项目,早已劝退尝试使用该方案的开发者。
这里对TCC的方案只是一言带过,对其依然感兴趣的同学,可以继续去收集一下其他关于TCC的学习资料。
我们继续寻找一些合适我们的方案,在寻找其他替代方案之前,我们需要了解下述两个跟分布式相关的理论:
BASE理论:
**BA:**表示业务基本可用,支持分区失败;
**S:**表示柔性状态,允许短时间内不同步,不一致;
**E:**表示最终一致性,也就是说实时上无法做到一致,但是在未来某个时间点会一致;
CAP定理:
**C:**表示一致性
**A:**表示可用性
**P:**表示分区容错性
这个定理尝试约束的是,对于一些共享数据的分布式系统中,最多只能同时满足CAP中的2个,不可能三种情况下都满足。
基于上述理论或者定理,我们可提出一些折中的事务处理方案,来解决传统分布式事务在数据库性能和处理能力上暴露的瓶颈问题,而这里的一些折中方案称之为柔性事务解决方案:
柔性事务有两个特性:
第一:基本可用;(也即是 BA,分布式系统允许损失一部分可用性)
第二:最终一致性;(也就是说不要求强一致性或弱一致性,允许系统存在短暂时间内的不一致性,而这短暂的状态并不影响整体系统的可用性)
这里提出的柔性事务解决方案是:基于可靠消息最终一致性方案
柔性事务方案的实现:可靠消息服务一致性方案
在可靠消息最终一致性方案,在实现上又分为以下几种:
第一:本地消息服务方案;
本地消息这种方案,主要根据具体业务将发送消息的可靠性依托在本地消息的存储上,也就是说,主动方执行业务成功后,在发送消息给MQ之前,还需要将该消息存储到本地对应的数据库中(通常是本地存储业务相关的DB),然后业务被动方通过监听MQ消费该消息后,执行完毕相关业务后,会远程调用主动方提供的rpc接口来完成消息的确认,确认后,就可以将之前存储在主动方这边的消息从DB中删除,否则超过一定时间后,主动方会认为被动方还未收到消息,于是重新执行消息恢复,即消息会从主动方的定时器重新从DB中拉取消息发送到MQ,尝试让被动方重新接受该消息(相当于利用DB中消息的备份,确保消息务必能通知到被动方);
当然被动方在通过rpc接口完成消息确认的同时,还可以携带一些参数来告知主动方,被动方执行业务是否成功,如果失败,主动方可以调用相关的业务回滚之前的业务操作,当然这部分逻辑是需要程序员额外开发。
从上述流程来看,是通过一种本地持久化的消息,以及消息的恢复机制来确保消息的投递的可靠性,即能确保消息能通知到被动方,当然这个通知过程中,如果被动方挂掉,无法通过rpc比较及时地返回确认消息给主动方,当然主动方会有消息恢复机制反复尝试,去确保消息能投递给被动方,但是这个过程当中,我们那可以看出主动方与被动方的业务进程,会在这个期间内数据是无法做到一致性,但是一定时间后,被动方恢复后,即可正常接受来自主动方的消息后,被动方完成业务处理后,那么就可以让主动方和被动方数据在未来的某一刻完成数据一致性,这就是我们经常说的最终一致性。
从上述分析过程中,我们可以通过这种异步的、松耦合、滞后的方式,去实现这种最终会一致的柔性折中的方案,我们在大部分应用场合使用这种柔性方案(除了一些对并发要求不高,但需求是强一致性的场合,还是需要使用传统的分布式事务方案),去替代以XA协议接口去实现传统分布式事务的方案,因为以引入XA协议去实现分布式事务,带来非常多的局限性,包括但不限于引入更加多的复杂性,比如:
第一、要求业务逻辑操作资源(大部分是数据存储的地方)需要支持XA协议,但并非所有资源方都支持XA协议,如一些项目内部实现的存储方案等;
第二、2PC/3PC提交协议各种堵塞的成本;
第三、持久化等DTP模型的成本,如:全局锁定、高成本、低性能,这些情况都是严重影响系统并发的能力;
综上所述,我们可以看出通过可靠消息来确保最终数据最终一致性的柔性方案,相比于使用XA协议接口实现的分布式事务而言,优点是非常明显的。
这里主要阐述了可靠消息来确保最终一致性,但这也仅仅是其中一种实现方案,下面还阐述了其他两种实现方案,大家可以做下对比,加深对可靠消息来如何确保一致性的理解。
第二:独立消息服务方案;
从上图来看,相对于本地消息服务方案,我们可以很轻松看出,原来的本地消息从主动方的内部,独立抽出来成为一个通用的消息服务子系统,其囊括了消息存储、消息恢复等功能,这个子系统也有对应独立消息数据库,相对于本地消息方案,这个方案将可靠消息功能模块彻底从具体业务里面解藕出来,可以作为一个公共模块,为各种跨业务进程的提供可靠消息机制。
整个消息如何确保一致性的运作流程,流程跟本地消息服务方案大同小异,相比于本地消息服务方案,存在差异的地方有:主动方在执行业务之前会预发送消息到这个消息服务子系统,先存储起来,当主动方将业务正确执行完毕后,主动方又通知消息服务子系统,将之前的预发送消息状态改成 “可发送” ,然后再马上将该消息发送到实时消息服务中,待被动方去消费该消息,被动方消费该消息并且正常执行业务后,就往实时消息服务中发送ACK确认消息,除了删除实时消息服务中的消息,同时还通知或者调用消息服务子系统,将该子系统里面的这条预消息状态修改成“被消费”状态。
其实,整理流程可以参考上一个本地消息服务方案,这里就不展开详细说明了。
第三:事务消息中间件服务方案;
这个方案需要依赖中间件的MQ需要支持事务消息,也即是说发送出去的消息缓存在MQ中,当满足条件的条件的时候,消息才可以被消费出去,否则就会从MQ中撤回,也就是所谓的事务消息,有点跟数据库的事务类似,支持提交和回滚,这里推荐使用 RocketMQ,因为其比较好支持这种事务消息,具体方案流程图如下:
主动方在执行业务之前,可以预发送消息到 RocketMQ 中(这个时候消息还不可被消费),当主动方正确执行业务后,主动方会发送确认消息,告知 RocketMQ 之前预发送的消息可以被消费了,这个时候被动方就可以消费到该消息,然后执行被动方相关的业务,当然执行后的结果可通过RPC回调告知主动方,来决定是否需要执行相关撤回操作。
还有另外一点需要说明的是,RocketMQ 这里为了确保主动方能正常发送消息,防止主动方发送了预消息后挂掉,RocketMQ 会每隔一段反复咨询主动方是否可以发送确认消息,防止之前预消息一直堵塞在 RocketMQ 中,也可以继续执行 RocketMQ 挂掉之前的本该执行的业务逻辑,同时,RocketMQ 还会检查投递出去的消息是否返回确认消息,来确保消息是否正确投递成功,否则一直尝试投递,通过这种机制去确保主动方和被动方之间的消息交互是可靠的。
总结
通过上述的分析,我们可以总结出分布式事务的一些实现方案的利弊:
第一、2PC和3PC属于强一致性方案,通过使用全局事务管理器将多个单个业务的事务协调在一起,但是由于各种堵塞,甚至影响到其他事务的并发处理,来达到其强一致性的效果,但明显降低整体系统对外的并发处理能力,由于该方案是通过数据库自带的事务回滚,我们在业务中无需做额外的业务回滚逻辑,也算是其中一个优点;
第二、TCC也算是一种强一致性方案,我认为它是在2PC或3PC上的一种改进,其通过预先冻结所需资源,让其无需与其他事务,竞争公共资源,也就是说其他业务无需堵塞等待资源,可以大大提高系统的整体并发能力,但是这个方案需要我们我们在业务层面实现资源冻结、事务回滚等逻辑,虽然可以让事务控制的粒度由开发人员把控,但是,又增加开发人员的工作量,而且对旧项目的改造入侵性非常强,改动非常大;
第三、可靠消息服务最终一致性方案,应该是我们极力推荐的方案,相比于TCC方案,在同一个分布式事务上,各个业务进程里面的子事务无需相互等待堵塞,业务进程之间通过消息的异步与解藕方式,大大提高整体系统对外的并发处理能力,但是这个方案的一些回滚业务还是需要程序员开发,这种方案如果封装优雅,相信是最好的方案,我后续打算会参考一些 seata-golang 开源方案,或者基于这开源方案,实现该柔性事务方案,并且应用到具体的分布式业务逻辑当中,并且将组件和例子开源出来。
还有计划基于怎样使用 seata-golang 具体怎样使用,怎样集成到 tarsgo 开源项目里,并且通过优雅的方式使用起来,基于这些点,我会后续计划编写另外一篇关于这个话题的文章。
关于分布式事务这个话题,基于个人知识面的问题,也许有些地方阐述得还不全面或不足,欢迎大家耐心阅读后指出来,后面我将会根据大家指出的点,再持续完善该文章。