分布式缓存Redis之Pipeline(管道)

写在前面

  本学习教程全部示例代码见GitHub:https://github.com/selfconzrr/Redis_Learningjava

  Redis的pipeline(管道)功能在命令行中没有,但redis是支持pipeline的,并且在各个语言版的client中都有相应的实现。 因为网络开销延迟,就算redis server端有很强的处理能力,也会因为收到的client消息少,而形成吞吐量小。当client 使用pipelining 发送命令时,redis server必须将部分请求放到队列中(使用内存),执行完毕后一次性发送结果;若是发送的命令不少的话,建议对返回的结果加标签,固然这也会增长使用的内存;git

  Pipeline在某些场景下很是有用,好比有多个command须要被“及时的”提交,并且他们对相应结果没有互相依赖,对结果响应也无需当即得到,那么pipeline就能够充当这种“批处理”的工具;并且在必定程度上,能够较大的提高性能,性能提高的缘由主要是TCP链接中减小了“交互往返”的时间github

  不过在编码时请注意,pipeline期间将“独占”连接,此期间将不能进行非“管道”类型的其余操做,直到pipeline关闭;若是你的pipeline的指令集很庞大,为了避免干扰连接中的其余操做,你能够为pipeline操做新建Client连接,让pipeline和其余正常操做分离在2个client中。不过pipeline事实上所能容忍的操做个数,和socket-output缓冲区大小/返回结果的数据尺寸都有很大的关系;同时也意味着每一个redis-server同时所能支撑的pipeline连接的个数,也是有限的,这将受限于server的物理内存或网络接口的缓冲能力。web

(一)简介

  Redis使用的是客户端-服务器(CS)模型请求/响应协议的TCP服务器。这意味着一般状况下一个请求会遵循如下步骤:redis

  • 客户端向服务端发送一个查询请求,并监听Socket返回,一般是以阻塞模式,等待服务端响应。
  • 服务端处理命令,并将结果返回给客户端。

  Redis客户端与Redis服务器之间使用TCP协议进行链接,一个客户端能够经过一个socket链接发起多个请求命令。每一个请求命令发出后client一般会阻塞并等待redis服务器处理,redis处理完请求命令后会将结果经过响应报文返回给client,所以当执行多条命令的时候都须要等待上一条命令执行完毕才能执行。好比:编程

  这里写图片描述

  其执行过程以下图所示:缓存

  这里写图片描述

  因为通讯会有网络延迟,假如client和server之间的包传输时间须要0.125秒。那么上面的三个命令6个报文至少须要0.75秒才能完成。这样即便redis每秒能处理100个命令,而咱们的client也只能一秒钟发出四个命令。这显然没有充分利用 redis的处理能力。服务器

  而管道(pipeline)能够一次性发送多条命令并在执行完后一次性将结果返回,pipeline经过减小客户端与redis的通讯次数来实现下降往返延时时间,并且Pipeline 实现的原理是队列,而队列的原理是时先进先出,这样就保证数据的顺序性。 Pipeline 的默认的同步的个数为53个,也就是说arges中累加到53条数据时会把数据提交。其过程以下图所示:client能够将三个命令放到一个tcp报文一块儿发送,server则能够将三条命令的处理结果放到一个tcp报文返回。网络

  这里写图片描述

  须要注意到是用 pipeline方式打包命令发送,redis必须在处理完全部命令前先缓存起全部命令的处理结果。打包的命令越多,缓存消耗内存也越多。因此并非打包的命令越多越好。具体多少合适须要根据具体状况测试。app

(二)比较普通模式与PipeLine模式

  测试环境:
  Windows:Eclipse + jedis2.9.0 + jdk 1.7
  Ubuntu:部署在虚拟机上的服务器 Redis 3.0.7

/* * 测试普通模式与PipeLine模式的效率: * 测试方法:向redis中插入10000组数据 */
	public static void testPipeLineAndNormal(Jedis jedis)
			throws InterruptedException {
		Logger logger = Logger.getLogger("javasoft");
		long start = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			jedis.set(String.valueOf(i), String.valueOf(i));
		}
		long end = System.currentTimeMillis();
		logger.info("the jedis total time is:" + (end - start));

		Pipeline pipe = jedis.pipelined(); // 先建立一个pipeline的连接对象
		long start_pipe = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			pipe.set(String.valueOf(i), String.valueOf(i));
		}
		pipe.sync(); // 获取全部的response
		long end_pipe = System.currentTimeMillis();
		logger.info("the pipe total time is:" + (end_pipe - start_pipe));
		
		BlockingQueue<String> logQueue = new LinkedBlockingQueue<String>();
		long begin = System.currentTimeMillis();
		for (int i = 0; i < 10000; i++) {
			logQueue.put("i=" + i);
		}
		long stop = System.currentTimeMillis();
		logger.info("the BlockingQueue total time is:" + (stop - begin));
	}

  这里写图片描述

  从上述代码以及结果中能够明显的看到PipeLine在“批量处理”时的优点。

(三)适用场景

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

  还有的系统,多是批量的将数据写入redis,容许必定比例的写入失败,那么这种场景就可使用了,好比10000条一下进入redis,可能失败了2条无所谓,后期有补偿机制就好了,好比短信群发这种场景,若是一下群发10000条,按照第一种模式去实现,那这个请求过来,要好久才能给客户端响应,这个延迟就太长了,若是客户端请求设置了超时时间5秒,那确定就抛出异常了,并且自己群发短信要求实时性也没那么高,这时候用pipeline最好了。

(四)管道(Pipelining) VS 脚本(Scripting)

  大量 pipeline 应用场景可经过 Redis 脚本(Redis 版本 >= 2.6)获得更高效的处理,后者在服务器端执行大量工做。脚本的一大优点是可经过最小的延迟读写数据,让读、计算、写等操做变得很是快(pipeline 在这种状况下不能使用,由于客户端在写命令前须要读命令返回的结果)。

  应用程序有时可能在 pipeline 中发送 EVAL 或 EVALSHA 命令。Redis 经过 SCRIPT LOAD 命令(保证 EVALSHA 成功被调用)明确支持这种状况。

(五)源码分析

  关于Pipeline的源码分析 请看后续文章分析。

------至全部正在努力奋斗的程序猿们!加油!!
有码走遍天下 无码步履维艰
1024 - 梦想,永不止步!
爱编程 不爱Bug
爱加班 不爱黑眼圈
执拗 但不偏执
疯狂 但不疯癫
生活里的菜鸟
工做中的大神
身怀宝藏,一心憧憬星辰大海
追求极致,目标始于高山之巅
一群怀揣好奇,梦想改变世界的孩子
一群追日逐浪,正在改变世界的极客
大家用最美的语言,诠释着科技的力量
大家用极速的创新,引领着时代的变迁

——乐于分享,共同进步,欢迎补充
——Any comments greatly appreciated
——诚心欢迎各位交流讨论!QQ:1138517609
——CSDN:https://blog.csdn.net/u011489043
——简书:https://www.jianshu.com/u/4968682d58d1
——GitHub:https://github.com/selfconzrr