Redis通讯协议及事件处理机制

Redis通讯协议及事件处理机制

通信协议

Redis是单进程单线程的。

应用系统和Redis通过Redis协议(RESP)进行交互。

请求响应模式

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

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

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

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

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

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

特点:

有问有答

耗时在网络传输命令

性能较低

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

批量请求,批量响应

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

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

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

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

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();
  • 原子化的批量请求响应模式(事务)
  • 发布订阅模式(pub/sub)
  • 脚本化的批量执行(lua)

事件处理机制

Redis服务器是典型的事件驱动系统。

Redis将事件分为两大类:文件事件和时间事件。

文件事件

文件事件即Socket的读写事件,也就是IO事件。

客户端的连接、命令请求、数据回复、连接断开

  • socket

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

  • Reactor

Redis事件处理机制采用单线程的Reactor模式,属于I/O多路复用的一种常见模式。

IO多路复用( I/O multiplexing )指的通过单个线程管理多个Socket。

Reactor pattern(反应器设计模式)是一种为处理并发服务请求,并将请求提交到一个或者多个服务处理。

主程序向事件分派器(Reactor)注册要监听的事件

Reactor调用OS提供的事件处理分离器,监听事件(wait)

当有事件产生时,Reactor将事件派给相应的处理器来处理 handle_event()

4种IO多路复用模型与选择

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

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

select

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

优点:

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

windows linux …

缺点:

单个进程打开的文件描述是有一定限制的,它由FD_SETSIZE设置,默认值是1024,采用数组存储
另外在检查数组中是否有文件描述需要读写时,采用的是线性扫描的方法,即不管这些socket是不是活跃的,都轮询一遍,所以效率比较低

poll

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

优点:

采样链表的形式存储,它监听的描述符数量没有限制,可以超过select默认限制的1024大小

缺点:

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

epoll

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

优点:

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

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

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

kqueue

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

优点:

能处理大量数据,性能较高

文件事件分派器

在redis中,对于文件事件的处理采用了Reactor模型。采用的是epoll的实现方式。

Redis在主循环中统一处理文件事件和时间事件,信号事件则由专门的handler来处理。

主循环

1
2
3
4
5
6
7
8
9
10
11
void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0;
while (!eventLoop->stop) { //循环监听事件
// 阻塞之前的处理
if (eventLoop->beforesleep != NULL)
eventLoop->beforesleep(eventLoop);
// 事件处理,第二个参数决定处理哪类事件
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP);
}
}

事件处理器

  • 连接处理函数 acceptTCPHandler

当客户端向 Redis 建立 socket时,aeEventLoop 会调用 acceptTcpHandler 处理函数,服务器会为每个链接创建一个 Client 对象,并创建相应文件事件来监听socket的可读事件,并指定事件处理函数。

  • 请求处理函数 readQueryFromClient

当客户端通过 socket 发送来数据后,Redis 会调用 readQueryFromClient 方法,readQueryFromClient方法会调用 read 方法从 socket 中读取数据到输入缓冲区中,然后判断其大小是否大于系统设置的client_max_querybuf_len,如果大于,则向 Redis返回错误信息,并关闭 client。

  • 命令回复处理器 sendReplyToClient
    sendReplyToClient函数是Redis的命令回复处理器,这个处理器负责将服务器执行命令后得到的命令回复通过套接字返回给客户端。

1、将outbuf内容写入到套接字描述符并传输到客户端

2、aeDeleteFileEvent 用于删除 文件写事件

时间事件

时间事件分为定时事件与周期事件:

一个时间事件主要由以下三个属性组成:

id(全局唯一id)

when (毫秒时间戳,记录了时间事件的到达时间)

timeProc(时间事件处理器,当时间到达时,Redis就会调用相应的处理器来处理事件)

定时事件

定时事件:让一段程序在指定的时间之后执行一次

aeTimeProc(时间处理器)的返回值是AE_NOMORE

该事件在达到后删除,之后不会再重复。

周期性事件

周期性事件:让一段程序每隔指定时间就执行一次

aeTimeProc(时间处理器)的返回值不是AE_NOMORE

当一个时间事件到达后,服务器会根据时间处理器的返回值,对时间事件的 when 属性进行更新,让这个事件在一段时间后再次达到。

serverCron就是一个典型的周期性事件。

aeEventLoop

aeEventLoop 是整个事件驱动的核心,Redis自己的事件处理机制

它管理着文件事件表和时间事件列表,不断地循环处理着就绪的文件事件和到期的时间事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct aeEventLoop {
//最大文件描述符的值
int maxfd; /* highest file descriptor currently registered */
//文件描述符的最大监听数
int setsize; /* max number of file descriptors tracked */
//用于生成时间事件的唯一标识id
long long timeEventNextId;
//用于检测系统时间是否变更(判断标准 now<lastTime)
time_t lastTime; /* Used to detect system clock skew */
//注册的文件事件
aeFileEvent *events; /* Registered events */
//已就绪的事件
aeFiredEvent *fired; /* Fired events */
//注册要使用的时间事件
aeTimeEvent *timeEventHead;
//停止标志,1表示停止
int stop;
//这个是处理底层特定API的数据,对于epoll来说,该结构体包含了epoll fd和epoll_event
void *apidata; /* This is used for polling API specific data */
//在调用processEvent前(即如果没有事件则睡眠),调用该处理函数
aeBeforeSleepProc *beforesleep;
//在调用aeApiPoll后,调用该函数
aeBeforeSleepProc *aftersleep;
} aeEventLoop;

重点:在redis中,对于文件事件的处理采用了Reactor模型。采用的是epoll的实现方式。

其他都是redis源码里面的部分内容,适当了解即可。