Redis客户端通过RESP(REdis Serialization Protocol )协议与服务端通讯。Redis集群是则是通过另一协议来处理节点间的通讯。
RESP有以下特点:
- 简单且容易实现
- 能够快速解析
- 可读的
RESP 能够序列化各种类型,包括整形,字符串,数组,错误类型. 客户端连接服务端的数据是以字符串数组的形式发送给服务端,相当于命令的参数形式。Redis 响应命令执行结果数据.
RESP 是二进制安全的,不需要从一个进程传送大量的数据,因为使用了前缀长度(prefixed-length)传送数据。
二进制安全
二进制化的字符串,不关心具体格式.只会严格的按照二进制的数据存取。不会妄图已某种特殊格式解析数据。也就是二进制安全的字符串是只有一种正确解。
网络层
Redis使用TCP进行客户端的连接,但是技术上不完全依赖TCP,更像是 Unix sockets
Unix sockets
Unix Domain Socket 是在Socket架构上发展起来的用于同一台主机的进程间通讯(IPC)。它不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序列号应答等。只是将应用层数据从一个进程拷贝到另一个进程。Unix Domain Socket有SOKCET_DGRAM和SOCKET_STREAM两种模式,类似于UDP和TCP,但是面向消息的Unix socket也是可靠的,消息既不会丢失也不会顺序错乱。比如的mysql socket文件:/tmp/mysql.sock
, 直接操作该文件就可以与mysql通讯。
请求响应模型
Redis使用的是最简单的协议,接收一个命令参数时,会立即执行该命令并返回执行结果数据。
除以下情况,Redis都是通过这种简单协议通讯:
- Redis 支持管道操作。导致一次性会有很多的命令发送后需要等待返回
- Redis订阅了一个Pub/Sub 频道,redis将更改协议语义,此时转变成了push协议。客服端不在发送指令,而是由服务端发送数据到客户端。
RESP 协议描述
RESP实际上就是一个序列化协议。支持简单字符串、错误、整形,批量字符串和数组类型。
Redis 的请求响应协议方式有以下两种:
- 所有客户端发送到服务端的命令都是一个字符串数组.
- Redis根据指令的执行结果,回复一个相应的数据类型
RESP 通过第一个个字节来标记数据类型:
+
标记字符串-
标记错误类型:
标记整形$
标记批量字符串*
标记数据类型
RESP 总是以\r\n
(CRLF)为结束符。
简单字符串
简单字符串,及非二进制安全的字符串。使用+
开始标记:
1 | "+OK\r\n" |
如果要发送二进制安全的字符串,则需要通过字符串数组方式。
错误类型
与简单字符串类似,不过是以-
开头。如
1 | "-Error message\r\n" |
只有在发生错误时才会返回错误。
1 | -ERR unknown command 'foobar' |
1 | -WRONGTYPE Operation against a key holding the wrong kind of value |
ERR
,WRONGTYPE
是错误类型前缀,以便区分不同的错误类型,ERR
是基本的错误类型。
整形
以:
开头,例如:
1 | ":0\r\n" |
1 | ":1000\r\n" |
多用与INCR
,LLEN
,LASTSAVE
等命令。整形的1
,0
可以用来表示真假。也可以用来表示指令是否成功执行
字符串数组(Bulk Strings)
用来传送二进制安全的字符串,高达512M长度的发送。
通过以下方式进行编码:
- 以
$
开头接收一个长度的数字,拼接上CFLR\r\n
- 再次连接实际的字符串
- 以CFLR结束
1
"$6\r\nfoobar\r\n"
特别的空表示方式如下:
1 | "$0\r\n\r\n" |
Null表示方式如下:
1 | "$-1\r\n" |
以上两种特别情况下,不同的API库不会返回一个可空的字符串,不同语言的封装包,中例如Ruby返回nil对象,C语言放回Null。
数组
LRANGE
命令返回的就是数组格式。
通过以下方式格式化:
以
*
开头跟上数组的长度,以CRLF结尾。1
"*0\r\n"
数组元素是可选的
1
"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"
表示两个数组,由两个字符串$3\r\nfoo\r\n
,$3\r\nbar\r\n
组成。
数据可以进行类型混合
1 | *5\r\n |
为了清晰可见这里做了换行处理。
特别的,Null数组表示方式:
1 | "*-1\r\n" |
数组的元素支持Null,如:
1 | *3\r\n |
为了清晰可见这里做了换行处理。不同语言包处理的结果不同,可能是一下结果:
1 | ["foo",null,"bar"] |
1 | ["foo",nil,"bar"] |
原理实践
- 客服端发送字符串数组到服务端
- 服务端根据实际执行结果返回内容
如果我们要执行LLEN mylist
则客户端发送:
1 | *2\r\n |
为了清晰可见这里做了换行处理,实际上发送的不能进行换行处理,实际发送内容是
1 | *2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n |
服务端返回:
1 | ":3445\r\n" |
多命令管道化
客户端可以在一个连接上提交多个命令,管道提供了一个可以一次提交多个命令的方法。
内联命令
如果我们手头上的没有redis-cli工具,只有telnet
或者单纯的其他TCP连接工具,那么我么可以使用内联命令使用redis
1 | telnet 127.0.0.1 6379 |
输入PING
1 | Trying 127.0.0.1... |
例如可输入得到结果:
1 | EXISTS somekey |