受欢迎的博客标签

Redis优化性能实践(6):使用管道(PipeLine)-批量(Batch)操作大量数据大幅性能提升

Published

redis大幅性能提升之使用管道(PipeLine)和批量(Batch)操作  

Redis中的管道(PipeLine)特性:简述一下就是,Redis如何从客户端一次发送多个命令,服务端到客户端如何一次性响应多个命令。

Redis使用的是客户端-服务器模型和请求/响应协议的TCP服务器,这就意味着一个请求要有以下步骤才能完成:

1、客户端向服务器发送查询命令,然后通常以阻塞的方式等待服务器相应。

2、服务器处理查询命令,并将相应发送回客户端。

这样便会通过网络连接,如果是本地回环接口那么就能特别迅速的响应,但是如果走外网,甚至外网再做一系列的层层转发,那就显的格外蛋疼。无论网络延时是多少,那么都将占用整体响应的时间。这样一来如果一次发送1个命令,网络延时为100ms,我们不得不做。那么如果1次发1000个命令,那么网络延时100*1000ms就很难容忍啦。

针对与上面的问题,Redis在2.6版本以后就都提供啦管道(Pipeline)功能。

他可以使客户端在没有读取旧的响应时,处理新的请求。这样便可以向服务器发送多个命令,而不必等待答复,直到最后一个步骤中读取答复。这被称为管线(PipeLine),并且是几十年来广泛使用的技术。例如,许多POP3协议实现已经支持此功能,大大加快了从服务器下载新电子邮件的过程。

StackExchange.Redis实现Redis管线(Pipeline) 上两张图片管线便一目了然啦。

客户端对redis服务器进行多次请求的话,一般普通模式是这样子的 客户端对redis服务器进行多次请求的话,管道模式是这样子的          

       前段时间在做用户画像的时候,遇到了这样的一个问题,记录某一个商品的用户购买群,刚好这种需求就可以用到Redis中的Set,key作为productID,value

就是具体的customerid集合,后续的话,我就可以通过productid来查看该customerid是否买了此商品,如果购买了,就可以有相关的关联推荐,当然这只是系统中

的一个小业务条件,这时候我就可以用到SADD操作方法,代码如下:

 
        static void Main(string[] args)
        {
            ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");

            var db = redis.GetDatabase();

            var productID = string.Format("productID_{0}", 1);

            for (int i = 0; i < 10; i++)
            {
                var customerID = i;

                db.SetAdd(productID, customerID);
            }
        }
 

 

一:问题

    但是上面的这段代码很明显存在一个大问题,Redis本身就是基于tcp的一个Request/Response protocol模式,不信的话,可以用wireshark监视一下:

从图中可以看到,有很多次的192.168.23.1 => 192.168.23.151 之间的数据往返,从传输内容中大概也可以看到有一个叫做productid_xxx的前缀,

那如果有百万次局域网这样的round trip,那这个延迟性可想而知,肯定达不到我们预想的高性能。

 

二:解决方案【Batch】

     刚好基于我们现有的业务,我可以定时的将批量的productid和customerid进行分组整合,然后用batch的形式插入到某一个具体的product的set中去,

接下来我可以把上面的代码改成类似下面这样:

 
 1         static void Main(string[] args)
 2         {
 3             ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");
 4 
 5             var db = redis.GetDatabase();
 6 
 7             var productID = string.Format("productID_{0}", 1);
 8 
 9             var list = new List<int>();
10 
11 
12             for (int i = 0; i < 10; i++)
13             {
14                 list.Add(i);
15             }
16 
17             db.SetAdd(productID, list.Select(i => (RedisValue)i).ToArray());
18         }
 

 

从截图中传输的request,response可以看到,这次我们一次性提交过去,极大的较少了在网络传输方面带来的尴尬性。。

 

三:再次提出问题

product维度的画像我们可以解决了,但是我们还有一个customerid的维度,也就是说我需要维护一个customerid为key的set集合,其中value的值为

该customerid的各种平均值,比如说“总交易次数”,“总交易金额”。。。等等这样的聚合信息,然后推送过来的是批量的customerid,也就是说你需要定时

维护一小嘬set集合,在这种情况下某一个set的批量操作就搞不定了。。。原始代码如下:

 
 1         static void Main(string[] args)
 2         {
 3             ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");
 4 
 5             var db = redis.GetDatabase();
 6 
 7 
 8             //批量过来的数据: customeridlist, ordertotalprice,具体业务逻辑省略
 9             var orderTotalPrice = 100;
10 
11             var customerIDList = new List<int>();
12 
13             for (int i = 0; i < 10; i++)
14             {
15                 customerIDList.Add(i);
16             }
17 
18             //foreach更新每个redis 的set集合
19             foreach (var item in customerIDList)
20             {
21                 var customerID = string.Format("customerid_{0}", item);
22 
23                 db.SetAdd(customerID, orderTotalPrice);
24             }
25         }
 

 

四:解决方案【PipeLine】

    上面这种代码在生产上当然是行不通的,不过针对这种问题,redis早已经提出了相关的解决方案,那就是pipeline机制,原理还是一样,将命令集整合起来通过

一条request请求一起送过去,由redis内部fake出一个client做批量执行操作,代码如下:

 
 1         static void Main(string[] args)
 2         {
 3             ConnectionMultiplexer redis = ConnectionMultiplexer.Connect("192.168.23.151:6379");
 4 
 5             var db = redis.GetDatabase();
 6 
 7 
 8             //批量过来的数据: customeridlist, ordertotalprice,具体业务逻辑省略
 9             var orderTotalPrice = 100;
10 
11             var customerIDList = new List<int>();
12 
13             for (int i = 0; i < 10; i++)
14             {
15                 customerIDList.Add(i);
16             }
17 
18             var batch = db.CreateBatch();
19 
20             foreach (var item in customerIDList)
21             {
22                 var customerID = string.Format("customerid_{0}", item);
23 
24                 batch.SetAddAsync(customerID, orderTotalPrice);
25             }
26 
27             batch.Execute();
28         }
 

 

然后,我们再看下面的wireshark截图,可以看到有很多的SADD这样的小命令,这就说明有很多命令是一起过去的,大大的提升了性能。

 

最后可以再看一下redis,数据也是有的,是不是很爽~~~

 
192.168.23.151:6379> keys *
 1) "customerid_0"
 2) "customerid_9"
 3) "customerid_1"
 4) "customerid_3"
 5) "customerid_8"
 6) "customerid_2"
 7) "customerid_7"
 8) "customerid_5"
 9) "customerid_6"
10) "customerid_4"
 
more:

https://www.cnblogs.com/knowledgesea/p/6552799.html