Redis是单进程单线程的。应用系统和Redis通过Redis协议(RESP)进行交互。

请求响应模式

Redis协议位于TCP层之上,即客户端和Redis实例保持双工的连接


串行的请求响应模式(ping-pong)

串行化是最简单模式,客户端与服务器端建立长连接

连接通过心跳机制检测(ping-pong) ack应答

客户端发送请求,服务端响应,客户端收到响应后,再发起第二个请求,服务器端再响应。


telnet和redis-cli 发出的命令 都属于该种模式

特点:

有问有答

耗时在网络传输命令

性能较低

双工的请求响应模式(pipeline)

批量请求,批量响应

请求响应交叉进行,不会混淆(TCP双工)


pipeline的作用是将一批命令进行打包,然后发送给服务器,服务器执行完按顺序打包返回。

通过pipeline,一次pipeline(n条命令)=一次网络时间 + n次命令时间

1
2
3
4
5
6
7
8
Jedis redis = new Jedis("192.168.1.111", 6379);
redis.auth("12345678");//授权密码 对应redis.conf的requirepass密码
Pipeline pipe = jedis.pipelined();
for (int i = 0; i <50000; i++) {
pipe.set("key_"+String.valueOf(i),String.valueOf(i));
}
//将封装后的PIPE一次性发给redis
pipe.sync();

原子化的批量请求响应模式(事务)

Redis可以利用事务机制批量执行命令。后面会详细讲解。

发布订阅模式(pub/sub)

发布订阅模式是:一个客户端触发,多个客户端被动接收,通过服务器中转。后面会详细讲解。

脚本化的批量执行(lua)

客户端向服务器端提交一个lua脚本,服务器端执行该脚本。后面会详细讲解。

请求数据格式

Redis客户端与服务器交互采用序列化协议(RESP)。

请求以字符串数组的形式来表示要执行命令的参数

Redis使用命令特有(command-specific)数据类型作为回复。

Redis通信协议的主要特点有:

客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。

客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。

在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。

简单,高效,易读。

内联格式

可以使用telnet给Redis发送命令,首字符为Redis命令名的字符,格式为 str1 str2 str3…

1
2
3
4
5
6
7
8
[root@localhost bin]# telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
ping
+PONG
exists name
:1

规范格式(redis-cli) RESP

  1. 间隔符号,在Linux下是\r\n,在Windows下是\n

  2. 简单字符串 Simple Strings, 以 "+"加号 开头

  3. 错误 Errors, 以"-"减号 开头

  4. 整数型 Integer, 以 “:” 冒号开头

  5. 大字符串类型 Bulk Strings, 以 "$"美元符号开头,长度限制512M

  6. 数组类型 Arrays,以 "*"星号开头

用SET命令来举例说明RESP协议的格式。

1
2
redis> SET mykey Hello
"OK"

实际发送的请求数据:

1
2
3
4
5
6
7
8
9
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$5\r\nHello\r\n

*3
$3
SET
$5
mykey
$5
Hello

实际收到的响应数据:

1
+OK\r\n

命令处理流程

整个流程包括:服务器启动监听、接收命令请求并解析、执行命令请求、返回命令回复等。


Server启动时监听socket

启动调用 initServer方法

创建eventLoop(事件机制)

注册时间事件处理器

注册文件事件(socket)处理器

监听 socket 建立连接

建立Client

redis-cli建立socket

redis-server为每个连接(socket)创建一个 Client 对象

创建文件事件监听socket

指定事件处理函数

读取socket数据到输入缓冲区

从client中读取客户端的查询缓冲区内容

解析获取命令

将输入缓冲区中的数据解析成对应的命令

判断是单条命令还是多条命令并调用相应的解析器解析

执行命令

解析成功后调用processCommand 方法执行命令,如下图:


大致分三个部分:

调用 lookupCommand 方法获得对应的 redisCommand

检测当前 Redis 是否可以执行该命令

调用 call 方法真正执行命令

协议响应格式

状态回复

对于状态,回复的第一个字节是“+”

1
"+OK"

错误回复

对于错误,回复的第一个字节是“ - ”

1
2
3
-ERR unknown command 'foobar'

-WRONGTYPE Operation against a key holding the wrong kind of value

整数回复

对于整数,回复的第一个字节是“:”

1
":6"

批量回复

对于批量字符串,回复的第一个字节是“$”

1
"$6 foobar"

多条批量回复

对于多条批量回复(数组),回复的第一个字节是“*”

1
"*3"

协议解析及处理

包括协议解析、调用命令、返回结果。

协议解析

用户在Redis客户端键入命令后,Redis-cli会把命令转化为RESP协议格式,然后发送给服务器。服务器再对协议进行解析,分为三个步骤

    1. 解析命令请求参数数量

    命令请求参数数量的协议格式为"*N\r\n" ,其中N就是数量,比如

    1
    127.0.0.1:6379> set name:1 zhaoyun

    我们打开aof文件可以看到协议内容

    1
    2
    3
    4
    5
    6
    7
    *3(/r/n)
    $3(/r/n)
    set(/r/n)
    $7(/r/n)
    name:10(/r/n)
    $7(/r/n)
    zhaoyun(/r/n)

    首字符必须是“*”,使用"\r"定位到行尾,之间的数就是参数数量了。

    1. 循环解析请求参数

    首字符必须是"",使用"/r"定位到行尾,之间的数是参数的长度,从/n后到下一个"",使用"/r"定位到行尾,之间的数是参数的长度,从/n后到下一个"“之间就是参数的值了,循环解析直到没有”$"。

协议执行

协议的执行包括命令的调用和返回结果

判断参数个数和取出的参数是否一致

RedisServer解析完命令后,会调用函数processCommand处理该命令请求

quit校验,如果是“quit”命令,直接返回并关闭客户端

命令语法校验,执行lookupCommand,查找命令(set),如果不存在则返回:“unknown command”错误。

参数数目校验,参数数目和解析出来的参数个数要匹配,如果不匹配则返回:“wrong number of arguments”错误。

此外还有权限校验,最大内存校验,集群校验,持久化校验等等。

校验成功后,会调用call函数执行命令,并记录命令执行时间和调用次数

如果执行命令时间过长还要记录慢查询日志

执行命令后返回结果的类型不同则协议格式也不同,分为5类:状态回复、错误回复、整数回复、批量回复、多条批量回复。