RESP 协议
Redis 基于 RESP (Redis Serialization Protocal)协议来完成客户端和服务端通讯的。RESP 本质是一种文本协议,实现简单、易于解析。如下表所示:
类型 | 协议描述 | 实例 |
---|---|---|
网络层 | 客户端和服务端通过 tcp/流式套接字来进行通讯,为了 防止粘包 因此命令或数据均以 rn (CRLF) 结尾 | okrn |
请求 | *<参数数量> CR LF<参数字节数量 > CR LF<参数的数据> CR LF<参数 N 的字节数量 >CR LF<参数 N 的数据> CR LF | *2rn3rngetrn$13rnusername:1234rn。见 callSendCommond -> redis AppendConnadnArgv -> redisFromatCommandArgv |
简单字符串回复 | 第一个字节 | okrn |
错误回复 | 第一个字节- | -ERR unknown command 'sa' rn |
整数回复 | 第一个字节: | :0rn |
批量回复 | 第一个字节$ | $6rnfoobarrn 空回复 $-1 |
多条批量回复 | 第一个字节* | 5rn:1rn:2rn:3rn:4rn$6rnfoobarrn, 空回复 0rn |
如果客户端和服务端在一台机器上。那么会对通讯协议进行优化,直接走本地回环
我们可以通过 tcpdump 命令来抓取客户端和服务端请求、响应的数据包, 命令如下:
代码语言:javascript复制# linux
tcpdump -i lo part 6379 -Ann
# mac
tcpdump -i lo0 port 6379 -Ann
我们以一条 `set msg100 1` 这条命令测试一下 ( 我本机是 mac 环境):
代码语言:javascript复制# 客户端 A
127.0.0.1:6379> set msg100 1
OK
服务端抓包结果如下所示:
代码语言:javascript复制➜ ~ sudo tcpdump -i lo0 port 6379 -Ann
Password:
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on lo0, link-type NULL (BSD loopback), capture size 262144 bytes
21:52:53.447885 IP 127.0.0.1.51645 > 127.0.0.1.6379: Flags [P.], seq 1564111974:1564112006, ack 169183468, win 6272, options [nop,nop,TS val 774447713 ecr 772455554], length 32: RESP "set" "msg100" "1"
E..T..@.@...............]:tf
........H.....
.)"a.
..*3
$3
set
$6
msg100
$1
1
21:52:53.447912 IP 127.0.0.1.6379 > 127.0.0.1.51645: Flags [.], ack 32, win 6376, options [nop,nop,TS val 774447713 ecr 774447713], length 0
E..4..@.@...............
...]:t......(.....
.)"a.)"a
21:52:53.528935 IP 127.0.0.1.6379 > 127.0.0.1.51645: Flags [P.], seq 1:6, ack 32, win 6376, options [nop,nop,TS val 774447793 ecr 774447713], length 5: RESP "OK"
E..9..@.@...............
...]:t......-.....
.)"..)"a OK
21:52:53.528966 IP 127.0.0.1.51645 > 127.0.0.1.6379: Flags [.], ack 6, win 6272, options [nop,nop,TS val 774447793 ecr 774447793], length 0
E..4..@.@...............]:t.
........(.....
.)"..)".
redis-cli 客户端效果:
客户端是对显示结果做了转化,在 redis-cli.c 文件中下面是它的部分源码
代码语言:javascript复制static sds cliFormatReplyTTY(redisReply *r, char *prefix) {
sds out = sdsempty();
switch (r->type) {
case REDIS_REPLY_ERROR:
out = sdscatprintf(out,"(error) %sn", r->str);
break;
case REDIS_REPLY_STATUS:
out = sdscat(out,r->str);
out = sdscat(out,"n");
break;
case REDIS_REPLY_INTEGER:
out = sdscatprintf(out,"(integer) %lldn",r->integer);
break;
case REDIS_REPLY_DOUBLE:
out = sdscatprintf(out,"(double) %sn",r->str);
break;
case REDIS_REPLY_STRING:
case REDIS_REPLY_VERB:
/* If you are producing output for the standard output we want
* a more interesting output with quoted characters and so forth,
* unless it's a verbatim string type. */
if (r->type == REDIS_REPLY_STRING) {
out = sdscatrepr(out,r->str,r->len);
out = sdscat(out,"n");
} else {
out = sdscatlen(out,r->str,r->len);
out = sdscat(out,"n");
}
break;
case REDIS_REPLY_NIL:
out = sdscat(out,"(nil)n");
break;
case REDIS_REPLY_BOOL:
out = sdscat(out,r->integer ? "(true)n" : "(false)n");
break;
case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP:
case REDIS_REPLY_SET:
case REDIS_REPLY_PUSH:
if (r->elements == 0) {
if (r->type == REDIS_REPLY_ARRAY)
out = sdscat(out,"(empty array)n");
else if (r->type == REDIS_REPLY_MAP)
out = sdscat(out,"(empty hash)n");
else if (r->type == REDIS_REPLY_SET)
out = sdscat(out,"(empty set)n");
else if (r->type == REDIS_REPLY_PUSH)
out = sdscat(out,"(empty push)n");
else
out = sdscat(out,"(empty aggregate type)n");
} else {
unsigned int i, idxlen = 0;
char _prefixlen[16];
char _prefixfmt[16];
sds _prefix;
sds tmp;
/* Calculate chars needed to represent the largest index */
i = r->elements;
if (r->type == REDIS_REPLY_MAP) i /= 2;
do {
idxlen ;
i /= 10;
} while(i);
/* Prefix for nested multi bulks should grow with idxlen 2 spaces */
memset(_prefixlen,' ',idxlen 2);
_prefixlen[idxlen 2] = '