Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。java
根据上图可知整个TXC模型有三个重要的组件spring
简单理解就是TM事务管理器经过RPC与TC通信请求开启一个全局事务数组
简单理解过程就是: Business做为服务起始方(此时它是TM)发起全局事务并注册到TC。在调用协同服务时,协同服务的事务分支事务会先完成阶段一的事务提交或回滚,并生成事务回滚的undo_log日志,同时注册当前服务到TC并上报其事务状态,归并到同一个业务的全局事务中。此时若没有问题继续下一个服务的调用,期间任何服务的分支事务回滚,都会通知到TC,TC在通知全局事务包含的全部已完成一阶段提交的分支事务回滚。若是全部分支事务都正常,最后回到全局事务发起方时,也会通知到TC,TC在通知全局事务包含的全部分支删除回滚日志。在这个过程当中为了解决写隔离和度隔离的问题会涉及到TC管理的全局锁。服务器
那么全局事务是如何在服务中传递的呢?实际在TM向TC请求开启一个全局事务的时候,TC会响应一个全局事务XID,只须要TM在调用其余协同服务时把XID传递给协同服务,这样就能够实现全局事务在分布式服务中传播,以及分支事务属于哪一个全局事务。框架
Seata目前已经支持许多框架中的XID的自动传递了分布式
用户在使用Seata的时候对于XID的传递彻底是无感知。函数
上文提到Seata中三个重要的组件TC TM RM
.性能
其中TC做为事务协调者, 它负责驱动全局事务的提交与回滚。根据它的职责可知。它的重要性不言而喻。ui
那么做为一个优秀的协调者它须要具有哪些功能呢?this
那么咱们根据咱们的猜想来看看TC的实现模块Server是怎么来实现这写功能的。
整个Server模块能够分红7个主要模块
就一个Server端而言, 它就有7个模块。那么咱们改从何看起呢。
咱们能够用Server启动的main函数来理解清楚整个TC的运行流程
本文全部源码基于Seata1.1.0
我的能力有限,若有不对欢迎指出。
整个Server端是一个java
应用,它是经过java -jar
启动的,因此主入口是一个main函数。
入口地址是io.seata.server.Server#main()
public static void main(String[] args) throws IOException { //一、 参数解析 ParameterParser parameterParser = new ParameterParser(args); //二、 监控初始化 MetricsManager.get().init(); // 三、将存储模式放到系统环境变量÷ System.setProperty(ConfigurationKeys.STORE_MODE, parameterParser.getStoreMode()); // 四、建立与RM TM通信的rpc服务器 RpcServer rpcServer = new RpcServer(WORKING_THREADS); //server port rpcServer.setListenPort(parameterParser.getPort()); UUIDGenerator.init(parameterParser.getServerNode()); //log store mode : file, db // 五、设置资源存储模式 SessionHolder.init(parameterParser.getStoreMode()); // 六、核心事务协调器建立 DefaultCoordinator coordinator = new DefaultCoordinator(rpcServer); coordinator.init(); // 七、把协调器做为一个回调 传给netty rpc模块 rpcServer.setHandler(coordinator); // 八、注册JVM关闭构造函数 ShutdownHook.getInstance().addDisposable(coordinator); ShutdownHook.getInstance().addDisposable(rpcServer); //127.0.0.1 and 0.0.0.0 are not valid here. if (NetUtil.isValidIp(parameterParser.getHost(), false)) { XID.setIpAddress(parameterParser.getHost()); } else { XID.setIpAddress(NetUtil.getLocalIp()); } XID.setPort(rpcServer.getListenPort()); try { // 九、启动RPC模块 监听TM RM的请求 rpcServer.init(); } catch (Throwable e) { LOGGER.error("rpcServer init error:{}", e.getMessage(), e); System.exit(-1); } System.exit(0); }
首先看看参数解析,其实参数解析很简单主要是经过JCommander
解析main函数中的args数组,不过在须要注意的是,因为Seata Server已经支持容器部署, 因此在容器环境启动参数的建立跟正常启动的参数是不一样的。容器部署的启动参数须要经过System.getenv
获取
io.seata.server.ParameterParser#init()
private void init(String[] args) { try { // 判断启动环境是不是容器 boolean inContainer = this.isRunningInContainer(); // 若是是容器启动 则从系统环境变量读取参数配置 if (inContainer) { if (LOGGER.isInfoEnabled()) { LOGGER.info("The server is running in container."); } this.seataEnv = StringUtils.trimToNull(System.getenv(ENV_SYSTEM_KEY)); this.host = StringUtils.trimToNull(System.getenv(ENV_SEATA_IP_KEY)); this.serverNode = NumberUtils.toInt(System.getenv(ENV_SERVER_NODE_KEY), SERVER_DEFAULT_NODE); this.port = NumberUtils.toInt(System.getenv(ENV_SEATA_PORT_KEY), SERVER_DEFAULT_PORT); this.storeMode = StringUtils.trimToNull(System.getenv(ENV_STORE_MODE_KEY)); } else { // 不然使用JCommander 解析启动参数 JCommander jCommander = JCommander.newBuilder().addObject(this).build(); jCommander.parse(args); if (help) { jCommander.setProgramName(PROGRAM_NAME); jCommander.usage(); System.exit(0); } } if (StringUtils.isNotBlank(seataEnv)) { System.setProperty(ENV_PROPERTY_KEY, seataEnv); } if (StringUtils.isBlank(storeMode)) { storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE, SERVER_DEFAULT_STORE_MODE); } } catch (ParameterException e) { printError(e); } }
拿到启动参数后咱们就要根据启动参数依次 启动监控、设置存储模型,建立协调核心对象、启动Rpc服务器。
为何Rpc服务器要在最后一个启动呢? 下篇文章会解答。
因为监控对Seata的核心功能暂无影响因此本文已经后续文章暂不对监控进行分析。
本文简单的介绍了一下Seata Server
模块启动流程的一个分析,了解Seata的启动流程,可是都是比较简单没有深刻,后续会陆续深刻分析Rpc模块与核心协调模块。