redis基础和使用(二)--pipeline

1.pipeline出现的背景

1.1 为什么需要pippeline

redis客户端执行一条命令分4个过程:

  发送命令-〉命令排队-〉命令执行-〉返回结果

这个过程称为Round trip time(简称RTT, 往返时间),mget mset有效节约了RTT,但大部分命令(如hgetall,并没有mhgetall)不支持批量操作,需要消耗N次RTT ,同理,如果查询某个key的get操作相对频繁时(比如数据平台,消费kafka数据后,进行用户映射等操作的时候),这个时候,我们就期望一批批的key去查询,以节约jedis响应时间和jedis线程利用率,pipeline就应运而生。

1.2 pepeline的性能

1、未使用pipeline执行N条命令
在这里插入图片描述

2、使用了pipeline执行N条命令
在这里插入图片描述
从上面的两种方式交互就能看出来,忽略逻辑运算时长(数据量级对单纯的逻辑运算影响是毫秒级别甚至更低)和线程池的影响(使用pipeline的场景,肯定每秒会超过线程池的上限的key查询),单个key能节约的时间是一次RTT(网络返回时间),当查询redis key的qps越高的时候,这个时效差距会更加明显(当然,不会成函数式的越大就肯定越高,这种方式增长。当数据量过大时,可能增加客户端的等待时间,还可能造成网络阻塞,这个时候可以将大量命令的拆分多个小的pipeline命令完成。)。

那具体能节约多少时间呢,这个针对网络环境和机器部署环境而定,当网络延时较高时,这个节约时长会越大,以下是我针对1000个key的时耗结果测试:
在这里插入图片描述

2.pipeline使用方式

2.1 使用简介

  1. 获取jedis对象(一般从连接池中获取)
  2. 获取jedis对象的pipeline对象
  3. 添加指令
  4. 执行指令

2.2 代码示例

//2.1使用简介的步骤1
Jedis jedis = RedisClient.getJedis();
//2.1使用简介的步骤2
Pipeline pipeline = jedis.pipelined();
//2.1使用简介的步骤3
Map<String, Response<String>> responses = new HashMap<String,Response<String>>(keys.size());
            for(String key : keys) {
                responses.put(key, pipeline.get(key));
            }
            //2.1使用简介的步骤4
            pipeline.sync();

2.3 封装pipeline工具类

 /**
     * pipline批量查询获取key
     * @param keys
     * @return 存在的key,value 映射map
     */
    public static Map<String, String> getByPipeline(List<String> keys) {
        Map<String, String> result = new HashMap<>();
        try (Jedis jedis = RedisClient.getJedis()) {
            Pipeline pipeline = jedis.pipelined();
            Map<String, Response<String>> responses = new HashMap<String,Response<String>>(keys.size());
            for(String key : keys) {
                responses.put(key, pipeline.get(key));
            }
            pipeline.sync();
            responses.forEach((key, response) ->{
                if(StringUtils.isNotEmpty(response.get())){
                    result.put(key, response.get());
                }
            });
            return result;
        }
    }

3.pipeline不保证原子性

首先,再回忆一下原子性概念:一个事务是一个不可分割的最小工作单位,要么都成功要么都失败。原子操作是指你的一个业务逻辑必须是不可拆分的. 处理一件事情要么都成功,要么都失败,原子不可拆分。
redis的原生批命令是原子性,但是pipelinepipeline需要服务端与客户端共同完成,是非原子性的。

3.pipeline适用场景

有些系统可能对可靠性要求很高,每次操作都需要立马知道这次操作是否成功,是否数据已经写进 redis 了,那这种场景就不适合。

还有的系统,可能是批量的将数据写入 redis,允许一定比例的写入失败,那么这种场景就可以使用了,比如10000条一下进入 redis,可能失败了2条无所谓,后期有补偿机制就行了,比如短信群发这种场景,如果一下群发10000条,按照第一种模式去实现,那这个请求过来,要很久才能给客户端响应,这个延迟就太长了,如果客户端请求设置了超时时间5秒,那肯定就抛出异常了,而且本身群发短信要求实时性也没那么高,这时候用 pipeline 最好了。
  
  其实,我们能发现,pipline使用的场景qps会很高,然后需要从redis里面进行某些key的提取或者判断,其实这个场景很适合结合guava的缓存去使用,缓存一部分已经拉取到或者check的数据,设置一个失效时间,优先从缓存中拉取,没有的再批量从pipeline去redis里拉取。

4.pipeline缺点

  1. 如上面提到过的无法保证原子性,事务操作也很复杂,所以对可靠性要求很高的不适合;
  2. 鉴于Pipepining发送命令的特性,Redis服务器是以队列来存储准备执行的命令,而队列是存放在有限的内存中的,所以不宜一次性发送过多的命令。如果需要大量的命令,可分批进行,效率不会相差太远滴,总好过内存溢出嘛~~
  3. 由于pipeline的原理是收集需执行的命令,到最后才一次性执行。所以无法在中途立即查得数据的结果(需待pipelining完毕后才能查得结果),这样会使得无法立即查得数据进行条件判断(比如判断是非继续插入记录)。

5.pipeline源码解析

请听下回分解。鉴于Pipepining发送命令的特性,Redis服务器是以队列来存储准备执行的命令,而队列是存放在有限的内存中的,所以不宜一次性发送过多的命令。如果需要大量的命令,可分批进行,效率不会相差太远滴,总好过内存溢出嘛~~
由于pipeline的原理是收集需执行的命令,到最后才一次性执行。所以无法在中途立即查得数据的结果(需待pipelining完毕后才能查得结果),这样会使得无法立即查得数据进行条件判断(比如判断是非继续插入记录)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值