Seata Server 在分布式事务中, 担任事物协调者(TC)的角色
从TC的角度: 我们可以把 TC 看成处理 RM,TM 请求的一个业务系统
核心组件初始化
为了了解TC的核心实现, 要先介绍核心组件支持
服务端分为八大组件,支持服务的启动和运行
- ParameterParser 启动时参数解析,支持k8s,docker
- MetricManager 统计分析组件, 默认不启用
- ShutdownHook 应用程序关闭时通知已注册的组件卸载
- DefaultCoordinator 处理业务逻辑, 例如事物开启,分支注册, 初始化调度
- NettyRemotingServer 监听端口,编码解码
- SessionManager 会话管理器 管理全局事物,分支事物的状态
- ServerHandler 服务处理器, 五大请求处理: 请求,响应,TM注册,RM注册,心跳
- UUIDGenerator 生成全局事物ID
服务的启动过程也就是组件的初始化过程
// 代码取自: seata:1.4.0 , 并删减了和组件无关的部分
public static void main(String[] args) throws IOException {
//初始化组件 ParameterParser
ParameterParser parameterParser = new ParameterParser(args);
//初始化组件 MetricsManager
MetricsManager.get().init();
NettyRemotingServer nettyRemotingServer = new NettyRemotingServer(WORKING_THREADS);
nettyRemotingServer.setListenPort(parameterParser.getPort());
//初始化组件 UUIDGenerator
UUIDGenerator.init(parameterParser.getServerNode());
//初始化组件 SessionHolder, log store mode : file, db, redis
SessionHolder.init(parameterParser.getStoreMode());
//初始化组件 DefaultCoordinator
DefaultCoordinator coordinator = new DefaultCoordinator(nettyRemotingServer);
coordinator.init();
nettyRemotingServer.setHandler(coordinator);
// register ShutdownHook
ShutdownHook.getInstance().addDisposable(coordinator);
ShutdownHook.getInstance().addDisposable(nettyRemotingServer);
//初始化组件 NettyRemotingServer , ServerHandler
nettyRemotingServer.init();
}
请求处理
虽然提供了这么多组件, 和处理请求相关的却不多。 既然是服务,就需要接受请求,处理请求
从接口的角度来讲,分为两大模块:
- 接受请求组件: RemotingServer
- 处理请求组件: TransactionMessageHandler
Seata Server 的主要结构类图
RemotingServer
启动时,由NettyRemotingServer 将消息注册到 消息处理器,消息处理器 的处理器映射关系在AbstractRemotingServer的属性processorTable, 这是一个HashMap, 由于不需要动态注册,也就不需要关心线程安全
在处理消息之前, 首先通过Netty解码器, 获取 RpcMessage对象
TC的通道处理器的顺序如下:
- 心跳处理器
- 协议解码器
- 协议编码器
- ServerHandler
消息首先经过协议解码器,然后被ServerHandler处理
协议解码器解析Seata的自定义协议。 通过消息类型、压缩方式、以及序列化方式将客户端数据组装成RpcMessage对象
ServerHandler是AbstractNettyRemoting的内部类, 当收到请求时触发channelRead,处理 RpcMessage
ServerHandler 是 AbstractNettyRemotingServer 的内部类,所以能调用父类方法 AbstractNettyRemoting#processMessage.
processMessage方法首先根据请求类型从processorTable拿到对应的消息处理器和线程池。
- 如果有线程池就在线程池中处理
- 没有线程池就同步处理
AbstractNettyRemoting#processMessage 处理消息的方式如下:
// 篇幅限制, 删除大量日志和try,catch
public abstract class AbstractNettyRemoting {
// 一个消息类型, 映射两个对象: 消息处理器, 线程池
protected final HashMap<Integer/*消息类型*/,
Pair<RemotingProcessor/*消息处理器*/, ExecutorService/*线程池*/>
> processorTable = new HashMap<>(32);
// 处理消息
protected void processMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception {
Object body = rpcMessage.getBody();
if (body instanceof MessageTypeAware) {
MessageTypeAware messageTypeAware = (MessageTypeAware) body;
// 通过请求的消息类型,从已注册的处理映射器找线程池和处理器
final Pair<RemotingProcessor, ExecutorService> pair =
this.processorTable.get((int) messageTypeAware.getTypeCode());
if (pair != null) {
// 如果线程池存在,由线程池处理业务
if (pair.getSecond() != null) {
pair.getSecond().execute(() -> {
// 消息处理器处理业务逻辑
pair.getFirst().process(ctx, rpcMessage);
});
} else {
// 如果没有线程池, 同步处理业务。 目前只有心跳和事务管理器注册的消息是同步处理
// 消息处理器处理业务逻辑
pair.getFirst().process(ctx, rpcMessage);
}
}
}
}
}
消息处理器
消息处理器在TC一共有五种类型, 服务启动时将消息处理器注册到 AbstractNettyRemoting#processorTable 。
Seata Server 消息处理器的类型:
- 请求处理器
- 响应处理器
- 注册事物管理器处理器
- 注册资源管理器处理器
- 服务端心跳处理器
这五种消息处理器, 对应的消息类型和功能如下
消息处理器 | 消息类型 | 中文类型 | 描述 | 执行方式 |
ServerOnRequestProcessor | TYPE_BRANCH_REGISTER | 分支事物注册 | RM发起分支事物注册 | 异步 |
ServerOnRequestProcessor | TYPE_BRANCH_STATUS_REPORT | 分支状态报告 | 可选项, 告知TC状态已成功 | 异步 |
ServerOnRequestProcessor | TYPE_GLOBAL_BEGIN | 开启全局事物 | TM发起一阶段开启事物 | 异步 |
ServerOnRequestProcessor | TYPE_GLOBAL_COMMIT | 全局事物提交 | TM发起一阶段事物提交 | 异步 |
ServerOnRequestProcessor | TYPE_GLOBAL_LOCK_QUERY | 开启全局锁 | ... | 异步 |
ServerOnRequestProcessor | TYPE_GLOBAL_REPORT | 全局事物报告 | ... | 异步 |
ServerOnRequestProcessor | TYPE_GLOBAL_ROLLBACK | 全局事物回滚 | TM发起一阶段事物回滚 | 异步 |
ServerOnRequestProcessor | TYPE_GLOBAL_STATUS | 获取全局事物状态 | 根据XID返回全局事物的状态 | 异步 |
ServerOnRequestProcessor | TYPE_SEATA_MERGE | 合并消息 | 批量处理合并后的消息 | 异步 |
ServerOnResponseProcessor | TYPE_BRANCH_COMMIT_RESULT | 分支事物提交 | RM接受二阶段事物提交 | 异步 |
ServerOnResponseProcessor | TYPE_BRANCH_ROLLBACK_RESULT | 分支事物回滚 | RM接受二阶段事物回滚 | 异步 |
RegRmProcessor | TYPE_REG_RM | 注册资源管理器 | 客户端启动时, 向TC发起注册 | 异步 |
RegTmProcessor | TYPE_REG_CLT | 注册事务管理器 | 客户端启动时, 向TC发起注册 | 同步 |
ServerHeartbeatProcessor | TYPE_HEARTBEAT_MSG | 心跳 | 收到客户端的PING,返回一个PONG | 同步 |
例如接收到一个分支事物注册(TYPE_BRANCH_REGISTER)类型的请求,
找到处理器: ServerOnRequestProcessor
,然后调用 process 方法
ServerOnRequestProcessor 的处理流程如下, 其他处理器处理流程各不一样
- 判断通道必须已注册
- 事物消息处理器(TransactionMessageHandler)处理请求
- 异步发送响应内容
ServerOnRequestProcessor#processs 代码如下:
// 由于篇幅限制, 删除大部分try,catch. 日志等..
public class ServerOnRequestProcessor implements RemotingProcessor {
@Override
public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage) throws Exception {
// 判断通道是否已注册,(RM启动时,会发送TYPE_REG_RM类型的请求,服务端会缓存这个通道)
if (ChannelManager.isRegistered(ctx.channel())) {
onRequestMessage(ctx, rpcMessage);
}
}
private void onRequestMessage(ChannelHandlerContext ctx, RpcMessage rpcMessage) {
final AbstractMessage msg = (AbstractMessage) message;
// 事物消息处理器(TransactionMessageHandler)处理请求
AbstractResultMessage result = transactionMessageHandler.onRequest(msg, rpcContext);
// 异步发送响应内容
remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), result);
}
}
TransactionMessageHandler
事物消息处理器的实现只有一个: DefaultCoordinator, 在启动时初始化字段 RemotingServer和DefaultCore。
DefaultCoordinator 是如何根据请求找到具体的方法呢?
利用java的 动态单分派,静态多分派的特性。通过请求类型找到处理业务逻辑的方法
// DefaultCoordinator 的使用方式
@Override
public AbstractResultMessage onRequest(AbstractMessage request, RpcContext context) {
if (!(request instanceof AbstractTransactionRequestToTC)) {
throw new IllegalArgumentException();
}
AbstractTransactionRequestToTC transactionRequest = (AbstractTransactionRequestToTC) request;
transactionRequest.setTCInboundHandler(this);
// 动态单分派的方式调用每个请求自己的handler
return transactionRequest.handle(context);
}
// Request的handler实现
public AbstractTransactionResponse handle(RpcContext rpcContext) {
// 静态多分派的方式调用 DefaultCoordinator 的handle, ( handler对象就是DefaultCoordinator )
return handler.handle(this, rpcContext);
}
通过两次分派后进入具体的请求处理, 接着进入分支注册处理.
例如这里的分支注册处理方法:
// 找到DefaultCoordinator中具体的处理方法
public BranchRegisterResponse handle(BranchRegisterRequest request, final RpcContext rpcContext) {
BranchRegisterResponse response = new BranchRegisterResponse();
// exceptionHandleTemplate 处理事务状态修改. 没抛异常: Success 抛出异常: Failed
exceptionHandleTemplate(new AbstractCallback<BranchRegisterRequest, BranchRegisterResponse>() {
@Override
public void execute(BranchRegisterRequest request, BranchRegisterResponse response)
throws TransactionException {
try {
// 处理分支注册
doBranchRegister(request, response, rpcContext);
} catch (StoreException e) {
throw new TransactionException(TransactionExceptionCode.FailedStore, String
.format("branch register request failed. xid=%s, msg=%s", request.getXid(), e.getMessage()), e);
}
}
}, request, response);
return response;
}
所有由DefaultCoordinator调用的处理方法模板都是一样的, 只是请求对象和响应对象的不同。
分支注册和和其他请求处理的方法声明是类似的, 其他处理声明:
// 开启全局事物
GlobalBeginResponse handle(GlobalBeginRequest globalBegin, RpcContext rpcContext);
// 提交全局事物
GlobalCommitResponse handle(GlobalCommitRequest globalCommit, RpcContext rpcContext);
// 回滚全局事物
GlobalRollbackResponse handle(GlobalRollbackRequest globalRollback, RpcContext rpcContext);
// 分支注册
BranchRegisterResponse handle(BranchRegisterRequest branchRegister, RpcContext rpcContext);
//... 篇幅限制,省略其他的请求方法声明
会话管理器
Seata 定义了四种会话管理器, 通过SPI方式加载, 使用SessionHolder的静态方法可以获取会话管理器
会话管理器的处理分为实时部分和非实时部分
实时部分用于处理全局事物,分支事物的增删改查
非实时部分处理异常中断的重新拉起,非实时部分其实就是调度, 调度完善了会话管理器的生命周期, 实现自动关闭,回滚,提交会话。
会话管理器在后台承担持久层的工作,支持文件、数据库、Redis 三种形式。 对于线上环境 ,务必不能使用文件形式。 DB和Redis都是高可用的, 应该尽可能部署多台。
- 全局会话管理器, 处理的状态:
UnKnown,Begin,Committing,CommitRetrying,Rollbacking,RollbackRetrying,
TimeoutRollbacking,TimeoutRollbackRetrying,AsyncCommitting - 异步提交会话管理器, 处理的状态: AsyncCommitting
- 重试提交会话管理器, 处理的状态: CommitRetrying
- 重试回滚会话管理器, 处理的状态: RollbackRetrying,Rollbacking,TimeoutRollbacking,TimeoutRollbackRetrying
调度
TC的调度一共有6种, 其中前4种用于完善会话的生命周期
重试回滚调度
默认每秒执行一次, 通过配置修改调度 server.recovery.rollbackingRetryPeriod=3000
执行周期, 单位为毫秒
执行过程如下
- 从重试回滚会话管理器获取所有会话进行遍历(如果是DB,就是查询指定状态的会话)
- 判断如果是重试中并且距离全局事物开始时间大于12秒, 大于就强制重试,否则忽略这个会话
- 判断是否重试超时,根据配置清理会话, 直接移除全局会话, 会话处理完成
- 处理全局事物回滚
重试提交调度
默认每秒执行一次, 通过配置修改调度 server.recovery.committingRetryPeriod=3000
执行周期, 单位为毫秒
通过SessionHolder获取重试提交会话管理器,遍历所有会话
- 判断是否设置了超时参数
server.maxCommitRetryTimeout
,如果设置的值大于0,并且当前会话达到超时时间则移除当前会话,处理下一个会话 - 将当前会话添加到全局会话管理器
- 处理全局事物提交
异步提交调度
默认每秒执行一次, 通过配置修改调度 server.recovery.asynCommittingRetryPeriod=3000
执行周期, 单位为毫秒
通过SessionHolder获取异步提交会话管理器,遍历所有会话
- 判断当前会话状态必须是异步提交中( AsyncCommitting), 状态不匹配则处理下一个会话
- 将当前会话添加到全局会话管理器
- 处理全局事物提交
超时检查调度
默认每秒执行一次, 通过配置修改调度 server.recovery.timeoutRetryPeriod=3000
执行周期, 单位为毫秒
通过SessionHolder获取全局会话管理器,遍历所有会话
- 判断是否超时,如果超时:
- 关闭当前会话
- 当前会话状态变更为超时回滚中
- 将当前会话添加到重试回滚会话管理器
回滚日志调度
从服务器启动后第三分钟开始, 每24小时执行一次, 通过 undo.logDeletePeriod=86400000
修改调度周期, 单位为毫秒
(86400000是一天的毫秒数)
- 从ChannelManager获取当前在TC注册的所有的资源管理器。
- 遍历所有资源管理器,发送回滚日志删除请求
- RM端收到请求后, 根据TC发送的保存天数, 保存指定天数内的数据。
回滚日志保存天数由TC设置, 默认7天。 设置方式: undo.logSaveDays=7
移除超时同步请求
默认每3秒执行一次, 不可更改
应用场景: TC同步发送消息时, 会等待客户端的返回结果。
如果等待的时间超时了,就由这个定时器移除.( 将future的值设置为null, 认为客户端没返回)
默认超时时间 30 秒, 并且不可更改