Redis高级特性:事务和pipelined以及在RedisTemplate中的应用

  created  by  鱼鱼 {{tag}}
创建于 2020年06月12日 19:04:22 最后修改于 2020年06月21日 10:37:07

Redis pipelined

      Redis Pipelined是由Client提供的(是防止client端 阻塞的操作)一种请求redis的方式。Redis本身具有很高的吞吐量,因此性能最大的考察便是网络状况,如果应用到redis的网络状况不好,每次请求都将会出现轻微的 阻塞和延迟,这种延迟对于批量请求是很可怕的,譬如要进行数千次数据插入,或是批量获取数据时,我们就需要用到Pipelined。

      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

    注意以下几点:

  1. WATCH要发生在事务开启之前(即使Redis不加此限制,在事务开启之后再WATCH也会有不可预料的问题),他的监控结果与事务的开始时间无关,只看WATCH的执行时间,即执行WATCH后,即时没有开启事务修改值,也会引发后续事务的中断;

  2. 假定没有WATCH,图中第5步数据以在执行EXEC前为准,而不是以开启事务为准的。例如自增操作的事务未执行EXEC时发生了修改,则执行时会以修改后的数字为基准自增;

  3. 所谓的不能插队仅发生在第5步执行期间,在开启事务到指令进入队列期间Redis可以接受来自其他客户端的指令,并不会引起任何 阻塞

  4. 可以将DISCARD或WATCH值变化类比理解为MySQL的事务回滚,但本质上Redis一旦取消事务,其指令是从未执行过的。

  5. 在事务EXEC或DISCARD后,WATCH的所有key值WATCH状态都会被清空

  6. 事务的一切指令都是以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()时会抛出以下异常(截取):


评论区
评论
{{comment.creator}}
{{comment.createTime}} {{comment.index}}楼
评论

Redis高级特性:事务和pipelined以及在RedisTemplate中的应用

Redis高级特性:事务和pipelined以及在RedisTemplate中的应用

Redis pipelined

      Redis Pipelined是由Client提供的(是防止client端 阻塞的操作)一种请求redis的方式。Redis本身具有很高的吞吐量,因此性能最大的考察便是网络状况,如果应用到redis的网络状况不好,每次请求都将会出现轻微的 阻塞和延迟,这种延迟对于批量请求是很可怕的,譬如要进行数千次数据插入,或是批量获取数据时,我们就需要用到Pipelined。

      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只会跳过这一语句,而并不会打断或是回滚事务,因此每次执行事务都要确保事务的正确。

    相关命令:

    为了弥补Redis单线程事务在开启后执行前可被“队头插队”的问题,Redis提供了Watch指令,它可以在事务开启前指定多个key,在事务执行前会校验key值是否发生改变,如果在unwatch之前变化过,则事务也不会执行,可以利用WATCH实现简单的CAS操作,假定key值不会被其他应用修改,一旦修改便回退选择放弃或者重新执行。流程图如下:

WATCH机制与相关tips

    注意以下几点:

  1. WATCH要发生在事务开启之前(即使Redis不加此限制,在事务开启之后再WATCH也会有不可预料的问题),他的监控结果与事务的开始时间无关,只看WATCH的执行时间,即执行WATCH后,即时没有开启事务修改值,也会引发后续事务的中断;

  2. 假定没有WATCH,图中第5步数据以在执行EXEC前为准,而不是以开启事务为准的。例如自增操作的事务未执行EXEC时发生了修改,则执行时会以修改后的数字为基准自增;

  3. 所谓的不能插队仅发生在第5步执行期间,在开启事务到指令进入队列期间Redis可以接受来自其他客户端的指令,并不会引起任何 阻塞

  4. 可以将DISCARD或WATCH值变化类比理解为MySQL的事务回滚,但本质上Redis一旦取消事务,其指令是从未执行过的。

  5. 在事务EXEC或DISCARD后,WATCH的所有key值WATCH状态都会被清空

  6. 事务的一切指令都是以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()时会抛出以下异常(截取):



Redis高级特性:事务和pipelined以及在RedisTemplate中的应用2020-06-21鱼鱼

{{commentTitle}}

评论   ctrl+Enter 发送评论