Seata 是一款开源的分布式事务解决方案,star高达24000+,社区活跃度 极高,致力于在微服务架构下提供高性能和简单易用的分布式事务服务.
目前Seata的分布式事务数据存储模式有file,db,redis,而本篇文章将Seata-Server Raft模式的架构,部署使用,压测对比,及为什么Seata需要Raft,并领略从调研对比,设计,到具体实现,再到知识沉淀的过程.
分享人:陈健斌(funkye) github id: funky-eyes
2. 架构介绍
2.1 Raft 模式是什么?
首先需要明白什么是raft分布式一致性算法,这里直接摘抄sofa-jraft官网的相关介绍:
RAFT 是一种新型易于理解的分布式一致性复制协议,由斯坦福大学的 Diego Ongaro 和 John Ousterhout 提出,作为 RAMCloud 项目中的中心协调组件。Raft 是一种 Leader-Based 的 Multi-Paxos 变种,相比 Paxos、Zab、View Stamped Replication 等协议提供了更完整更清晰的协议描述,并提供了清晰的节点增删描述。 Raft 作为复制状态机,是分布式系统中最核心最基础的组件,提供命令在多个节点之间有序复制和执行,当多个节点初始状态一致的时候,保证节点之间状态一致。
简而言之Seata的Raft模式就是基于Sofa-Jraft组件实现可保证Seata-Server自身的数据一致性和服务高可用.
2.2 为什么需要raft模式
看完上述的Seata-Raft模式是什么的定义后,是否就有疑问,难道现在Seata-Server就无法保证一致性和高可用了吗?那么下面从一致性和高可用来看看目前Seata-Server是如何做的.
2.2.1 现有存储模式
在当前的 Seata 设计中,Server 端的作用是保证事务的二阶段被正确执行。然而,这取决于事务记录的正确存储。为确保事务记录不丢失,需要在保持状态正确的前提下,驱动所有的 Seata-RM 执行正确的二阶段行为。那么,Seata 目前是如何存储事务状态和记录的呢?
首先介绍一下 Seata 支持的三种事务存储模式:file、db 和 redis。根 据一致性的排名,db 模式下的事务记录可以得到最好的保证,其次是 file 模式的异步刷盘,最后是 redis 模式下的 aof 和 rdb
顾名思义:
- file 模式是 Seata 自实现的事务存储方式,它以顺序写的形式将事务信息存储到本地磁盘上。为了兼顾性能,默认采用异步方式,并将事务信息存储在内存中,确保内存和磁盘上的数据一致性。当 Seata-Server(TC)意外宕机时,在重新启动时会从磁盘读取事务信息并恢复到内存中,以便继续运行事务上下文。
- db 是 Seata 的抽象事务存储管理器(AbstractTransactionStoreManager)的另一种实现方式。它依赖于数据库,如 PostgreSQL、MySQL、Oracle 等,在数据库中进行事务信息的增删改查操作。一致性由数据库的本地事务保证,数据也由数据库负责持久化到磁盘。
- redis 和 db 类似,也是一种事务存储方式。它利用 Jedis 和 Lua 脚本来进行事务的增删改查操作,部分操作(如竞争锁)在 Seata 2.x 版本中全部采用了 Lua 脚本。数据的存储与 db 类似,依赖于存储方(Redis)来保证数据的一致性。与 db 类似,redis 在 Seata 中采用了计算和存储分离的架构设计.
2.2.2 高可用
高可用简单理解就是集群能够在主节点宕机后继续正常运行,常见的方式是通过部署多个提供相同服务的节点,并通过注册中心实时感知主节点的上下线情况,以便及时切换到可用的节点。
看起来似乎只需要加几台机器进行部署,但实际上背后存在一个问题,即如何确保多个节点像一个整体一样运作。如果其中一个节点宕机,另一个节点能够完美接替宕机节点的工作,包括处理宕机节点的数据。解决这个问题的答案其实很简单,在计算与存储分离的架构下,只需将数据存储在共享的存储中间件中,任何一个节点都可以通过访问该公共存储区域获取所有节点操作的事务信息,从而实现高可用的能力。
然而,前提条件是计算与存储必须分离。为什么计算与存储一体化设计不可行呢?这就要说到 File 模式的实现了。如之前描述的,File 模式将数据存储在本地磁盘和节点内存中,数据写操作没有任何同步,这意味着目前的 File 模式无法实现高可用,仅支持单机部署。作为初级的快速入门和简单使用而言,File 模式适用性较低,高性能的基于内存的 File 模式也基本上不再被生产环境使用。
2.3 Seata-Raft是如何设计的呢?
2.3.1 设计原理
Seata-Raft模式的设计思路是通过封装无法高可用的file模式,利用Raft算法实现多个TC之间数据的同步。该模式保证了使用file模式时多个TC的数据一致性,同时将异步刷盘操作改为使用Raft日志和快照进行数据恢复。
在Seata-Raft模式中,client端在启动时会从配置中心获取当前client的事务分组(例如default)以及相关Raft集群节点的IP地址。通过向Seata-Server 的控制端口发送请求,client可以获取到default分组对应的Raft集群的元数据,包括leader、follower和learner成员节点。然后,client会监视(watch)非leader节点的任意成员节点。
假设TM开始一个事务,并且本地的metadata中的leader节点指向了TC1的地址,那么TM只会与TC1进行交互。当TC1添加一个全局事务信息时,通过Raft协议,即图中标注为步骤1的日志发送,TC1会将日志发送给其他节点,步骤2是follower节点响应日志接收情况。当超过半数的节点(如TC2)接受并响应成功时,TC1上的状态机(FSM)将执行添加全局事务的动作。
如果TC1宕机或发生重选举,会发生什么呢?由于首次启动时已经获取到了元数据,client会执行watch follower节点的接口来更新本地的metadata信息。因此,后续的事务请求将发送到新的leader(例如TC2)。同时,TC1的数据已经被同步到了TC2和TC3,因此数据一致性不会受到影响。只在选举发生的瞬间,如果某个事务正好发送给了旧的leader,该事务会被主动回滚,以确保数据的正确性。
需要注意的是,在该模式下,如果事务处于决议发送请求或一阶段流程还未走完的时刻,并且恰好在选举时发生,这些事务会被主动回滚。因为RPC节点已经宕机或发生了重选举,当前没有实现RPC重试。TM侧默认有5次重试机制,但由于选举需要大约1s-2s的时间,这些处于begin状态的事务可能无法成功决议,因此会优先回滚,释放锁,以避免影响其他业务的正确性。
2.3.2 故障恢复
在Seata中,当TC发生故障时,数据恢复的过程如下:
如上图所示
-
检查是否存在最新的数据快照:首先,系统会检查是否存在最新的数据快照文件。数据快照是基于内存的数据状态的一次全量拷贝,如果有最新的数据快照,则系统将直接加载该快照到内存中。
-
根据快照后的Raft日志进行回放:如果存在最新的快照或者没有快照文件,系统将根据之前记录的Raft日志进行数据回放。每个Seata-Server中的请求最终会经过ServerOnRequestProcessor进行处理,然后转移到具体的协调者类(DefaultCoordinator或RaftCoordinator)中,再转向具体的业务代码(DefaultCore)进行相应的事务处理(如begin、commit、rollback等)。
-
当日志回放完成后,便会由leader发起日志的同步,并继续执行相关事务的增删改动作。f
通过以上步骤,Seata能够实现在故障发生后的数据恢复。首先尝试加载最新的快照,如果有的话可以减少回放的时间;然后根据Raft日志进行回放,保证数据操作的一致性;最后通过日志同步机制,确保数据在多节点之间的一致性。