5-1-3 通讯协议及事件处理机制

学习目标

知道Redis的请求响应模式
理解请求数据格式(RESP)
描述命令处理流程
知道Redis的响应格式
掌握4种IO多路复用模式(epoll)
理解aeEventLoop

一、通信协议

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

1.1 请求响应模式

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

在这里插入图片描述
串行的请求响应模式(ping-pong)
串行化是最简单模式,客户端与服务器端建立长连接
连接通过心跳机制检测(ping-pong) ack应答
客户端发送请求,服务端响应,客户端收到响应后,再发起第二个请求,服务器端再响应。

在这里插入图片描述
telnet和redis-cli 发出的命令 都属于该种模式
特点:
有问有答
耗时在网络传输命令
性能较低

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

批量请求,批量响应
请求响应交叉进行,不会混淆(TCP双工)
在这里插入图片描述
pipeline的作用是将一批命令进行打包,然后发送给服务器,服务器执行完按顺序打包返回。
通过pipeline,一次pipeline(n条命令)=一次网络时间 + n次命令时间

通过Jedis可以很方便的使用pipeline

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脚本,服务器端执行该脚本。后面会详细讲解。

1.2 请求数据格式

Redis客户端与服务器交互采用序列化协议(RESP)。
请求以字符串数组的形式来表示要执行命令的参数
Redis使用命令特有(command-specific)数据类型作为回复。
Redis通信协议的主要特点有:
客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。
客户端和服务器发送的命令或数据一律以 \r\n (CRLF)结尾。
在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。
简单,高效,易读。

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

[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)

1、间隔符号,在Linux下是\r\n,在Windows下是\n
2、简单字符串 Simple Strings, 以 "+“加号 开头
3、错误 Errors, 以”-"减号 开头
4、整数型 Integer, 以 “:” 冒号开头
5、大字符串类型 Bulk Strings, 以 "$"美元符号开头,长度限制512M
6、数组类型 Arrays,以 "*"星号开头
用SET命令来举例说明RESP协议的格式。

redis> SET mykey Hello 
"OK"

实际发送的请求数据:

*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


实际收到的响应数据:

+OK\r\n

1.3 命令处理流程

整个流程包括:服务器启动监听、接收命令请求并解析、执行命令请求、返回命令回复等。
在这里插入图片描述
Server启动时监听socket
启动调用 initServer方法:
创建eventLoop(事件机制)
注册时间事件处理器
注册文件事件(socket)处理器
监听 socket 建立连接

建立Client
redis-cli建立socket
redis-server为每个连接(socket)创建一个 Client 对象
创建文件事件监听socket
指定事件处理函数

读取socket数据到输入缓冲区
从client中读取客户端的查询缓冲区内容。

解析获取命令
将输入缓冲区中的数据解析成对应的命令
判断是单条命令还是多条命令并调用相应的解析器解析

执行命令
解析成功后调用processCommand 方法执行命令,如下图:
在这里插入图片描述
大致分三个部分:

调用 lookupCommand 方法获得对应的 redisCommand
检测当前 Redis 是否可以执行该命令
调用 call 方法真正执行命令

1.4 协议响应格式

状态回复

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

"+OK"

错误回复

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

1. -ERR unknown command 'foobar' 
2. 2. -WRONGTYPE Operation against a key holding the wrong kind of value

整数回复

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

":6"

批量回复

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

"$6 foobar"

多条批量回复

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


"*3"
1.5 协议解析及处理

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

协议解析

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

  1. 解析命令请求参数数量
    命令请求参数数量的协议格式为"*N\r\n" ,其中N就是数量,比如
127.0.0.1:6379> set name:1 zhaoyun

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

*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后到下一个" "使"/r"/n"“之间就是参数的值了
    循环解析直到没有”$"。

协议执行
协议的执行包括命令的调用和返回结果
判断参数个数和取出的参数是否一致
RedisServer解析完命令后,会调用函数processCommand处理该命令请求

quit校验,如果是“quit”命令,直接返回并关闭客户端
命令语法校验,执行lookupCommand,查找命令(set),如果不存在则返回:“unknowncommand”错误。
参数数目校验,参数数目和解析出来的参数个数要匹配,如果不匹配则返回:“wrong number of arguments”错误。
此外还有权限校验,最大内存校验,集群校验,持久化校验等等。

校验成功后,会调用call函数执行命令,并记录命令执行时间和调用次数
如果执行命令时间过长还要记录慢查询日志
执行命令后返回结果的类型不同则协议格式也不同,分为5类:状态回复、错误回复、整数回复、批量回复、多条批量回复。

二、事件处理机制

Redis服务器是典型的事件驱动系统。
MVC : java 上层调下层
事件驱动: js
Redis将事件分为两大类:文件事件和时间事件。

2.1 文件事件

文件事件即Socket的读写事件,也就是IO事件。
客户端的连接、命令请求、数据回复、连接断开

socket
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据。

Reactor
Redis事件处理机制采用单线程的Reactor模式,属于I/O多路复用的一种常见模式。
IO多路复用( I/O multiplexing )指的通过单个线程管理多个Socket。
Reactor pattern(反应器设计模式)是一种为处理并发服务请求,并将请求提交到 一个或者多个服务处理
程序的事件设计模式。
Reactor模式是事件驱动的
有一个或多个并发输入源(文件事件)
有一个Service Handler
有多个Request Handlers
这个Service Handler会同步的将输入的请求(Event)多路复用的分发给相应的Request Handler

在这里插入图片描述
在这里插入图片描述

Handle:I/O操作的基本文件句柄,在linux下就是fd(文件描述符)

Synchronous Event Demultiplexer :同步事件分离器,阻塞等待Handles中的事件发生。(系统)

Reactor: 事件分派器,负责事件的注册,删除以及对所有注册到事件分派器的事件进行监控, 当事件发生时会调用Event Handler接口来处理事件。

Event Handler: 事件处理器接口,这里需要Concrete Event Handler来实现该接口

Concrete Event Handler:真实的事件处理器,通常都是绑定了一个handle,实现对可读事件 进行读取或对可写事件进行写入的操作。

在这里插入图片描述
主程序向事件分派器(Reactor)注册要监听的事件
Reactor调用OS提供的事件处理分离器,监听事件(wait)
当有事件产生时,Reactor将事件派给相应的处理器来处理 handle_event()

2.2.1 四种IO多路复用模型与选择

select,poll,epoll、kqueue都是IO多路复用的机制。

I/O多路复用就是通过一种机制,一个进程可以监视多个描述符(socket),一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。

select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分3类,分别是:

writefds
readfds
exceptfds

调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fd列表,来找到就绪的描述符。

优点
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。
windows linux …

缺点
单个进程打开的文件描述是有一定限制的,它由FD_SETSIZE设置,默认值是1024,采用数组存储

另外在检查数组中是否有文件描述需要读写时,采用的是线性扫描的方法,即不管这些socket是不是活跃的,都轮询一遍,所以效率比较低

poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout); 
struct pollfd { 
	int fd; //文件描述符 
	short events; //要监视的事件 
	short revents; //实际发生的事件 
};

poll使用一个 pollfd的指针实现,pollfd结构包含了要监视的event和发生的event,不再使用select“参 数-值”传递的方式。

优点:
采样链表的形式存储,它监听的描述符数量没有限制,可以超过select默认限制的1024大小
缺点:
另外在检查链表中是否有文件描述需要读写时,采用的是线性扫描的方法,即不管这些socket是不是活跃的,都轮询一遍,所以效率比较低。

epoll
epoll是在linux2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次

int epoll_create(int size)

创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

poll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先
注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待内核返回的可读写事件,最多返回maxevents个事件。

优点:
epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,举个例子,在1GB内存的机器上大约是10万左 右

效率提升, epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境 中, epoll 的效率就会远远高于 select 和 poll 。

epoll使用了共享内存,不用做内存拷贝

kqueue
kqueue 是 unix 下的一个IO多路复用库。最初是2000年Jonathan Lemon在FreeBSD系统上开发的一个高性能的事件通知接口。注册一批socket描述符到 kqueue 以后,当其中的描述符状态发生变化时,kqueue 将一次性通知应用程序哪些描述符可读、可写或出错了


struct kevent { 
	uintptr_t ident; //是事件唯一的 key,在 socket() 使用中,它是 socket 的 fd 句柄 
	int16_t filter; //是事件的类型(EVFILT_READ socket 可读事件 EVFILT_WRITE socket 可 写事件) 
	uint16_t flags; //操作方式 
	uint32_t fflags; // 
	intptr_t data; //数据长度 
	void *udata; //数据 
};

优点:
能处理大量数据,性能较高

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值