在我们的 Spring 4 项目中,我们希望拥有涉及 Redis 和 Hibernate 的数据库事务。每当 Hibernate 失败时,例如由于乐观锁定,Redis 事务也应该中止。
这似乎适用于
- 单线程事务执行。
- 多线程事务执行,只要事务只包含单个 Redis 调用即可。
- 如果 Hibernate 从我们的配置中排除,则使用多个 Redis 调用执行多线程事务。
只要一个事务包含多个 Redis 调用,并且 Hibernate 被配置为参与事务,连接绑定和多线程似乎就会出现问题。线程卡在RedisConnectionUtils.bindConnection()
,可能是因为JedisPool
连接用完了。
这可以复制如下。
@Service
public class TransactionalService {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
@Transactional
public void processTask(int i){
redisTemplate.convertAndSend("testChannel", new Message());
redisTemplate.convertAndSend("testChannel", new Message());
}
}
我们使用ThreadPoolTaskExecutor
具有 50 大小的核心池来模拟多线程事务。
@Service
public class TaskRunnerService {
@Autowired
private TaskExecutor taskExecutor;
@Autowired
private TransactionalService transactionalService;
public void runTasks() {
for (int i = 0; i < 100; i++) {
final int j = i;
taskExecutor.execute(new Runnable() {
@Override
public void run() {
transactionalService.processTask(j);
}
});
}
}
}
运行这个会导致所有 taskExecutor 线程挂在 JedisPool.getResource() 中:
"taskExecutor-1" - Thread t@18
java.lang.Thread.State: WAITING
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <1b83c92c> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at org.apache.commons.pool2.impl.LinkedBlockingDeque.takeFirst(LinkedBlockingDeque.java:524)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:438)
at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:361)
at redis.clients.util.Pool.getResource(Pool.java:40)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:84)
at redis.clients.jedis.JedisPool.getResource(JedisPool.java:10)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:90)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:143)
at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.getConnection(JedisConnectionFactory.java:41)
at org.springframework.data.redis.core.RedisConnectionUtils.doGetConnection(RedisConnectionUtils.java:128)
at org.springframework.data.redis.core.RedisConnectionUtils.bindConnection(RedisConnectionUtils.java:66)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:175)
at org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:152)
at org.springframework.data.redis.core.RedisTemplate.convertAndSend(RedisTemplate.java:675)
at test.TransactionalService.processTask(TransactionalService.java:23)
at test.TransactionalService$$FastClassBySpringCGLIB$$9b3de279.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:708)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:644)
at test.TransactionalService$$EnhancerBySpringCGLIB$$a1b3ba03.processTask(<generated>)
at test.TaskRunnerService$1.run(TaskRunnerService.java:28)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Locked ownable synchronizers:
- locked <7d528cf7> (a java.util.concurrent.ThreadPoolExecutor$Worker)
Redis 配置
@Configuration
public class RedisConfig {
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
jedisConnectionFactory.setPoolConfig(new JedisPoolConfig());
return jedisConnectionFactory;
}
@Bean
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper());
return jackson2JsonRedisSerializer;
}
@Bean
public StringRedisSerializer stringRedisSerializer() {
return new StringRedisSerializer();
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(jedisConnectionFactory());
redisTemplate.setKeySerializer(stringRedisSerializer());
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setEnableTransactionSupport(true);
return redisTemplate;
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
return objectMapper;
}
}
休眠配置
@EnableTransactionManagement
@Configuration
public class HibernateConfig {
@Bean
public LocalContainerEntityManagerFactoryBean admin() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setPersistenceUnitName("test");
return entityManagerFactoryBean;
}
@Bean
public JpaTransactionManager transactionManager(
@Qualifier("admin") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactoryBean.getObject());
transactionManager.setDataSource(entityManagerFactoryBean.getDataSource());
return transactionManager;
}
}
这是 spring-data-redis 中的错误还是我们的配置有问题?