spring-如何监控提交事务耗时?

1,604 阅读4分钟

说明

用到的软件是
1.spring
2.hibernate

背景

为什么要做这个需求?
1.生产故障
间隔性出现提交事务耗时几分钟的情况,为什么确定是提交事务耗时几分钟,而不是其他原因?因为整个事务方法开始、执行sql1/sql2、方法结束,都是正常的,但是一出事务方法,就过了好几分钟。但是又无法证明这一点,暂时只是通过日志观察发现提交事务确实耗时了几分钟,所以要监控起来,到时候生产告警的时候,如果同时出现提交事务耗时几分钟的告警,那么就可以证明确实是提交事务耗时这么久。

2.提交事务的时候释放资源也可能耗时很久
获取资源和释放资源都可能耗时很久,获取数据库连接可能会耗时很久,释放数据库连接也可能要耗时很久。既然可能耗时很久,就需要监控起来。之前我们已经做了获取连接耗时监控。

提交事务-方法调用栈

1.提交事务方法调用栈
待补充

2.提交事务之后,释放资源方法调用栈

returnObject:1393, GenericObjectPool (org.apache.commons.pool.impl) //不是真的关闭,而是归还连接到连接池
close:90, PoolableConnection (org.apache.commons.dbcp)
close:191, PoolingDataSource$PoolGuardConnectionWrapper (org.apache.commons.dbcp) //关闭连接

doCloseConnection:341, DataSourceUtils (org.springframework.jdbc.datasource) //关闭连接
closeConnection:97, LocalDataSourceConnectionProvider (org.springframework.orm.hibernate3)

closeConnection:474, ConnectionManager (org.hibernate.jdbc) //关闭连接
cleanup:408, ConnectionManager (org.hibernate.jdbc)
close:347, ConnectionManager (org.hibernate.jdbc) //关闭连接
close:343, SessionImpl (org.hibernate.impl)

closeSession:802, SessionFactoryUtils (org.springframework.orm.hibernate3)
closeSessionOrRegisterDeferredClose:788, SessionFactoryUtils (org.springframework.orm.hibernate3) //关闭会话
doCleanupAfterCompletion:738, HibernateTransactionManager (org.springframework.orm.hibernate3) //hibernate事务管理器

cleanupAfterCompletion:1009, AbstractPlatformTransactionManager (org.springframework.transaction.support) //释放资源
processCommit:805, AbstractPlatformTransactionManager (org.springframework.transaction.support)
commit:724, AbstractPlatformTransactionManager (org.springframework.transaction.support)
commitTransactionAfterReturning:475, TransactionAspectSupport (org.springframework.transaction.interceptor) //提交事务
invokeWithinTransaction:270, TransactionAspectSupport (org.springframework.transaction.interceptor) //事务拦截器
invoke:94, TransactionInterceptor (org.springframework.transaction.interceptor) //事务拦截器

proceed:172, ReflectiveMethodInvocation (org.springframework.aop.framework)
invoke:91, ExposeInvocationInterceptor (org.springframework.aop.interceptor)
proceed:172, ReflectiveMethodInvocation (org.springframework.aop.framework)
intercept:631, CglibAopProxy$DynamicAdvisedInterceptor (org.springframework.aop.framework)
merModifyNoAudit:-1, UrmtminfService$$EnhancerByCGLIB$$57be0f77 (xxx.qrcode.register.dbservice.impl)
merModifyNoAudit:517, QrcodeRegisterServer (xxx.qrcode.register.remoteserver.impl)
invokeMethod:-1, Wrapper20 (com.alibaba.dubbo.common.bytecode)
doInvoke:47, JavassistProxyFactory$1 (com.alibaba.dubbo.rpc.proxy.javassist)
invoke:76, AbstractProxyInvoker (com.alibaba.dubbo.rpc.proxy)
invoke:52, DelegateProviderMetaDataInvoker (com.alibaba.dubbo.config.invoker)
invoke:56, InvokerWrapper (com.alibaba.dubbo.rpc.protocol)
invoke:11, AppNameAppendFilter (com.xxx.log.rpc.dubbo)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:62, ExceptionFilter (com.alibaba.dubbo.rpc.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:42, TimeoutFilter (com.alibaba.dubbo.rpc.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:60, ExecuteLimitFilter (com.alibaba.dubbo.rpc.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:75, MonitorFilter (com.alibaba.dubbo.monitor.support)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:78, TraceFilter (com.alibaba.dubbo.rpc.protocol.dubbo.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:85, AccessLogExtFilter (xxx.qrcode.common.filter.dubbo)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:82, CatTransaction (com.xxx.log.rpc.dubbo)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:73, ContextFilter (com.alibaba.dubbo.rpc.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:138, GenericFilter (com.alibaba.dubbo.rpc.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:38, ClassLoaderFilter (com.alibaba.dubbo.rpc.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
invoke:38, EchoFilter (com.alibaba.dubbo.rpc.filter)
invoke:72, ProtocolFilterWrapper$1 (com.alibaba.dubbo.rpc.protocol)
reply:104, DubboProtocol$1 (com.alibaba.dubbo.rpc.protocol.dubbo)
handleRequest:96, HeaderExchangeHandler (com.alibaba.dubbo.remoting.exchange.support.header)
received:173, HeaderExchangeHandler (com.alibaba.dubbo.remoting.exchange.support.header)
received:51, DecodeHandler (com.alibaba.dubbo.remoting.transport)
run:57, ChannelEventRunnable (com.alibaba.dubbo.remoting.transport.dispatcher)
runWorker:1145, ThreadPoolExecutor (java.util.concurrent)
run:615, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

spring源码-AbstractPlatformTransactionManager

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {

public final void commit(TransactionStatus status) throws TransactionException {
    if (status.isCompleted()) {
      throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
    } else {
      DefaultTransactionStatus defStatus = (DefaultTransactionStatus)status;
      if (defStatus.isLocalRollbackOnly()) {
        if (defStatus.isDebug()) {
          this.logger.debug("Transactional code has requested rollback");
        }

        this.processRollback(defStatus);
      } else if (!this.shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
        if (defStatus.isDebug()) {
          this.logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
        }

        this.processRollback(defStatus);
        if (status.isNewTransaction() || this.isFailEarlyOnGlobalRollbackOnly()) {
          throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
        }
      } else {
        this.processCommit(defStatus); //提交事务
      }
    }
  }
  
/**
 * Process an actual commit.
 * Rollback-only flags have already been checked and applied.
 * @param status object representing the transaction
 * @throws TransactionException in case of commit failure
 */
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
 try {
  boolean beforeCompletionInvoked = false;

  try {
   boolean unexpectedRollback = false;
   prepareForCommit(status);
   triggerBeforeCommit(status);
   triggerBeforeCompletion(status);
   beforeCompletionInvoked = true;

   if (status.hasSavepoint()) {
    if (status.isDebug()) {
     logger.debug("Releasing transaction savepoint");
    }
    unexpectedRollback = status.isGlobalRollbackOnly();
    status.releaseHeldSavepoint();
   }
   else if (status.isNewTransaction()) {
    if (status.isDebug()) {
     logger.debug("Initiating transaction commit");
    }
    unexpectedRollback = status.isGlobalRollbackOnly();
    doCommit(status); //提交事务
   }
   else if (isFailEarlyOnGlobalRollbackOnly()) {
    unexpectedRollback = status.isGlobalRollbackOnly();
   }

   // Throw UnexpectedRollbackException if we have a global rollback-only
   // marker but still didn't get a corresponding exception from commit.
   if (unexpectedRollback) {
    throw new UnexpectedRollbackException(
      "Transaction silently rolled back because it has been marked as rollback-only");
   }
  }
  catch (UnexpectedRollbackException ex) {
   // can only be caused by doCommit
   triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
   throw ex;
  }
  catch (TransactionException ex) {
   // can only be caused by doCommit
   if (isRollbackOnCommitFailure()) {
    doRollbackOnCommitException(status, ex);
   }
   else {
    triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
   }
   throw ex;
  }
  catch (RuntimeException | Error ex) {
   if (!beforeCompletionInvoked) {
    triggerBeforeCompletion(status);
   }
   doRollbackOnCommitException(status, ex);
   throw ex;
  }

  // Trigger afterCommit callbacks, with an exception thrown there
  // propagated to callers but the transaction still considered as committed.
  try {
   triggerAfterCommit(status);
  }
  finally {
   triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
  }

 }
 finally {
  cleanupAfterCompletion(status); //提交事务之后,释放资源(其中包括归还连接到连接池)
 }
}

类继承图

找到监控点-hibernate事务管理器

从方法调用栈和源码里面可以找到监控点,就是Hibernate事务管理器(HibernateTransactionManager),因为配置文件里会配置事务管理器。

<!-- transaction -->
	<bean id="transactionManager_trade"
		class="org.springframework.orm.hibernate3.HibernateTransactionManager">
		<property name="sessionFactory">
			<ref bean="sessionFactory_trade" />
		</property>
	</bean>

因此可以通过重写两个方法,来实现监控
1.提交事务方法
2.提交事务之后,释放资源方法

为什么要重写这两个方法?因为spring源码-AbstractPlatformTransactionManager里调用的这两个方法最终都是调用Hibernate事务管理器里的这两个方法,所以只要我们重写Hibernate事务管理器里的这两个方法,就可以获取到调用这两个方法的耗时。

重写方法

package xxx.xxx.common.monitor.dbcp;

import lombok.extern.slf4j.Slf4j;
import org.springframework.orm.hibernate3.HibernateTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;

/**
 * hibernate事务监控
 * 1.提交事务耗时
 * 2.提交事务之后,释放资源(其中包括归还连接到连接池)耗时
 *
 * @author gzh
 * @createTime 2020/12/19 1:19 PM
 */
@Slf4j
public class HibernateTransactionManagerMonitor extends HibernateTransactionManager {

  /**
   * 提交事务耗时
   * @param status
   */
  protected void doCommit(DefaultTransactionStatus status) {
    long startTime = System.currentTimeMillis();
    super.doCommit(status);
    long endTime = System.currentTimeMillis();

    getDataSourceStatus(endTime-startTime);
  }

  /**
   * 提交事务之后,释放资源(其中包括归还连接到连接池)耗时
   * @param transaction
   */
  protected void doCleanupAfterCompletion(Object transaction) {
    long startTime = System.currentTimeMillis();
    super.doCleanupAfterCompletion(transaction);
    long endTime = System.currentTimeMillis();

    getDataSourceStatus(endTime-startTime);
  }

  /**
   * 写监控数据到cat
   * @param durationInMills 耗时
   */
  private void getDataSourceStatus(long durationInMills) {
    String dataSourceName = "dataSourceName";

    try {
      int connectionPoolMonitorThresholdTimeout; //获取数据库连接超时时间
      //获取apollo配置数据
      connectionPoolMonitorThresholdTimeout = MonitorConfig.getSysConfigIntVal("connectionPool.monitor.threshold.timeout.return","100");

      StringBuilder sb = new StringBuilder();
      sb.append(",归还连接耗时:").append(durationInMills).append("ms");
      //归还连接超时监控
      if (durationInMills > connectionPoolMonitorThresholdTimeout) {
        StringBuilder desc2 = (new StringBuilder("归还连接超时监控,数据源名字:")).append(dataSourceName).append(",阈值:").append(connectionPoolMonitorThresholdTimeout).append(sb.toString());;
        String monitorContent2 = desc2.toString();
        log.warn(monitorContent2);
        MonitorConfig.sendMonitorEvent("connectionPool.monitor.threshold.timeout.return", dataSourceName, monitorContent2, "01");
      }

      //归还连接耗时监控
      StringBuilder desc3 = (new StringBuilder("归还连接耗时监控,数据源名字:")).append(dataSourceName).append(sb.toString());;
      String monitorContent3 = desc3.toString();
      log.info(monitorContent3);
      MonitorConfig.sendMonitor("connectionPool.monitor.connection.time.return", dataSourceName, monitorContent3, "01", durationInMills);
    } catch (Exception var12) {
      log.error("监控数据源执行情况异常", var12);
    }

  }
}