Spring Data Redis 二:RedisTemplate实现事物问题剖析和解决

一、问题描述
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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值