Redis管道命令Pipeline使用

Redis管道技术Pipeline使用

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  • 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式等待服务端响应(所以如果超过了redis的处理能力,会导致其他查询超时???)。服务端处理命令,并将结果返回给客户端。
  • Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。
  • pipeline就是把一组命令进行打包,然后一次性通过网络发送到Redis。同时将执行的结果批量的返回回来. 使用管道发送命令时,服务器将被迫回复一个队列答复,占用很多内存。所以,如果你需要发送大量的命令,最好是把他们按照合理数量分批次的处理,例如10K的命令,读回复,然后再发送另一个10k的命令,等等。

Pipeline(管道) 的优点:

  1. pipeline 通过打包命令,一次性执行,可以节省 连接->发送命令->返回结果 所产生的往返时间,
  2. 减少的I/O的调用次数。

Pipeline(管道) 的缺点:

  1. pipeline 每批打包的命令不能过多,因为 pipeline 方式打包命令再发送,Redis服务器是以队列来存储准备执行的命令,而队列是存放在有限的内存中的. 同时 redis 必须在处理完所有命令前先缓存起所有命令的处理结果。都有一个内存的消耗,如果需要大量的命令,可分批进行(官方的推荐是命令10k 每批),效率不会相差太远滴,太大会影响网络性能;。 ( 如果批量读key的返回的数据量太大会有异常    org.springframework.data.redis.RedisConnectionFailureException: java.net.SocketException: Broken pipe; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: java.net.SocketException: Broken pipe )
  2. Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性, pipeline的原理是收集需执行的命令,到最后才一次性执行(pipeline里面的命令不能保证命令的顺序执行,因此命令之间不要有数据依赖, 有说能保证顺序的),所以无法在中途立即查得数据的结果(需待pipelining完毕后才能查得结果),这样会使得无法立即查得数据进行条件判断(比如判断是非继续插入记录)。
  3. pipeline 是责任链模式,这个模式的缺点是,每次它对于一个输入都必须从链头开始遍历(参考Http Server处理请求就能明白),这确实存在一定的性能损耗。
  4. pipeline 不保证原子性,如果要求原子性的,不推荐使用 pipeline
  5. Pipeline 批量执行的时候Redis 采用多路I/O复用模型,非阻塞IO,所以 Pipeline 批量写入的时候,Redis不会进行锁定导致其他应用无法再进行读写, 一定范围内不影响其他的读操作。

下面的话感觉有误导, 命令是先到先执行 区分开也没用吧

 

 

为什么Pipelining这么快?

先看看原来的多条命令,是如何执行的:

1

2

3

4

5

6

7

sequenceDiagram

Redis Client->>Redis Server: 发送第1个命令

Redis Server->>Redis Client: 响应第1个命令

Redis Client->>Redis Server: 发送第2个命令

Redis Server->>Redis Client: 响应第2个命令

Redis Client->>Redis Server: 发送第n个命令

Redis Server->>Redis Client: 响应第n个命令

Pipeling机制是怎样的呢:

1

2

3

4

5

6

sequenceDiagram

Redis Client->>Redis Server: 发送第1个命令(缓存在Redis Client,未即时发送)

Redis Client->>Redis Server: 发送第2个命令(缓存在Redis Client,未即时发送)

Redis Client->>Redis Server: 发送第n个命令(缓存在Redis Client,未即时发送)

Redis Client->>Redis Server: 发送累积的命令

Redis Server->>Redis Client: 响应第12、n个命令

  实际应用场景: 查询用户下礼券列表

关于Pipeline 同步数据的问题
A、Pipeline 有与redis形同的操作,但是在数据落盘的时候需要在执行的方法后添加sync()方法,如果insert时有多条数据,在数据拼接完之后,在执行sync()方法,这样可以提高效率。
B、如果在hget()时没有sync()时会报Please close pipeline or multi block before calling this method,没有在hget()同步数据
C、如果在hset(),hdel(),hget()获取数据时都没有执行sync()方法,但是在最后执行了pl.close()方法,Pipeline 同样会执行sync()方法


1.pipelined.sync()表示我一次性的异步发送到redis,不关注执行结果。
2.pipelined.syncAndReturnAll()程序会阻塞,等到所有命令执行完之后返回一个List集合。
3.pipeline也不适合组装特别多的命令,因此如果是成千上万的这种命令,我们还是要进行命令的拆分。

//最原始的写法,需要注意连接泄漏
public void testPipeline(){
    Jedis jedis = new Jedis("172.23.88.107", 6379);
    try {
        Pipeline pipelined = jedis.pipelined();
        pipelined.set("addr", "chongqing");
        pipelined.del("addr");
        //pipelined.sync();//没有返回值
        List<Object> list= pipelined.syncAndReturnAll();
        for (Object object : list) {
            System.out.println(object);
        }
    } finally {
        redis.dispose(jedis);
    }
}

package com.opec.shop.core.cache.jedis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.Response;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 *  @date 2019/10/29 15:28
 */
public class Test11 {
    
    
     public static void main(String[] args) {
        //最原始的写法,需要注意连接泄漏
        Jedis jedis = new Jedis("localhost",6379);
        jedis.auth("root");
        noPipelineHset(jedis);
        pipelineHget(jedis);
        jedis.close();

    }

    //jedis.hset数据
    public static void noPipelineHset(Jedis jedis){
        jedis.select(4);
        jedis.flushDB();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            jedis.hset("k_" + i,"k_" + i,"v_" + i);
        }
        long end = System.currentTimeMillis();
        System.out.println("datasize=" + jedis.dbSize());
        System.out.println("hset without pipeline used=" + (end - start) / 1000 + "seconds!");
    }

    //pipeline.hset命令
    public static void pipelineHset(Jedis jedis){
        //用pipeline存储数据
        jedis.select(4);
        jedis.flushDB();
        Pipeline pipeline = jedis.pipelined();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            pipeline.hset("k_" + i,"k_" + i,"v_" + i); //通过pipeline set数据
        }
        pipeline.sync(); //Pipeline 有与redis形同的操作,但是在数据落盘的时候需要在执行的方法后添加sync()方法
        long end = System.currentTimeMillis();
        System.out.println("datasize=" + jedis.dbSize());
        System.out.println("hset with pipeline used=" + (end - start) / 1000 + "seconds!");
    }

    //直接使用jedis.hgetAll 拿到map
    public static void noPipelineHget(Jedis jedis){
        jedis.select(4);
        Set<String> keys = jedis.keys("*");//keys模糊查询拿到keys
        long start = System.currentTimeMillis();
        Map<String, Map<String, String>> resultMap = new HashMap<>();
        for (String key : keys) {
            resultMap.put(key, jedis.hgetAll(key));//将数据填充到resultMap中
        }
        long end = System.currentTimeMillis();
        System.out.println("result size:[" + resultMap.size() + "] ..");
        System.out.println("hgetAll without pipeline used [" + (end - start) / 1000 + "] seconds ..");
    }

    public static void pipelineHget(Jedis jedis){
        jedis.select(4);
        Map<String, Map<String, String>> result = new HashMap<>();
        Set<String> keys = jedis.keys("*");
        Pipeline pipeline = jedis.pipelined();
        // 使用pipeline hgetall
        Map<String, Response<Map<String, String>>> responseMap = new HashMap<>(keys.size());
        long start = System.currentTimeMillis();
        for (String key : keys) {
            responseMap.put(key, pipeline.hgetAll(key));
        }
        pipeline.sync();
        //此处需要将结果集 Response<Map<String, String>>> 转成 Map<String, String>
        for (String k : responseMap.keySet()) {
            result.put(k, responseMap.get(k).get());
        }
        long end = System.currentTimeMillis();
        System.out.println("result size:[" + responseMap.size() + "] ..");
        System.out.println("hgetAll with pipeline used [" + (end - start) / 1000 + "] seconds ..");
    }


}
//只能返回Object??
这个list是放在匿名类内部,对于数据处理不太友好,代码会看起来相当难受,想取出来使用还是不可变的。如果要获取返回值,我们可以调用如下代码executePipelined,这样就可以返回我们需要的结果,下面我们可以对得到list进行操作。
Object execute = redisTemplate.execute(new RedisCallback<Long>() {

            @Nullable
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                connection.openPipeline();
                for (int i = 0; i < 1000000; i++) {
                    String key = "123" + i;
                    connection.zCount(key.getBytes(), 0,Integer.MAX_VALUE);
                }
                List<Object> result=connection.closePipeline();
                return null;
            }
        });

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值