目录
1.管道
1.1 RESP
Redis的协议称为RESP,它将协议数据分为不同类型,根据数据的首字符决定,不过所有类型的数据均以CRLF(即“\r\n”)结束。
1)简单字符串
首字符为“+”,后续为字符串内容,字符串内容不能包括'\r'或'\n'。例如“+OK\r\n”,经过客户端转换就变成了“OK”。由于简单字符串没有转义的情况,所以反序列化就是直接把“+”和“\r\n”之间的部分复制出去。
2)块字符串
如果发送的字符串本身包含了'\r'或'\n',就不能使用简单字符串,此时有两种解决方案:一是加入转义机制,这种方式效率比较低;二是在字符串前/后加上一个数字,表示整个字符串的长度。Redis使用了后者。块字符串的首字符是'$',后面跟着整个字符串的长度,以“\r\n”分隔后,跟上字符串内容,最后以“\r\n”结尾。例如:“$12\r\nhello\r\nworld\r\n”转换后就是:
hello
world
如果字符串长度为0,说明是空字符串,如果为-1("$-1\r\n"),则说明是null
3)错误字符串
正常的字符串是“+”开头,所以错误字符串就是“-”开头,剩下的和简单字符串一样
4)数字
以“:”开头,紧跟着数字本身,以“\r\n”结尾
5)数组
以“*”开头,紧跟着数组长度,类似于块字符串,长度为0代表空数组,为-1代表null,数组内容夹在两个“\r\n”中间,每一个数组元素都是这五种类型的数据,例如:"*2\r\n+hello\r\n+world\r\n"代表["hello","world"],数组内元素类型可以不一样,也支持嵌套数组。
客户端在向服务端发送数据时,参数以块字符串的形式发送,命令本身因为不属于以上任何类型,所以以原型发送
服务器向客户端发送消息时,有两种情况:第一种是对客户端的命令返回响应,可以返回以上五种类型;第二种是对Pub/Sub模式的订阅者推送消息,此时以数组类型发送。
1.2 通过管道交互
在Linux中,管道是进程间通信的重要手段,可以让前一条命令的结果传递给下一条命令作为参数,但是Redis的管道和Linux的不太一样,它的作用是将一批命令序列化地发送到服务端,执行后再将结果集序列化发送回客户端,从而节约网络I/O的时间。
Redis是单线程架构,即一次只能处理一条命令,在串行实现中,客户端命令的执行有如下四个步骤:
- 命令发送
- 命令排队
- 命令执行
- 返回结果
该实现的优点在于简单,缺点则是效率低,因为每一条命令必须等待前一条命令完全执行后才会执行,同样地,从客户端角度来看,每一条命令的发送都需要前一条命令的响应被接收,导致吞吐量很低,尤其是内存速度比网络I/O快太多,会导致客户端空等待。
为了解决这个问题,Redis提供了两种方案:
第一种就是如mget、mset这样的批量操作,这种操作具有很多优点,如原子执行、原生支持等,缺点也很明显,并不是所有操作都有批量版本;
第二种就是管道机制,Redis管道可以将一批命令一次性发送出去,和原生批量操作的区别在于:首先,管道不是原子操作;其次,批量操作是对多个key执行相同操作,而管道可以将不同的操作封装成一批;最后,管道的实现需要客户端和服务端共同完成。
对于Redis自带的客户端,即redis-cli,在启动时加上--pipe选项即可使用pipe机制,例如:
root@Yhc-Surface:~# echo -en '*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n*2\r\n$4\r\nincr\r\n$9\r\ntestcount\r\
n' | redis-cli --pipe
All data transferred. Waiting for the last reply...
Last reply received from server.
errors: 0, replies: 2
该命令将 set hello world 和 incr testcount 两个命令合并,通过管道发送给服务端,美中不足的是看不到服务端响应内容,下面的用法可以看到服务端响应:
root@Yhc-Surface:~# printf '*3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n*2\r\n$4\r\nincr\r\n$9\r\ntestcount\r\n'
| nc localhost 6379
+OK
:2
可以看到,服务端返回了一条简单字符串和一个数字。
现在计算pipeline的性能提升幅度:
对于串行模式,每一条命令都有两倍的网络延迟RTT(一收一发),还要加上客户端和服务端对命令的处理时间,分别设为m和n,则其吞吐量公式为:
对于管道模式,设一批执行了k条命令,仍然只有两倍的网络延迟,不过客户端和服务端的处理时间变成了k*m、k*n,即:
当k足够大时,可以忽略网络延迟,即TPS=1/(m+n)。如果假设网络I/O所需时间是命令处理时间的10倍,那么使用管道最大可以将