Как Redis общается?

Модель

Модель протокола Redis — это простая модель запроса-ответа, которая чем-то похожа на обычный протокол Http. Клиент отправляет команду Redis, а сервер обрабатывает команду и возвращает результат клиенту. Представители Redis говорят, что это, вероятно, самая простая модель сетевого протокола.

изображение-20220415110028732

Есть два случая, когда эта модель неприменима: один — это команда пакетного конвейера, а другой — функция публикации/подписки.

изображение-20220415110051982

Описание протокола

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

  • Простые строки : первый символ в начале — +, соответствующее шестнадцатеричное значение:0x2b
  • Информация об ошибке (ошибки) : первый байтовый символ -, соответствующее шестнадцатеричное значение:0x2d
  • Значение (целые числа) : первый байтовый символ равен :, соответствующее шестнадцатеричное значение:0x3a
  • Массовые строки : первый байтовый символ — $, соответствующее шестнадцатеричное значение:0x24
  • Массивы : первый байтовый символ равен *, соответствующее шестнадцатеричное значение:0x2a

Эти пять типов данных можно использовать в комбинации.Каждый тип данных заканчивается на CRLF , что является нормальным.Соответствующее \r\nшестнадцатеричное значение: 0x0d,0x0a. Как правило, когда мы оцениваем, закончился ли тип данных, нам нужно только определить, \rпоявляется . Эти правила используются для связи между клиентом Redis и сервером.

простая строка

Как правило, простые строки используются для возврата системного ответа сервера Redis.Если вы хотите ответить на данные, сохраненные пользователем, для возврата обычно используется тип данных Bulk Strings.

比如说客户端发送 set 命令新增一个 Key 来存储字符串,此时客户端就会返回 +OK。这种方式返回的数据不能有空格和换行,因为空格和换行表示该类型的数据结尾。

redis:0>set name 灰灰
"OK"

# Redis 服务端响应数据
0000  2b 4f 4b 0d 0a                                     +OK··
复制代码

错误信息

当我们执行的命令发生错误时,Redis 服务端就会返回错误信息

redis:0>incr name
"ERR value is not an integer or out of range"

# Redis 服务端响应数据
0000  2d 45 52 52 20 76 61 6c  75 65 20 69 73 20 6e 6f   -ERR val ue is no
0010  74 20 61 6e 20 69 6e 74  65 67 65 72 20 6f 72 20   t an int eger or 
0020  6f 75 74 20 6f 66 20 72  61 6e 67 65 0d 0a         out of r ange··
复制代码

数值

返回数值的其中一种情况就是执行 exists 命令来判断某个 Key 存不存在,返回 1 表示存在,返回 0 表示不存在

redis:0>exists name
"1"

# Redis 服务端响应数据
0000  3a 31 0d 0a                                        :1··
---------------------------------------------------------------
redis:0>exists hui
"0"

# Redis 服务端响应数据
0000   3a 30 0d 0a                                       :0..
复制代码

大字符串

大字符串返回值有两部分组成,一部分是表示字符串长度的数据,一部分是字符串本身数据。它由 $ 符号开头,后面跟着的是表示字符串长度的数据,该数值直接用字符串的形式表示,也就说读取该字节数据的时候,要用读取字符串数据的方式来读取。读取完后再转换为数值数据,然后再根据这个数值来读取相对应长度的字节数据。这样数据中就可以包含空格和换行了,因为是根据开头的长度数值来读取相对应的字节数据的,而不是通过判断 \r\n 符号来读取。

比如客户端获取前面设置的 Key 为 name 的数据:

redis:0>get name
"灰灰"

# Redis 服务端响应数据
0000   24 36 0d 0a e7 81 b0 e7 81 b0 0d 0a               $6..........
复制代码

其中 e7 81 b0 e7 81 b0 就是 灰灰 字符的字节数据

数组

当服务端返回数组数据时,它由 * 符号开头,后面紧跟着的是这个数值的长度,和大字符串的字节长度一样,该长度也是以字符串的形式返回。数组中的每个元素再通过相对应的数据类型来表示。

*2
+value1
+value2

# or
*3
:22
:52
:99

# 当然也可以表示嵌套数组
*2
*1
:123
*2
:433
:92
复制代码

比如客户端设置一个 list 数据,然后获取它

redis:0>lpush mylist value1 value2
2

# 获取 list
redis:0>lrange mylist 0 1
["value2","value1"]

0000   2a 32 0d 0a 24 36 0d 0a 76 61 6c 75 65 32 0d 0a   *2..$6..value2..
0010   24 36 0d 0a 76 61 6c 75 65 31 0d 0a               $6..value1..
复制代码

发送命令

当客户端发送命令给服务端时,客户端一般会把命令组装成上面的数组大字符串的数据格式再发送给服务端。比如上面的我们发送一个简单的新增一个 Key 命令:

redis:0>set name 灰灰

# 客户端发送给服务端端数据
0000   2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 34 0d   *3..$3..set..$4.
0010   0a 6e 61 6d 65 0d 0a 24 36 0d 0a e7 81 b0 e7 81   .name..$6.......
0020   b0 0d 0a                                          ...

复制代码

Redis 数据结构

В Redis обычно используются типы данных 5. Давайте посмотрим, как взаимодействуют данные между клиентом и сервером, соответствующие типам данных 5.

Строка Строка

redis:0>set name greycode

0000   2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 34 0d   *3..$3..set..$4.
0010   0a 6e 61 6d 65 0d 0a 24 38 0d 0a 67 72 65 79 63   .name..$8..greyc
0020   6f 64 65 0d 0a                                    ode..

# 响应
0000   2b 4f 4b 0d 0a                                    +OK..
-------------------------------------------------------------------------
redis:0>get name

0000   2a 32 0d 0a 24 33 0d 0a 67 65 74 0d 0a 24 34 0d   *2..$3..get..$4.
0010   0a 6e 61 6d 65 0d 0a                              .name..

# 响应
0000   24 38 0d 0a 67 72 65 79 63 6f 64 65 0d 0a         $8..greycode..
复制代码

Хеш-таблица

redis:0>hset myHash name huihui

0000   2a 34 0d 0a 24 34 0d 0a 68 73 65 74 0d 0a 24 36   *4..$4..hset..$6
0010   0d 0a 6d 79 48 61 73 68 0d 0a 24 34 0d 0a 6e 61   ..myHash..$4..na
0020   6d 65 0d 0a 24 36 0d 0a 68 75 69 68 75 69 0d 0a   me..$6..huihui..

# 响应
0000   3a 31 0d 0a                                       :1..
-------------------------------------------------------------------------
redis:0>hget myHash name

0000   2a 33 0d 0a 24 34 0d 0a 68 67 65 74 0d 0a 24 36   *3..$4..hget..$6
0010   0d 0a 6d 79 48 61 73 68 0d 0a 24 34 0d 0a 6e 61   ..myHash..$4..na
0020   6d 65 0d 0a                                       me..

# 响应
0000   24 36 0d 0a 68 75 69 68 75 69 0d 0a               $6..huihui..
-------------------------------------------------------------------------
redis:0>hgetall myHash

0000   2a 32 0d 0a 24 37 0d 0a 68 67 65 74 61 6c 6c 0d   *2..$7..hgetall.
0010   0a 24 36 0d 0a 6d 79 48 61 73 68 0d 0a            .$6..myHash..

# 响应
0000   2a 32 0d 0a 24 34 0d 0a 6e 61 6d 65 0d 0a 24 36   *2..$4..name..$6
0010   0d 0a 68 75 69 68 75 69 0d 0a                     ..huihui..
复制代码

Список

redis:0>lpush lists huihui greycode

0000   2a 34 0d 0a 24 35 0d 0a 6c 70 75 73 68 0d 0a 24   *4..$5..lpush..$
0010   35 0d 0a 6c 69 73 74 73 0d 0a 24 36 0d 0a 68 75   5..lists..$6..hu
0020   69 68 75 69 0d 0a 24 38 0d 0a 67 72 65 79 63 6f   ihui..$8..greyco
0030   64 65 0d 0a                                       de..

# 响应
0000   3a 32 0d 0a                                       :2..
-------------------------------------------------------------------------
redis:0>lrange lists 0 1

0000   2a 34 0d 0a 24 36 0d 0a 6c 72 61 6e 67 65 0d 0a   *4..$6..lrange..
0010   24 35 0d 0a 6c 69 73 74 73 0d 0a 24 31 0d 0a 30   $5..lists..$1..0
0020   0d 0a 24 31 0d 0a 31 0d 0a                        ..$1..1..

# 响应
0000   2a 32 0d 0a 24 38 0d 0a 67 72 65 79 63 6f 64 65   *2..$8..greycode
0010   0d 0a 24 36 0d 0a 68 75 69 68 75 69 0d 0a         ..$6..huihui..
复制代码

Набор коллекций

redis:0>sadd myset hello hi

0000   2a 34 0d 0a 24 34 0d 0a 73 61 64 64 0d 0a 24 35   *4..$4..sadd..$5
0010   0d 0a 6d 79 73 65 74 0d 0a 24 35 0d 0a 68 65 6c   ..myset..$5..hel
0020   6c 6f 0d 0a 24 32 0d 0a 68 69 0d 0a               lo..$2..hi..

# 响应
0000   3a 32 0d 0a                                       :2..
-------------------------------------------------------------------------
redis:0>smembers myset

0000   2a 32 0d 0a 24 38 0d 0a 73 6d 65 6d 62 65 72 73   *2..$8..smembers
0010   0d 0a 24 35 0d 0a 6d 79 73 65 74 0d 0a            ..$5..myset..

#响应
0000   2a 32 0d 0a 24 35 0d 0a 68 65 6c 6c 6f 0d 0a 24   *2..$5..hello..$
0010   32 0d 0a 68 69 0d 0a                              2..hi..
复制代码

отсортированное множество ZSet

redis:0>zadd myZset 1 hello 2 world

0000   2a 36 0d 0a 24 34 0d 0a 7a 61 64 64 0d 0a 24 36   *6..$4..zadd..$6
0010   0d 0a 6d 79 5a 73 65 74 0d 0a 24 31 0d 0a 31 0d   ..myZset..$1..1.
0020   0a 24 35 0d 0a 68 65 6c 6c 6f 0d 0a 24 31 0d 0a   .$5..hello..$1..
0030   32 0d 0a 24 35 0d 0a 77 6f 72 6c 64 0d 0a         2..$5..world..

# 响应
0000   3a 32 0d 0a                                       :2..
-------------------------------------------------------------------------
redis:0>zrange myset 0 -1

0000   2a 34 0d 0a 24 36 0d 0a 7a 72 61 6e 67 65 0d 0a   *4..$6..zrange..
0010   24 36 0d 0a 6d 79 5a 73 65 74 0d 0a 24 31 0d 0a   $6..myZset..$1..
0020   30 0d 0a 24 32 0d 0a 2d 31 0d 0a                  0..$2..-1..

# 响应
0000   2a 32 0d 0a 24 35 0d 0a 68 65 6c 6c 6f 0d 0a 24   *2..$5..hello..$
0010   35 0d 0a 77 6f 72 6c 64 0d 0a                     5..world..
复制代码

Трубопровод

Redis выполняет команду Pipeline, которая может отправлять несколько команд в одном пакете данных за раз, а затем сервер упаковывает результаты выполнения всех команд в пакет данных и возвращает его клиенту, чтобы можно было уменьшить системные накладные расходы при выполнении. большое количество команд. Например, если мы хотим выполнить set num 998и incr numэти две команды, то мы можем собрать эти две команды по отдельности, а затем склеить их вместе и отправить на сервер.

0000   2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 33 0d   *3..$3..set..$3.
0010   0a 6e 75 6d 0d 0a 24 33 0d 0a 39 39 38 0d 0a 2a   .num..$3..998..*
0020   32 0d 0a 24 34 0d 0a 69 6e 63 72 0d 0a 24 33 0d   2..$4..incr..$3.
0030   0a 6e 75 6d 0d 0a                                 .num..
复制代码

После того, как сервер получает пакет данных, он обрабатывает каждую команду, а затем возвращает результат в пакете.

0000   2b 4f 4b 0d 0a 3a 39 39 39 0d 0a                  +OK..:999..
复制代码

Среди них set num 998результат команды: OK

incr numРезультат: 999

Код

Поняв правила приведенного выше протокола взаимодействия, мы можем сами написать клиент Redis для связи с сервером Redis.

Полный адрес кода: gist.github.com/greycodee/4…

рекомендация

отjuejin.im/post/7086733648434561037