Seata AT mode start source code analysis

From a "paper design principles of distributed transaction middleware Seata " talked about some of the design principles under Seata AT mode, which is also know three roles AT mode (RM, TM, TC), then I will update Seata source code analysis series. Today to analyze Seata AT mode at startup have done what operations.

Client starts logic

(TM) is responsible for the entire global transaction manager, thus a global transaction TM is opened, there is a TM of GlobalTransaction global management class, the following structure:

io.seata.tm.api.GlobalTransaction

public interface GlobalTransaction {

  void begin() throws TransactionException;

  void begin(int timeout) throws TransactionException;

  void begin(int timeout, String name) throws TransactionException;

  void commit() throws TransactionException;

  void rollback() throws TransactionException;
  
  GlobalStatus getStatus() throws TransactionException;
  
  // ...
}

GlobalTransactionContext can be created by a GlobalTransaction, and then open a global transaction with GlobalTransaction, commit, rollback and other operations, so the way we use Seata AT mode API directly:

//init seata;
TMClient.init(applicationId, txServiceGroup);
RMClient.init(applicationId, txServiceGroup);
//trx
GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();
try {
  tx.begin(60000, "testBiz");
  // 事务处理
  // ...
  tx.commit();
} catch (Exception exx) {
  tx.rollback();
  throw exx;
}

If every time you use global transactions are written, will inevitably result in code redundancy, our projects are based on the Spring container, then we can use the characteristics of Spring AOP mode with the template package these redundant code template, reference Mybatis -spring also do such a thing, then the next we have to analyze what had been done when you start Seata Spring-based projects and registered global transaction.

We start a global transaction is in the plus method @GlobalTransactionalcomment, Seata the Spring module, there is a GlobalTransactionScanner, its inheritance as follows:

public class GlobalTransactionScanner extends AbstractAutoProxyCreator implements InitializingBean, ApplicationContextAware, DisposableBean {
  // ...
}

During start-up Spring-based project, the class initialization procedure will be as follows:

image-20191124155455309

InitializingBean of afterPropertiesSet () method calls initClient () method:

io.seata.spring.annotation.GlobalTransactionScanner#initClient

TMClient.init(applicationId, txServiceGroup);
RMClient.init(applicationId, txServiceGroup);

The TM and RM did initialization.

  • TM Initialization

io.seata.tm.TMClient#init

public static void init(String applicationId, String transactionServiceGroup) {
  // 获取 TmRpcClient 实例
  TmRpcClient tmRpcClient = TmRpcClient.getInstance(applicationId, transactionServiceGroup);
  // 初始化 TM Client
  tmRpcClient.init();
}

Call TmRpcClient.getInstance () method will get a TM client instance, in the acquisition process, will create Netty client configuration file objects, and create messageExecutor thread pool, the thread pool used to handle various message interaction with the server, when you create TmRpcClient instance, create ClientBootstrap, Netty used to start and stop management services, as well as ClientChannelManager, which is dedicated to managing client object pool Netty, Netty part Seata used in conjunction with an object eating, speaking later in the network analysis module to.

io.seata.core.rpc.netty.AbstractRpcRemotingClient#init

public void init() {
  clientBootstrap.start();
  // 定时尝试连接服务端
  timerExecutor.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
      clientChannelManager.reconnect(getTransactionServiceGroup());
    }
  }, SCHEDULE_INTERVAL_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.SECONDS);
  mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD,
                                                    MAX_MERGE_SEND_THREAD,
                                                    KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS,
                                                    new LinkedBlockingQueue<>(),
                                                    new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD));
  mergeSendExecutorService.submit(new MergedSendRunnable());
  super.init();
}

TM client calls init () method will eventually start netty client (this time has not yet really started, will not actually start in the pool when the object is called); open a regular job, regular resend RegisterTMRequest (RM client sending RegisterRMRequest) server connection request attempts, particularly in the cache logic NettyClientChannelManager the channels in the channel the client, if the acquisition is not present at this time has expired channels, it will try to connect to the server to retrieve and cache the channel channels; turning on a separate thread for processing asynchronous requests, cleverly used here, after the network analysis module analyzes its specific.

io.seata.core.rpc.netty.AbstractRpcRemoting#init

public void init() {
  timerExecutor.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
      for (Map.Entry<Integer, MessageFuture> entry : futures.entrySet()) {
        if (entry.getValue().isTimeout()) {
          futures.remove(entry.getKey());
          entry.getValue().setResultMessage(null);
          if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("timeout clear future: {}", entry.getValue().getRequestMessage().getBody());
          }
        }
      }

      nowMills = System.currentTimeMillis();
    }
  }, TIMEOUT_CHECK_INTERNAL, TIMEOUT_CHECK_INTERNAL, TimeUnit.MILLISECONDS);
}

In the init method AbstractRpcRemoting in, but also opens up a timed task, the scheduled task is mainly futrue timing for clearing futures expired, futures are future need to return an object sends a request to save the results, the object has a timeout, too the timeout will automatically throw an exception, and therefore need to regularly clear the future of the object has expired.

  • RM initialization

io.seata.rm.RMClient#init

public static void init(String applicationId, String transactionServiceGroup) {
  RmRpcClient rmRpcClient = RmRpcClient.getInstance(applicationId, transactionServiceGroup);
  rmRpcClient.setResourceManager(DefaultResourceManager.get());
  rmRpcClient.setClientMessageListener(new RmMessageListener(DefaultRMHandler.get()));
  rmRpcClient.init();
}

RmRpcClient.getInstance processing logic and TM roughly the same; ResourceManager RM Explorer is a registered branch in charge of affairs, submission, reporting, and rollback, as well as a global lock query operations, DefaultResourceManager will hold all of the current resource manager RM , unified call processing, and get () method is to load the current Explorer, primarily a mechanism similar to SPI, the flexible loading, the following figure, the configuration under the category Seata scans the META-INF / services / directory and dynamic loading.

RM is ClientMessageListener messaging listener, is responsible for processing instructions sent from the TC over, branches and submit branch, branch rollback, and delete undo log file; last init method also substantially coincides with TM logic; DefaultRMHandler encapsulates Some specific logic operation RM transaction branches.

Then take a look at what is new method wrapIfNecessary operation.

io.seata.spring.annotation.GlobalTransactionScanner#wrapIfNecessary

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
  // 判断是否有开启全局事务
  if (disableGlobalTransaction) {
    return bean;
  }
  try {
    synchronized (PROXYED_SET) {
      if (PROXYED_SET.contains(beanName)) {
        return bean;
      }
      interceptor = null;
      //check TCC proxy
      if (TCCBeanParserUtils.isTccAutoProxy(bean, beanName, applicationContext)) {
        //TCC interceptor, proxy bean of sofa:reference/dubbo:reference, and LocalTCC
        interceptor = new TccActionInterceptor(TCCBeanParserUtils.getRemotingDesc(beanName));
      } else {
        Class<?> serviceInterface = SpringProxyUtils.findTargetClass(bean);
        Class<?>[] interfacesIfJdk = SpringProxyUtils.findInterfaces(bean);

        // 判断 bean 中是否有 GlobalTransactional 和 GlobalLock 注解
        if (!existsAnnotation(new Class[]{serviceInterface})
            && !existsAnnotation(interfacesIfJdk)) {
          return bean;
        }

        if (interceptor == null) {
          // 创建代理类
          interceptor = new GlobalTransactionalInterceptor(failureHandlerHook);
        }
      }

      LOGGER.info("Bean[{}] with name [{}] would use interceptor [{}]",
                  bean.getClass().getName(), beanName, interceptor.getClass().getName());
      if (!AopUtils.isAopProxy(bean)) {
        bean = super.wrapIfNecessary(bean, beanName, cacheKey);
      } else {
        AdvisedSupport advised = SpringProxyUtils.getAdvisedSupport(bean);
        // 执行包装目标对象到代理对象  
        Advisor[] advisor = super.buildAdvisors(beanName, getAdvicesAndAdvisorsForBean(null, null, null));
        for (Advisor avr : advisor) {
          advised.addAdvisor(0, avr);
        }
      }
      PROXYED_SET.add(beanName);
      return bean;
    }
  } catch (Exception exx) {
    throw new RuntimeException(exx);
  }
}

GlobalTransactionScanner inherited AbstractAutoProxyCreator, AOP support for Spring, it can be seen from the code, instead of a method using GlobalTransactionalInterceptor annotated GlobalTransactional and GlobalLock.

GlobalTransactionalInterceptor realized MethodInterceptor:

io.seata.spring.annotation.GlobalTransactionalInterceptor#invoke

public Object invoke(final MethodInvocation methodInvocation) throws Throwable {
  Class<?> targetClass = methodInvocation.getThis() != null ? AopUtils.getTargetClass(methodInvocation.getThis()) : null;
  Method specificMethod = ClassUtils.getMostSpecificMethod(methodInvocation.getMethod(), targetClass);
  final Method method = BridgeMethodResolver.findBridgedMethod(specificMethod);

  final GlobalTransactional globalTransactionalAnnotation = getAnnotation(method, GlobalTransactional.class);
  final GlobalLock globalLockAnnotation = getAnnotation(method, GlobalLock.class);
  if (globalTransactionalAnnotation != null) {
    // 全局事务注解
    return handleGlobalTransaction(methodInvocation, globalTransactionalAnnotation);
  } else if (globalLockAnnotation != null) {
    // 全局锁注解
    return handleGlobalLock(methodInvocation);
  } else {
    return methodInvocation.proceed();
  }
}

Logic as above is performed by the proxy method, wherein handleGlobalTransaction () method which calls TransactionalTemplate template:

io.seata.spring.annotation.GlobalTransactionalInterceptor#handleGlobalTransaction

private Object handleGlobalTransaction(final MethodInvocation methodInvocation,
                                       final GlobalTransactional globalTrxAnno) throws Throwable {
  try {
    return transactionalTemplate.execute(new TransactionalExecutor() {
      @Override
      public Object execute() throws Throwable {
        return methodInvocation.proceed();
      }
      @Override
      public TransactionInfo getTransactionInfo() {
        // ...
      }
    });
  } catch (TransactionalExecutor.ExecutionException e) {
    // ...
  }
}

handleGlobalTransaction () method performs the execute method is TransactionalTemplate template class:

io.seata.tm.api.TransactionalTemplate#execute

public Object execute(TransactionalExecutor business) throws Throwable {
  // 1. get or create a transaction
  GlobalTransaction tx = GlobalTransactionContext.getCurrentOrCreate();

  // 1.1 get transactionInfo
  TransactionInfo txInfo = business.getTransactionInfo();
  if (txInfo == null) {
    throw new ShouldNeverHappenException("transactionInfo does not exist");
  }
  try {

    // 2. begin transaction
    beginTransaction(txInfo, tx);

    Object rs = null;
    try {

      // Do Your Business
      rs = business.execute();

    } catch (Throwable ex) {

      // 3.the needed business exception to rollback.
      completeTransactionAfterThrowing(txInfo,tx,ex);
      throw ex;
    }

    // 4. everything is fine, commit.
    commitTransaction(tx);

    return rs;
  } finally {
    //5. clear
    triggerAfterCompletion();
    cleanUp();
  }
}

The above is not a feeling of deja vu? Yes, that's often when we use the API to write redundant code, now Spring through a proxy mode, these redundant code inside the package with the template, it will encapsulate all those redundant code processing unified process, do not need you write a show, who are interested can go and see Mybatis-spring source, is written very exciting.

Server processing logic

Server receives client connections, of course, is to channel also cached, also said in front of the client sends RegisterRMRequest / RegisterTMRequest request to the server, the listener will call ServerMessageListener receive treatment after the server:

io.seata.core.rpc.ServerMessageListener

public interface ServerMessageListener {
  // 处理各种事务,如分支注册、分支提交、分支上报、分支回滚等等
  void onTrxMessage(RpcMessage request, ChannelHandlerContext ctx, ServerMessageSender sender);
    // 处理 RM 客户端的注册连接
  void onRegRmMessage(RpcMessage request, ChannelHandlerContext ctx,
                      ServerMessageSender sender, RegisterCheckAuthHandler checkAuthHandler);
  // 处理 TM 客户端的注册连接
  void onRegTmMessage(RpcMessage request, ChannelHandlerContext ctx,
                      ServerMessageSender sender, RegisterCheckAuthHandler checkAuthHandler);
  // 服务端与客户端保持心跳
  void onCheckMessage(RpcMessage request, ChannelHandlerContext ctx, ServerMessageSender sender)

}

ChannelManager a channel manager server, each server and client communications, the client needs to obtain the corresponding channel from ChannelManager, which structure for storing buffer TM and RM client channel as follows:

/**
 * resourceId -> applicationId -> ip -> port -> RpcContext
 */
private static final ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer,
RpcContext>>>>
  RM_CHANNELS = new ConcurrentHashMap<String, ConcurrentMap<String, ConcurrentMap<String, ConcurrentMap<Integer,
RpcContext>>>>();

/**
 * ip+appname,port
 */
private static final ConcurrentMap<String, ConcurrentMap<Integer, RpcContext>> TM_CHANNELS
  = new ConcurrentHashMap<String, ConcurrentMap<Integer, RpcContext>>();

Map above structure a bit more complicated:

RM_CHANNELS:

  1. resourceId refers to the database of the RM client address;
  2. applicationId refers RM client service Id, such springboot configuration spring.application.name = account-service account-service that is in applicationId;
  3. ip refers to the RM client service address;
  4. port refers to the RM client service address;
  5. RpcContext save the information in this registration request.

TM_CHANNELS:

  1. ip + appname: Note here should be wrong, it should be appname + ip, i.e. TM_CHANNELS Map Structure of a key is appname + ip;
  2. port: Port number of the client.

The following is the RM Client registration logic:

io.seata.core.rpc.ChannelManager#registerRMChannel

public static void registerRMChannel(RegisterRMRequest resourceManagerRequest, Channel channel)
  throws IncompatibleVersionException {
  Version.checkVersion(resourceManagerRequest.getVersion());
  // 将 ResourceIds 数据库连接连接信息放入一个set中
  Set<String> dbkeySet = dbKeytoSet(resourceManagerRequest.getResourceIds());
  RpcContext rpcContext;
  // 从缓存中判断是否有该channel信息
  if (!IDENTIFIED_CHANNELS.containsKey(channel)) {
    // 根据请求注册信息,构建 rpcContext
    rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.RMROLE, resourceManagerRequest.getVersion(),
                                    resourceManagerRequest.getApplicationId(), resourceManagerRequest.getTransactionServiceGroup(),
                                    resourceManagerRequest.getResourceIds(), channel);
    // 将 rpcContext 放入缓存中
    rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS);
  } else {
    rpcContext = IDENTIFIED_CHANNELS.get(channel);
    rpcContext.addResources(dbkeySet);
  }
  if (null == dbkeySet || dbkeySet.isEmpty()) { return; }
  for (String resourceId : dbkeySet) {
    String clientIp;
    // 将请求信息存入 RM_CHANNELS 中,这里用了 java8 的 computeIfAbsent 方法操作
    ConcurrentMap<Integer, RpcContext> portMap = RM_CHANNELS.computeIfAbsent(resourceId, resourceIdKey -> new ConcurrentHashMap<>())
      .computeIfAbsent(resourceManagerRequest.getApplicationId(), applicationId -> new ConcurrentHashMap<>())
      .computeIfAbsent(clientIp = getClientIpFromChannel(channel), clientIpKey -> new ConcurrentHashMap<>());
        // 将当前 rpcContext 放入 portMap 中
    rpcContext.holdInResourceManagerChannels(resourceId, portMap);
    updateChannelsResource(resourceId, clientIp, resourceManagerRequest.getApplicationId());
  }
}

As can be seen from the above code logic, primarily to register RM client information registration request, into the cache RM_CHANNELS, while also determines whether this request is verified through the channel from the IDENTIFIED_CHANNELS, IDENTIFIED_CHANNELS the following structure:

private static final ConcurrentMap<Channel, RpcContext> IDENTIFIED_CHANNELS
  = new ConcurrentHashMap<>();

IDENTIFIED_CHANNELS channel contains all the TM and RM registered.

The following is a TM registration logic:

io.seata.core.rpc.ChannelManager#registerTMChannel

public static void registerTMChannel(RegisterTMRequest request, Channel channel)
  throws IncompatibleVersionException {
  Version.checkVersion(request.getVersion());
  // 根据请求注册信息,构建 RpcContext
  RpcContext rpcContext = buildChannelHolder(NettyPoolKey.TransactionRole.TMROLE, request.getVersion(),
                                             request.getApplicationId(),
                                             request.getTransactionServiceGroup(),
                                             null, channel);
  // 将 RpcContext 放入 IDENTIFIED_CHANNELS 缓存中
  rpcContext.holdInIdentifiedChannels(IDENTIFIED_CHANNELS);
  // account-service:127.0.0.1:63353
  String clientIdentified = rpcContext.getApplicationId() + Constants.CLIENT_ID_SPLIT_CHAR
    + getClientIpFromChannel(channel);
  // 将请求信息存入 TM_CHANNELS 缓存中
  TM_CHANNELS.putIfAbsent(clientIdentified, new ConcurrentHashMap<Integer, RpcContext>());
  // 将上一步创建好的get出来,之后再将rpcContext放入这个map的value中
  ConcurrentMap<Integer, RpcContext> clientIdentifiedMap = TM_CHANNELS.get(clientIdentified);
  rpcContext.holdInClientChannels(clientIdentifiedMap);
}

Registered TM client generally similar to the current registration information into the corresponding cache saved, but simple logic RM client registration than some, mainly involves information RM client branch affairs resources, information will need to register than TM client and more.

No public more exciting articles please pay attention to the maintenance of a "back-end Advanced", which is a focus on back-end technology-related public number.
No public attention and reply "back-end" to receive free e-books related to the back-end.
Welcome to share, reproduced Please keep the source.

No public "back-end Advanced", focused on the back-end technology to share!

Guess you like

Origin www.cnblogs.com/objcoding/p/12031179.html