一、问题描述
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,Redis对事物支持不会很复杂,当一个客服端连接Redis服务时,发出了MULTI命令时,这个连接会进入事物,在执行MULTI命令之后,执行所有的命令都不会执行,会先放到一个队列中,会提示正在Query,当最后执行EXEC命令之后,Redis会按照之前的进入队列的顺序,执行命令。
Spring Data Redis 是对JRedis的客服端进行很好的封装, Spring Data Redis的RedisTemplate提供了MULTI、EXEC命令进行封装,但RedisTemplate先执行调用MULTI方法,然后在执行其它的命令,最后执行EXEC方法时,会出现报错:Caused by:Redis.clents.jedis.exceptions.JedisDataException:ERR EXEC without MULTI问题。
二、原因分析
Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,我们需要重新添加数据,对原先的数据进行删除,在多个线程情况下数据会丢失,所以我们需要事务完成相应的效果。
Spring Data Redis的RedisTemplate提供了MULTI、EXEC命令进行封装,远看可以解决问题时,代码实现:
stringRedisTemplate.multi();
stringRedisTemplate.delete("test");
stringRedisTemplate.opsForValue().set("test","2");
stringRedisTemplate.exec();
结果报错误: 我们查询multi、delete等源代码,发现会执行RedisTemplate类中execute()方法进行跟踪发现 RedisCallback中doInRedis获取的RedisConnection每次都是新的,所以才导致该问题。分析Redis源代码:我们查看multi实现跟踪发现RedisConnection conn每次都是新的,导致出现那个错误
三、解决方案
只能自己实现RedisCallBack底层,采用RedisTemplate的SesionCallback来完成在同一个Connection中,完成多个操作的方法:
SessionCallback<Object> sessionCallback=new SessionCallback<Object>(){
@Override
public Object execute(RedisOperations operations) throws DataAccessException{
operations.multi();
operations.delete("test");
operations.opsForValue.set("test","2");
Object val=operations.exec();
return val;
}
}
StringRedisTemplate.execute(sessionCallback);
kotlin 的写法:
inner class RedisSessionCallback : SessionCallback<Any> {
@Throws(DataAccessException::class)
override fun <K : Any?, V : Any?> execute(operations: RedisOperations<K, V>): Any {
operations.multi()
operations.delete("test");
operations.opsForValue.set("test","2");
return operations.exec()
}
}
redisTemplate.execute( RedisSessionCallback())
如果redis里的value 值是对象,则:
inner class RedisSessionCallback : SessionCallback<Any> {
@Throws(DataAccessException::class)
override fun <K : Any?, V : Any?> execute(operations: RedisOperations<K, V>): Any {
operations.multi()
operations.opsForValue().set(RedisConstant.START_ANSWER as K , true as V) //类型的转换
operations.delete(RedisConstant.STATISTIC_KEY as K)
return operations.exec()
}
}
四、测试结果
模拟多个线程调用,我采用了3个线程进行模拟测试:
线程3=================获取key:test值为:1
线程2=================获取key:test值为:1
线程1=================获取key:test值为:1
线程3=================获取key:test值为:1
线程2=================获取key:test值为:1
线程3=================获取key:test值为:1
线程1=================获取key:test值为:2
线程2=================获取key:test值为:2