Redis中单机数据库的实现 (6)

协议本身是支持二进制数据传输的, 只需要将二进制数据作为长字符串传输就可以. 所以这并不能算是一个严格意义上的字符协议. 但如果传输的数据多数是可阅读的数据的话, 协议本身的可读性是很强的

6.3 请求体

客户端向服务端发送请求时, 语法格式基本如下:

6.3.1 单命令请求

单命令请求体, 就是一个数组, 数组中将命令与参数各作为数组元素存储, 如下是一个 LLEN mylist 的请求

*2\r\n$4\r\nLLEN\r\n$6\r\nmylist\r\n

这个数组容量为2, 两个元素均为长字符串, 内容分别是LLEN与mylist

6.3.2 多命令请求(pipelining request)

多命令请求体, 直接把多个单命令请求体拼起来就行了, 每个命令是一个数组, 即请求体中有多个数组

6.3.3 内联命令

如果按协议向服务端发送请求, 可以看到, 请求体的第一字节始终要为*. 为了方便一些弱客户端与服务器交互, REDIS支持所谓的内联命令. 内联命令仅是请求体的另一种表达方式.

内联命令的使用场合是:

手头没有redis-cli

手写请求体太麻烦

只想做一些简单操作

这种情况下, 直接把适用于redis-cli的字符命令, 发送给服务端就行了. 内联命令支持通过telnet发送.

6.4 回应体

Redis服务端接收到任何一个有效的命令时, 都会给服务端写回应. 请求-回应的最小单位是命令, 所以对于多命令请求, 服务端会把这多个请求的对应的回应, 按顺序返回给客户端.

回应体依然遵守通信协议规约.

6.5 手撸协议示例

单命令请求

[root@localhost ~]# echo -e '*2\r\n$4\r\nINCR\r\n$7\r\ncounter\r\n' | nc localhost 6379 :2 [root@localhost ~]#

多命令请求

[root@localhost ~]# echo -e '*1\r\n$4\r\nPING\r\n*2\r\n$4\r\nINCR\r\n$7\r\ncounter\r\n*2\r\n$4\r\nINCR\r\n$7\r\ncounter\r\n*2\r\n$4\r\nINCR\r\n$7\r\ncounter\r\n' | nc localhost 6379 +PONG :4 :5 :6 [root@localhost ~]#

内联命令请求

[root@localhost ~]# echo -e 'PING' | nc localhost 6379 +PONG [root@localhost ~]# echo -e 'INCR counter' | nc localhost 6379 :7 [root@localhost ~]# echo -e 'INCR counter' | nc localhost 6379 :8 [root@localhost ~]#

通过telnet发送内联命令请求

[root@localhost ~]# telnet localhost 6379 Trying ::1... Connected to localhost. Escape character is '^]'. PING +PONG INCR counter :9 INCR counter :10 INCR counter :11 ^] telnet> quit Connection closed. [root@localhost ~]# 6.6 协议总结及注意事项

优点:

可读性很高, 也可以容纳二进制数据(长字符串类型)

协议简单, 解析容易, 解析性能也很高. 得益于各种数据类型头字节的设计, 以及长字符串类型自带长度字段, 所以解析起来基本飞快, 就算是二流程序员写出来的协议解析器, 跑的也飞快

支持多命令请求, 特定场合下充分发挥这个特性, 可以提高命令的吞吐量

支持内联命令请求. 这种请求虽然限制颇多(不支持二进制数据类型, 不支持\r\n这种数据, 不支持多命令请求), 但对于运维人员来说, 可读性更高, 对阅读更友好

注意事项:

非内联命令请求, 请求体中的每个数据都是长字符串类型. 即便是在表达数值, 也需要写成长字符串形式(数值的ASCII表示). 这给服务端解析协议, 以及区分请求命令是否为内联命令带来了很大的便利

短字符串, 数值, 错误信息这三种数据类型, 仅出现在回应体中

7. 服务端处理请求及写回应的过程

当客户端与服务端建立连接后, 双方就要进行协议交互. 简单来说就是以下几步:

客户端发送请求

服务端解析请求, 进行逻辑处理

服务端写回包

显然, 从三层(tcp或unix)上接收到的数据, 首先要解析成协议数据, 再进一步解析成命令, 服务端才能进行处理. 三层上的协议是流式协议, 一个请求体可能要分多次才能完整接收, 这必然要涉及到一个接收缓冲机制. 先来看一看struct client结构中与接收缓冲机制相关的一些字段:

type struct client { uint64_t id; /* Client incremental unique ID. */ // 客户端ID, 自增, 唯一 int fd; /* Client socket. */ // 客户端数据连接的底层文件描述符 redisDb *db; /* Pointer to currently SELECTed DB. */ // 指向客户端当前选定的DB robj *name; /* As set by CLIENT SETNAME. */ // 客户端的名称. 由命令 CLIENT SETNAME 设定 sds querybuf; /* Buffer we use to accumulate client queries. */ // 请求体接收缓冲区 sds pending_querybuf; /* If this is a master, this buffer represents the // cluster特性相关的一个缓冲区, 暂忽视 yet not applied replication stream that we are receiving from the master. */ size_t querybuf_peak; /* Recent (100ms or more) peak of querybuf size. */ // 近期(100ms或者更长时间中)接收缓冲区大小的峰值 int argc; /* Num of arguments of current command. */ // 当前处理的命令的参数个数 robj **argv; /* Arguments of current command. */ // 当前处理的命令的参数列表 struct redisCommand *cmd, *lastcmd; /* Last command executed. */ // 当前正在处理的命令字, 以及上一次最后执行的命令字 int reqtype; /* Request protocol type: PROTO_REQ_* */ // 区分客户端是否以内联形式上载请求 int multibulklen; /* Number of multi bulk arguments left to read. */ // 命令字剩余要读取的参数个数 long bulklen; /* Length of bulk argument in multi bulk request. */ // 当前读取到的参数的长度 } client; 7.1 通过事件处理器, 在传输层获取客户端发送的数据

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsydf.html