前言

大家都在说Redis很快!没错!非常快!但你真的感受到它的快了吗?尤其在高并发或极差网络环境的情况下,真的快了吗?你用对了吗?

在讨论这些问题之前,我们需要先来了解一下什么是RTT,以及RTT带来的影响。

什么是RTT?

一个TCP请求经历的步骤:

1
2
3
4
1,客户端向服务器发送请求,并以阻塞的方式从套接字中读取服务器的响应。
2,服务器接收到请求,处理命令并将响应发送回客户端。

注:套接字是通信的基石,是支持TCP/IP协议的路通信的基本操作单元。 --来自百度百科

客户端和服务器通过网络链接进行连接,这样的链接可能非常快(环回接口:本机内的请求),也可能非常慢(通过网络连接的两台主机,它们之间可能存在很多跃点)。无论网络快慢,请求都会有一段等待时间(即:从客户端传输到服务器,再由服务器传输回客户端消耗的时间),这段时间称为 RTT(请求往返时间)

而Redis的通讯,就是建立在TCP之上的,它就是典型的使用客户端-服务器交互模型进行请求响应的TCP服务器,所以RTT直接影响到它的每一个请求。

试想一下,如果我们的Redis服务器的 RTT时间 为200毫秒,那么即使服务器每秒有处理10万个请求的能力,但是在这个网络环境下,它每秒最多也只能处理5个请求。

那么到现在,你还认为它快吗?先别急着否定,这毕竟是受到了 网络因素的影响。下面我们就来看看,Redis有什么办法在这样的网络环境下,提升请求速度!

Redis管道

解决问题的思路?导致问题的根源是RTT,没有办法干预RTT的时候,如何在一个请求中处理更多的命令就是它的优化方向。

解决这个问题的技术已经非常的成熟,业界也有大量技术正在使用它,它就是pipelining流水线(管道)技术,它的出现 有效的解决了单个请求的成本问题,Redis也从很早就开始支持管道传输。

下面我们看看,如何在Redis中使用管道。

Redis管道示例

如下示例使用Jedis客户端:

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test001 {
public static void main(String[] args) {
// 创建连接
Jedis jedis = new Jedis("k.32e.co", 6379);

// 普通调用
long t1 = System.currentTimeMillis();
for(int i = 0 ; i < 500 ; i ++){
jedis.set("test:" + i,"abc");
}
System.out.println("Jedis耗时:" + (System.currentTimeMillis() - t1));

// 通过管道调用
long t2 = System.currentTimeMillis();
Pipeline pipeline = jedis.pipelined();
for(int i = 0 ; i < 500 ; i ++){
pipeline.set("test2:" + i,"abc");
}
pipeline.sync();
System.out.println("Jedis Pipeline耗时:" + (System.currentTimeMillis() - t2));
}
}

耗时输出:

1
2
Jedis耗时:11314
Jedis Pipeline耗时:51

如上示例,我们看到 正常执行 和 通过管道执行 大量的命令时,它们两者之间的耗时整整相差了百倍不止。所以在合适的场合下,合理使用管道命令 能非常有效的加快Redis命令处理速度,有效的提高程序性能。

不过,虽然管道能够帮我们批量处理大量的命令,但是,美酒虽好,可不要贪杯哟!因为管道里的命令在服务器上执行的时候,是需要在内存中排队执行的,所以最好不要塞的太多哦。

管道之外的方法

Redis除了支持Pipeline之外,它还支持Lua脚本,我们可以使用Lua编写一个脚本,将多个Redis命令放在一起执行,达到同样的效果。

那么我们要如何选择呢?它们之间有什么区别呢?

我们看看下面这个表格:

特性 Pipeline Lua
批量执行命令 支持 支持
保证原子性 不支持 支持
干预命令执行逻辑 不支持 支持

整体来说,Lua脚本更加的灵活,因为是脚本,所以你可以随心所欲的控制各个命令之间的执行逻辑,以及处理它们之间的依赖关系。更重要的是,Lua是保证原子性的,所以在一些特定场景,只有它才能胜任。而Pipeline管道,更多的是为了解决RTT带来的问题。