Seata源码(十四)RM资源注册客户端处理,很肝~脑壳疼~!!


铿然架构 | 作者 / 铿然一叶 这是铿然架构的第 104 篇原创文章


相关阅读:

萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
Seata源码(一)初始化
Seata源码(二)事务基础对象
Seata源码(三)事务处理类结构和流程
Seata源码(四)全局锁GlobalLock
Seata源码(五)Seata数据库操作
Seata源码(六)Seata的undo日志操作
Seata源码(七)Seata事务故障处理
Seata源码(八)Seata事务生命周期hook
Seata源码(九)TCC核心类和处理逻辑
Seata源码(十)RM接收到请求后的调用过程
Seata源码(十一)TC接收到请求后的处理过程
Seata源码(十二)Session管理和持久化
Seata源码(十三)Seata消息处理框架,掌握了可以当大佬~!!


码字不易,先赞后看!!!

1. 概念

1.1 TC (Transaction Coordinator) - 事务协调者

维护全局和分支事务的状态,驱动全局事务提交或回滚。

1.2 RM (Resource Manager) - 资源管理器

管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

RM资源由RM向TC注册。

2. 源码分析

2.1 RM注册请求类结构

类结构如下:

● 紫色部分是最开始的触发点,如果不使用seata数据源,直接创建DataSourceProxy和DataSourceProxyXA,则不会走从SeataDataSourceAutoConfiguration触发的路径。

● 绿色部分是要注册的资源。

● 粉红色是每类资源对应的RM。

● 图中的标号是关键调用顺序。

2.2 构造资源

2.2.1 DataSourceProxy

构造资源时没有设置resourceId,会在使用DataSourceProxy时,通过getResourceId()方法返回:

DataSourceProxy.java

java 复制代码
    private void init(DataSource dataSource, String resourceGroupId) {
        this.resourceGroupId = resourceGroupId;
        try (Connection connection = dataSource.getConnection()) {
            jdbcUrl = connection.getMetaData().getURL();
            dbType = JdbcUtils.getDbType(jdbcUrl);
            if (JdbcConstants.ORACLE.equals(dbType)) {
                userName = connection.getMetaData().getUserName();
            }
        } catch (SQLException e) {
            throw new IllegalStateException("can not init dataSource", e);
        }
        DefaultResourceManager.get().registerResource(this);

获取resourceId:

DataSourceProxy.java

java 复制代码
    public String getResourceId() {
        if (JdbcConstants.POSTGRESQL.equals(dbType)) {
            return getPGResourceId();
        } else if (JdbcConstants.ORACLE.equals(dbType) && userName != null) {
            return getDefaultResourceId() + "/" + userName;
        } else {
            return getDefaultResourceId();
        }
    }

    private String getDefaultResourceId() {
        if (jdbcUrl.contains("?")) {
            return jdbcUrl.substring(0, jdbcUrl.indexOf('?'));
        } else {
            return jdbcUrl;
        }
    }

    private String getPGResourceId() {
        if (jdbcUrl.contains("?")) {
            StringBuilder jdbcUrlBuilder = new StringBuilder();
            jdbcUrlBuilder.append(jdbcUrl.substring(0, jdbcUrl.indexOf('?')));
            StringBuilder paramsBuilder = new StringBuilder();
            String paramUrl = jdbcUrl.substring(jdbcUrl.indexOf('?') + 1, jdbcUrl.length());
            String[] urlParams = paramUrl.split("&");
            for (String urlParam : urlParams) {
                if (urlParam.contains("currentSchema")) {
                    paramsBuilder.append(urlParam);
                    break;
                }
            }

            if (paramsBuilder.length() > 0) {
                jdbcUrlBuilder.append("?");
                jdbcUrlBuilder.append(paramsBuilder);
            }
            return jdbcUrlBuilder.toString();
        } else {
            return jdbcUrl;
        }
    }

2.2.2 XA资源

JdbcUtils.java

构造资源时设置了resourceId:

java 复制代码
    public static void initDataSourceResource(BaseDataSourceResource dataSourceResource, DataSource dataSource, String resourceGroupId) {
        dataSourceResource.setResourceGroupId(resourceGroupId);
        try (Connection connection = dataSource.getConnection()) {
            String jdbcUrl = connection.getMetaData().getURL();
            dataSourceResource.setResourceId(buildResourceId(jdbcUrl));
            String driverClassName = com.alibaba.druid.util.JdbcUtils.getDriverClassName(jdbcUrl);
            dataSourceResource.setDriver(loadDriver(driverClassName));
            dataSourceResource.setDbType(com.alibaba.druid.util.JdbcUtils.getDbType(jdbcUrl, driverClassName));
        } catch (SQLException e) {
            throw new IllegalStateException("can not init DataSourceResource with " + dataSource, e);
        }
        DefaultResourceManager.get().registerResource(dataSourceResource);
    }

resourceId生成规则:

java 复制代码
    public static String buildResourceId(String jdbcUrl) {
        if (jdbcUrl.contains("?")) {
            return jdbcUrl.substring(0, jdbcUrl.indexOf('?'));
        }
        return jdbcUrl;
    }

取jdbcUrl的?号前面部分,如:

jdbc:mysql://192.68.5.43:3306/accountdb?serverTimezone=GMT

则为:

jdbc:mysql://192.68.5.43:3306/accountdb

2.2.3 TCC资源

构造资源时没有设置resourceId,会在使用TCCResource时,通过getResourceId()方法返回:

DefaultRemotingParser.java

java 复制代码
for (Method m : methods) {
                    TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class);
                    if (twoPhaseBusinessAction != null) {
                        TCCResource tccResource = new TCCResource();
                        tccResource.setActionName(twoPhaseBusinessAction.name());
                        tccResource.setTargetBean(targetBean);
                        tccResource.setPrepareMethod(m);
                        tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod());
                        tccResource.setCommitMethod(ReflectionUtil
                            .getMethod(interfaceClass, twoPhaseBusinessAction.commitMethod(),
                                new Class[] {BusinessActionContext.class}));
                        tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod());
                        tccResource.setRollbackMethod(ReflectionUtil
                            .getMethod(interfaceClass, twoPhaseBusinessAction.rollbackMethod(),
                                new Class[] {BusinessActionContext.class}));
                        //registry tcc resource
                        DefaultResourceManager.get().registerResource(tccResource);
                    }
                }

获取resourceId:

TCCResource.java

java 复制代码
    public String getResourceId() {
        return actionName;
    }

可以看到是actionName,取值来自业务方法上的TwoPhaseBusinessAction注解的name属性:

java 复制代码
    @TwoPhaseBusinessAction(name = "TccActionOne", commitMethod = "commit", rollbackMethod = "rollback")
    public boolean prepare(BusinessActionContext actionContext, int a);

2.3 标号1 获取资源实例调用注册方法

根据资源分支类型获取对应的AbstractResourceManager子类实例:

DefaultResourceManager.java

java 复制代码
    public void registerResource(Resource resource) {
        getResourceManager(resource.getBranchType()).registerResource(resource);
    }

其中DataSourceManager和TCCResourceManager实现了registerResource方法,ResourceManagerXA则调用父类AbstractDataSourceCacheResourceManager的registerResourc方法,三者最后都会调用父类AbstractResourceManager的registerResourc方法。

这些子类的registerResourc方法中都将资源映射关系放入了缓存,在后面会通过此缓存获取resourceId:

TCCResourceManager.java

java 复制代码
    public void registerResource(Resource resource) {
        TCCResource tccResource = (TCCResource)resource;
        // 这里的缓存在后面会用于获取resourceId
        tccResourceCache.put(tccResource.getResourceId(), tccResource);
        super.registerResource(tccResource);
    }

DataSourceManager.java

java 复制代码
    public void registerResource(Resource resource) {
        DataSourceProxy dataSourceProxy = (DataSourceProxy) resource;
        // 这里的缓存在后面会用于获取resourceId
        dataSourceCache.put(dataSourceProxy.getResourceId(), dataSourceProxy);
        super.registerResource(dataSourceProxy);
    }

AbstractDataSourceCacheResourceManager.java

java 复制代码
    public void registerResource(Resource resource) {
        // 这里的缓存在后面会用于获取resourceId
        dataSourceCache.put(resource.getResourceId(), resource);
        super.registerResource(resource);
    }

2.4 标号2 调用RmNettyRemotingClient

直接调用RmNettyRemotingClient的registerResource方法:

AbstractResourceManager.java

java 复制代码
    public void registerResource(Resource resource) {
        RmNettyRemotingClient.getInstance().registerResource(resource.getResourceGroupId(), resource.getResourceId());
    }

2.5 标号3 调用NettyClientChannelManager

这里的调用逻辑需要注意,并不是想当然的会调用后面的sendRegisterMessage方法(相信初看代码的人都会这么认为),实则是走到了前面的getClientChannelManager().reconnect方法内,多数人也不会想到RM注册发生在reconnect这条路径上:

RmNettyRemotingClient.java

java 复制代码
   public void registerResource(String resourceGroupId, String resourceId) {

        // Resource registration cannot be performed until the RM client is initialized
        if (StringUtils.isBlank(transactionServiceGroup)) {
            return;
        }

        if (getClientChannelManager().getChannels().isEmpty()) {
            // 实际走到了这里
            getClientChannelManager().reconnect(transactionServiceGroup);
            return;
        }
        synchronized (getClientChannelManager().getChannels()) {
            for (Map.Entry<String, Channel> entry : getClientChannelManager().getChannels().entrySet()) {
                String serverAddress = entry.getKey();
                Channel rmChannel = entry.getValue();
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("will register resourceId:{}", resourceId);
                }
                // 刚开始会想当然理解走到这里,顺着这条路走,最终似乎收不到应答消息,也可能特殊场景会走到,没有验证到
                sendRegisterMessage(serverAddress, rmChannel, resourceId);
            }
        }
    }

2.6 标号4 和标号5 调用NettyClientChannelManager

这两个调用点都在同一个方法内,方法触发顺序是reconnect -> acquireChannel -> doConnect:

NettyClientChannelManager.java

java 复制代码
    private Channel doConnect(String serverAddress) {
        Channel channelToServer = channels.get(serverAddress);
        if (channelToServer != null && channelToServer.isActive()) {
            return channelToServer;
        }
        Channel channelFromPool;
        try {
            // 回调RmNettyRemotingClient的getPoolKeyFunction方法
            NettyPoolKey currentPoolKey = poolKeyFunction.apply(serverAddress);
            NettyPoolKey previousPoolKey = poolKeyMap.putIfAbsent(serverAddress, currentPoolKey);
            if (previousPoolKey != null && previousPoolKey.getMessage() instanceof RegisterRMRequest) {
                RegisterRMRequest registerRMRequest = (RegisterRMRequest) currentPoolKey.getMessage();
                ((RegisterRMRequest) previousPoolKey.getMessage()).setResourceIds(registerRMRequest.getResourceIds());
            }
            // GenericKeyedObjectPool的borrowObject方法
            channelFromPool = nettyClientKeyPool.borrowObject(poolKeyMap.get(serverAddress));
            channels.put(serverAddress, channelFromPool);
        } catch (Exception exx) {
            LOGGER.error("{} register RM failed.",FrameworkErrorCode.RegisterRM.getErrCode(), exx);
            throw new FrameworkException("can not register RM,err:" + exx.getMessage());
        }
        return channelFromPool;
    }

其中:

java 复制代码
NettyPoolKey currentPoolKey = poolKeyFunction.apply(serverAddress);

回调了RmNettyRemotingClient的getPoolKeyFunction方法,是个函数接口的匿名实现,返回NettyPoolKey,NettyPoolKey的其中一个属性是RegisterRMRequest,后面将获取到它并发送RM注册请求:

RmNettyRemotingClient.java

java 复制代码
    protected Function<String, NettyPoolKey> getPoolKeyFunction() {
        return serverAddress -> {
            // 获取资源id列表
            String resourceIds = getMergedResourceKeys();
            if (resourceIds != null && LOGGER.isInfoEnabled()) {
                LOGGER.info("RM will register :{}", resourceIds);
            }
            // 构造资源注册请求,所有RM资源一起注册了
            RegisterRMRequest message = new RegisterRMRequest(applicationId, transactionServiceGroup);
            message.setResourceIds(resourceIds);
            // 记住NettyPoolKey的message属性存的就是RegisterRMRequest
            return new NettyPoolKey(NettyPoolKey.TransactionRole.RMROLE, serverAddress, message);
        };
    }

这个getPoolKeyFunction方法在AbstractNettyRemotingClient的构造方法中,构造NettyClientChannelManager时传入:

AbstractNettyRemotingClient.java

java 复制代码
    public AbstractNettyRemotingClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup,
                                       ThreadPoolExecutor messageExecutor, NettyPoolKey.TransactionRole transactionRole) {
        super(messageExecutor);
        this.transactionRole = transactionRole;
        clientBootstrap = new NettyClientBootstrap(nettyClientConfig, eventExecutorGroup, transactionRole);
        clientBootstrap.setChannelHandlers(new ClientHandler());
        // 这里的getPoolKeyFunction方法是其子类RmNettyRemotingClient的方法
        clientChannelManager = new NettyClientChannelManager(
            new NettyPoolableFactory(this, clientBootstrap), getPoolKeyFunction(), nettyClientConfig);
    }

AbstractNettyRemotingClient是RmNettyRemotingClient的父类,因此是在构造RmNettyRemotingClient的时候创建:

RmNettyRemotingClient.java

java 复制代码
    private RmNettyRemotingClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup,
                                  ThreadPoolExecutor messageExecutor) {
        super(nettyClientConfig, eventExecutorGroup, messageExecutor, TransactionRole.RMROLE);
    }

再接着前面,看看RmNettyRemotingClient的getPoolKeyFunction方法里调用的getMergedResourceKeys是如何获取resourceId列表的:

RmNettyRemotingClient.java

java 复制代码
    public String getMergedResourceKeys() {
        // 这里的resourceManager是DefaultResourceManager,获取到所有RM的子类和resourceId映射关系Map,key是resourceId
        Map<String, Resource> managedResources = resourceManager.getManagedResources();
        Set<String> resourceIds = managedResources.keySet();
        if (!resourceIds.isEmpty()) {
            StringBuilder sb = new StringBuilder();
            boolean first = true;
            for (String resourceId : resourceIds) {
                if (first) {
                    first = false;
                } else {
                    sb.append(DBKEYS_SPLIT_CHAR);
                }
                sb.append(resourceId);
            }
            return sb.toString();
        }
        return null;
    }

在DefaultResourceManager中通过rm.getManagedResources()获取到所有RM子类的资源数据:

DefaultResourceManager.java

java 复制代码
    public Map<String, Resource> getManagedResources() {
        Map<String, Resource> allResource = new HashMap<>();
        for (ResourceManager rm : resourceManagers.values()) {
            // RM子类方法,返回前面缓存的数据
            Map<String, Resource> tempResources = rm.getManagedResources();
            if (tempResources != null) {
                allResource.putAll(tempResources);
            }
        }
        return allResource;
    }

子类中的dataSourceCache为缓存数据,在之前存入,以DataSourceManager为例:

DataSourceManager.java

java 复制代码
    // 返回缓存数据
    public Map<String, Resource> getManagedResources() {
        return dataSourceCache;
    }

    // 前面章节提到过,注册时放入了缓存
    public void registerResource(Resource resource) {
        DataSourceProxy dataSourceProxy = (DataSourceProxy) resource;
        dataSourceCache.put(dataSourceProxy.getResourceId(), dataSourceProxy);
        super.registerResource(dataSourceProxy);
    }

2.7 标号6 调用NettyPoolableFactory

GenericKeyedObjectPool是一个开源的池化管理框架,其结构是:

GenericKeyedObjectPool的构造器参数传入一个KeyedPoolableObjectFactory子类对象,当调用GenericKeyedObjectPool的borrowObject方法时,会自动调用传入的子类对象的makeObject方法,这里是NettyPoolableFactory的makeObject方法。

两者的关系在构造NettyClientChannelManager时建立:

NettyClientChannelManager.java

java 复制代码
    NettyClientChannelManager(final NettyPoolableFactory keyPoolableFactory, final Function<String, NettyPoolKey> poolKeyFunction,
                                     final NettyClientConfig clientConfig) {
        nettyClientKeyPool = new GenericKeyedObjectPool<>(keyPoolableFactory);
        nettyClientKeyPool.setConfig(getNettyPoolConfig(clientConfig));
        this.poolKeyFunction = poolKeyFunction;
    }

2.8 标号7 调用RmNettyRemotingClient

NettyPoolableFactory的makeObject方法调用RmNettyRemotingClient发送注册请求,并处理应答:

NettyPoolableFactory.java

java 复制代码
   public Channel makeObject(NettyPoolKey key) {
        InetSocketAddress address = NetUtil.toInetSocketAddress(key.getAddress());
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("NettyPool create channel to " + key);
        }
        Channel tmpChannel = clientBootstrap.getNewChannel(address);
        long start = System.currentTimeMillis();
        Object response;
        Channel channelToServer = null;
        if (key.getMessage() == null) {
            throw new FrameworkException("register msg is null, role:" + key.getTransactionRole().name());
        }
        try {
            // 这里的key.getMessage(),就是前面放入的RegisterRMRequest
            response = rpcRemotingClient.sendSyncRequest(tmpChannel, key.getMessage());
            if (!isRegisterSuccess(response, key.getTransactionRole())) {
                rpcRemotingClient.onRegisterMsgFail(key.getAddress(), tmpChannel, response, key.getMessage());
            } else {
                channelToServer = tmpChannel;
                rpcRemotingClient.onRegisterMsgSuccess(key.getAddress(), tmpChannel, response, key.getMessage());
            }
        } catch (Exception exx) {
            if (tmpChannel != null) {
                tmpChannel.close();
            }
            throw new FrameworkException(
                "register " + key.getTransactionRole().name() + " error, errMsg:" + exx.getMessage());
        }
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("register success, cost " + (System.currentTimeMillis() - start) + " ms, version:" + getVersion(
                response, key.getTransactionRole()) + ",role:" + key.getTransactionRole().name() + ",channel:"
                + channelToServer);
        }
        return channelToServer;
    }

2.9 RmNettyRemotingClient注册请求处理

发送请求调用的是父类AbstractNettyRemotingClient的方法:

AbstractNettyRemotingClient.java

java 复制代码
    public Object sendSyncRequest(Channel channel, Object msg) throws TimeoutException {
        if (channel == null) {
            LOGGER.warn("sendSyncRequest nothing, caused by null channel.");
            return null;
        }
        RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
        return super.sendSync(channel, rpcMessage, NettyClientConfig.getRpcRequestTimeout());
    }

后续的消息发送部分在"Seata消息处理框架"那篇文章中已经阐述。

RM注册成功后的处理:

RmNettyRemotingClient.java

java 复制代码
    public void onRegisterMsgSuccess(String serverAddress, Channel channel, Object response,
                                     AbstractMessage requestMessage) {
        RegisterRMRequest registerRMRequest = (RegisterRMRequest)requestMessage;
        RegisterRMResponse registerRMResponse = (RegisterRMResponse)response;
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("register RM success. client version:{}, server version:{},channel:{}", registerRMRequest.getVersion(), registerRMResponse.getVersion(), channel);
        }
        // 建立地址和channel的关系
        getClientChannelManager().registerChannel(serverAddress, channel);
        // 下面这段逻辑应该加上注释,说明什么场景下还需要再次注册,虽然比较关系说明不一致就注册,但是什么场景下会不一致应描述
        String dbKey = getMergedResourceKeys();
        if (registerRMRequest.getResourceIds() != null) {
            if (!registerRMRequest.getResourceIds().equals(dbKey)) {
                sendRegisterMessage(serverAddress, channel, dbKey);
            }
        }
    }

RM注册失败后的处理,抛出异常:

RmNettyRemotingClient.java

java 复制代码
    public void onRegisterMsgFail(String serverAddress, Channel channel, Object response,
                                  AbstractMessage requestMessage) {
        RegisterRMRequest registerRMRequest = (RegisterRMRequest)requestMessage;
        RegisterRMResponse registerRMResponse = (RegisterRMResponse)response;
        String errMsg = String.format(
            "register RM failed. client version: %s,server version: %s, errorMsg: %s, " + "channel: %s", registerRMRequest.getVersion(), registerRMResponse.getVersion(), registerRMResponse.getMsg(), channel);
        throw new FrameworkException(errMsg);
    }

阅过留痕,点赞,收藏,关注三连!

相关推荐
BestAns32 分钟前
一文带你吃透 Java 反射机制
java·后端
wasp52040 分钟前
AgentScope Java 核心架构深度解析
java·开发语言·人工智能·架构·agentscope
2501_916766541 小时前
【Springboot】数据层开发-数据源自动管理
java·spring boot·后端
自在极意功。1 小时前
MyBatis 动态 SQL 详解:从基础到进阶实战
java·数据库·mybatis·动态sql
软件管理系统1 小时前
基于Spring Boot的便民维修管理系统
java·spring boot·后端
百***78752 小时前
Step-Audio-2 轻量化接入全流程详解
android·java·gpt·php·llama
快乐肚皮2 小时前
MySQL递归CTE
java·数据库·mysql·递归表达式
廋到被风吹走2 小时前
【Spring】DispatcherServlet解析
java·后端·spring
廋到被风吹走2 小时前
【Spring】PlatformTransactionManager详解
java·spring·wpf