一、为何用?

减少请求次数,将多条请求命令合成一次请求通过管道发给redis server,再通过回调函数一次性接收多个命令的结果,减少网络IO次数,在高并发情况下可带来明显性能提升。注意的是,redis server是单线程,多个命令合成一次请求到达redis server依然还是顺序一个个执行的,仅仅只是减少了请求IO次数。

二、如何用?

RedisCallback和SessionCallBack:

1.作用: 让RedisTemplate进行回调,通过他们可以在同一条连接中一次执行多个redis命令。
2.SessionCalback提供了良好的封装,优先使用它。
3.RedisCallback使用的是原生RedisConnection,用起来比较麻烦,可读性差,但原生api提供的功能比较齐全。

RedisTemplate引入

spring boot 提供了RedisTemplate简化redis客户端的操作,引入:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

代码使用

@Component
public class RedisTest {

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    @PostConstruct
    public void init() {
        test1();
    }

    public void test1() {
        List<Object> pipelinedResultList = redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                ValueOperations<String, Object> valueOperations = (ValueOperations<String, Object>) operations.opsForValue();

                valueOperations.set("yzh1", "hello world");
                valueOperations.set("yzh2", "redis");

                valueOperations.get("yzh1");
                valueOperations.get("yzh2");

                // 返回null即可,因为返回值会被管道的返回值覆盖,外层取不到这里的返回值
                return null;
            }
        });
        System.out.println("pipelinedResultList=" + pipelinedResultList);
    }
}

运行结果:

pipelinedResultList=[true, true, hello world, redis]

由于 RedisCallback 的RedisConnection api都是类似于 Boolean set(byte[] key, byte[] value); key、value都需要转成字节数组这种,不推荐使用。

管道预热代码

有的系统对延迟要求很高,那么redis管道第一次请求很慢,就需要在系统启动时进行管道的预热,保证系统启动后每次请求的低延迟。

    @PostConstruct
    public void init() {
        long startTime = System.currentTimeMillis();
        redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
                operations.hasKey((K) "");
                return null;
            }
        });
        log.info("redis初始化管道请求end,耗时:{}ms", System.currentTimeMillis() - startTime);
    }

其它记录:

redis的读写速度十分快,所以系统的瓶颈往往是在网络通信中的延迟,redis可能会在很多时候处于空闲状态而等待命令的到达。为了解决这个问题,可以使用redis的流水线(Pipeline),流水线是一种通讯协议,类似一个队列批量执行一组命令。

集群下优化RedisPipeline操作:https://blog.csdn.net/xiaoliu598906167/article/details/82218525,自己没有实际测试,todo,先记录一下。

pipelining官方解释:

Redis服务器可以实现即使没有读取旧响应的情况下也可以发送新的请求,以这种方式可以发送多个命令到服务器而不用等待回复,最后一次获取全部的回复。这就是Redis Pipelining。

executePipelined() 的官方注释:

在一个管道连接中执行给定的动作对象,并返回结果。但是需要注意的是callback不能返回一个非null的值,callback的值将被pipeline覆盖。这个方法将使用默认的序列化和反序列化方式处理结果集。

execute和executePipelined区别图示:

 

execute和executePipelined都支持事务管理器,支持multi,watch,exec,discard等事务操作。

redis事务代码:

     @ApiOperation(value = "multi测试接口", notes = "redis事务测试接口")
    @RequestMapping(value = "/multi", method = RequestMethod.GET)
    @ResponseBody
    public Map<String, Object> testmulti() {
        redisManager.setStr("wanwan", "wd小兔兔");
        List list = (List) redisTemplate.execute((RedisOperations res) ->
        {
            //设置监控key,在exec执行前如果这个key对应的值,发生了变化,事务bu执行
            //通常监控的key可以是ID,也可以是一个对象
            res.watch("wanwan");
            // 其实watch可以注释掉,或者设置成不监控
            res.unwatch();
            //开启事务,在exec执行前
            res.multi();
            res.opsForValue().increment("wanwan", 1);
            res.opsForValue().set("wanwan2", "我的小兔兔1");
            Object value2 = res.opsForValue().get("wanwan2");
            System.out.println("命令在队列,所以取值为空" + value2 + "----");
            res.opsForValue().set("wanwan3", "我的小兔兔3");
            Object value3 = res.opsForValue().get("wanwan3");
            System.out.println("命令在队列,所以取值为空" + value3 + "----");
            return res.exec();
        });
        System.out.println(list);
        Map<String, Object> map = new HashMap<>();
        map.put("success", true);
        System.out.println(";;;" + map.toString());
        return map;
    }

事务代码,尚未验证,todo,这里就是记录一下。

redis事务就是基于SessionCallback实现了一个监听watch,如果被监听的键发生了变化就会取消事务,没有变化就执行事务。(注意:即使被赋予了相同的值,同样视为发生变化,不予执行事务) 

疑问:如果redis事务中要执行100条命令,那么watch会watch几次?

在执行前一直在watch,但是执行过程中比如开始执行100条命令中的第一条后,
就不会watch了,因为redis时单线程的,你在执行过程中,别的命令根本无法执行。
如果这个事务要执行10分钟,我在这10分钟内通过手动更改一个键的值可以不,答案是不行!因为redis单线程。

redis的事务类似mysql的串行化隔离界别,执行期间不会插入其他语句。redis使用的是乐观锁方式,这种方式允许exec前修改,这时会触发异常通知。

redis通过watch来监测数据,在执行exec前,监测的数据被其他人更改会抛出错误,取消执行。而exec执行时,redis保证不会插入其他语句来实现隔离。(可以预见到此机制如果事务中包裹过多的执行长指令,可能导致长时间阻塞其他人)

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐