js 网站校验,借个网站备案号,做交互的设计网站,网站域名怎么购买吗前言
在SOA、微服务架构流行的年代#xff0c;许多复杂业务上需要支持多资源占用场景#xff0c;而在分布式系统中因为某个资源不足而导致其它资源占用回滚的系统设计一直是个难点。我所在的团队也遇到了这个问题#xff0c;为解决这个问题上#xff0c;团队采用的是阿里开…前言
在SOA、微服务架构流行的年代许多复杂业务上需要支持多资源占用场景而在分布式系统中因为某个资源不足而导致其它资源占用回滚的系统设计一直是个难点。我所在的团队也遇到了这个问题为解决这个问题上团队采用的是阿里开源的分布式中间件Fescar的解决方案并详细了解了Fescar内部的工作原理解决在使用Fescar中间件过程中的一些疑虑的地方也为后续团队在继续使用该中间件奠定理论基础。
目前分布式事务解决方案基本是围绕两阶段提交模式来设计的按对业务是有侵入分为对业务无侵入的基于XA协议的方案但需要数据库支持XA协议并且性能较低对业务有侵入的方案包括TCC等。Fescar就是基于两阶段提交模式设计的以高效且对业务零侵入的方式解决微服务场景下面临的分布式事务问题。Fescar设计上将整体分成三个大模块即TM、RM、TC具体解释如下
TMTransaction Manager全局事务管理器控制全局事务边界负责全局事务开启、全局提交、全局回滚。RMResource Manager资源管理器控制分支事务负责分支注册、状态汇报并接收事务协调器的指令驱动分支本地事务的提交和回滚。TCTransaction Coordinator事务协调器维护全局事务的运行状态负责协调并驱动全局事务的提交或回滚。
本文将深入到Fescar的RM模块源码去介绍Fescar是如何在完成分支提交和回滚的基础上又做到零侵入进而极大方便业务方进行业务系统开发。
一、从配置开始解读 上图是Fescar源码examples模块dubbo-order-service.xml内的配置数据源采用druid的DruidDataSource但实际jdbcTemplate执行时并不是用该数据源而用的是Fescar对DruidDataSource的代理DataSourceProxy所以与RM相关的代码逻辑基本上都是从DataSourceProxy这个代理数据源开始的。
Fescar采用2PC来完成分支事务的提交与回滚具体怎么做到的呢下面就分别介绍Phase1、Phase2具体做了些什么。
二、Phase1—分支本地事务执行
Fescar将一个本地事务做为一个分布式事务分支所以若干个分布在不同微服务中的本地事务共同组成了一个全局事务结构如下。
那么一个本地事务中SQL是如何执行呢在Spring中本质上都是从jdbcTemplate开始的比如下面的SQL语句
jdbcTemplate.update(update storage_tbl set count count - ? where commodity_code ?, new Object[] {count, commodityCode});
一般JdbcTemplate执行流程如下图所示
由于在配置中JdbcTemplate数据源被配置成了Fescar实现DataSourceProxy进而控制了后续的数据库连接使用的是Fescar提供的ConnectionProxyStatment使用的是Fescar实现的StatmentProxy最终Fescar就顺理成章地实现了在本地事务执行前后增加所需要的逻辑比如完成分支事务的快照记录和分支事务执行状态的上报等等。
DataSourceProxy获取ConnectionProxy:
ConnectionProxy获取StatmentProxy
在获取到StatmentProxy后可以调用excute方法执行sql了
而真正excute实现逻辑如下
首先会检查当前本地事务是否处于全局事务中如果不处于直接使用默认的Statment执行避免因引入Fescar导致非全局事务中的SQL执行性能下降。解析Sql有缓存机制因为有些sql解析会比较耗时可能会导致在应用启动后刚开始的那段时间里处理全局事务中的sql执行效率降低。对于INSERT、UPDATE、DELETE、SELECT..FOR UPDATE这四种类型的sql会专门实现的SQL执行器进行处理其它SQL直接是默认的Statment执行。返回执行结果如有异常则直接抛给上层业务代码进行处理。
再来看一下关键的INSERT、UPDATE、DELETE、SELECT..FOR UPDATE这四种类型的sql如何执行的先看一下具体类图结构
为结省篇幅选择UpdateExecutor实现源码看一下先看入口BaseTransactionalExecutor.execute该方法将ConnectionProxy与Xid事务ID进行绑定,这样后续判断当前本地事务是否处理全局事务中只需要看ConnectionProxy中Xid是否为空。
然后执行AbstractDMLBaseExecutor中实现的doExecute方法
基本逻辑如下
先判断是否为Auto-Commit模式如果非Auto-Commit模式则先查询Update前对应行记录的快照beforeImage再执行Update语句完成后再查询Update后对应行记录的快照afterImage最后将beforeImage、afterImage生成UndoLog追加到Connection上下文ConnectionContext中。注获取beforeImage、afterImage方法在UpdateExecutor类下一般是构造一条Select...For Update语句获取执行前后的行记录同时会检查是否有全局锁冲突具体可参考源码如果是Auto-Commit模式先将提交模式设置成非自动Commit再执行2中的逻辑再执行connectionProxy.commit()方法由于执行2过程和commit时都可能会出现全局锁冲突问题增加了一个循环等待重试逻辑最后将connection的模式设置成Auto-Commit模式
如果本地事务执行过程中发生异常业务上层会接收到该异常至于是给TM模块返回成功还是失败由业务上层实现决定如果返回失败则TM裁决对全局事务进行回滚如果本地事务执行过程未发生异常不管是非Auto-Commit还是Auto-Commit模式最后都会调用connectionProxy.commit()对本地事务进行提交在这里会创建分支事务、上报分支事务的状态以及将UndoLog持久化到undo_log表中具体代码如下图 基本逻辑
判断当前本地事务是否处于全局事务中也就判断ConnectionContext中的xid是否为空。如果不处于全局事务中则调用targetConnection对本地事务进行commit。如果处于全局事务中首先创建分支事务再将ConnectionContext中的UndoLog写入到undo_log表中然后调用targetConnection对本地事务进行commit将UndoLog与业务SQL一起提交最后上报分支事务的状态成功 or 失败并将ConnectionContext上下文重置。
综上所述RM模块通过对JDBC数据源进行代理干预业务SQL执行过程加入了很多流程比如业务SQL解析、业务SQL执行前后的数据快照查询并组织成UndoLog、全局锁检查、分支事务注册、UndoLog写入并随本地事务一起Commit、分支事务状态上报等。通过这种方式Fescar真正做到了对业务代码无侵入只需要通过简单的配置业务方就可以轻松享受Fescar所带来的功能。Phase1整体流程引用Fescar官方图总结如下 三、Phase2-分支事务提交或回滚
阶段2完成的是全局事物的最终提交或回滚当全局事务中所有分支事务全部完成并且都执行成功这时TM会发起全局事务提交,TC收到全全局事务提交消息后会通知各分支事务进行提交同理当全局事务中所有分支事务全部完成并且某个分支事务失败了TM会通知TC协调全局事务回滚进而TC通知各分支事务进行回滚。
在业务应用启动过程中由于引入了Fescar客户端RmRpcClient会随应用一起启动该RmRpcClient采用Netty实现可以接收TC消息和向TC发送消息因此RmRpcClient是与TC收发消息的关键模块。
public class RMClientAT {public static void init(String applicationId, String transactionServiceGroup) {RmRpcClient rmRpcClient RmRpcClient.getInstance(applicationId, transactionServiceGroup);AsyncWorker asyncWorker new AsyncWorker();asyncWorker.init();DataSourceManager.init(asyncWorker);rmRpcClient.setResourceManager(DataSourceManager.get());rmRpcClient.setClientMessageListener(new RmMessageListener(new RMHandlerAT()));rmRpcClient.init();}
}
上述代码展示是的RmRpcClient初始化过程有三个关键类RMHandlerAT、AsyncWorker和DataSourceManager。RMHandlerAT具有了分支提交和回滚两个方法分支提交或回滚的逻辑可以从这里开始看AsyncWorker是一个异步Worker,主要是完成分支事务异步提交的功能具有失败重试功能DataSourceManager对数据源管理和维护。
下面分成两部分来讲分支事务提交、分去事务回滚。
3.1、分支事务提交
在接收到TC发起的全局提交消息后经RmRpcClient对通信协议的处理再交由RMHandlerAT来完成对分支事务的提交分支事务提交从RMHandlerAT.doBranchCommit()开始但最后由AsyncWorker异步Worker完成直接看AsyncWorker中的代码实现
分支事务提交关键逻辑在doBranchCommits方法中
该方法主要是批量删除UndoLog日志但并未使用ConnectionProxy去执行删除SQL可能原因是1、完全没必要 2、考虑效率优先
同样对于分支事务提交也引用Fescar官方一张图来结尾
3.2、分支事务回滚
同样分支事务回滚是从RMHandlerAT.doBranchRollback开始的然后到了dataSourceManager.branchRollback最后完成分支事务回滚逻辑的是UndoLogManager.undo方法。 Overrideprotected void RMHandlerATdoBranchRollback(BranchRollbackRequest request, BranchRollbackResponse response) throws TransactionException {String xid request.getXid();long branchId request.getBranchId();String resourceId request.getResourceId();String applicationData request.getApplicationData();LOGGER.info(AT Branch rolling back: xid branchId resourceId);BranchStatus status dataSourceManager.branchRollback(xid, branchId, resourceId, applicationData);response.setBranchStatus(status);LOGGER.info(AT Branch rollback result: status);}Overridepublic BranchStatus DataSourceManagerbranchRollback(String xid, long branchId, String resourceId, String applicationData) throws TransactionException {DataSourceProxy dataSourceProxy get(resourceId);if (dataSourceProxy null) {throw new ShouldNeverHappenException();}try {UndoLogManager.undo(dataSourceProxy, xid, branchId);} catch (TransactionException te) {if (te.getCode() TransactionExceptionCode.BranchRollbackFailed_Unretriable) {return BranchStatus.PhaseTwo_RollbackFailed_Unretryable;} else {return BranchStatus.PhaseTwo_RollbackFailed_Retryable;}}return BranchStatus.PhaseTwo_Rollbacked;}
UndoLogManager.undo方法源码如下 从上图可以看出整个回滚到全局事务之前状态的代码逻辑集中在如下代码中
AbstractUndoExecutor undoExecutor UndoExecutorFactory.getUndoExecutor(dataSourceProxy.getDbType(), sqlUndoLog);
undoExecutor.executeOn(conn);
首先通过UndoExecutorFactory获取到对应的UndoExecutor然后再执行UndoExecutor的executeOn方法完成回滚操作。目前三种类型的UndoExecutor结构如下
undoExecutor.executeOn源码如下
至此整个分支事务回滚就结束了分支事务回滚整体时序图如下 引入Fescar官方对分支事务回滚原理介绍图作为结尾 综合上述Fescar在Phase2通过UndoLog自动完成分支事务提交与回滚在这个过程中不需要业务方做任何处理业务方无感知因些在该阶段对业务代码也是无侵入的。
四、总结
本文主要介绍了RM模块的相关代码将RM模块按2PC模式分成Phase1和Phase2分别进行介绍从Fescar源码上看整个源码结构清晰有利于研发人员快速学习Fescar的原理。在使用方面只需进行简单的配置就可以享受Fescar带来的便捷功能对业务做到了无侵入同时在性能方面Fescar在分支事务提交过程中采用异步模式减少了全局锁的占用时间进而提升了整体性能。后续将继续学习Fescar的其它模块TM、TC与全局锁的实现逻辑并做相关总结介绍。 原文链接 本文为云栖社区原创内容未经允许不得转载。