Redis通讯协议及事件处理机制
2021-08-23 17:53:52 0 举报
AI智能生成
Redis通讯协议及事件处理机制
作者其他创作
大纲/内容
Redis通讯协议及事件处理机制
请求响应模式
通讯协议
Redis是单进程单线程的
应用系统和Redis通过Redis协议(RESP)进行交互
主要特点
客户端和服务端通过TCP连接来进行数据交互,服务端默认端口6379
客户端和服务器发送的命令或数据一律以\r\n结尾
本协议中,所有发送至Redis服务器的参数都是二进制安全的
Redis协议位于TCP层之上,即客户端和Redis实例保持双工的连接
类型
串行(ping-pong)
最简单的模式,客户端和服务器端建立长连接
连接通过心跳机制检测,ack应答
客户端发送请求,服务端响应,客户端收到响应后,再发起另一个请求,服务端再响应
redis-cli发出的命令,属于该模式
特点
有问有答
耗时在网络传输
性能较低
双工(pipeline)
批量请求,批量响应,请求相应交叉进行,不会混淆(TCP双工)
pipeline作用是将一批命令进行打包,然后发送到服务器,服务器执行完按顺序打包返回
通过pipeline,一次pipeline(n条命令)= 一次网络时间 + n次命令时间
原子化的批量(事务)
Redis可以利用事务机制批量执行命令
发布订阅(pub/sub)
一个客户端触发,多个客户端被动接收,通过服务器中转
脚本化的批量执行(Lua)
客户端想服务器端提交一个Lua脚本,服务器端执行该脚本
请求数据格式
Redis客户端与服务器交互采用序列化协议(RESP)
请求以字符串数组的形式来表示要执行命令的参数
Redis使用命令特有数据类型作为回复
类型
内联格式
可以使用telnet给Redis发送命令,首字符为Redis命令名的字符
规范格式(redis-cli)
1. 间隔符号,在Linux下是\r\n,在windows下是\n
2. 简单字符串Simple Strings,以“+”开头
3. 错误Errors,以“-”开头
4. 整数型Integar,以“:”开头
5. 大字符串类型Bulk Strings,以“$”开头,长度限制521M
6. 数组类型Arrays,以“*”开头
命令处理流程
流程
Server启动时监听socket
启动调用initServer()
创建eventLoop(事件机制)
注册时间事件处理器
注册文件事件(socket)处理器
监听socket建立连接
建立client
redis-cli建立socket
redis-server为每个连接(socket)创建一个Client对象
创建文件事件监听socket
指定事件处理函数
读取socket数据到输入缓冲区
从Client中读取客户端的查询缓冲区内容
解析获取命令
将输入缓冲区中的数据解析成对应的命令
判断是单条命令还是多条命令并调用相应的解析器解析
执行命令
解析成功后调用processCommand()执行命令
调用lookupCommand()获取对应的redisCommand
检测当前Redis是否可以执行该命令
调用call()真正执行命令
协议解析及处理
协议解析
用户在Redis客户端键入命令后,Redis-cli会把命令转化为RESP协议格式,然后发送给服务器,服务器再对协议进行解析
解析步骤
1. 解析命令请求参数数量
命令请求参数数量的协议格式为”*N\r\n“,N就是数量
2. 循环解析请求参数
首字符必须是”$“,使用”\r“定位到行尾,之间就是参数
循环解析直到没有”$“
调用命令
判断参数个数和取出参数是否一致
RedisServer解析完命令后,会调用processCommand()处理该命令请求
quit校验,如果是”quit“命令,直接返回并关闭客户端
命令语法校验,执行lookupCommand,查找命令,如果不存在则返回”unknown command“错误
参数数据校验,参数数目和解析出来的参数个数要匹配,不匹配则返回”wrong number of arguments“错误
此外还有权限校验,最大内存校验,集群校验,持久化校验等
校验成功后,会调用call()执行命令,并记录命令执行时间和调用次数
如果执行命令过长,要记录慢查询日志
返回结果
返回结果的类型不同,则协议格式也不同
协议响应格式
状态回复
回复的第一个字节是“+”
+OK
错误回复
回复的第一个字节是”-“
-ERR unknown command 'foobar'
整数回复
回复的第一个字节是”:“
批量回复
回复的第一个字节是"$"
多条批量回复
回复的第一个字节是”*“
事件处理机制
Redis服务器是典型的事件驱动系统,将事件分为两大类:文件事件和时间事件
文件事件
即Socket的读写事件,也即IO事件
客户端的连接、命令请求、数据回复、连接断开
Socket
套接字是一个抽象层,应用程序可以通过它发送或接收数据
Reactor
Reactor模式
是一种为处理并发服务请求,并将请求提交到一个或多个服务处理程序的事件设计模式
它是事件驱动的,有一个或多个并发输入源,有一个Service_Handler,多个Request_Handlers
Service_Handler会同步的将输入的请求多路复用的分发给相应的Request_Handler
Redis事件处理机制采用单线程的Reactor模式,属于IO多路复用的一种常见模式
IO多路复用指通过单个线程管理多个Socket
Redis中的Reactor
事件分派器,负责事件的注册,删除以及对所有注册到事件分派器的事件进行监控,当事件发生时会调用Event_Handler接口来处理事件
主程序向事件分派器注册要监听的事件,Reactor调用OS提供的事件处理分离器,监听事件(wait),当有事件产生时,Reactor将事件分派给相应的处理器来处理handle_event()
多路复用模型
IO多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作
select
select函数监视的文件描述符有
writefds
readfds
exceptfds
调用后select函数会阻塞,直到有描述符就绪(有数据可读,可写或有except),或超时,函数返回。select函数返回后,可以通过遍历fd列表,来找到就绪的描述符
优点
目前在几乎所有平台上支持
缺点
单个进程打开的文件描述有一定限制,它由FD_SETSIZE设置,默认1024,采用数组存储
在检查数组中是否有文件描述需要读写时,采用的是线性扫描的方法,即不管这些socket是否活跃,都轮询一遍,所以效率比较低
poll
使用一个pollfd指针实现,pollfd结构包含了要建使的event和发生的event,不再使用select”参数-值“传递的方式
优点
采样链表的形式存储,它监听的描述符数量没有限制,可以超过select默认的1024
缺点
在检查链表中是否有文件描述需要读写时,采用的是线性扫描的方法,即不管这些socket是否活跃,都轮询一遍,效率比较低
epoll
在linux2.6内核中提出,是select和poll的增强版本,相对于它们,epoll更加灵活,没有描述符限制
epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次
优点
epoll没有最大并发连接的限制,上限是最大可以打开文件的数目
最大优点是它只管活跃的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率会远高于select和poll
使用了共享内粗,不用做内存拷贝
kqueue
unix下的一个IO多路复用库。注册一批socket描述符到kqueue以后,当其中的描述符状态发生变化时,kqueue将一次性通知应用程序哪些描述符可读、可写或出错了
优点
能处理大量数据,性能较高
事件分派与处理
Redis中,对文件事件的处理采用Reactor模型,epoll的实现方式
Redis在主循环中统一处理文件事件和时间事件,信号事件则由专门的handler来处理
事件处理器
连接处理函数acceptTCPHandler
当客户端向Redis建立socket时,aeEventLoop会调用acceptTcpHander(),服务器会为每个连接创建一个Client对象,并创建相应文件事件来监听socket的可读事件,并指定事件处理函数
请求处理函数readQueryFromClient
当客户端通过socket发送来数据后,Redis会调用readQueryFromClient(),它会调用read()从socket中读取数据到输入缓冲区中,然后判断其大小是否大于系统设置的client_max_querybuf_len,如果大于,则向Redis返回错误信息,并关闭client
命令回复处理器sendReplyToClient
它是Redis的命令回复处理器,负责将服务器执行命令后得到的命令回复通过套接字返回给客户端
作用
1. 将outbuf内容写入到socket描述符并传输到客户端
2. aeDeleteFileEvent用于删除文件写事件
时间事件
介绍
时间事件分为定时事件和周期事件
一个时间事件主要由三个属性组成
全局唯一id
when(毫秒时间戳,记录了时间事件的到达时间)
timeProc(时间事件处理器,当时间到达时,Redis就会调用相应的处理器来处理事件)
serverCron
时间事件最主要的应用是在Redis服务器需要对自身的资源与配置进行定期的调整,从而确保服务器的长久运行,这些操作有redis.c中的serverCron()实现
主要进行以下操作
1. 更新Redis服务器各类统计信息,包括时间、内存占用、数据库占用等情况
2. 清理数据库中的过期键值对
3. 关闭和清理连接失败的客户端
4. 尝试进行aof和rdb持久化操作
5. 如果服务器是主服务器,会定期将数据向从服务器做同步操作
6. 如果处于集群模式,对集群定期进行同步与连接测试操作
Redis服务器开启后,会周期性执行此函数,直到Redis服务器关闭
默认每秒执行10次,可在redis配置文件的hz选项调整执行周期
server.hz
serverCron每秒执行次数
run_with_period
定时任务执行都是在10毫秒基础上定时处理自己的任务,即调用run_with_period来确定自己是否需要执行
假如有些任务需要每500ms执行一次,就可以在serverCron中用run_with_period(500)把每500ms需要执行一次的工作控制起来
定时事件
让一段程序在指定的时间之后执行一次
aeTimeProc(时间处理器)的返回值是AE_NOMORE
该事件在达到后删除,之后不会再重复
周期性事件
让一段程序每隔指定时间就执行一次
aeTimeProc(时间处理器)的返回值不是AE_NOMORE
当一个时间事件到达后,服务器会根据事件处理器的返回值,对时间事件的when属性进行更新,让这个事件在一段时间后再次到达
serverCron就是一个典型的周期事件
aeEventLoop
它是整个事件驱动的核心,Redis自己的事件处理机制
它管理着文件事件表和时间事件列表,不断地循环处理就绪的文件事件和到期的时间事件
初始化
Redis服务端在初始化函数initServer()中,会创建aeEventLoop对象
aeCreateEventLoop
将创建一个事件管理器,主要是初始化aeEventLoop的各个属性值
流程
首先创建aeEventLoop对象
初始化注册的文件事件表、就绪文件事件表
events指针指向注册的文件事件表
fired指针指向就绪文件事件表
表的内容在后面添加具体事件时进行初变更
初始化时间事件列表,设置timeEventHead和timeEventNextId属性
调用aeApiCreate()创建epoll实例,并初始化apidata
属性
stop
停止标志,1:停止,0初始化
文件事件
events(aeFileEvent)
为已经注册并需要监听的事件的结构体
fired(aeFiredEvent)
已就绪的文件事件
apidata
在ae创建时,会被赋值为aeApiState结构体
为了epoll所准备的数据结构,Redis可以选择不同的IO多路复用方法,因此apidata是个void类型,根据不同的io多路复用库来选择不同的实现
时间事件
timeEventHead(aeTimeEvent)
为时间事件,Redis将所有时间时间都放在一个无序链表中,每次Redis会遍历整个链表,查找所有已经到达的时间事件,并且调用相应的事件处理器
beforesleep
是一个回调函数,在redis-server初始化是已经设置好
功能
检测集群状态
随机释放已过期的键
在数据同步复制阶段取消客户端的阻塞
处理输入数据,并且同步副本信息
处理非阻塞的客户端请求
AOF持久化存储策略,类似于mysql的binlog
使用挂起的输出缓冲区处理写入
aftersleep
是一个回调函数,在IO多路复用与IO事件处理之间被调用
aeMain
就是一个封装的while循环,代码会一直运行直到eventLoop的stop被设置为1
它会不停尝试调用aeProcessEvents对可能存在的多种事件进行处理
它首先调用Beforesleep,这个方法在Redis每次进入sleep/wait去等待监听的端口发生IO事件之前被调用
当有事件发生时,调用aeProcessEvent进行处理
aeProcessEvent
首先计算距离当前时间最近的时间事件,以此计算一个超时时间
然后调用aeApiPoll()去等待底层的IO多路复用事件就绪
aeApiPoll返回之后,会处理所有已经产生文件事件和已经达到的时间事件
0 条评论
下一页