在开始项目之前,我们首先需要了解redis客户端与服务端之间是利用RESP协议完成通信,在RESP中,一些数据类型通过他的第一个字节进行判断 如:
- 单行回复:回复的第一个字节是“+”
- 错误信息:回复的第一个字节是“-”
- 整形数字:回复的第一个字节是“:”
- 多行字符串:回复的第一个字节是“$”
- 数组:回复的第一个字节是“*”
作为客户端,要在接受服务端的信息之后用一种可读性更强的方式反馈给用户,所以,在RESP中 相应服务端的客户端返回值是除了“+”和CRLF以外的内容
+OK\r\n # 服务端实际返回
---
OK # redis-cli 客户端显示
单行回复的返回值格式为:
get name
$9\r\nillumwang
前缀为"$"则需要读取m个字符(不包含\r\n),如反应的"$9"则意味着返回illumwang是一个9个字符的返回值
错误信息与单行回复很相似,不过是将“-”替换为错误信息本身
127.0.0.1:6379> illumwang
-ERR unknown command 'illumwang'\r\n # 服务端实际返回, 下同
---
(error) ERR unknown command 'illumwang' # redis-cli 客户端显示, 下同
127.0.0.1:6379> set name illumwang konomo
-ERR syntax error\r\n
---
(error) ERR syntax error
使用以 ":" 作为前缀,以CRLF作为结尾的字符串来表示整数,很多命令都会返回整数,如INCR``LLEN``LPUSH
等命令
127.0.0.1:6379> LPUSH info illumwang MoeLove
:2\r\n # 服务端实际返回
---
(integer) 2 # redis-cli 客户端显示
数组类型可以用于客户端向服务端发送消息,当某些命令将元素结合返回给客户端的时候,也是用数组作为回复类型
,它以"*"开头,随后是CRLF,最典型的是LRRANGE
命令
127.0.0.1:6379> LPUSH info illumwang konomo.info
:2\r\n # 服务端实际返回, 下同
---
(integer) 2 # redis-cli 客户端显示, 下同
127.0.0.1:6379> LRANGE info 0 -1
*2\r\n$12\r\nkonomo.info\r\n$8\r\n illumwang\r\n
---
1) "konomo.info"
2) "illumwang"
127.0.0.1:6379> LPOP info
$12\r\nkonomo.info\r\n
---
"konomo.info"
127.0.0.1:6379> LPOP info
$8\r\nillumwang\r\n
---
"illumwang"
127.0.0.1:6379> LRANGE info 0 -1
*0\r\n
---
(empty list or set)
如上所示,返回值为"*"开头则代表要循环几次
用于返回长度最大为512MB的二进制字符串,以"$"开头,后跟实际要发送的字节数,随后是 CRLF, 然后是实际的字符串数据,最后以 CRLF 结束。如果一个要发送一个空字符串,则会编码为 "$0\r\n\r\n" 。 某些情况下,当要表示不存在的值时候,则以 "$-1\r\n" 返回,这被叫做空多行字符串,当客户端库接收到这个 响应的时候,同样应该返回一个空值(例如 nil)而不是一个空字符串
resp是基于TCP来实现的Redis通信协议,通过上面的内容我们来进一步拆分一下resp协议的具体实现 因为上述*代表数组,即有多少组数据需要处理,图中为n。 而$表示复杂字符串,即需要获取m个字符数据,不包含/r/n 所以我们又面临了一个问题:如何计算字符的个数?
若要拆解命令,则当我们获取命令时,报文$m代表要记录长度为m的数据(不包含\r\n)
经过对RESF的学习,Redis客户端需要满足最基础的两个功能:
- 通信:通信采用TCP来完成通信,客户端与服务端需保持双工连接
- 传参:将程序中输入的字符串用RESP协议转化后发送给服务端,再将服务端返回的数据转化为可读性较好的数据
我将客户端分为了三大模块:
- 通信模块:connection类,负责链接redis服务端,从服务端接受resp数据,然后将接受的数据发送给数据处理模块
- 数据处理模块:接受通信传来的resp数据转化为数字
- api模块:定义调用服务端数据的api
感谢掘金社区山水怡情对redis协议的分析https://pdudo.gitee.io/