一. 客户端启动
1. 读取配置文件,创建客户端
2. 客户端初始化
GlobalTransactionScanner 创建TMClient和RMClient
1.AbstractNettyRemoting
主要属性:
- ConcurrentHashMap<Integer, MessageFuture> futures = new ConcurrentHashMap<>(); 调用发送消息方法,会将消息存放在其中,key为消息的id
- MessageFuture:包含RpcMessage和一个CompletableFuture类型的origin对象
- HashMap<Integer/MessageType/, Pair<RemotingProcessor, ExecutorService>> processorTable = new HashMap<>(32); 类型和处理器的关系表。类型是诸如全局事务开启、分支提交、分支回滚等
主要功能:
- sendSync 发送消息。可以发送TM、RM的心跳请求,事务注册、回滚等请求。可以执行钩子函数。使用Netty Channel发送消息,它会返回一个Future。增加了一个监听器,将发送成功的消息从futures里删除,并销毁channel
- processMessage 处理消息,将各个类型的消息分发到各个处理器进行处理;消息需要携带类型!
- 定时任务timeoutChecker(ScheduledThreadPoolExecutor),主要是将futures中超时的消息进行清理和设置异常
- Hooks before/after
2.AbstractNettyRemotingClient
主要功能:
- 定时任务timeoutChecker(ScheduledThreadPoolExecutor),定时与服务端进行重连
- 批量发送,单独一个线程进行merge、send消息。以服务端地址为维度进行merge。
- 启动客户端引导类,NettyClientBootstrap.start()
- sendSyncRequest(Object msg) 发送消息的封装。包括服务端的负载均衡选址、将Object类型的消息msg封装为RpcMessage、批量发送处理等,若单条发送则会调用AbstractNettyRemoting的sendSync
- sendAsyncResponse 当触发回滚或提交后,需要返回给服务端结果(实际上也是发送消息)
3.TmNettyRemotingClient
主要功能
- 注册处理器,全局事务开启、提交、回滚等等使用一个处理器ClientOnResponseProcessor,该处理器的功能主要是日志记录事务状态。心跳使用一个处理器ClientHeartbeatProcessor
4.RmNettyRemotingClient
主要功能
- 注册处理器,分支提交、回滚、心跳等分别使用一个处理器,如RmBranchCommitProcessor、RmBranchRollbackProcessor,同样有心跳处理器
以commit为例,先根据resourceId找到对应的commit方法,执行后封装response返回给TC服务端(status只要commit方法没有报错或者返回false则为成功)
/**
* Do branch commit.
*
* @param request the request
* @param response the response
* @throws TransactionException the transaction exception
*/
protected void doBranchCommit(BranchCommitRequest request, BranchCommitResponse response)
throws TransactionException {
String xid = request.getXid();
long branchId = request.getBranchId();
String resourceId = request.getResourceId();
String applicationData = request.getApplicationData();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Branch committing: " + xid + " " + branchId + " " + resourceId + " " + applicationData);
}
BranchStatus status = getResourceManager().branchCommit(request.getBranchType(), xid, branchId, resourceId,
applicationData);
response.setXid(xid);
response.setBranchId(branchId);
response.setBranchStatus(status);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Branch commit result: " + status);
}
将结果返回给服务端
private void handleBranchCommit(RpcMessage request, String serverAddress, BranchCommitRequest branchCommitRequest) {
BranchCommitResponse resultMessage;
resultMessage = (BranchCommitResponse) handler.onRequest(branchCommitRequest, null);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("branch commit result:" + resultMessage);
}
try {
this.remotingClient.sendAsyncResponse(serverAddress, request, resultMessage);
} catch (Throwable throwable) {
LOGGER.error("branch commit error: {}", throwable.getMessage(), throwable);
}
}
二、服务端NettyRemotingServer启动
1.注册处理器
- ServerOnRequestProcessor 消费TM、RM发送的消息的处理器,包含开启、提交、回滚全局事务,返回结果等
- RegRmProcessor、RegTmProcessor 注册RM、TM处理器
- ServerHeartbeatProcessor 心跳处理器
2.启动服务端引导类NettyServerBootstrap
三、客户端、服务端的交互
1.ServerHandler/ClientHandler extends ChannelDuplexHandler 客户端、服务端的接收消息的接口
ChannelDuplexHandler 同时实现了ChannelInboundHandler、ChannelOutboundHandler,即可以做数据和操作的入站和出站
- ChannelInboundHandler: 入站。
- ChannelOutboundHandler:出站。
对入站操作中的channelRead、channelWritabilityChanged、channelInactive等方法 进行复写
- channelRead 查看read的参数msg是否为RpcMessage,若是,则执行AbstractNettyRemoting的processMessage方法,即根据消息类型分发给指定的处理器处理。
- channelWritabilityChanged 在channel的可写入状态(writable state)变化后立即调用,它会解锁该channel的发送请求(发送消息时,若channel处于不可写入的状态,会将线程阻塞10ms)
Q: channel的writable state如何定义?
A:
1. 业务线程调用 writeAndFlush 发送消息,会生成 WriteAndFlushTask,交由 IO 线程处理,write 操作将消息写入 ChannelOutboundBuffer(不会写到 socket),flush 操作将 ChannelOutboundBuffer 写 入socket 的发送缓冲区;(这里注意,writeAndFlush 它只是一个语法糖,意味着这不是原子操作,因此在此方法执行的中间,可能在多个线程之间进行了上下文切换。)
2. ChannelOutboundBuffer 它配置一个高水位线和低水位线,当 buffer 的大小超过高水位线的时候对应 channel 的 isWritable 就会变成 false,当 buffer 的大小低于低水位线的时候,isWritable 就会变成 true。
其中,高水位线和低水位线是字节数,默认高水位是64K,低水位是32K,通过以下方式进行设置:
.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, 64 * 1024)
.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, 32 * 1024)
- channelInactive 非活动的channel进行销毁
上面最核心的即channelRead方法,它定义了netty发送消息与seata工作机制的调度
2.DefaultTransactionManager 客户端(TM)发送、接收消息的实现
i.发送消息
TM向TC发送消息,进行全局事务的开启、提交或回滚。在GlobalxxxRequest中定义了TypeCode,服务端接受后分派给各个处理器进行操作。
@Override
public String begin(String applicationId, String transactionServiceGroup, String name, int timeout)
throws TransactionException {
GlobalBeginRequest request = new GlobalBeginRequest();
request.setTransactionName(name);
request.setTimeout(timeout);
GlobalBeginResponse response = (GlobalBeginResponse) syncCall(request);
if (response.getResultCode() == ResultCode.Failed) {
throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());
}
return response.getXid();
}
@Override
public GlobalStatus commit(String xid) throws TransactionException {
GlobalCommitRequest globalCommit = new GlobalCommitRequest();
globalCommit.setXid(xid);
GlobalCommitResponse response = (GlobalCommitResponse) syncCall(globalCommit);
return response.getGlobalStatus();
}
@Override
public GlobalStatus rollback(String xid) throws TransactionException {
GlobalRollbackRequest globalRollback = new GlobalRollbackRequest();
globalRollback.setXid(xid);
GlobalRollbackResponse response = (GlobalRollbackResponse) syncCall(globalRollback);
return response.getGlobalStatus();
}
当TC端发出消息后,会同步接收响应,获取操作的结果。若超时或其他异常,可能影响事务状态。 对commit和rollback操作,如果有异常会重试5次(可配置),若重试5次后还是异常,则整个事务请求执行失败,抛出异常。而此时事务状态可能出现不一致情况, 事务的执行依然没有停止,客户端使用时间轮定时获取事务状态,而服务端则不断向RM发送commit、rollback请求。
ii.客户端接收消息
- RM:见RmNettyRemotingClient的功能。执行具体的提交、回滚方法并返回结果给TC。
- TM:见上面发送消息的过程。执行事务的开启、提交、回滚等操作的触发,即将操作发送给TC,并接收TC的响应,将结果返回给用户。
3.DefaultCoordinator 服务端接收消息的实现
@Override
protected void doGlobalBegin(GlobalBeginRequest request, GlobalBeginResponse response, RpcContext rpcContext)
throws TransactionException {
response.setXid(core.begin(rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(),
request.getTransactionName(), request.getTimeout()));
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Begin new global transaction applicationId: {},transactionServiceGroup: {}, transactionName: {},timeout:{},xid:{}",
rpcContext.getApplicationId(), rpcContext.getTransactionServiceGroup(), request.getTransactionName(), request.getTimeout(), response.getXid());
}
}
@Override
protected void doGlobalCommit(GlobalCommitRequest request, GlobalCommitResponse response, RpcContext rpcContext)
throws TransactionException {
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
response.setGlobalStatus(core.commit(request.getXid()));
}
@Override
protected void doGlobalRollback(GlobalRollbackRequest request, GlobalRollbackResponse response,
RpcContext rpcContext) throws TransactionException {
MDC.put(RootContext.MDC_KEY_XID, request.getXid());
response.setGlobalStatus(core.rollback(request.getXid()));
}
在DefaultCore中有其业务实现,以回滚为例:
public boolean doGlobalRollback(GlobalSession globalSession, boolean retrying) throws TransactionException {
boolean success = true;
// start rollback event
MetricsPublisher.postSessionDoingEvent(globalSession, retrying);
if (globalSession.isSaga()) {
success = getCore(BranchType.SAGA).doGlobalRollback(globalSession, retrying);
} else {
Boolean result = SessionHelper.forEach(globalSession.getReverseSortedBranches(), branchSession -> {
BranchStatus currentBranchStatus = branchSession.getStatus();
if (currentBranchStatus == BranchStatus.PhaseOne_Failed) {
SessionHelper.removeBranch(globalSession, branchSession, !retrying);
return CONTINUE;
}
try {
BranchStatus branchStatus = branchRollback(globalSession, branchSession);
if (isXaerNotaTimeout(globalSession, branchStatus)) {
LOGGER.info("Rollback branch XAER_NOTA retry timeout, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
branchStatus = BranchStatus.PhaseTwo_Rollbacked;
}
switch (branchStatus) {
case PhaseTwo_Rollbacked:
SessionHelper.removeBranch(globalSession, branchSession, !retrying);
LOGGER.info("Rollback branch transaction successfully, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
return CONTINUE;
case PhaseTwo_RollbackFailed_Unretryable:
SessionHelper.endRollbackFailed(globalSession, retrying);
LOGGER.info("Rollback branch transaction fail and stop retry, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
return false;
default:
LOGGER.info("Rollback branch transaction fail and will retry, xid = {} branchId = {}", globalSession.getXid(), branchSession.getBranchId());
if (!retrying) {
globalSession.queueToRetryRollback();
}
return false;
}
} catch (Exception ex) {
StackTraceLogger.error(LOGGER, ex,
"Rollback branch transaction exception, xid = {} branchId = {} exception = {}",
new String[] {globalSession.getXid(), String.valueOf(branchSession.getBranchId()), ex.getMessage()});
if (!retrying) {
globalSession.queueToRetryRollback();
}
throw new TransactionException(ex);
}
});
// Return if the result is not null
if (result != null) {
return result;
}
}
// In db mode, lock and branch data residual problems may occur.
// Therefore, execution needs to be delayed here and cannot be executed synchronously.
if (success) {
SessionHelper.endRollbacked(globalSession, retrying);
LOGGER.info("Rollback global transaction successfully, xid = {}.", globalSession.getXid());
}
return success;
}
需要注意的几个点: i. 回滚时由TM触发,然后将所有分支查询出来并通过时间倒序:globalSession.getReverseSortedBranches(),然后依次进行回滚。也就是说提交、回滚严格按照分支顺序进行!
public static Boolean forEach(Collection<BranchSession> sessions, BranchSessionHandler handler) throws TransactionException {
Boolean result;
for (BranchSession branchSession : sessions) {
try {
MDC.put(RootContext.MDC_KEY_BRANCH_ID, String.valueOf(branchSession.getBranchId()));
result = handler.handle(branchSession);
if (result == null) {
continue;
}
return result;
} finally {
MDC.remove(RootContext.MDC_KEY_BRANCH_ID);
}
}
return null;
}
ii. 若回滚失败,需要将该事务重新加入重试队列中。也就是说通过不断重试达到最终事务的一致性。
public void queueToRetryRollback() throws TransactionException {
this.addSessionLifecycleListener(SessionHolder.getRetryRollbackingSessionManager());
GlobalStatus currentStatus = this.getStatus();
if (SessionStatusValidator.isTimeoutGlobalStatus(currentStatus)) {
this.setStatus(GlobalStatus.TimeoutRollbackRetrying);
} else {
this.setStatus(GlobalStatus.RollbackRetrying);
}
SessionHolder.getRetryRollbackingSessionManager().addGlobalSession(this);
}