Redis pipelined
Redis Pipelined是由Client提供的(是防止client端
Pipelined可以将多个请求无
注意,Pipelined并不能保证原子性,即pipelined执行的内容可能会被其他客户端或是线程的指令"插队",若想要原子性操作,需要使用事务。
基于RedisTemplate的pipelined
使用RedisTemplate可以轻松实现pipelined,需要依靠原生的RedisConnection对象实现相关操作:
RedisSerializer keySerializer = redisTemplate.getKeySerializer(); RedisSerializer keySerializer = redisTemplate.getKeySerializer(); RedisSerializer valueSerializer = redisTemplate.getValueSerializer(); List<Object> list = redisTemplate.executePipelined( (RedisConnection connection)->{ //此行可以略去,调用与否均可 //connection.openPipeline(); //模拟一千次写入与查询 for(int i =0 ;i<1000;i++){ //选择直接复用redisTemplate的序列化方法 connection.set(keySerializer.serialize("testKey"), valueSerializer.serialize("value")); connection.get(keySerializer.serialize("testKey")); } //不可以调用connection.closePipeline()方法, 因为本来就有隐式的代理调用收集了返回值 //List<Object> closePipeline =connection.closePipeline(); //依靠代理的返回,此处应该固定返回null return null; },redisTemplate.getValueSerializer());
最终读写全部操作完会返回list,list的长度为2000(set是有布尔类型返回值的),按次序输出了connection对象每次的操作返回值,其内容为[true,"value",true,"value",true,"value"……],注意需要使用正确的序列化方法,因为使用RedisConnection对象操作使用的均为bytes,序列化异常可能会导致执行出错。
执行性能上,在网络条件很差的情况下pipelined优势尤为突出。
Redis事务
Redis事务具有事务的特性,他很像是pipelined,同时又具有原子性,在指令提交之前不会执行,其执行结果也会一并返回,但是Redis的事务并不对异常回滚做可靠性的保障,当执行过程中发生异常(例如非法的字符等),很多情况下Redis只会跳过这一语句,而并不会打断或是回滚事务,因此每次执行事务都要确保事务的正确。
相关命令:
MULTI :开启事务
EXEC:执行事务
WATCH:监控一个或多个key值
UNWATCH:取消对所有key值的监控
为了弥补Redis单线程事务在开启后执行前可被“队头插队”的问题,Redis提供了Watch指令,它可以在事务开启前指定多个key,在事务执行前会校验key值是否发生改变,如果在unwatch之前变化过,则事务也不会执行,可以利用WATCH实现简单的CAS操作,假定key值不会被其他应用修改,一旦修改便回退选择放弃或者重新执行。流程图如下:
WATCH机制与相关tips
注意以下几点:
WATCH要发生在事务开启之前(即使Redis不加此限制,在事务开启之后再WATCH也会有不可预料的问题),他的监控结果与事务的开始时间无关,只看WATCH的执行时间,即执行WATCH后,即时没有开启事务修改值,也会引发后续事务的中断;
假定没有WATCH,图中第5步数据以在执行EXEC前为准,而不是以开启事务为准的。例如自增操作的事务未执行EXEC时发生了修改,则执行时会以修改后的数字为基准自增;
所谓的不能插队仅发生在第5步执行期间,在开启事务到指令进入队列期间Redis可以接受来自其他客户端的指令,并不会引起任何
阻塞; 可以将DISCARD或WATCH值变化类比理解为MySQL的事务回滚,但本质上Redis一旦取消事务,其指令是从未执行过的。
在事务EXEC或DISCARD后,WATCH的所有key值WATCH状态都会被清空
事务的一切指令都是以connection为生命周期(pipelined亦然)。
基于RedisTemplate的事务
在稍早些版本的RedisTemplate并不支持事务,提供了相应的SDK但没有对应的实现,因为每一条指令可能都对应不同的connection,需要使用SessionCallback类实现,现在通过配置参数已经可以支持这一特性:
public List<Object> transactionSetget(){ //开启事务可用,指定后才能使用事务,否则会抛出RedisCommandExecutionException: ERR EXEC without MULTI stringRedisTemplate.setEnableTransactionSupport(true); //watch key2 stringRedisTemplate.watch("key2"); //开启事务 stringRedisTemplate.multi(); for(int i = 0 ;i<100;i++){ //进行了100次反复的set和get stringRedisTemplate.opsForValue().set("setkey","value"); stringRedisTemplate.opsForValue().get("setkey"); //取消当前事务 //stringRedisTemplate.discard(); } return stringRedisTemplate.exec(); }
如果没有问题,最终将会返回List:[true,"value",true,"value"……],如果WATCH的键值被修改,将会返回空list,但是不会报错。
注意watch方法执行需要早于multi方法,否则watch没有意义,也会抛出UnsupportOperationException异常。
首行的设置也不可或缺,否则在执行exec()或discard()时会抛出以下异常(截取):