深度剖析一站式分布式事务方案Seata(Fescar)-Server

jujusharp  •  Apr 11, 2019 10:46:04 PM

原文地址


再前不久,我写了一篇关于分布式事务中间件Fescar的解析,没过几天Fescar团队对其进行了品牌升级,取名为Seata(Simpe Extensible Autonomous Transcaction Architecture),而以前的Fescar的英文全称为Fast & EaSy Commit And Rollback。可以看见Fescar从名字上来看更加局限于Commit和Rollback,而新的品牌名字Seata旨在打造一套一站式分布式事务解决方案。更换名字之后,我对其未来的发展更有信心。

这里先大概回忆一下Seata的整个过程模型:

  • TM:事务的发起者。用来告诉TC,全局事务的开始,提交,回滚。

  • RM:具体的事务资源,每一个RM都会作为一个分支事务注册在TC。

  • TC:事务的协调者。也可以看做是Fescar-servr,用于接收我们的事务的注册,提交和回滚。

在之前的文章中对整个角色有个大体的介绍,在这篇文章中我将重点介绍其中的核心角色TC,也就是事务协调器。

2.Transcation Coordinator

为什么之前一直强调TC是核心呢?那因为TC这个角色就好像上帝一样,管控着云云众生的RM和TM。如果TC一旦不好使,那么RM和TM一旦出现小问题,那必定会乱的一塌糊涂。所以要想了解Seata,那么必须要了解他的TC。

那么一个优秀的事务协调者应该具备哪些能力呢?我觉得应该有以下几个:

  • 正确的协调:能正确的协调RM和TM接下来应该做什么,做错了应该怎么办,做对了应该怎么办。

  • 高可用: 事务协调器在分布式事务中很重要,如果不能保证高可用,那么他也没有存在的必要了。

  • 高性能:事务协调器的性能一定要高,如果事务协调器性能有瓶颈那么他所管理的RM和TM那么会经常遇到超时,从而引起回滚频繁。

  • 高扩展性:这个特点是属于代码层面的,如果是一个优秀的框架,那么需要给使用方很多自定义扩展,比如服务注册/发现,读取配置等等。

下面我也将逐步阐述Seata是如何做到上面四点。

2.1 Seata-Server的设计

Seata-Server整体的模块图如上所示:

  • Coordinator Core: 在最下面的模块是事务协调器核心代码,主要用来处理事务协调的逻辑,如是否commit,rollback等协调活动。

  • Store:存储模块,用来将我们的数据持久化,防止重启或者宕机数据丢失。

  • Discover: 服务注册/发现模块,用于将Server地址暴露给我们Client。

  • Config: 用来存储和查找我们服务端的配置。

  • Lock: 锁模块,用于给Seata提供全局锁的功能。

  • Rpc:用于和其他端通信。

  • HA-Cluster:高可用集群,目前还没开源。为Seata提供可靠的高可用功能。

2.2 Discover

首先来讲讲比较基础的Discover模块,又称服务注册/发现模块。我们将Seata-Sever启动之后,需要将自己的地址暴露给其他使用者,那么就需要我们这个模块帮忙。


这个模块有个核心接口RegistryService,如上图所示:
  • register:服务端使用,进行服务注册。

  • unregister:服务端使用,一般在JVM关闭钩子,ShutdownHook中调用。

  • subscribe:客户端使用,注册监听事件,用来监听地址的变化。

  • unsubscribe:客户端使用,取消注册监听事件。

  • looup:客户端使用,根据key查找服务地址列表。

  • close:都可以使用,用于关闭Register资源。

如果需要添加自己定义的服务注册/发现,那么实现这个接口即可。截止目前在社区的不断开发推动下,已经有四种服务注册/发现,分别是redis,zk,nacos,eruka。下面简单介绍下Nacos的实现:

2.2.1 register接口:

step1:校验地址是否合法

step2:获取Nacos的Name实例,然后将地址注册到当前Cluster名称上面。

unregister接口类似,这里不做详解。

2.2.2 lookup接口:

step1:获取当前clusterName名字

step2:判断当前cluster是否已经获取过了,如果获取过就从map中取。

step3:从Nacos拿到地址数据,将其转换成我们所需要的。

step4:将我们事件变动的Listener注册到Nacos

2.2.3 subscribe接口


这个接口比较简单,具体分两步:

step1:将clstuer和listener添加进map中。

step2:向Nacos注册。

2.3 Config

配置模块也是一个比较基础,比较简单的模块。我们需要配置一些常用的参数比如:Netty的select线程数量,work线程数量,session允许最大为多少等等,当然这些参数再Seata中都有自己的默认设置。

同样的在Seata中也提供了一个接口Configuration,用来自定义我们需要的获取配置的地方:

  • getInt/Long/Boolean/Config():通过dataId来获取对应的值。

  • putConfig:用于添加配置。

  • removeConfig:删除一个配置。

  • add/remove/get ConfigListener:添加/删除/获取 配置监听器,一般用来监听配置的变更。

目前为止有四种方式获取Config:File(文件获取),Nacos,Apollo,ZK。再Seata中首先需要配置registry.conf,来配置conf的类型。实现conf比较简单这里就不深入分析。

2.4 Store

存储层的实现对于Seata是否高性能,是否可靠非常关键。
如果存储层没有实现好,那么如果发生宕机,在TC中正在进行分布式事务处理的数据将会被丢失,既然使用了分布式事务,那么其肯定不能容忍丢失。如果存储层实现好了,但是其性能有很大问题,RM可能会发生频繁回滚那么其完全无法应对高并发的场景。

在Seata中默认提供了文件方式的存储,下面我们定义我们存储的数据为Session,而我们的TM创造的全局事务数据叫GloabSession,RM创造的分支事务叫BranchSession,一个GloabSession可以拥有多个BranchSession。我们的目的就是要将这么多Session存储下来。

在FileTransactionStoreManager#writeSession代码中:

上面的代码主要分为下面几步:

  • step1:生成一个TransactionWriteFuture。

  • step2:将这个futureRequest丢进一个LinkedBlockingQueue中。为什么需要将所有数据都丢进队列中呢?当然这里其实也可以用锁来实现,再另外一个阿里开源的RocketMQ中,使用的锁。不论是队列还是锁他们的目的是为了保证单线程写,这又是为什么呢?有人会解释说,需要保证顺序写,这样速度就很快,这个理解是错误的,我们的FileChannel其实是线程安全的,已经能保证顺序写了。保证单线程写其实是为了让我们这个写逻辑都是单线程的,因为可能有些文件写满或者记录写数据位置等等逻辑,当然这些逻辑都可以主动加锁去做,但是为了实现简单方便,直接再整个写逻辑加锁是最为合适的。

  • step3:调用future.get,等待我们该条数据写逻辑完成通知。

我们将数据提交到队列之后,我们接下来需要对其进行消费,代码如下:

这里将一个WriteDataFileRunnable()提交进我们的线程池,这个Runnable的run()方法如下:


分为下面几步:

step1: 判断是否停止,如果stopping为true则返回null。

step2:从我们的队列中获取数据。

step3:判断future是否已经超时了,如果超时,则设置结果为false,此时我们生产者get()方法会接触阻塞。

step4:将我们的数据写进文件,此时数据还在pageCahce层并没有刷新到磁盘,如果写成功然后根据条件判断是否进行刷盘操作。

step5:当写入数量到达一定的时候,或者写入时间到达一定的时候,需要将我们当前的文件保存为历史文件,删除以前的历史文件,然后创建新的文件。这一步是为了防止我们文件无限增长,大量无效数据浪费磁盘资源。

在我们的writeDataFile中有如下代码:

step1:首先获取我们的ByteBuffer,如果超出最大循环BufferSize就直接创建一个新的,否则就使用我们缓存的Buffer。这一步可以很大的减少GC。

step2:然后将数据添加进入ByteBuffer。

step3:最后将ByteBuffer写入我们的fileChannel,这里会重试三次。此时的数据还在pageCache层,受两方面的影响,OS有自己的刷新策略,但是这个业务程序不能控制,为了防止宕机等事件出现造成大量数据丢失,所以就需要业务自己控制flush。下面是flush的代码:

这里flush的条件写入一定数量或者写的时间超过一定时间,这样也会有个小问题如果是停电,那么pageCache中有可能还有数据并没有被刷盘,会导致少量的数据丢失。目前还不支持同步模式,也就是每条数据都需要做刷盘操作,这样可以保证每条消息都落盘,但是性能也会受到极大的影响,当然后续会不断的演进支持。

我们的store核心流程主要是上面几个方法,当然还有一些比如,session重建等,这些比较简单,读者可以自行阅读。

0 回复
暂时没有回复,你也许会成为第一个哦