Redis系统分为Redis客户端和Redis服务器端。Redis客户端通过使用Redis序列化协议向Redis服务器发送命令和接收数据。Redis序列化协议称为REdis Serialization Protocol,简称RESP。 RESP可用于任何系统的通信,也可建立在任何常用的网络协议之上。在Redis系统中,RESP是建立于TCP协议上的,Redis服务器默认监听6379端口。
RESP是一个基于文本的协议(Text-based Protocol)。 Redis设计人员并没有采用二进制的格式,这是因为:其一、Redis系统的效率非常高,而且瓶颈不在于CPU上。所以,Redis服务器可以花费一些额外的CPU资源解析RESP中的文本;其二,RESP是一个简单的协议,易于实现、其三,采用文本协议易于观察和调试,毕竟,所有的内容都是文本,开发人员可以直接阅读。
本章将重点放在RESP协议的内容上,讲解客户端如何与服务器通信。理解了RESP协议之后,我们就能理解Redis客户端的工作方式了。
RESP协议遵守“一问一答”的方式,即,当客户端向服务器发送一个请求后,客户端能立刻收到服务器的应答。在服务器端,当服务器收到一个请求后,服务器立即处理该请求,并发出应答。这种"一问一答"的工作方式非常简单,易于实现。在这种工作方式下,一个例外是客户端可以使用Pipeline,即客户端一次可以发多条请求给服务器,然后将服务器的应答缓存起来,一次性处理应答。在Pipeline的工作方式下,服务器的工作方式不变:每收到一个请求,则处理一个请求,并返回处理结果。 另一个例外就是发布-订阅系统。当客户端订阅了一个频道之后,Redis服务器会向客户端推送消息。此时,RESP协议从"一问一答"方式变为单向的消息传递方式。
RESP协议支持五种数据类型,它们是简单字符串(Simple Strings)、错误(Errors)、整数(Integers)、长字符串(Bulk Strings)和数组(Arrays)。 客户端向服务器端发送的命令请求是由Bulk Strings数组(An Array of Bulk Strings)构成的。 服务器的应答则由以上五种数据类型组成。
"+OK\r\n"
"-ERR unkown command 'foobar'.\r\n"
在此基础之上,Redis服务器进一步制定了解析错误类型数据的规则,但是,这些规则并不是RESP协议中的一部分。在减号之后"-",第一个出现的单词表示着出错的类型。所以,客户端能根据第一个单词来决定发生的异常类型。这仅仅是Redis服务器提供的规则,Redis客户端可以使用这个规则,也可以自由决定处理方法。
":10\r\n"
长字符串类型的数据由一个美元符号"$"开始,然后紧接着一个整形类型的数据表示字符串的长度,然后,再接着是字符串的内容。最后,以回车换行符("\r\n")结束。例如:如下是表示"Little Waterdrop"字符串的长字符串类型的例子。值得注意的是,字符串的长度与字符串的内容之间由回车换行符("\r\n")分割。
"$16\r\nLittle Waterdrop\r\n"
因为有字符串长度字段的帮助,长字符串还能表达空字符串和Null类型的数据,例如,下面第一个例子是一个空字符串,其长度为0。第二个例子用于表示Null,其长度字段为-1,没有字符串内容(这是RESP协议的规定)。
"$0\r\n\r\n"
"$-1\r\n"
"*0\r\n"
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
我们在一台服务器上启动了一个Redis服务器和一个Redis客户端,并从客户端上输入"set key value"命令。所以,客户端会按照RESP协议的要求向Redis服务器发送命令。下面是使用tcpdump工具抓取的数据包,从倒数第三行起(位移在0x0034起)是发送的命令的内容。该内容是"*3\r\n$3\r\nset\r\n$3\r\nkey\r\n$5\r\nvalue\r\n"。这是一个有着3个元素的数组。第一个元素长度为3,第二个元素的长度为3,第三个元素的长度为5,如下图所示。
图一 Redis命令例子。
下面展示的是tcpdump命令抓取的数据包的全部内容。
> tcpdump -vv -x -i lo port 6379
tcpdump: listening on lo, link-type EN10MB (Ethernet), capture size 262144 bytes
21:09:07.723328 IP (tos 0x0, ttl 64, id 36034, offset 0, flags [DF], proto TCP (6), length 85)
localhost.42312 > localhost.6379: Flags [P.], cksum 0xfe49 (incorrect -> 0x3d06), seq 751800028:751800061, ack 3639686382, win 1024, options [nop,nop,TS val 3559180120 ecr 3559174905], length 33: RESP "set" "key" "value"
0x0000: 4500 0055 8cc2 4000 4006 afde 7f00 0001
0x0010: 7f00 0001 a548 18eb 2ccf 8edc d8f1 34ee
0x0020: 8018 0400 fe49 0000 0101 080a d424 c758
0x0030: d424 b2f9 2a33 0d0a 2433 0d0a 7365 740d
0x0040: 0a24 330d 0a6b 6579 0d0a 2435 0d0a 7661
0x0050: 6c75 650d 0a
在抓取的数据包中,上述命令的字符串内容在最后三行。这些数据是十六进制的数据,我们将命令内容截取出来,展示在下图中。字符串的内容可以参照ASCII表转换得到。0x2a表示的是字符星号;0x33表示的数字3;以此类推。
0x0030: 2a33 0d0a 2433 0d0a 7365 740d
0x0040: 0a24 330d 0a6b 6579 0d0a 2435 0d0a 7661
0x0050: 6c75 650d 0a
我们在本章中介绍了Redis客户端和服务器端通信的协议RESP。它是一个基于文本的协议,可以建立在任何网络协议之上。Redis采用文本协议的原因是RESP易于实现、易于观察。虽然二进制协议(Binary Protocol)在性能上有一定优势,但是,Redis开发人员认为Redis的性能瓶颈并不在协议处理上。所以,使用文本协议并不会降低Redis的性能。
注册用户登陆后可留言