redis协议介绍
🍎 Redis协议介绍
介绍 redis 服务器和 redis 客户端间的通信协议 (communication protocol)。
主要内容包括:
- Redis 介绍
- RESP, 5 种数据类型
- pipeline,协议如何支持
- 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 abc
和 get 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 有一些改进。几处主要的改进包括:
- 除 array 外,添加了其它的复杂类型,包括 set(
~
开头) 和 map(%
开头) - 添加了 新的 null,来替换两种 null ($-1\r\n 和 *-1\r\n)
- 添加了 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。
✅ 参考
- Redis 协议介绍
- 1. redis 介绍
- \2. redis 协议介绍
- 3. pipeline
- 4. RESP3
- 5. 总结
- 6. 参考