乐于分享
好东西不私藏

【分布式事务】5、分布式事务Seata源码学习

【分布式事务】5、分布式事务Seata源码学习
PART 01
Seata的AT模型回顾
1.1
Seata的AT模型
  • 在服务A中开启全局事务,向TC申请全局事务id

  • 服务A发起RPC远程调用,分布式事务参与方服务B和服务C分别向TC注册分支事务

  • 参与方提交本地事务,并记录undo_log表

  • 参与方上报分支事务的执行状态

  • TC根据各分支事务执行状态,通知全局事务是要提交还是回滚

  • 分布式事务各参与方TC指示,如果是要提交全局事务,就直接删除本地表的undo_log表里边对应的记录即可,如果是需要回滚,则根据undo_log表里边的before_image对对应的数据进行回滚

1.2
Seata的AT模型在实际开发中的应用

要在应用中使用Seata的AT模型也非常简单,只需要简单几步:

  • ① 引入seata依赖

  • ② 创建本地undo_log表

  • ③ 在配置文件中进行Seata相关的配置

  • ④ @GlobalTransactional注解开启分布式事务

具体操作见前面的博客内容

PART 02
核心步骤源码分析(AT)
2.1
RM、TM建立与TC的连接

要想开启分布式事务,前提是RM、TM持有和TC的连接,这样才能完成通信。那么这个连接是在什么时候建立的呢?首先我们看一下@GlobalTransactional注解的包结构:

可以看到这个注解定义在org.apache.seata.spring.annotation这个包下,与此同时还定义了一个以Scanner结尾的扫描器,点开看一下这个扫描器

public classGlobalTransactionScannerextendsAbstractAutoProxyCreator        implements CachedConfigurationChangeListenerInitializingBeanApplicationContextAwareDisposableBean {

这个扫描器实现了InitializingBean这个接口,那么就一定实现了它的afterPropertiesSet方法

@Overridepublic void afterPropertiesSet() {    if (disableGlobalTransaction) {        if (LOGGER.isInfoEnabled()) {            LOGGER.info("Global transaction is disabled.");        }        ConfigurationFactory.getInstance().addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION, (CachedConfigurationChangeListenerthis);        return;    }    if (initialized.compareAndSet(falsetrue)) {        initClient();   //初始化客户端,什么客户端呢?    }    this.findBusinessBeanNamesNeededEnhancement();}

这里有一个初始化客户端的方法,那么初始化的是什么客户端呢?继续点进去就可以看到初始化TM和RM的代码

protectedvoidinitClient() {    if (LOGGER.isInfoEnabled()) {        LOGGER.info("Initializing Global Transaction Clients ... ");    }    if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {        LOGGER.warn("the default value of seata.tx-service-group: {} has already changed to {} since Seata 1.5, " +                    "please change your default configuration as soon as possible " +                    "and we don't recommend you to use default tx-service-group's value provided by seata",                    DEFAULT_TX_GROUP_OLD, DEFAULT_TX_GROUP);    }    if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {        throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));    }    //init TM    TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);    if (LOGGER.isInfoEnabled()) {        LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);    }    //init RM    RMClient.init(applicationId, txServiceGroup);    if (LOGGER.isInfoEnabled()) {        LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);    }    if (LOGGER.isInfoEnabled()) {        LOGGER.info("Global Transaction Clients are initialized. ");    }    registerSpringShutdownHook();}

以初始化RM的代码为例,继续

public class RMClient {    /**     * Init.     *     * @param applicationId           the application id     * @param transactionServiceGroup the transaction service group     */    public static void init(String applicationId, String transactionServiceGroup) {        RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);        rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());        rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());        rmNettyRemotingClient.init();    }}

继续点进init方法来到org.apache.seata.core.rpc.netty.RmNettyRemotingClient#init

@Overridepublic void init() {    // registry processor    registerProcessor();    if (initialized.compareAndSet(falsetrue)) {        super.init();        // Found one or more resources that were registered before initialization        if (resourceManager != null            && !resourceManager.getManagedResources().isEmpty()            && StringUtils.isNotBlank(transactionServiceGroup)) {            boolean failFast = ConfigurationFactory.getInstance().getBoolean(                ConfigurationKeys.ENABLE_RM_CLIENT_CHANNEL_CHECK_FAIL_FAST,                DefaultValues.DEFAULT_CLIENT_CHANNEL_CHECK_FAIL_FAST);            getClientChannelManager().initReconnect(transactionServiceGroup, failFast);        }    }}

先看一下super.init(),进入org.apache.seata.core.rpc.netty.AbstractNettyRemotingClient#init

@Overridepublic void init() {    timerExecutor.scheduleAtFixedRate(() -> {        try {            clientChannelManager.reconnect(getTransactionServiceGroup());        } catch (Exception ex) {            LOGGER.warn("reconnect server failed. {}", ex.getMessage());        }    }, SCHEDULE_DELAY_MILLSSCHEDULE_INTERVAL_MILLSTimeUnit.MILLISECONDS);    if (this.isEnableClientBatchSendRequest()) {        mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD,                                                          MAX_MERGE_SEND_THREAD,                                                          KEEP_ALIVE_TIMETimeUnit.MILLISECONDS,                                                          new LinkedBlockingQueue<>(),                                                          new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD));        mergeSendExecutorService.submit(new MergedSendRunnable());    }    super.init();    clientBootstrap.start();}

这里调了一个getTransactionServiceGroup()方法,transactionServiceGroup我们之前就在配置文件中配置过,前面提到会根据这个serviceGroup去找到对应的SeataServer,不妨点clientChannelManager.reconnect() 进去看一下,看是不是根据这个serviceGroup去连接TC,点进去之后经过几个重载的方法后来到了doReconnect方法:

void doReconnect(String transactionServiceGroup, boolean failFast) {    List<String> availList;    try {        availList = getAvailServerList(transactionServiceGroup);    } catch (Exception e) {        LOGGER.error("Failed to get available servers: {}", e.getMessage(), e);        throwFailFastException(failFast, "Failed to get available servers");        return;    }    if (CollectionUtils.isEmpty(availList)) {        RegistryService registryService = RegistryFactory.getInstance();        String clusterName = registryService.getServiceGroup(transactionServiceGroup);        if (StringUtils.isBlank(clusterName)) {            LOGGER.error("can not get cluster name in registry config '{}{}', please make sure registry config correct",                         ConfigurationKeys.SERVICE_GROUP_MAPPING_PREFIX,                         transactionServiceGroup);            throwFailFastException(failFast, "can not get cluster name in registry config.");            return;        }        if (!(registryService instanceof FileRegistryServiceImpl)) {            LOGGER.error("no available service found in cluster '{}', please make sure registry config correct and keep your seata server running", clusterName);        }        throwFailFastException(failFast, "no available service found in cluster.");        return;    }    try {        doReconnect(availList, transactionServiceGroup);    } catch (Exception e) {        if (failFast) {            throw e;        }        LOGGER.error("connect server failed. {}", e.getMessage(), e);    }}

确实是拿到了一个可用的地址列表availList,然后根据这个列表继续调了doReconnect方法

void doReconnect(List<String> availList, String transactionServiceGroup) {    Set<String> channelAddress = new HashSet<>(availList.size());    Map<StringException> failedMap = new HashMap<>();    try {        for (String serverAddress : availList) {            try {                acquireChannel(serverAddress);                channelAddress.add(serverAddress);            } catch (Exception e) {                failedMap.put(serverAddress, e);            }        }        if (failedMap.size() > 0) {            if (LOGGER.isInfoEnabled()) {                LOGGER.error("{} can not connect to {} cause:{}"FrameworkErrorCode.NetConnect.getErrCode(),                             failedMap.keySet(),                             failedMap.values().stream().map(Throwable::getMessage).collect(Collectors.toSet()));            } else if (LOGGER.isDebugEnabled()) {                failedMap.forEach((key, value) -> {                    LOGGER.error("{} can not connect to {} cause:{} trace information:",                                 FrameworkErrorCode.NetConnect.getErrCode(), key, value.getMessage(), value);                });            }        }        if (availList.size() == failedMap.size()) {            String invalidAddress = StringUtils.join(failedMap.keySet().iterator(), ", ");            throw new FrameworkException("can not connect to [" + invalidAddress + "]");        }    } finally {        if (CollectionUtils.isNotEmpty(channelAddress)) {            List<InetSocketAddress> aliveAddress = new ArrayList<>(channelAddress.size());            for (String address : channelAddress) {                String[] array = NetUtil.splitIPPortStr(address);                aliveAddress.add(new InetSocketAddress(array[0], Integer.parseInt(array[1])));            }            RegistryFactory.getInstance().refreshAliveLookup(transactionServiceGroup, aliveAddress);        } else {            RegistryFactory.getInstance().refreshAliveLookup(transactionServiceGroup, Collections.emptyList());        }    }}

可以看到会根据这个列表迭代,然后根据每个serverAddress通过acquireChannel方法获取了一个Channel(如果没有现成的连接,则会通过doConnect(serverAddress)方法创建一个channel)

Channel acquireChannel(String serverAddress) {    Channel channelToServer = channels.get(serverAddress);    if (channelToServer != null) {        channelToServer = getExistAliveChannel(channelToServer, serverAddress);        if (channelToServer != null) {            return channelToServer;        }    }    if (LOGGER.isInfoEnabled()) {        LOGGER.info("will connect to {}", serverAddress);    }    Object lockObj = CollectionUtils.computeIfAbsent(channelLocks, serverAddress, key -> new Object());    synchronized (lockObj) {        return doConnect(serverAddress);    }}

TM的连接建立流程类似,总的来说大致的流程如下:

2.2
事务处理

前面我们知道,分布式事务的开启是靠@GlobalTransactional这个注解开启的,那么肯定就有一个地方来识别这个注解并做一些事情。如果是我们自己来做的化,最先想到的肯定是基于aop来做,而从最上面的那个包结构截图可以看到,紧挨着GlobalTransactional这个注解还定义了一个AspectTransactionalInterceptor类前

public classAspectTransactionalInterceptorimplementsMethodInterceptor{

既然这个类实现了MethodInterceptor接口,那程序在执行的时候肯定会调用它的invoke方法

@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {    Class<?> targetClass = invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null;    Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);    InvocationWrapper invocationWrapper = new DefaultInvocationWrapper(null, invocation.getThis(), specificMethod, invocation.getArguments());    return this.globalTransactionalInterceptorHandler.invoke(invocationWrapper);}

继续点进去最后一行的globalTransactionalInterceptorHandler.invoke方法,就来到了org.apache.seata.integration.tx.api.interceptor.handler.AbstractProxyInvocationHandler#invoke

@Overridepublic Object invoke(InvocationWrapper invocation) throws Throwable {    if (CollectionUtils.isNotEmpty(getMethodsToProxy()) && !getMethodsToProxy().contains(invocation.getMethod().getName())) {        return invocation.proceed();    }    if (nextInvocationHandlerChain != null) {        invocation = new NestInterceptorHandlerWrapper(nextInvocationHandlerChain, ihttp://blog.shengxiao.tech/imagesnvocation);    }    return doInvoke(invocation);}

最终来到org.apache.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler#doInvoke

@Overrideprotected Object doInvoke(InvocationWrapper invocation) throws Throwable {    Class<?> targetClass = invocation.getTarget().getClass();    Method specificMethod = ClassUtils.getMostSpecificMethod(invocation.getMethod(), targetClass);    if (specificMethod != null && !specificMethod.getDeclaringClass().equals(Object.class)) {        boolean localDisable = disable || (ATOMIC_DEGRADE_CHECK.get() && degradeNum >= degradeCheckAllowTimes);        if (!localDisable) {            final AspectTransactional globalTransactionalAnnotation = getAspectTransactional(specificMethod, targetClass);            final GlobalLockConfig globalLockAnnotation = getGlobalLockConfig(specificMethod, targetClass);            if (globalTransactionalAnnotation != null || this.aspectTransactional != null) {                AspectTransactional transactional;                if (globalTransactionalAnnotation != null) {                    transactional = globalTransactionalAnnotation;                } else {                    transactional = this.aspectTransactional;                }                return handleGlobalTransaction(invocation, transactional);            } else if (globalLockAnnotation != null) {                return handleGlobalLock(invocation, globalLockAnnotation);            }        }    }    return invocation.proceed();}

这里边有两个分支,一个是如果globalTransactionalAnnotation或者aspectTransactional不是空的话,就进入handleGlobalTransaction,否则进入handleGlobalLock

进入org.apache.seata.integration.tx.api.interceptor.handler.GlobalTransactionalInterceptorHandler#handleGlobalTransaction,其实只有一行transactionalTemplate.execute,但是这个方法的调用传了一个匿名内部类进来,看一下这个execute方法

public Object execute(TransactionalExecutor business) throws Throwable {    // 1. Get transactionInfo    TransactionInfo txInfo = business.getTransactionInfo();    if (txInfo == null) {        throw new ShouldNeverHappenException("transactionInfo does not exist");    }    // 1.1 Get current transaction, if not null, the tx role is 'GlobalTransactionRole.Participant'.    GlobalTransaction tx = GlobalTransactionContext.getCurrent();    // 1.2 Handle the transaction propagation.    Propagation propagation = txInfo.getPropagation();    SuspendedResourcesHolder suspendedResourcesHolder = null;    try {        switch (propagation) {            case NOT_SUPPORTED:                // If transaction is existing, suspend it.                if (existingTransaction(tx)) {                    suspendedResourcesHolder = tx.suspend(false);                }                // Execute without transaction and return.                return business.execute();            case REQUIRES_NEW:                // If transaction is existing, suspend it, and then begin new transaction.                if (existingTransaction(tx)) {                    suspendedResourcesHolder = tx.suspend(false);                }                tx = GlobalTransactionContext.createNew();                // Continue and execute with new transaction                break;            case SUPPORTS:                // If transaction is not existing, execute without transaction.                if (notExistingTransaction(tx)) {                    return business.execute();                }                // Continue and execute with new transaction                break;            case REQUIRED:                // If current transaction is existing, execute with current transaction,else create                tx = GlobalTransactionContext.getCurrentOrCreate();                break;            case NEVER:                // If transaction is existing, throw exception.                if (existingTransaction(tx)) {                    throw new TransactionException(                        String.format("Existing transaction found for transaction marked with propagation 'never', xid = %s"                                      , tx.getXid()));                } else {                    // Execute without transaction and return.                    return business.execute();                }            case MANDATORY:                // If transaction is not existing, throw exception.                if (notExistingTransaction(tx)) {                    throw new TransactionException("No existing transaction found for transaction marked with propagation 'mandatory'");                }                // Continue and execute with current transaction.                break;            default:                throw new TransactionException("Not Supported Propagation:" + propagation);        }        // set current tx config to holder        GlobalLockConfig previousConfig = replaceGlobalLockConfig(txInfo);        if (tx.getGlobalTransactionRole() == GlobalTransactionRole.Participant) {            LOGGER.info("join into a existing global transaction,xid={}", tx.getXid());        }        try {            // 2. If the tx role is 'GlobalTransactionRole.Launcher', send the request of beginTransaction to TC,            //    else do nothing. Of course, the hooks will still be triggered.            beginTransaction(txInfo, tx);            Object rs;            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, txInfo);            return rs;        } finally {            //5. clear            resumeGlobalLockConfig(previousConfig);            triggerAfterCompletion(tx);            cleanUp(tx);        }    } finally {        // If the transaction is suspended, resume it.        if (suspendedResourcesHolder != null) {            tx.resume(suspendedResourcesHolder);        }    }}

这里看到两个重点内容:

  • 事务传播属性

seata自己定义了几种事务传播属性,但基本上保持和spring的事务传播属性定义一致,比如NOT_SUPPORTED的时候,将当前事务挂起,以非事务的方式执行并返回;再比如REQUIRES_NEW的时候,将当前事务挂起,然后创建一个新的事务并继续执行后续的事务和业务逻辑。

这里一共定义了6种,相比较spring少了一种NESTED

  • NOT_SUPPORTED:不支持事务,如果有事务,则挂起,以非事务的方式运行

  • REQUIRES_NEW:如果当前有事务,则将当前事务挂起,然后新起一个事务执行

  • SUPPORTS:如果当前没有事务,则以非事务的方式执行,否则就以当前事务执行

  • REQUIRED:如果当前没有事务,则创建一个事务执行,否则就用当前事务执行

  • NEVER:如果当前存在事务,则报错,如果不存在事务,就正常按照非事务的方式执行

  • MANDATORY:如果当前事务不存在,则报错,如果存在,就以当前事务执行

  • 事务的处理

上面的代码可以明显看到beginTransactionccommitTransaction、以及最里边的catch里边的completeTransactionAfterThrowing

1、beginTransaction方法

private void beginTransaction(TransactionInfo txInfo, GlobalTransaction tx) throws TransactionalExecutor.ExecutionException { if (tx.getGlobalTransactionRole() != GlobalTransactionRole.Launcher) {     if (LOGGER.isDebugEnabled()) {         LOGGER.debug("Ignore begin: just involved in global transaction [{}]", tx.getXid());     }     return; } try {     triggerBeforeBegin();     tx.begin(txInfo.getTimeOut(), txInfo.getName());     triggerAfterBegin(); } catch (TransactionException txe) {     throw new TransactionalExecutor.ExecutionException(tx, txe,                                                        TransactionalExecutor.Code.BeginFailure); }}

这个方法里边就是触发事务开启之前、事务开启、以及触发开启事务之后三个方法,主要看下tx.begin

org.apache.seata.tm.api.DefaultGlobalTransaction#begin(int, java.lang.String)

@Overridepublic void begin(int timeout, String name) throws TransactionException { this.createTime = System.currentTimeMillis(); if (role != GlobalTransactionRole.Launcher) {     assertXIDNotNull();     if (LOGGER.isDebugEnabled()) {         LOGGER.debug("Ignore Begin(): just involved in global transaction [{}]", xid);     }     return; } assertXIDNull(); String currentXid = RootContext.getXID(); if (currentXid != null) {     throw new IllegalStateException("Global transaction already exists," +                                     " can't begin a new global transaction, currentXid = " + currentXid); } xid = transactionManager.begin(nullnull, name, timeout); status = GlobalStatus.Begin; RootContext.bind(xid); if (LOGGER.isInfoEnabled()) {     LOGGER.info("Begin new global transaction [{}]", xid); }}

这个方法开启了一个事务,并且得到一个xid,然后将这个xid绑定到上下文中,具体怎么开启的事务呢?其实就是执行一个远程调用org.apache.seata.tm.DefaultTransactionManager#begin

@Overridepublic String begin(String applicationId, String transactionServiceGroup, String name, int timeout) throws TransactionException { GlobalBeginRequest request = new GlobalBeginRequest(); request.setTransactionName(name); request.setTimeout(timeout); GlobalBeginResponse response = (GlobalBeginResponsesyncCall(request); if (response.getResultCode() == ResultCode.Failed) {     throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg()); } return response.getXid();}

也就是这里的syncCall,会得到一个响应

public class GlobalBeginResponse extends AbstractTransactionResponse { private String xid; private String extraData;}

2、commitTransaction

这个方法是提交事务的方法,方法里边如果超时,会回滚;如果正常提交,会在提交之前触发提交前的hook,在提交之后触发提交后的hook

private void commitTransaction(GlobalTransaction tx, TransactionInfo txInfo) throws TransactionalExecutor.ExecutionException, TransactionException { if (tx.getGlobalTransactionRole() != GlobalTransactionRole.Launcher) {     if (LOGGER.isDebugEnabled()) {         LOGGER.debug("Ignore commit: just involved in global transaction [{}]", tx.getXid());     }     return; } if (isTimeout(tx.getCreateTime(), txInfo)) {     // business execution timeout     Exception exx = new TmTransactionException(TransactionExceptionCode.TransactionTimeout,                                                String.format("client detected transaction timeout before commit, so change to rollback, xid = %s", tx.getXid()));     rollbackTransaction(tx, exx);     return; } try {     triggerBeforeCommit();     tx.commit();     GlobalStatus afterCommitStatus = tx.getLocalStatus();     TransactionalExecutor.Code code = TransactionalExecutor.Code.Unknown;     switch (afterCommitStatus) {         case TimeoutRollbacking:             code = TransactionalExecutor.Code.Rollbacking;             break;         case TimeoutRollbacked:             code = TransactionalExecutor.Code.RollbackDone;             break;         case Finished:             code = TransactionalExecutor.Code.CommitFailure;             break;         default:     }     Exception statusException = null;     if (GlobalStatus.isTwoPhaseHeuristic(afterCommitStatus)) {         statusException = new TmTransactionException(TransactionExceptionCode.CommitHeuristic,                                                      String.format("Global transaction[%s] not found, may be rollbacked.", tx.getXid()));     } else if (GlobalStatus.isOnePhaseTimeout(afterCommitStatus)) {         statusException = new TmTransactionException(TransactionExceptionCode.TransactionTimeout,                                                      String.format("Global transaction[%s] is timeout and will be rollback[TC].", tx.getXid()));     }     if (null != statusException) {         throw new TransactionalExecutor.ExecutionException(tx, statusException, code);     }     triggerAfterCommit(); } catch (TransactionException txe) {     // 4.1 Failed to commit     throw new TransactionalExecutor.ExecutionException(tx, txe,                                                        TransactionalExecutor.Code.CommitFailure); }}

3、completeTransactionAfterThrowing

这个方法在catch里边,也就是说当程序执行异常的时候会执行。点进去看到里边会进行事务的回滚(事务信息不为空 且 抛出的异常是指定的事务回滚异常)

private void completeTransactionAfterThrowing(TransactionInfo txInfo, GlobalTransaction tx, Throwable originalException) throws TransactionalExecutor.ExecutionException, TransactionException { //roll back if (txInfo != null && txInfo.rollbackOn(originalException)) {     rollbackTransaction(tx, originalException); } else {     // not roll back on this exception, so commit     commitTransaction(tx, txInfo); }}

4、business.execute

执行的业务方法

2.3
各事务角色的请求交互

前面看到事务的”拆解“执行过程,那么RM/TM与RC之间是如何交互的呢?总的来说有异步和同步的方式。下面以开启事务方法org.apache.seata.tm.DefaultTransactionManager#begin为例

@Overridepublic String begin(String applicationId, String transactionServiceGroup, String name, int timeout)    throws TransactionException {    GlobalBeginRequest request = new GlobalBeginRequest();    request.setTransactionName(name);    request.setTimeout(timeout);    GlobalBeginResponse response = (GlobalBeginResponsesyncCall(request);    if (response.getResultCode() == ResultCode.Failed) {        throw new TmTransactionException(TransactionExceptionCode.BeginFailed, response.getMsg());    }    return response.getXid();}

继续看syncCall方法

private AbstractTransactionResponse syncCall(AbstractTransactionRequest request) throws TransactionException {    try {        return (AbstractTransactionResponse) TmNettyRemotingClient.getInstance().sendSyncRequest(request);    } catch (TimeoutException toe) {        throw new TmTransactionException(TransactionExceptionCode.IO, "RPC timeout", toe);    }}

继续org.apache.seata.core.rpc.netty.AbstractNettyRemotingClient#sendSyncRequest(java.lang.Object)方法

@Overridepublic Object sendSyncRequest(Object msg) throws TimeoutException {    String serverAddress = loadBalance(getTransactionServiceGroup(), msg);    long timeoutMillis = this.getRpcRequestTimeout();    RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC);    // send batch message    // put message into basketMap, @see MergedSendRunnable    if (this.isEnableClientBatchSendRequest()) {        // send batch message is sync request, needs to create messageFuture and put it in futures.        MessageFuture messageFuture = new MessageFuture();        messageFuture.setRequestMessage(rpcMessage);        messageFuture.setTimeout(timeoutMillis);        futures.put(rpcMessage.getId(), messageFuture);        // put message into basketMap        BlockingQueue<RpcMessage> basket = CollectionUtils.computeIfAbsent(basketMap, serverAddress,                                                                           key -> new LinkedBlockingQueue<>());        if (!basket.offer(rpcMessage)) {            LOGGER.error("put message into basketMap offer failed, serverAddress:{},rpcMessage:{}",                         serverAddress, rpcMessage);            return null;        }        if (LOGGER.isDebugEnabled()) {            LOGGER.debug("offer message: {}", rpcMessage.getBody());        }        if (!isSending) {            synchronized (mergeLock) {                mergeLock.notifyAll();            }        }        try {            Object response = messageFuture.get(timeoutMillis, TimeUnit.MILLISECONDS);            return response;        } catch (Exception exx) {            LOGGER.error("wait response error:{},ip:{},request:{}", exx.getMessage(), serverAddress, rpcMessage.getBody());            if (exx instanceof TimeoutException) {                throw (TimeoutException)exx;            } else {                throw new RuntimeException(exx);            }        }    } else {        Channel channel = clientChannelManager.acquireChannel(serverAddress);        return super.sendSync(channel, rpcMessage, timeoutMillis);    }}

在这个方法里边,外边的if分支逻辑为如果开启了批量提交请求,则走if,否则,就同步调用else里边的逻辑。而在if这个逻辑里边seata的方案是将请求封装成一个org.apache.seata.core.protocol.RpcMessage对象,然后将其丢到一个阻塞队列里边,交由定时异步任务去消费处理。这个阻塞队列又以serverAddress作为key放到了一个map里边。异步任务真正获取数据是从这个map里边获取的。

这个basketMap在哪儿消费处理呢?

前面我们在看RM、TM建立与TC连接的代码的时候就看到了org.apache.seata.core.rpc.netty.AbstractNettyRemotingClient#init

@Overridepublic void init() {    timerExecutor.scheduleAtFixedRate(() -> {        try {            clientChannelManager.reconnect(getTransactionServiceGroup());        } catch (Exception ex) {            LOGGER.warn("reconnect server failed. {}", ex.getMessage());        }    }, SCHEDULE_DELAY_MILLSSCHEDULE_INTERVAL_MILLSTimeUnit.MILLISECONDS);    if (this.isEnableClientBatchSendRequest()) {        mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD,                                                          MAX_MERGE_SEND_THREAD,                                                          KEEP_ALIVE_TIMETimeUnit.MILLISECONDS,                                                          new LinkedBlockingQueue<>(),                                                          new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD));        mergeSendExecutorService.submit(new MergedSendRunnable());    }    super.init();    clientBootstrap.start();}

这里一个将一个MergedSendRunnable的任务丢到了线程池中去交由线程池调度执行。这个类的run方法如下:

@Overridepublic void run() {    while (true) {        synchronized (mergeLock) {            try {                mergeLock.wait(MAX_MERGE_SEND_MILLS);            } catch (InterruptedException e) {            }        }        isSending = true;        basketMap.forEach((address, basket) -> {            if (basket.isEmpty()) {                return;            }            MergedWarpMessage mergeMessage = new MergedWarpMessage();            while (!basket.isEmpty()) {                RpcMessage msg = basket.poll();                mergeMessage.msgs.add((AbstractMessage) msg.getBody());                mergeMessage.msgIds.add(msg.getId());            }            if (mergeMessage.msgIds.size() > 1) {                printMergeMessageLog(mergeMessage);            }            Channel sendChannel = null;            try {                // send batch message is sync request, but there is no need to get the return value.                // Since the messageFuture has been created before the message is placed in basketMap,                // the return value will be obtained in ClientOnResponseProcessor.                sendChannel = clientChannelManager.acquireChannel(address);                AbstractNettyRemotingClient.this.sendAsyncRequest(sendChannel, mergeMessage);            } catch (FrameworkException e) {                if (e.getErrcode() == FrameworkErrorCode.ChannelIsNotWritable && sendChannel != null) {                    destroyChannel(address, sendChannel);                }                // fast fail                for (Integer msgId : mergeMessage.msgIds) {                    MessageFuture messageFuture = futures.remove(msgId);                    Integer parentId = childToParentMap.remove(msgId);                    if (parentId != null) {                        mergeMsgMap.remove(parentId);                    }                    if (messageFuture != null) {                        messageFuture.setResultMessage(                            new RuntimeException(String.format("%s is unreachable", address), e));                    }                }                LOGGER.error("client merge call failed: {}", e.getMessage(), e);            }        });        isSending = false;    }}

这个任务类的run方法的try里边就是真正发送请求的方法AbstractNettyRemotingClient.this.sendAsyncRequest(sendChannel, mergeMessage);

@Overridepublic void sendAsyncRequest(Channel channel, Object msg) {    if (channel == null) {        LOGGER.warn("sendAsyncRequest nothing, caused by null channel.");        return;    }    RpcMessage rpcMessage = buildRequestMessage(msg, msg instanceof HeartbeatMessage                                                ? ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST                                                : ProtocolConstants.MSGTYPE_RESQUEST_ONEWAY);    Object body = rpcMessage.getBody();    if (body instanceof MergeMessage) {        Integer parentId = rpcMessage.getId();        mergeMsgMap.put(parentId, (MergeMessage)rpcMessage.getBody());        if (body instanceof MergedWarpMessage) {            for (Integer msgId : ((MergedWarpMessage)rpcMessage.getBody()).msgIds) {                childToParentMap.put(msgId, parentId);            }        }    }    super.sendAsync(channel, rpcMessage);}
基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-23 07:18:53 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/866578.html
  2. 运行时间 : 0.211793s [ 吞吐率:4.72req/s ] 内存消耗:4,877.39kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=2cca47908470348606a985573e3b6534
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.50 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000969s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000954s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000373s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000266s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000485s ]
  6. SELECT * FROM `set` [ RunTime:0.000183s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000634s ]
  8. SELECT * FROM `article` WHERE `id` = 866578 LIMIT 1 [ RunTime:0.000936s ]
  9. UPDATE `article` SET `lasttime` = 1784762333 WHERE `id` = 866578 [ RunTime:0.015231s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000358s ]
  11. SELECT * FROM `article` WHERE `id` < 866578 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000599s ]
  12. SELECT * FROM `article` WHERE `id` > 866578 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000533s ]
  13. SELECT * FROM `article` WHERE `id` < 866578 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000621s ]
  14. SELECT * FROM `article` WHERE `id` < 866578 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.008620s ]
  15. SELECT * FROM `article` WHERE `id` < 866578 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001347s ]
0.214510s