Redis 管道技术

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:
客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
服务端处理命令,并将结果返回给客户端。

Redis是一个cs模式的tcp server,使用和http类似的请求响应协议。

一个client可以通过一个socket连接发起多个请求命令。

每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。

基本的通信过程如下:

./bin/redis-cli -h 192.168.36.189 -p 6379  
192.168.36.189:6379> incr x  
(integer) 1  
192.168.36.189:6379> incr x  
(integer) 2  
192.168.36.189:6379> incr x  
(integer) 3

客户端和服务端通过网络进行连接。这样的连接可能非常快(在一个回路网络中),也可能非常慢(在广域网上经过多个结点才能互通的两个主机)。但是无论是否存在网络延迟,数据包从客户端传输到服务端,以及客户端从服务端获得相应都需要花费一些时间。这段时间就成为往返时延(Round Trip Time)。因此当客户端需要执行一串请求的时候,很容易看出它对性能的影响(例如往同一个队列中加入大量元素,或者往数据库中插入大量的键)。如果RTT时长为250毫秒(在基于广域网的低速连接环境下),即使服务器每秒可以处理10万个请求,但是实际上我们依然只能每秒处理最多4个请求。
如果处于一个回路网络中,RTT时长则相当短(我的主机ping 127.0.0.1时只需要0.044ms),但是如果你执行一大串写入请求的时候,还是会有点长。
幸运的是,redis给我们提供了管道技术。

1.Redis管道技术

一个请求/相应服务可以实现为,即使客户端没有读取到旧请求的响应,服务端依旧可以处理新请求。通过这种方式,可以完全无需等待服务端应答地发送多条指令给服务端,并最终一次性读取所有应答。管道技术最显著的优势是提高了redis服务的性能。
通过pipeline方式当有大批量的操作时候。我们可以节省很多原来浪费在网络延迟的时间。需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。打包的命令越多,缓存消耗内存也越多。所以并是不是打包的命令越多越好。具体多少合适需要根据具体情况测试。

$(echo -en "PING\r\n SET key redis\r\nGET key\r\nINCR x\r\nINCR x\r\nINCR x\r\n"; sleep 10) | nc 192.168.36.189 6379  
+PONG  
+OK  
$5  
redis  
:4  
:5  
:6

以上实例中我们通过使用 PING 命令查看redis服务是否可用, 之后我们们设置了key的值为 redis,然后我们获取key 的值并使得x自增 3 次。
在返回的结果中我们可以看到这些命令一次性向redis服务提交,并最终一次性读取所有服务端的响应。

2.java测试代码

package cn.slimsmart.redis.demo.pipeline;  
      
    import redis.clients.jedis.Jedis;  
    import redis.clients.jedis.Pipeline;  
      
    @SuppressWarnings("resource")  
    public class PipelineTest {  
      
        public static void main(String[] args) {  
            long start = System.currentTimeMillis();  
            usePipeline();  
            long end = System.currentTimeMillis();  
            System.out.println("usePipeline:"+(end - start));  
      
            start = System.currentTimeMillis();  
            withoutPipeline();  
            end = System.currentTimeMillis();  
            System.out.println("withoutPipeline:"+(end - start));  
        }  
      
        private static void withoutPipeline() {  
            try {  
                Jedis jedis = new Jedis("192.168.36.189", 6379);  
                for (int i = 0; i < 1000; i++) {  
                    jedis.incr("test2");  
                }  
                jedis.disconnect();  
            } catch (Exception e) {  
            }  
        }  
      
        private static void usePipeline() {  
            try {  
                Jedis jedis = new Jedis("192.168.36.189", 6379);  
                Pipeline pipeline = jedis.pipelined();  
                for (int i = 0; i < 1000; i++) {  
                    pipeline.incr("test2");  
                }  
                pipeline.sync();  
                jedis.disconnect();  
            } catch (Exception e) {  
            }  
        }  
    }

运行结果:

127.0.0.1 循环10000次 效果
usePipeline:151
withoutPipeline:6229

外网IP循环10000次 效果
usePipeline:201
withoutPipeline:94909
结果还是很明显有较大的差距,所以多次操作用pipeline还是有明显的优势。

介绍

Reids是一个cs模式的Tcp服务,类似于http的请求。 当客户端发送一个请求时,服务器处理之后会将结果通过响应报文返回给客户端 。
那么当需要发送多个请求时,难道每次都要等待请求响应,再发送下一个请求吗?

  • 当然不是,这里就可以采用Redis的管道技术。
    举个例子,如果说jedis是:request response,request response,…;
    那么pipeline则是:request request… response response的方式。
    下面,就简单测试一下使用管道的效果。

单条插入与批量插入

这里采用逐条和批量的方式往Redis中写入一些数据。
先从Mysql中查出需要的数据,这里大概是300条左右,数据量并不大,但是简单做个测试应该没问题。
单条插入—— Jedis:

Jedis jedis = jedisPool.getResource();
    long start = System.currentTimeMillis();
    List vehicleInfos  = vehicleInfoMapper.selectByParam(param);
    for (VehicleInfo vehicleInfo : vehicleInfos) {      
        //遍历每个vehicleInfo
        TVehicleRealReportMsg real = new TVehicleRealReportMsg();
        Map keysmap = new HashMap();
        keysmap.put("vehicleStatus", real.getVehicleStatus() + "");
        keysmap.put("chargeStatus", real.getChargeStatus() + "");
        keysmap.put("longitude", "9");
        keysmap.put("latitude", "9");
        List list1 = new ArrayList();
        Long l = 1000L;
        Long l2 = 22222L;
        list1.add(l);
        list1.add(l2);
        real.setEngineFaultsList(list1);
        keysmap.put("engineFaultsList", JSON.toJSONString(list1));
        //单条插入
        jedis.hmset(vehicleInfo.getVehicleSeq()+"", keysmap);
    }   
    jedis.close();          
    long end = System.currentTimeMillis(); System.out.println("耗时:"+(end-start) +"ms");

结果:467ms

批量插入—— Pipeline:

Jedis jedis = jedisPool.getResource();
    Pipeline pip = jedis.pipelined();
    long start = System.currentTimeMillis();
    List vehicleInfos  = vehicleInfoMapper.selectByParam(param);
    for (VehicleInfo vehicleInfo : vehicleInfos) {      
        //遍历每个vehicleInfo
        TVehicleRealReportMsg real = new TVehicleRealReportMsg();
        Map keysmap = new HashMap();
        keysmap.put("vehicleStatus", real.getVehicleStatus() + "");
        keysmap.put("chargeStatus", real.getChargeStatus() + "");
        keysmap.put("longitude", "9");
        keysmap.put("latitude", "9");
        List list1 = new ArrayList();
        Long l = 1000L;
        Long l2 = 22222L;
        list1.add(l);
        list1.add(l2);
        real.setEngineFaultsList(list1);
        keysmap.put("engineFaultsList", JSON.toJSONString(list1));
        //批量插入
        pip.hmset(vehicleInfo.getVehicleSeq()+"", keysmap);
    }
    pip.sync();//同步
    jedis.close();      
    long end = System.currentTimeMillis();
    System.out.println("耗时:"+(end-start) +"ms");

可以看到使用管道之后的时间为,相比于单条插入的总时间大大减少,性能更优。

单条读取和批量读取

单条读取—— Jedis:

Jedis jedis = jedisPool.getResource();
    long start = System.currentTimeMillis();
    //1.采用redis单条读取
    List vehicleInfos = vehicleInfoMapper.selectByParam(param);
    List list = new ArrayList();
    for(VehicleInfo key: vehicleInfos){
        String hashkey = key.getVehicleSeq()+"";
        if(jedis.exists(hashkey+"")){                           
        Coordinate coord = new Coordinate();
        coord.setVehicleSeq(key.getVehicleSeq());
        coord.setOrgId(key.getOrgId()); 
        coord.setVehiclemodelseq(key.getVehiclemodelseq()); 
        coord.setVin(jedis.hget(hashkey, "vin"));
        coord.setLongitude(Long.valueOf(jedis.hget(hashkey, "longitude")));
        coord.setLatitude(Long.valueOf(jedis.hget(hashkey, "latitude")));       
        list.add(coord);
        }
    }   
    jedis.close();
    long end = System.currentTimeMillis();
    System.out.println("耗时:"+(end-start)+" ms");
    return list;

结果: 第一次为1032ms,之后稳定在800~900ms

批量读取—— Pipeline:

    Jedis jedis = jedisPool.getResource();
    Pipeline pip = jedis.pipelined();
    long start = System.currentTimeMillis();
    //2.采用redis管道读取
    List vehicleInfos = vehicleInfoMapper.selectByParam(param);
    List list = new ArrayList();
    Map map = new HashMap();//map用来暂存属性
    Map>> responses  = new HashMap>>(vehicleInfos.size());
    for(VehicleInfo info: vehicleInfos){
        List> resls = new ArrayList>();
        resls.add(pip.hget(info.getVehicleSeq()+"","longitude"));
        resls.add(pip.hget(info.getVehicleSeq()+"","latitude"));
        responses.put(info.getVehicleSeq() + "", resls);//得到了一辆车所有的实时数据--300辆车
        map.put(info.getVehicleSeq()+"orgId", info.getOrgId());
        map.put(info.getVehicleSeq()+"vin", info.getVin());
        map.put(info.getVehicleSeq()+"vehiclemodelseq", info.getVehiclemodelseq());
    }
    pip.sync(); 
    for(String k:responses.keySet()){
        Coordinate coord = new Coordinate();
        coord.setLongitude(Long.valueOf(responses.get(k).get(0).get()));//是get,不是toString
        coord.setLatitude(Long.valueOf(responses.get(k).get(1).get()));
        coord.setVehicleSeq(Long.valueOf(k));
        coord.setOrgId((String) map.get(k+"orgId"));
        coord.setVin((String) map.get(k+"vin"));
        coord.setVehiclemodelseq((Long) map.get(k+"vehiclemodelseq"));
        list.add(coord);
    }
    jedis.close();
    long end = System.currentTimeMillis();
    System.out.println("耗时:"+(end-start)+" ms");
    return list;  

结果: 第一次为200ms,之后维持在30ms左右

总时间大概是单条读取总时间的1/5甚至更低,可以看出管道大大提升了效率,具有更好的性能。

注:使用管道所获取的值的类型是Response<\String>,因此需要转为String,如下代码片段:

Map>> responses  = new HashMap>>  (vehicleInfos.size());  
    //转String
 responses.get(k).get(0).get();

你可能感兴趣的:(Redis 管道技术)