Spring dataSource再探

承接上篇的问题,问题其实是出在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;
	}


因此,这个handleExist的逻辑就进不去了,转而到下面去执行一个新的doBegin操作,崩溃啊。。。。。
if (isExistingTransaction(transaction)) {
			// Existing transaction found -> check propagation behavior to find out how to behave.
			return handleExistingTransaction(definition, transaction, debugEnabled);
		}


doBegin中conconnect的获取在这边,对于conHoler已存在的,其实就是对于那些正常的方法来说的话,这个都不为null,并且它们都是SysnchroizedWithTranscation的,这个地方为什么都是true,暂时概念不清楚。但是对于异步线程来说的话,首先它的conHoler就是null了,这其实也是问题之一吧,根本原因不在此。

从这边我们也能发现,对于异步线程来说的话,它获取的链接其实也是从当前线程中的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();
			}


后来定位到MQ的链接操作,发现执行start方法时就一直卡住了。。。。
/**
     * 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();
            }
        }
    }


ensure方法是个同步方法,也就是第一次连接其实就已经失败了,一直没有连接上,并且又是个同步方法,所以导致后续的链接一直也进不来,一直卡住。

但是这边都有一个问题,就是在执行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);
            }
        }
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值