redis协议介绍

🍎 Redis协议介绍

介绍 redis 服务器和 redis 客户端间的通信协议 (communication protocol)。

主要内容包括:

  1. Redis 介绍
  2. RESP, 5 种数据类型
  3. pipeline,协议如何支持
  4. RESP3,新版协议的一些改进和提升

✅ redis介绍

redis 是一个 key value 数据库,可以把 redis 数据库看做是一个 hash 表(编程语言里的 map、dict、object),这个 hash 表以 (key, value) pairs 的形式存储数据。

下面的例子描述了一个 redis 数据库:

{
    "key1": "abc",
    "key2": ["a","b","a", "c"],
    "key3": {"a", "b", "c"},
    "key4": {"x": "a", "y": "b", "z": "c"},
    "key5": {"a": 1.1, "b": 1.2, "c": 1.3}
}

redis 支持多种 value 类型:string、list、set、zset、hash 等。

✅ redis 协议介绍

🍊什么是 redis 协议

client -> server (client 发送命令1)
server -> client (server 响应命令1)

client -> server (client 发送命令2)
server -> client (server 响应命令2)
....

redis 目前使用 RESP 协议进行 server 与 client 间通信。 RESP 是 Redis serialization protocol 的缩写。

serialization 是指将程序 runtime 中的数据结构转换为可以存储或传输的格式的过程。之后使用这些数据时,能够从存储或网络中读取出来,并在程序中重新创建出和之前等同的数据结构。

维基百科的解释:

In computing, serialization (US spelling) or serialisation (UK spelling) is the process of translating a data structure or object state into a format that can be stored (for example, in a file or memory data buffer) or transmitted (for example, over a computer network) and reconstructed later (possibly in a different computer environment). When the resulting series of bits is reread according to the serialization format, it can be used to create a semantically identical clone of the original object.

RESP 协议即规范了 redis 客户端和服务器间的 serialization 格式,规定了怎么在两者间通讯的规范。

RESP 协议作为应用层协议,一般用于 TCP 之上。

🍊为什么需要了解redis协议

拿 HTTP 协议做类比,在实际的使用中,我们不直接处理 HTTP 协议的各种细节,而是使用 HTTP 库,我们在开发中使用的是这些库抽象出的一些对象,如request, response,但是了解一些 HTTP 协议对于我们的日常开发是很有用的。Redis 也是一样,各种语言里面也有 redis 库处理 redis 协议,我们大多数时候不需要关心细节。但是学习 redis 协议也能够帮助我们更好的理解 redis 的工作机制,在日常的工作中,遇到一些问题或者需要调试的时候,还是很有帮助的。

另外 redis 协议设计的比较简单,可以作为案例进行学习,对以后理解其它协议或设计协议也能有一些帮助和启发。

🍊协议格式

RESP 协议定义了 5 种数据类型。以第一个字节来区分不同的数据类型。
数据都以 \r\n (<CR><LF>, 0d 0a) 作为结尾。
redis client 和 server 间传递的信息都是这几种数据类型的数据的组合。

redis 协议支持的 5 种数据类型包括:simple string, bulk string, integer, array, error。

simple string:
+<string>\r\n

bulk string:
$<string_len>\r\n<string>\r\n

integer:
:<integer>\r\n

array:
*<array_len>\r\n<item1><item2>

error:
-<error_message>
✨simple string

simple string 第一个字节是 +,后面是字符串内容,最后以 \r\n 结尾。
+OK\r\n 是一个 simple string。

在实际的使用中,simple string 用于服务器返回,表示操作的状态:成功/失败。

举例如下:

127.0.0.1:6379> set a b
OK

ngrep 抓包如下:

➜ sudo ngrep -d lo0  -x port 6379

interface: lo0 (127.0.0.0/255.0.0.0)
filter: ( port 6379 ) and (ip || ip6)

#
T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #1
  2a 33 0d 0a 24 33 0d 0a    73 65 74 0d 0a 24 31 0d    *3..$3..set..$1.
  0a 61 0d 0a 24 31 0d 0a    62 0d 0a                   .a..$1..b..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #3
  2b 4f 4b 0d 0a                                        +OK..
#

在 Go 语言的 redis 客户端中,返回的 simple string 被封装为字符串。

✨bulk string

bulk string 第一个字节是 $,后面是字符串长度,以 \r\n 结束,后面是字符串的具体内容,最后以 \r\n 结束。
比如 $3\r\nfoo\r\n 是一个 bulk string。

举例如下:

127.0.0.1:6379> get a
"b"

ngrep 抓包如下:

T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #5
  2a 32 0d 0a 24 33 0d 0a    67 65 74 0d 0a 24 31 0d    *2..$3..get..$1.
  0a 61 0d 0a                                           .a..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #7
  24 31 0d 0a 62 0d 0a                                  $1..b..
#
✨为什么需要 simple string 和 bulk string 两种字符串呢?

使用场景的不同,simple string 一般用于 redis server 返回状态,表示当前的操作状态是成功/失败,如 set 命令的返回。且 simple string 不是 binary safe 的,不能表示包含 \r\n 的字符串。
比如:+OK\r\n\r\n 表示字符串 OK 还是 OK\r\n?

bulk string 用于传输 key, value 数据,这种情况下,要求能表示包含特殊字符串的数据。因为 redis 支持 key,value 可以包含任意二进制数据。

simple string 也可以被表示为 bulk string,比如 +OK\r\n 可以被表示为 $2\r\nOK\r\n,不过前者更加简洁,减少了网络传输,可能也是一种考虑。

✨NULL string

bulk string 有一个特别的 string: NULL string,表示为 $-1\r\n。与长度为0的字符串($0\r\n\r\n)不同,是 null 值。

看下面的例子:

127.0.0.1:6379> get a
""
127.0.0.1:6379> get not_exist
(nil)

ngrep 抓包为:

T 127.0.0.1:53463 -> 127.0.0.1:6379 [AP] #42215
  2a 32 0d 0a 24 33 0d 0a    67 65 74 0d 0a 24 31 0d    *2..$3..get..$1.
  0a 61 0d 0a                                           .a..
##
T 127.0.0.1:6379 -> 127.0.0.1:53463 [AP] #42217
  24 30 0d 0a 0d 0a                                     $0....
##
T 127.0.0.1:53463 -> 127.0.0.1:6379 [AP] #42219
  2a 32 0d 0a 24 33 0d 0a    67 65 74 0d 0a 24 39 0d    *2..$3..get..$9.
  0a 6e 6f 74 5f 65 78 69    73 74 0d 0a                .not_exist..
##
T 127.0.0.1:6379 -> 127.0.0.1:53463 [AP] #42221
  24 2d 31 0d 0a                                        $-1..

上面的两种场景,在 Go 的 redis 客户端中,分别被表示为长度为0的字符串和 redis.Nil 错误。实际使用时需要注意。

✨integer

integer 第一个字节是 :, 后面是具体的数字,并以 \r\n 结束。

127.0.0.1:6379> strlen a
(integer) 1
T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #17
  2a 32 0d 0a 24 36 0d 0a    73 74 72 6c 65 6e 0d 0a    *2..$6..strlen..
  24 31 0d 0a 61 0d 0a                                  $1..a..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #19
  3a 31 0d 0a                                           :1..
✨array

array 第一个字节是 *,然后是一个数字,表示 array 中的元素个数,以 \r\n 结尾,后面是 array 中的每个元素。

比如 *3\r\n$2\r\nab\r\n$1\r\na\r\n$3\r\nxyz\r\n 是一个 array,表示 [“ab”, “a”, “xyz”]
array 中的各个元素可以是不同的类型。

以上例子中,客户端发送的命令都是 array 类型。

作为命令的返回结果,在 Go 客户端中,array 类型根据场景不同被解析为 map(hash), slice(list、set), redis.Z (zset)。

Go redis 客户端库的部分函数:

func (c cmdable) HGetAll(ctx context.Context, key string) *StringStringMapCmd

func (c cmdable) LRange(ctx context.Context, key string, start, stop int64) *StringSliceCmd 

func (c cmdable) SMembers(ctx context.Context, key string) *StringSliceCmd 

func (c cmdable) ZRangeWithScores(ctx context.Context, key string, start, stop int64) *ZSliceCmd

type Z struct {
	Score  float64
	Member interface{}
}
✨empty array 与 null array

empty array: *0\r\n
null array: *-1\r\n,用于某些命令的返回中,表示 null。

两个 null: null array (*-1\r\n) 和 null string ($-1\r\n)

✨error

error 第一个字节是 -, 后面是具体的错误信息,最后以 \r\n 结尾。

127.0.0.1:6379> get a b
(error) ERR wrong number of arguments for 'get' command

127.0.0.1:6379> llen a
(error) WRONGTYPE Operation against a key holding the wrong kind of value

ngrep 抓包如下:

T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #1797
  2a 33 0d 0a 24 33 0d 0a    67 65 74 0d 0a 24 31 0d    *3..$3..get..$1.
  0a 61 0d 0a 24 31 0d 0a    62 0d 0a                   .a..$1..b..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #1799
  2d 45 52 52 20 77 72 6f    6e 67 20 6e 75 6d 62 65    -ERR wrong numbe
  72 20 6f 66 20 61 72 67    75 6d 65 6e 74 73 20 66    r of arguments f
  6f 72 20 27 67 65 74 27    20 63 6f 6d 6d 61 6e 64    or 'get' command
  0d 0a                                                 ..

  
T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #1813
  2a 32 0d 0a 24 34 0d 0a    6c 6c 65 6e 0d 0a 24 31    *2..$4..llen..$1
  0d 0a 61 0d 0a                                        ..a..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #1815
  2d 57 52 4f 4e 47 54 59    50 45 20 4f 70 65 72 61    -WRONGTYPE Opera
  74 69 6f 6e 20 61 67 61    69 6e 73 74 20 61 20 6b    tion against a k
  65 79 20 68 6f 6c 64 69    6e 67 20 74 68 65 20 77    ey holding the w
  72 6f 6e 67 20 6b 69 6e    64 20 6f 66 20 76 61 6c    rong kind of val
  75 65 0d 0a                                           ue..

在 redis server 的具体实现中,- 后面有时会有一个单词描述具体的错误类型(一般是只包含大写字母),然后才是具体的错误信息。这个描述错误信息的单词叫做 error prefix,客户端可以据此区分错误类型,但是这些错误类型并没有在协议中进行严格的说明,只是一种习惯用法。

以上就是 redis 协议支持的所有数据类型:simple string、bulk string、integer、error、array。

✨如何表示 float 类型,如 zset 中的 score?

float类型被表示为 bulk string

127.0.0.1:6379> zrange zseta 0 -1 withscores
1) "a"
2) "1.5"
3) "b"
4) "2"
5) "c"
6) "10"

T 127.0.0.1:50727 -> 127.0.0.1:6379 [AP] #1
  2a 35 0d 0a 24 36 0d 0a    7a 72 61 6e 67 65 0d 0a    *5..$6..zrange..
  24 35 0d 0a 7a 73 65 74    61 0d 0a 24 31 0d 0a 30    $5..zseta..$1..0
  0d 0a 24 32 0d 0a 2d 31    0d 0a 24 31 30 0d 0a 77    ..$2..-1..$10..w
  69 74 68 73 63 6f 72 65    73 0d 0a                   ithscores..
##
T 127.0.0.1:6379 -> 127.0.0.1:50727 [AP] #3
  2a 36 0d 0a 24 31 0d 0a    61 0d 0a 24 33 0d 0a 31    *6..$1..a..$3..1
  2e 35 0d 0a 24 31 0d 0a    62 0d 0a 24 31 0d 0a 32    .5..$1..b..$1..2
  0d 0a 24 31 0d 0a 63 0d    0a 24 32 0d 0a 31 30 0d    ..$1..c..$2..10.
  0a                                                    .
#
127.0.0.1:6379> INCRBYFLOAT float_key 20.22
"20.22"

T 127.0.0.1:53463 -> 127.0.0.1:6379 [AP] #42227
  2a 33 0d 0a 24 31 31 0d    0a 49 4e 43 52 42 59 46    *3..$11..INCRBYF
  4c 4f 41 54 0d 0a 24 39    0d 0a 66 6c 6f 61 74 5f    LOAT..$9..float_
  6b 65 79 0d 0a 24 35 0d    0a 32 30 2e 32 32 0d 0a    key..$5..20.22..
##
T 127.0.0.1:6379 -> 127.0.0.1:53463 [AP] #42229
  24 35 0d 0a 32 30 2e 32    32 0d 0a                   $5..20.22..

✅ pipeline

介绍 pipeline 模式,以及看一下 pipeline 模式下的协议数据格式。

redis 协议在使用时采用了 request、response 模型,但是在实际的使用中,redis 支持 pipeline 模式。

client -> server (client 发送命令1)
server -> client (server 响应命令1)

client -> server (client 发送命令2)
server -> client (server 响应命令2)
....

pipeline 模式指 client 发送多条命令,服务器进行处理,处理完成后一次性返回所有命令的响应,多条响应按照请求的顺序排序。

client -> server (client 发送命令1..N)
server -> client (server 响应命令1..N)

client -> server (client 发送命令N+1..2N)
server -> client (server 响应命令N+1..2N)
....

🍊RESP 协议对 pipeline 的支持

RESP 协议没有对 pipeline 模式进行额外的处理,如下所示是 pipeline 模式下处理set a abcget a两条命令的网络包:

T 127.0.0.1:51785 -> 127.0.0.1:6379 [AP] #7
  2a 33 0d 0a 24 33 0d 0a    73 65 74 0d 0a 24 31 0d    *3..$3..set..$1.
  0a 61 0d 0a 24 33 0d 0a    61 62 63 0d 0a 2a 32 0d    .a..$3..abc..*2.
  0a 24 33 0d 0a 67 65 74    0d 0a 24 31 0d 0a 61 0d    .$3..get..$1..a.
  0a                                                    .
##
T 127.0.0.1:6379 -> 127.0.0.1:51785 [AP] #9
  2b 4f 4b 0d 0a 24 33 0d    0a 61 62 63 0d 0a          +OK..$3..abc..

如果 pipeline 中有命令出错,并不会影响后面的命令继续执行。
下面的例子在一个 pipeline 中执行了 3 条命令:

set a abc
hgetall a
get a

其中第二条命令会返回错误,但是第三条命令仍返回了。

T 127.0.0.1:52641 -> 127.0.0.1:6379 [AP] #35
  2a 33 0d 0a 24 33 0d 0a    73 65 74 0d 0a 24 31 0d    *3..$3..set..$1.
  0a 61 0d 0a 24 33 0d 0a    61 62 63 0d 0a 2a 32 0d    .a..$3..abc..*2.
  0a 24 37 0d 0a 68 67 65    74 61 6c 6c 0d 0a 24 31    .$7..hgetall..$1
  0d 0a 61 0d 0a 2a 32 0d    0a 24 33 0d 0a 67 65 74    ..a..*2..$3..get
  0d 0a 24 31 0d 0a 61 0d    0a                         ..$1..a..
##
T 127.0.0.1:6379 -> 127.0.0.1:52641 [AP] #37
  2b 4f 4b 0d 0a 2d 57 52    4f 4e 47 54 59 50 45 20    +OK..-WRONGTYPE
  4f 70 65 72 61 74 69 6f    6e 20 61 67 61 69 6e 73    Operation agains
  74 20 61 20 6b 65 79 20    68 6f 6c 64 69 6e 67 20    t a key holding
  74 68 65 20 77 72 6f 6e    67 20 6b 69 6e 64 20 6f    the wrong kind o
  66 20 76 61 6c 75 65 0d    0a 24 33 0d 0a 61 62 63    f value..$3..abc
  0d 0a                                                 ..

🍊go代码演示pipeline性能提升

当使用 pipeline 时,性能会有所提升,示例如下所示。
例子中对比了没用 pipeline 以及不同 pipeline size 时,程序处理 10000条 Get 命令所用的时长。

// 性能提升的例子
package main

import (
	"context"
	"fmt"
	"time"

	"github.com/go-redis/redis/v8"
)

func main() {
	opts := &redis.ClusterOptions{
		Addrs: []string{"bytepower-server-redis-debug.qvy2lg.clustercfg.cnw1.cache.amazonaws.com.cn:6379"}}
	client := redis.NewClusterClient(opts)
	ctx := context.TODO()
	key := "a"

	commandCount := 10000

	// no pipeline
	startTime := time.Now()
	for i := 0; i < commandCount; i++ {
		client.Get(ctx, key)
	}
	duration := time.Since(startTime)
	fmt.Printf("no pipline tasks %s\n", duration)

	// different pipeline sizes
	pipelineSizes := []int{50, 200, 500, 1000}
	for _, pipelineSize := range pipelineSizes {
		startTime = time.Now()
		for i := 0; i < commandCount/pipelineSize; i++ {
			pipeline := client.Pipeline()
			for j := 0; j < pipelineSize; j++ {
				pipeline.Get(ctx, key)
			}
			pipeline.Exec(ctx)
		}
		duration = time.Since(startTime)
		fmt.Printf("pipeline with size %d takes %s\n", pipelineSize, duration)
	}
}

程序输出如下:

no pipline tasks 986.422169ms
pipeline with size 50 takes 40.275496ms
pipeline with size 200 takes 21.251794ms
pipeline with size 500 takes 15.70927ms
pipeline with size 1000 takes 16.133405ms

🍊pipeline性能提升的原因是什么?

1、网络延迟
假设 rtt (round-trip time) 是 t,那么 N 条命令,依次发送的话,网络所耗的时间是 t * N
如果 pipeline size 是 M,那么网络所消耗的时间是 t * (N/M)

client -> server (client 发送命令 1)
server -> client (server 响应命令 1)

client -> server (client 发送命令 2)
server -> client (server 响应命令 2)
...
client -> server (client 发送命令 1..M)
server -> client (server 响应命令 1..M)

client -> server (client 发送命令 M+1..2M)
server -> client (server 响应命令 M+1..2M)
....

2、系统调用
server 每次从 socket 上读写数据时,需要进行系统调用(read/write)陷入内核态,相比用户态的函数调用,系统调用代价较高,使用 pipeline 后,server 可以一次读/写多个命令的数据,减少了系统调用次数。

✅ RESP3

从 redis 6.0 开始,redis 服务器支持两种协议,RESP 和 RESP3。
RESP3 是 RESP 的一个超集,在 RESP 的基础上,RESP3 有一些改进。几处主要的改进包括:

  1. 除 array 外,添加了其它的复杂类型,包括 set(~ 开头) 和 map(% 开头)
  2. 添加了 新的 null,来替换两种 null ($-1\r\n 和 *-1\r\n)
  3. 添加了 bool 类型(#t\r\n#f\r\n) 和 double 类型(, 开头)

另外还有其它的一些改进,具体可以参考文档

连接建立时默认是 RESP,可以用 hello 命令切换模式至 RESP3。

127.0.0.1:6379> hello 3
1# "server" => "redis"
2# "version" => "6.0.9"
3# "proto" => (integer) 3
4# "id" => (integer) 4
5# "mode" => "standalone"
6# "role" => "master"
7# "modules" => (empty array)

对比 RESP 协议和 RESP3 协议如何表示 redis 的各种数据类型

🍊set 类型表示的变化

RESP

127.0.0.1:6379> smembers seta
1) "c"
2) "b"
3) "a"
4) "d"
T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #124601
  2a 32 0d 0a 24 38 0d 0a    73 6d 65 6d 62 65 72 73    *2..$8..smembers
  0d 0a 24 34 0d 0a 73 65    74 61 0d 0a                ..$4..seta..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #124603
  2a 34 0d 0a 24 31 0d 0a    63 0d 0a 24 31 0d 0a 62    *4..$1..c..$1..b
  0d 0a 24 31 0d 0a 61 0d    0a 24 31 0d 0a 64 0d 0a    ..$1..a..$1..d..

RESP3

127.0.0.1:6379> smembers seta
1~ "c"
2~ "b"
3~ "a"
4~ "d"

T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #124625
  2a 32 0d 0a 24 38 0d 0a    73 6d 65 6d 62 65 72 73    *2..$8..smembers
  0d 0a 24 34 0d 0a 73 65    74 61 0d 0a                ..$4..seta..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #124627
  7e 34 0d 0a 24 31 0d 0a    63 0d 0a 24 31 0d 0a 62    ~4..$1..c..$1..b
  0d 0a 24 31 0d 0a 61 0d    0a 24 31 0d 0a 64 0d 0a    ..$1..a..$1..d..

🍊zset 类型表示的变化

RESP:

127.0.0.1:6379> ZRANGE zseta 0 -1 withscores
1) "a"
2) "1.5"
3) "b"
4) "2"
5) "c"
6) "10"

T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #124605
  2a 35 0d 0a 24 36 0d 0a    5a 52 41 4e 47 45 0d 0a    *5..$6..ZRANGE..
  24 35 0d 0a 7a 73 65 74    61 0d 0a 24 31 0d 0a 30    $5..zseta..$1..0
  0d 0a 24 32 0d 0a 2d 31    0d 0a 24 31 30 0d 0a 77    ..$2..-1..$10..w
  69 74 68 73 63 6f 72 65    73 0d 0a                   ithscores..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #124607
  2a 36 0d 0a 24 31 0d 0a    61 0d 0a 24 33 0d 0a 31    *6..$1..a..$3..1
  2e 35 0d 0a 24 31 0d 0a    62 0d 0a 24 31 0d 0a 32    .5..$1..b..$1..2
  0d 0a 24 31 0d 0a 63 0d    0a 24 32 0d 0a 31 30 0d    ..$1..c..$2..10.
  0a                                                    .

RESP3

127.0.0.1:6379> ZRANGE zseta 0 -1 withscores
1) 1) "a"
   2) (double) 1.5
2) 1) "b"
   2) (double) 2
3) 1) "c"
   2) (double) 10


T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #124629
  2a 35 0d 0a 24 36 0d 0a    5a 52 41 4e 47 45 0d 0a    *5..$6..ZRANGE..
  24 35 0d 0a 7a 73 65 74    61 0d 0a 24 31 0d 0a 30    $5..zseta..$1..0
  0d 0a 24 32 0d 0a 2d 31    0d 0a 24 31 30 0d 0a 77    ..$2..-1..$10..w
  69 74 68 73 63 6f 72 65    73 0d 0a                   ithscores..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #124631
  2a 33 0d 0a 2a 32 0d 0a    24 31 0d 0a 61 0d 0a 2c    *3..*2..$1..a..,
  31 2e 35 0d 0a 2a 32 0d    0a 24 31 0d 0a 62 0d 0a    1.5..*2..$1..b..
  2c 32 0d 0a 2a 32 0d 0a    24 31 0d 0a 63 0d 0a 2c    ,2..*2..$1..c..,
  31 30 0d 0a                                           10..

🍊hash 类型表示的变化

RESP

127.0.0.1:6379> hgetall hasha
1) "field_1"
2) "value_1"
3) "field_2"
4) "value_2"

T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #124641
  2a 32 0d 0a 24 37 0d 0a    68 67 65 74 61 6c 6c 0d    *2..$7..hgetall.
  0a 24 35 0d 0a 68 61 73    68 61 0d 0a                .$5..hasha..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #124643
  2a 34 0d 0a 24 37 0d 0a    66 69 65 6c 64 5f 31 0d    *4..$7..field_1.
  0a 24 37 0d 0a 76 61 6c    75 65 5f 31 0d 0a 24 37    .$7..value_1..$7
  0d 0a 66 69 65 6c 64 5f    32 0d 0a 24 37 0d 0a 76    ..field_2..$7..v
  61 6c 75 65 5f 32 0d 0a                               alue_2..

RESP3

127.0.0.1:6379> hgetall hasha
1# "field_1" => "value_1"
2# "field_2" => "value_2"

T 127.0.0.1:49862 -> 127.0.0.1:6379 [AP] #124633
  2a 32 0d 0a 24 37 0d 0a    68 67 65 74 61 6c 6c 0d    *2..$7..hgetall.
  0a 24 35 0d 0a 68 61 73    68 61 0d 0a                .$5..hasha..
##
T 127.0.0.1:6379 -> 127.0.0.1:49862 [AP] #124635
  25 32 0d 0a 24 37 0d 0a    66 69 65 6c 64 5f 31 0d    %2..$7..field_1.
  0a 24 37 0d 0a 76 61 6c    75 65 5f 31 0d 0a 24 37    .$7..value_1..$7
  0d 0a 66 69 65 6c 64 5f    32 0d 0a 24 37 0d 0a 76    ..field_2..$7..v
  61 6c 75 65 5f 32 0d 0a                               alue_2..

多添加了几种类型,更方便 redis 客户端库进行类型转换。

✅ 总结

分享了 redis 协议 RESP 的协议格式,讨论了 pipeline 以及新版的协议 RESP3。

✅ 参考

  1. RESP 协议官方文档
  2. Pipeline document
  3. RESP3 协议文档
posted on 2023-08-09 13:54  江湖乄夜雨  阅读(47)  评论(0编辑  收藏  举报