Что такое протокол связи Redis (RESP)

Redis

Что такое РЕСП

RESP — это аббревиатура от REdis Serialization Protocol, который представляет собой протокол сериализации, специально разработанный для redis.Этот протокол фактически появился в версии 1.2 redis, но, наконец, стал стандартом протокола связи redis после redis 2.0.

Этот протокол сериализации звучит грандиозно, но на самом деле это текстовый протокол.Согласно официальному заявлению, этот протокол разработан на основе следующих моментов (и компромиссов):

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

Зачем понимать RESP

На самом деле, RESP - очень простая вещь, и чтобы ее понять, не нужно и дня, но мое понимание ее всегда было в очень расплывчатом состоянии, раньше я знал только, что разные типы, которые она возвращает, начинаются с разных символы, в чем специфика?Не вникал внимательно.

Пока я не столкнулся с ошибкой несколько дней назад, при отладке клиента Redis я обнаружил, что очень не знаком с возвращаемым содержимым Redis, Сегодня я снова столкнулся с этим, когда просматривал файл AOF, и внезапно понял, что Книга будет использоваться только тогда, когда она используется.

Отсюда и этот блог.

Подводя итог, сценарии применения RESP:

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

Детали RESP

тип данных

Вообще говоря, RESP необходимо сериализовать только три типа массивов: строки, целые числа и массивы.В реальных сценариях RESP уточняет строки до трех типов: простая строка, строка ошибки и объемная строка.

Таким образом, RESP включает в себя в общей сложности 5 типов данных:

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

Подобно тому, как некоторые языки высокого уровня используют int long для представления различных типов данных, RESP также имеет свою собственную «грамматику» для идентификации различных типов данных, которая заключается в использовании символа первого байта для представления различных типов данных:

  1. Первый байт простой строки — это «+» (знак плюс), за которым следует содержимое строки и, наконец, заканчивается CRLF (\r\n), например:
"+OK\r\n"
  1. error.error на самом деле похож на строку, но RESP специально разработал эти данные, чтобы позволить разным клиентам отличить эту ошибку от обычных возвращаемых результатов (например, если redis вернет ошибку, будет выдано исключение). байт типа ошибки - "-" (знак минус), за которым следует сообщение об ошибке и, наконец, заканчивается CRLF (\r\n), например:
"-ERR unknown command 'foobar'\r\n"
  1. Первым байтом целочисленного типа является «:» (двоеточие), за которым следует целое число и, наконец, заканчивается CRLF (\r\n), например:
 ":1000\r\n"
  1. Объемная строка.Это тоже строка по сути.В отличие от обычных строк,первым байтом является "$"(знак доллара),за которым следует целое число,указывающее количество байтов строки,количество байтов.За ним следует CRLF. За CRLF следует содержимое строки, которая заканчивается на CRLF. Например:
"$0\r\n"   --$后面的0表示这是一个空字符串

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


"$6\r\nABCDEF\r\n"  -- ABCDEF是6个字节,所以$后面是6
  1. Первым байтом массивов является "*" (звездочка), за которым следует число, указывающее длину массива, за которым следует CRLF. Следует отметить, что после этого CRLF идет реальное содержимое массива, а содержимое массив может быть любого типа, включая массивы и объемные строки, и каждый элемент должен заканчиваться на 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元素
"   

модель запрос-ответ

Вообще говоря, взаимодействие между клиентом Redis и сервером осуществляется через следующие два шага:

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

Это называется моделью запроса/ответа.Большинство команд Redis используют эту модель для связи, за исключением двух случаев:

  1. pipeline模式. 在pipeline模式下, 客户端可能会把多个命令收集在一起, 然后一并发送给服务端, 最后等待服务端把所有命令的执行响应一并发送回来
  2. pub/sub, 发布订阅模式下, redis客户端只需要发送一次订阅命令

Модель запроса/ответа протокола RESP можно резюмировать как следующие два шага.

 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 --一个简单的字符串 

Другой пример — команда «получить тест1»:

   *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, мы обычно хотим проверить, действительно ли он работает в соответствии с теорией.На данный момент существует два метода.

Телнет-метод

Когда у нас нет под рукой redis-cli, иногда нам сложнее отлаживать команду redis. На данный момент Redis более удобен для пользователя: когда он обнаружит, что данные, которые он получает, не начинаются с «*», он попытается разобрать строку, обработать ее как команду, а затем вернуть соответствующий ответ в формате RESP. . . .

Давайте посмотрим на использование telnet для выполнения трех команд, которые мы тестировали выше:

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 для выполнения «get test1» в формате RESP:

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

*2
$3
get
$5
test1
$1
1

метод сокета

Когда под рукой нет условия для написания кода, действительно удобно использовать телнет, но неудобно редактировать, при использовании IDE у нас все же есть лучший способ, который заключается в написании кода для тестирования и проверки.

Ведь "говорить дешево, покажи код".

Redis основан на TCP-соединении, поэтому лучше просто использовать сокет, код выглядит следующим образом:


  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/pro T…