redis通信协议(RESP )是什么

什么是RESP

RESP是REdis Serialization Protocol的简称,也就是专门为redis设计的一套序列化协议. 这个协议其实在redis的1.2版本时就已经出现了,可是到了redis2.0才最终成为redis通信协议的标准redis

这个序列化协议听起来很高大上, 但实际上就是一个文本协议.根据官方的说法, 这个协议是基于如下几点(而妥协)设计的:json

1. 实现简单.能够减低客户端出现bug的机率
 2. 解析速度快.因为RESP能知道返回数据的固定长度,因此不用像json那样扫描整个payload去解析, 因此它的性能是能跟解析二进制数据的性能相媲美的.
 3. 可读性好.
复制代码

为啥要理解RESP

其实RESP是个很简单的东西,不用一天就能吃透. 可是我对它的认识一直都停留在一个很模糊的状态, 以前只知道它返回的不一样的类型是以不一样的符号开始的,具体是什么没有仔细去深究.数组

直到前几天遇到一个bug, 调试redis客户端的时候发现对redis的返回内容特别陌生. 今天在看AOF文件时又遇到了它,才忽然悟到:书到用时方恨少啊bash

因而就有这一篇博客.并发

总结来讲,RESP的应用场景有:socket

1. 开发定制化的客户端. RESP设计成简单的文本协议, 一大缘由就是为了下降各类语言开发客户端的复杂度
2. 理解RESP方便咱们分析AOF文件,了解redis的内部设计
3. 平时经过抓包软件,能够帮助快速定位redis的相关问题
4. 在没有redis-cli的状况下, 方便开发调试redis命令
复制代码

RESP详解

数据类型

通常来讲,RESP只须要序列化三种数组便可: 字符串, 整数, 数组. 而在实际场景中, RESP又把字符串细化成了simple string, error string和bulk string三种.tcp

因此RESP一共涉及到5种数据类型:编辑器

1. simple string. 简单的字符串
   2. error. 就是表示这是一个错误(异常)状况
   3. integer 表示这是一个整数
   4. bulk string. 表示是长字符串,可是必须小于512M.
   5. arrays. 表示这是一个数组,数组元素能够是上面的任意一种类型,也能够是一个数组
复制代码

像一些高级语言用int long等来表示不一样数据类型同样, RESP也有它本身标识不一样数据类型的"语法", 就是用第一个字节的符号来表示不一样的数据类型:性能

  1. simple string 的第一个字节是个"+"(加号), 后面接着的是字符串的内容, 最后以CRLF(\r\n)结尾.例如:
"+OK\r\n"
复制代码
  1. error. error其实和string是相似的, 可是RESP为了能让不一样客户端把这种error和正常的返回结果区分开来对待 (例如redis返回error的话,就抛出异常),特地多设计了这个数据类型. error类型的第一个字节是"-"(减号), 后面接着的是错误的信息, 最后以CRLF(\r\n)结尾,例如:
"-ERR unknown command 'foobar'\r\n"
复制代码
  1. integer 类型的第一个字节是":"(冒号), 后面接着的是整数,最后以CRLF(\r\n)结尾, 例如:
":1000\r\n"
复制代码
  1. bulk string. 本质上也是字符串.跟普通字符串区分开来, 它的第一个字节是"$"(美圆符号),紧接着是一个整数,表示字符串的字节数,字节数后面接一个CRLF. CRLF后面是字符串的内容, 最后以一个CRLF结尾. 例如:
"$0\r\n"   --$后面的0表示这是一个空字符串

"$-1\r\n"  -- $后面的-1表示这是一个null字符串,Null Bulk String要求客户端返回空对象,而不能简单地返回个空字符串


"$6\r\nABCDEF\r\n"  -- ABCDEF是6个字节,因此$后面是6
复制代码
  1. arrays的第一个字节是"*"(星号), 紧接着后面是一个数字,表示这个数组的长度,数字后面是一个CRLF. 须要注意的是这个CRLF以后才是数组的真正内容, 并且数组内容能够是任意类型, 包括arrays和bulk string, 每一个元素也要以CRLF结尾. 最后以CRLF(\r\n)结尾. 举例:
"*0\r\n"   --*后面的0表示表示空的数组

"*-1\r\n"  --*后面的-1表示表示是null数组

"*5\r\n -- *5表示这是一个拥有5个元素的数组 +bar\r\n -- 第1个元素是简单的字符串 -unknown command\r\n -- 第2个元素是个异常 :3\r\n -- 第3个元素是个整数 $3\r\n -- 第4个元素是长度为3个字节的长字符串foo foo\r\n -- 第4个元素的内容 *3\r\n -- 第5个元素又是个数组 :1\r\n -- 第5个元素数组的第1元素 :2\r\n -- 第5个元素数组的第2元素 :3\r\n -- 第5个元素数组的第3元素 "   
复制代码

request-response模型

通常来讲,redis客户端和服务端交互都是经过如下两个步骤:测试

1. redis发送一个命令到服务端, 而后阻塞在socket.read()方法, 等待服务端的返回
 2. 服务端收到一个命令, 处理完成后将数据发送回去给客户端
复制代码

这个就被称为request/reponse模型. redis的大部分命令都是使用这种模型进行通信, 除了两种状况:

1. pipeline模式. 在pipeline模式下, 客户端可能会把多个命令收集在一块儿, 而后一并发送给服务端, 最后等待服务端把全部命令的执行响应一并发送回来
  2. pub/sub, 发布订阅模式下, redis客户端只须要发送一次订阅命令
复制代码

RESP协议的request/response模型能够总结为如下两个步骤

1. 客户端发送命令, 通常组装成bulk string的数组
 2. 服务端处理命令, 根据不一样的命令,可能返回不一样的数据类型
复制代码

例如命令"set test1 1" 通常被序列化成

*3\r\n$3\r\nset\r\n$5\r\ntest1\r\n$1\r\n1\r\n


-- 为了方便理解, 每一个CRLF咱们给它换一下行
*3\r\n        -- 这个命令包含3个(bulk)字符串
$3\r\n        -- 第一个bulk string有3个字节
set\r\n       -- 第一个bulk string是set
$5\r\n        -- 第二个bulk string有5个字节
test1\r\n     -- 第二个bulk string是test1
$1\r\n        -- 第三个bulk string有1个字节
1\r\n         -- 第三个bulk string是1
复制代码

它的返回是:

+OK\r\n --一个简单的字符串 
复制代码

再例如命令"get test1":

*2\r\n$3\r\nget\r\n$5\r\ntest1\r\n
即:
*2\r\n     -- 这个命令是2个bulk字符串的数组
$3\r\n     -- 第一个bulk字符串有3个字节:  get
get\r\n
$5\r\n     -- 第二个bulk字符串有5个字节: test1
test1\r\n
   
复制代码

这个命令的返回是:

$1\r\n   -- 只有一个字节的bulk string
1\r\n
复制代码

再来看一个错误的命令"get ", 这里咱们get的命令故意不传参数

request:

*1\r\n
$3\r\n
get\r\n

response(跟咱们在redis-cli里面获取的提示是同样的):

-ERR wrong number of arguments for 'get' command\r\n
复制代码

测试和验证

了解了RESP是什么以后, 咱们一般都会想动手验证一下,它实际的运行是否跟理论一致. 这个时候有两种方法.

telnet方式

当咱们手上没有redis-cli的时候, 有时候咱们想调试redis命令就显得比较麻烦. 这点redis作得比较人性化, 当它发现它收到的数据不是以"*"开头时, 它就会尝试解析这个字符串, 把它当作一个命令来处理, 而后返回对应的RESP格式的响应.

来看一下用telnet执行咱们上面测试的3个命令:

lhh-Mac:~ lhh$ telnet localhost 6379
Trying ::1...
Connected to localhost.
Escape character is '^]'.

set test1 1
+OK

get test1
$1
1

get 
-ERR wrong number of arguments for 'get' command

quit
+OK
复制代码

能够看到,每一个命令返回的都是RESP格式(\r\n不可见,体现为换行).

固然, 你也能够发送RESP格式的命令, 可是要在本文编辑器里面把\r\n换成换行符, 再复制过去,否则会报错.

下面例如例子中, 我执行的命令是"get test1",RESP格式就是"*2\r\n$3get\r\n$5\r\ntets1".

返回的数据是"1", RESP格式就是"$1\r\n1\r\n"

因为telnet窗口的缘由, request和response是连着的, 注意区分

使用telnet执行RESP格式的"get test1":

lhh-Mac:~ lhh$ telnet localhost 6379
Trying ::1...
Connected to localhost.
Escape character is '^]'.

*2
$3
get
$5
test1
$1
1

复制代码
socket方式

在手上没有写代码的条件时, 使用telnet确实很方便,当编辑起来不方便.当若是用IDE的话, 咱们仍是有更好的方式的, 就是写代码来测试验证.

毕竟"talk is cheap, show me the code"嘛.

redis是基于tcp通信的, 因此简单使用socket就好, 代码以下:

public static void main(String[] args) throws IOException {
      Socket socket = new Socket("localhost", 6379);
      OutputStream outputStream = socket.getOutputStream();
      BufferedReader bufferedReader
              = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      outputStream.write("*2\r\n$3\r\nget\r\n$5\r\ntest1\r\n".getBytes());
      int num = 0;
      char ch;
      while((num=bufferedReader.read()) != -1){
          ch = (char)num;
          System.out.print(ch);
      }
      socket.close();
  }
复制代码

参考 redis.io/topics/prot…

相关文章
相关标签/搜索