SAAS化中动态数据源多线程间歇性事务异常

背景:

使用动态数据源方式实现SAAS化,请参见文章:https://blog.csdn.net/wuwen2049/article/details/111216837

和本文所描述异常相关的类为:DynamicDataSource

问题描述:

当单个post请求发送到后台,服务端进行数据库插入、更新操作,此时没有异常。(估计是请求并发数量只有1个,服务问题没有暴露出来。)

使用SoapUI同时模拟发送10个post请求给服务端,这时异常出现:

java.lang.IllegalStateException: No value for key [DynamicDataSource(defaultDataSources={jnhz={
	CreateTime:"2021-01-12 16:50:14",
	ActiveCount:0,
	PoolingCount:1,
	CreateCount:1,
	DestroyCount:0,
	CloseCount:1,
	ConnectCount:1,
	Connections:[
		{ID:1844410541, ConnectTime:"2021-01-12 16:50:15", UseCount:1, LastActiveTime:"2021-01-12 16:50:15"}
	]
}})] bound to thread [http-nio-8381-exec-6]
	at org.springframework.transaction.support.TransactionSynchronizationManager.unbindResource(TransactionSynchronizationManager.java:213)
	at org.springframework.jdbc.datasource.DataSourceTransactionManager.doCleanupAfterCompletion(DataSourceTransactionManager.java:367)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.cleanupAfterCompletion(AbstractPlatformTransactionManager.java:1007)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:793)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:533)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)

这个异常发生的位置在:TransactionSynchronizationManager.unbindResource(Object key)这个方法。

public static Object unbindResource(Object key) throws IllegalStateException {
		Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
		Object value = doUnbindResource(actualKey);
		if (value == null) {
			throw new IllegalStateException(
					"No value for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
		}
		return value;
	}

为何value==null,继续进到私有方法doUnbindResource(actualKey);

@Nullable
	private static Object doUnbindResource(Object actualKey) {
		Map<Object, Object> map = resources.get();
		if (map == null) {
			return null;
		}
		Object value = map.remove(actualKey);
		// Remove entire ThreadLocal if empty...
		if (map.isEmpty()) {
			resources.remove();
		}
		// Transparently suppress a ResourceHolder that was marked as void...
		if (value instanceof ResourceHolder && ((ResourceHolder) value).isVoid()) {
			value = null;
		}
		if (value != null && logger.isTraceEnabled()) {
			logger.trace("Removed value [" + value + "] for key [" + actualKey + "] from thread [" +
					Thread.currentThread().getName() + "]");
		}
		return value;
	}

问题出现在 Object value = map.remove(actualKey)这一行,入参actualKey不在map中,所以value值为null,导致外面的异常抛出。

分析:

debug模式下在Object value = map.remove(actualKey)这一行打上断点,发现诡异的地方:

actualKey的hashCode与map的一个key的hashCode是一样的,但map处理是却找不到actualKey这个值的key!

这里就要提到两个类的对比。map.remove的时候,key的hash与value都相等才认为这个key是map中的一个key。

刚才debug时候看到了key的hash值是一样的,出现问题的地方就又想到是value,其实value按理说也一定是一模一样的,但为何就不相等呢。于是乎,暴力的解决方式:

解决

自己动手,丰衣足食。

既然是DynamicDataSource在进行类相等比较的时候出现了问题,那我就自己override一下equals方法。

 @Override
    public boolean equals(Object obj) {
    	boolean result = super.equals(obj);
    	 if(this == obj){
             return true;//地址相等
         }

         if(obj == null){
             return false;//非空性:对于任意非空引用x,x.equals(null)应该返回false。
         }
         
         if(obj instanceof DynamicDataSource ){
        	 DynamicDataSource dynamicDataSource = (DynamicDataSource)obj;
        	 Map<Object, Object> objDataSourceMap = dynamicDataSource.getDefaultDataSources();
        	 if(objDataSourceMap.equals(this.defaultDataSources)) {
        		 result = true;
        	 }
         }
    	return result;
    }

这样,问题居然解决了!

延伸:

探求问题的时候,顺便也了解了spring在多数据源事务方面的处理。

AbstractPlatformTransactionManager是spring事务管理的关键类,本案例涉及到的DataSourceTransactionManager就集成这个抽象类。

TransactionSynchronizationManager是另个关键的类。当前线程进行数据库更新操作前,会进行bindResource,把动态数据源和数据连接加到resources这个ThreadLocal中。事务commit前后,分别会进行两次unbindResource。

 

 

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值