承接上篇的问题,问题其实是出在dataSource的获取上。
近期其实又发现了一个问题,count get the resource from the poor。对于错误其实大家都不陌生,无法从连接池获取连接了呗。
但是笔者项目是Spring来管理连接池的,众所周知,使用spring的话就是在避免连接池没有释放的问题啊,完全不用我们自己去关心连接池的获取,和释放啊,为什么Spring在这个问题中没有释放掉呢?难道是Spring的Bug?其实一般的场景spring应该给我们解决的很好了,这点毋庸置疑。只能从本身代码去查查问题。
大部分代码都是一般常见的写法,只有这块是不同的,使用了异步注解,那么其实问题很好定位的,猜测问题就是出在这边了。事实上问题就出现在这块。
通过这个问题其实发现在平时的问题查找时,更多的还是从项目代码入手,追溯框架源码的处理过程,然后通过了解这部分的知识,定位出为什么此处会出现问题,源码走多了,看问题也就很轻松了。
activeMQFilePublisher.fileAddMsgPublisher(fileAdd);
@Async
@Override
public void fileAddMsgPublisher(HttpSynFile fileAdd)
看见此处的异步方法,其实有了之前的经验,我们知道对于每个线程来说的话,它都会维护自己的一个dataSource的,这边的异步线程也不例外,那么会不会是当前的这个异步线程的链接没有释放呢?
继续看,依然是这个方法,get事务,但是通过调试我发现本应该conHolder是 非null的,但是调试到这个异步方法时,这边是null了,因为此前的理解,只要在当前的service内的话,最初的时候这边会获取到一个连接池,下面所有的方法都是在这个连接池中去获取可用链接,而不会再新开一个。
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
txObject.setSavepointAllowed(isNestedTransactionAllowed());
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(this.dataSource);
txObject.setConnectionHolder(conHolder, false);
return txObject;
}
调试到这边发现,此处的resources是空的,里面没有任何链接池对象,这边应该会维护一个map键值对的,但是没有,忽然想到,这是一个新线程,也就是说新线程中这个链接对象在此处本身就不存在啊,从哪儿能获取到呢?
private static Object doGetResource(Object actualKey) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.get(actualKey);
// Transparently remove ResourceHolder that was marked as void...
if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
map.remove(actualKey);
// Remove entire ThreadLocal if empty...
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(definition, transaction, debugEnabled);
}
从这边我们也能发现,对于异步线程来说的话,它获取的链接其实也是从当前线程中的dataSource获取的。
if (txObject.getConnectionHolder() == null ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
Connection newCon = this.dataSource.getConnection();
if (logger.isDebugEnabled()) {
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
}
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
执行完一大堆的绑定什么的都是正常操作。直到这边,真正去执行这个方法时,发现调试没有任何结果了~~~~~~~,一直卡在那边,没任何反应。
要命的是,这个方法本省就是异步方法,所以它并不影响当前线程的执行,页面已经给出正确的反应了,但是这个地方没有执行,一直卡着,就会导致正常的clean操作执行不了,也就是当前连接释放不掉了。。。。。悲剧啊。
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
retVal = invocation.proceedWithInvocation();
}
/**
* Starts (or restarts) a connection's delivery of incoming messages. A call
* to <CODE>start</CODE> on a connection that has already been started is
* ignored.
*
* @throws JMSException if the JMS provider fails to start message delivery
* due to some internal error.
* @see javax.jms.Connection#stop()
*/
public void start() throws JMSException {
checkClosedOrFailed();
ensureConnectionInfoSent();
if (started.compareAndSet(false, true)) {
for (Iterator<ActiveMQSession> i = sessions.iterator(); i.hasNext();) {
ActiveMQSession session = i.next();
session.start();
}
}
}
但是这边都有一个问题,就是在执行MQ的链接时都实现获取了dataSource,这个问题跟上次的问题很像。
其实最终排查时,发现是开发环境的MQ地址更新过了,但是我们项目的链接地址并没有改,而且MQ的链接好像也并没有设置超时时间,这个跟公司整体框架有关了,并没有提供超时链接的配置,我也不想去搞清楚是为什么。但是从程序角度出发,这边的代码还是存在隐患的,对于MQ的这个方法,还是要以非事务的方式去配置。
这个方法是我目前知道的解决方案,不知道是不是还有更好的解决方案呢?留待以后再说吧。这次这个问题其实很难遇见到啊,平时开发时也是几乎碰不见的问题,不过深入研究过发现其实里面的东西还是很多啊
/**
* Send the ConnectionInfo to the Broker
*
* @throws JMSException
*/
protected void ensureConnectionInfoSent() throws JMSException {
synchronized(this.ensureConnectionInfoSentMutex) {
// Can we skip sending the ConnectionInfo packet??
if (isConnectionInfoSentToBroker || closed.get()) {
return;
}
//TODO shouldn't this check be on userSpecifiedClientID rather than the value of clientID?
if (info.getClientId() == null || info.getClientId().trim().length() == 0) {
info.setClientId(clientIdGenerator.generateId());
}
syncSendPacket(info.copy());
this.isConnectionInfoSentToBroker = true;
// Add a temp destination advisory consumer so that
// We know what the valid temporary destinations are on the
// broker without having to do an RPC to the broker.
ConsumerId consumerId = new ConsumerId(new SessionId(info.getConnectionId(), -1), consumerIdGenerator.getNextSequenceId());
if (watchTopicAdvisories) {
advisoryConsumer = new AdvisoryConsumer(this, consumerId);
}
}
}