全局事务初始化和连接到Seata Server的过程

元始:在回答刚刚的问题之前,老大,问你一个问题,客户端和服务端之间如果通过长连接进行通信的话,你会怎么做?
傅青阳:其他人一般用Netty这个框架来实现基于TCP的长连接吧。但我一向是自己手写Socket来实现。
元始:没错,老大功力深厚,可以自己直接基于底层的Socket实现连接、IO传输等。但是Seata这个还是普遍使用了Netty框架来实现的,所以Seata框架内RM、TM与Server端的通信就是基于Netty实现的。
傅青阳:嗯,继续吧。
元始:好的,那我从上一次的全局事务初始化讲起。

全局事务初始化

我接着上一篇提到的开启全局事务的方法beginTransaction继续跟进代码,看看如何申请事务xid。

@Override
public void begin(int timeout, String name) throws TransactionException {
    // 必须是主干事务才能发起获取事务xid的请求
    if (role != GlobalTransactionRole.Launcher) {
        //......
        return;
    }
    //......
    // 真正获取xid
    xid = transactionManager.begin(null, null, name, timeout);
    status = GlobalStatus.Begin;
    RootContext.bind(xid);
    if (LOGGER.isInfoEnabled()) {
        LOGGER.info("Begin new global transaction [{}]", xid);
    }
}

这里的代码主要是判断一下是否满足可以获取全局事务xid的条件:主干事务发起、原本没有获取过xid,获取xid成功后会缓存在当前线程缓存RootContext中。然后继续跟进transactionManager.begin方法。

/**
 * 启动全局事务,注册到server端
 */
@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());
    }
    // 获取到分配的xid
    return response.getXid();
}

其实就是发送开启全局事务的请求GlobalBeginRequest给Seata Server,由Server端统一分配全局事务xid。
关于如何启动Netty客户端,并且连接到Server端的逻辑待会再讲,我们接着看看获取到xid之后,如何跨服务传递?

如何传递事务xid到其他服务?

由于我的那个Demo的服务间调用是基于Dubbo实现的,所以在这里还需要引入一个Seata提交的Dubbo Filter:AlibabaDubboTransactionPropagationFilter。我们看下这个Filter的实现:

@Activate(group = {DubboConstants.PROVIDER, DubboConstants.CONSUMER}, order = 100)
public class AlibabaDubboTransactionPropagationFilter implements Filter {
//......
    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        if (!DubboConstants.ALIBABADUBBO) {
            return invoker.invoke(invocation);
        }
        // 传递事务id
        String xid = RootContext.getXID();
        BranchType branchType = RootContext.getBranchType();

        String rpcXid = getRpcXid();
        String rpcBranchType = RpcContext.getContext().getAttachment(RootContext.KEY_BRANCH_TYPE);
        //......
        boolean bind = false;
        if (xid != null) {
            // 设置隐式参数
            RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
            RpcContext.getContext().setAttachment(RootContext.KEY_BRANCH_TYPE, branchType.name());
        } else {
            if (rpcXid != null) {
                RootContext.bind(rpcXid);
                if (StringUtils.equals(BranchType.TCC.name(), rpcBranchType)) {
                    RootContext.bindBranchType(BranchType.TCC);
                }
                bind = true;
                //......
            }
        }
        try {
            return invoker.invoke(invocation);
        } finally {
            //......
        }
    }

    /**
     * get rpc xid
     * 从隐式参数获取事务xid
     */
    private String getRpcXid() {
        String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID);
        if (rpcXid == null) {
            rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID.toLowerCase());
        }
        return rpcXid;
    }
}

这里的代码其实都是一目了然,直接通过Dubbo提供的隐式参数实现的,调用前将从当前线程缓存取出事务xid写入隐式参数,被调用者就可以从隐式参数获得事务xid,然后设置到当前线程缓存RootContext中。

如何连接到Seata server?

傅青阳:嗯,那前面提到获取事务xid是需要连接到Server端,由Server端分配的,那么如何业务服务如何连接到Seata Server端呢?

元始:这里我们就需要回去看看之前提到的事务扫描组件GlobalTransactionScanner,看看里面做了什么。

public class GlobalTransactionScanner extends AbstractAutoProxyCreator
    implements ConfigurationChangeListener, InitializingBean, ApplicationContextAware, DisposableBean {

由于GlobalTransactionScanner实现了InitializingBean 这个接口,所以在Spring容器初始化这个bean的时候就会先执行afterPropertiesSet这个方法,我们跟进看看:

/**
 * 启动时执行的入口
 */
@Override
public void afterPropertiesSet() {
    //......
    if (initialized.compareAndSet(false, true)) {
        // 初始化客户端
        initClient();
    }
}
private void initClient() {
    //......
    //init TM 初始化TM事务管理器
    TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
   //......
    //init RM 初始化RM资源管理器
    RMClient.init(applicationId, txServiceGroup);
    //......
    registerSpringShutdownHook();
}

上面的代码可以清楚看到,就是在这里初始化了事务管理器TM和资源管理器RM。
我们先简单看看RM是如何初始化的,因为TM涉及到了对象池的应用,我们后面再看。

public class RMClient {

    public static void init(String applicationId, String transactionServiceGroup) {
        RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);
        rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());
        rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());
        rmNettyRemotingClient.init();
    }
}
@Override
public void init() {
    // registry processor
    registerProcessor();
    if (initialized.compareAndSet(false, true)) {
        // 继续调用父类初始化
        super.init();
        if (resourceManager != null
                && !resourceManager.getManagedResources().isEmpty()
                && StringUtils.isNotBlank(transactionServiceGroup)) {
            getClientChannelManager().reconnect(transactionServiceGroup);
        }
    }
}
@Override
public void init() {
    //......
    super.init();
    // 初始化连接客户端
    clientBootstrap.start();
}

这里的RM初始化就是层层调用了init方法,最后的重点就是在clientBootstrap这里。

/**
 * 初始化netty client
 */
@Override
public void start() {
    //......
    this.bootstrap.group(this.eventLoopGroupWorker).channel(
        nettyClientConfig.getClientChannelClazz())
            // 不启用开启Nagle算法,Nagle算法会收集网络小包再一次性发送,不启用则是即时发送
            .option(ChannelOption.TCP_NODELAY, true)
            // 连接保活
            .option(ChannelOption.SO_KEEPALIVE, true)
            // 客户端连接超时时间
            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyClientConfig.getConnectTimeoutMillis())
            // 发送数据缓冲区大小
            .option(ChannelOption.SO_SNDBUF, nettyClientConfig.getClientSocketSndBufSize())
            // 接收数据缓冲区大小
            .option(ChannelOption.SO_RCVBUF, nettyClientConfig.getClientSocketRcvBufSize());

    //......
    bootstrap.handler(
        new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(
                        // 基于心跳保持会话状态
                    new IdleStateHandler(nettyClientConfig.getChannelMaxReadIdleSeconds(),
                        nettyClientConfig.getChannelMaxWriteIdleSeconds(),
                        nettyClientConfig.getChannelMaxAllIdleSeconds()))
                        // 解码器
                    .addLast(new ProtocolV1Decoder())
                        // 编码器
                    .addLast(new ProtocolV1Encoder());
                if (channelHandlers != null) {
                    addChannelPipelineLast(ch, channelHandlers);
                }
            }
        });
}

看到这里就明白了,RM其实就是一个Netty Client,通过这个连接到Seata Server(也即是Netty Server)端,使用TCP长连接进行网络通信。

傅青阳:那么关于Netty的客户端和服务端的参数调优、RM和TM的初始化过程,知道吗?
元始:我掐指一算就知道你要问这个问题,早有准备了。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gemini技术窝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值