Spring RedisConnectionFactory,事务不返回到池的连接,然后在用尽时阻塞
Posted
技术标签:
【中文标题】Spring RedisConnectionFactory,事务不返回到池的连接,然后在用尽时阻塞【英文标题】:Spring RedisConnectionFactory with transaction not returning connection to Pool and then blocks when exhausted 【发布时间】:2018-02-24 14:25:44 【问题描述】:我使用连接池创建连接工厂的配置。我确实有一个连接池。大部分代码是从 Spring 的 RedisAutoConfiguration
复制而来的,我出于特殊原因禁用了它。
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class JedisConfiguration implements RedisConfiguration
@Bean
@Scope("prototype")
@Override
public RedisConnectionFactory connectionFactory(RedisProperties redisProperties)
return createFactory(redisProperties);
private static JedisConnectionFactory applyProperties(RedisProperties properties, JedisConnectionFactory factory)
factory.setHostName(properties.getHost());
factory.setPort(properties.getPort());
factory.setDatabase(properties.getDatabase());
return factory;
private static JedisPoolConfig jedisPoolConfig(RedisProperties properties)
return Optional.ofNullable(properties.getPool())
.map(props ->
JedisPoolConfig config = new JedisPoolConfig();
config.setMaxTotal(props.getMaxActive());
config.setMaxIdle(props.getMaxIdle());
config.setMinIdle(props.getMinIdle());
config.setMaxWaitMillis(props.getMaxWait());
return config;
)
.orElseGet(JedisPoolConfig::new);
public static JedisConnectionFactory createFactory(RedisProperties properties)
return applyProperties(properties, new JedisConnectionFactory(jedisPoolConfig(properties)));
用例
我有字符串键 "A"
、"B"
、"C"
映射到具有字符串散列键和散列值 json 的散列映射,分别从类 A
、B
和 C
序列化。
即"A"
-> A::toString
-> json(A)
和B
和C
相同。
@Component
public final class UseCase implements InitializingBean
private static final String A_KEY = "A";
private static final String B_KEY = "B";
private static final String C_KEY = "C";
private final RedisConnectionFactory factory;
private final ObjectMapper objectMapper;
private HashOperations<String, String, A> aMap;
private HashOperations<String, String, B> bMap;
private HashOperations<String, String, C> cMap;
private RedisTemplate<String, ?> template;
private UseCase(RedisConnectionFactory factory, ObjectMapper objectMapper)
this.factory = factory;
this.objectMapper = objectMapper;
private <T> RedisTemplate<String, ?> hashMap(Class<T> vClass)
RedisTemplate<String, ?> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(stringSerializer());
redisTemplate.setHashKeySerializer(stringSerializer());
redisTemplate.setHashValueSerializer(jacksonSerializer(vClass));
return configure(redisTemplate);
private <K, V> RedisTemplate<K, V> configure(RedisTemplate<K, V> redisTemplate)
redisTemplate.setConnectionFactory(factory);
redisTemplate.setEnableTransactionSupport(true);
redisTemplate.afterPropertiesSet();
return redisTemplate;
private <T> RedisSerializer<T> jacksonSerializer(Class<T> clazz)
Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<>(clazz);
serializer.setObjectMapper(objectMapper);
return serializer;
private RedisSerializer<String> stringSerializer()
return new StringRedisSerializer();
@Override
public void afterPropertiesSet() throws Exception
template = hashMap(String.class);
aMap = hashMap(A.class).opsForHash();
bMap = hashMap(B.class).opsForHash();
cMap = hashMap(C.class).opsForHash();
void put(A a, B b, C c)
template.multi();
aMap.put(A_KEY, a.toString(), a);
bMap.put(B_KEY, b.toString(), b);
cMap.put(C_KEY, c.toString(), c);
template.exec();
A getA(String aKey)
return aMap.get(A_KEY, aKey);
期望
-
put 操作仅使用一个连接执行,如果连接丢失或损坏,应该会失败。
即put操作,在multi调用时获取连接,exec调用后返回Pool。
即对于getA操作,执行后连接返回池中。
我有测试证明 1 有效,但我对此有点怀疑,但我的问题在于最后两个。经过调试,我观察到在任何操作后连接都没有返回到池,因此池在耗尽时被阻塞。
尝试返回但未在连接上调用,因为下面的两个分支失败。取自RedisConnectionUtils
// release transactional/read-only and non-transactional/non-bound connections.
// transactional connections for read-only transactions get no synchronizer registered
if (isConnectionTransactional(conn, factory)
&& TransactionSynchronizationManager.isCurrentTransactionReadOnly())
unbindConnection(factory);
else if (!isConnectionTransactional(conn, factory))
if (log.isDebugEnabled())
log.debug("Closing Redis Connection");
conn.close();
问题
-
我做错了什么?
为什么连接没有返回到池中?
如何解决此问题,以便将连接返回到池?
【问题讨论】:
【参考方案1】:我认为问题在于调用 exec()
不会告诉模板您实际上已完成连接,因此无法将其返回到池中。
根据docs,您应该将代码包装在SessionCallback 中并使用RedisTemplate.execute(SessionCallback<T> callback)
执行它,这将在您的回调执行后返回到池的连接。
像这样:
template.execute(new SessionCallback<List<Object>>()
public List<Object> execute(RedisOperations operations) throws DataAccessException
operations.multi();
aMap.put(A_KEY, a.toString(), a);
bMap.put(B_KEY, b.toString(), b);
cMap.put(C_KEY, c.toString(), c);
return operations.exec();
);
Spring Data Redis 还支持 @Transactional,它会自动为您绑定/取消绑定连接,但需要您在可以拦截的 bean 中实现该方法(即它不能是final
) 并且事务只有在从 bean 外部执行时才会启动(即不是从同一类或子/父类中的另一个方法)。
您已经使用 redisTemplate.setEnableTransactionSupport(true);
在模板上启用了事务支持,所以您应该一切顺利:
@Transactional
public void put(A a, B b, C c)
aMap.put(A_KEY, a.toString(), a);
bMap.put(B_KEY, b.toString(), b);
cMap.put(C_KEY, c.toString(), c);
【讨论】:
感谢您的回答。使用 SessionCallback 执行似乎确实将连接返回到池。您知道为什么只读操作不会将连接返回到池吗? 据我所知,除非您使用execute
,否则永远不会调用返回池连接的方法。
非常感谢。我想我可以不必将每个调用都包含在 SessionCallback 中
很高兴能帮上忙!以上是关于Spring RedisConnectionFactory,事务不返回到池的连接,然后在用尽时阻塞的主要内容,如果未能解决你的问题,请参考以下文章
Spring全家桶笔记:Spring+Spring Boot+Spring Cloud+Spring MVC
学习笔记——Spring简介;Spring搭建步骤;Spring的特性;Spring中getBean三种方式;Spring中的标签
Spring框架--Spring事务管理和Spring事务传播行为