Redis底层的I/O模型

Posted 原创工厂

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Redis底层的I/O模型相关的知识,希望对你有一定的参考价值。

Redis底层的I/O模型

Redis是什么

百度百科的定义:

Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

Redis官网的定义:

Redis是一个开源(BSD licensed),基于内存的数据结构,常常用作数据库、缓存以及消息服务器。它支持的数据结构包括字符串、哈希、list、集合、有序集合以及范围查询、bitmap、HyperLogLog 算法、地理空间索引以及范围查询、流等。Redis内置分片、lua脚本、LRU淘汰策略、事务以及不同级别的持久化等功能,以及Redis哨兵和基于Redis Cluster实现自动分区保证高可用。

Redis为什么那么快

  • Redis是基于内存的,而内存的读写速度是非常快的

  • Redis基于单线程,省去了很多线程上下文切换的时间

  • Redis使用I/O多路复用技术,可以处理并发的连接。非阻塞I/O内部实现epoll,实现了事件处理框架。epoll中的读、写、关闭、连接都转换为了事件,利用epoll的多路复用特性。

Redis操作key-value是单进程单线程的。对于保持数据快照或者增量AOF写入,是通过FORK子集成去完成的。

I/O多路复用技术

多路:多个j客户端(socket)连接

复用:复用一个线程,使用单进程就能够实现同时处理多个客户端的连接

采用多路I/O复用技术可以让单线程高效地处理多个连接请求,而Redis在内存中操作数据的速度非常快,以上2点保证了Redis具有很高的吞吐量。

现代的UNIX系统提供了select、poll、kquue、epoll系统调用。这些系统调用的功能是:当socket可读或者可写事件发生时,系统将通知服务端这些信息。

I/O多路复用模块封装了底层的select、epoll、avport以及kqueue这些I/O多路复用函数,为上层提供了接口。

(1)select

最早的套接字集合管理员是select()系统调用。select()函数会在某个或某些套接字的状态从不可读变为可读、或不可写变为可写的时候通知服务器主进程。所以select()本身的调用是阻塞的。但是具体哪一个套接字或哪些套接字变为可读或可写我们是不知道的,所以我们需要遍历所有select()返回的套接字来判断哪些套接字可以进行处理了。select的优点是:

  • 实现了对多个套接字的同时、集中管理

  • 通过遍历所有的套接字集合,能够获取所有已就绪的套接字,对这些就绪的套接字进行操作不会阻塞

select的问题:

  • select管理的套接字描述符存在数量限制。在Unix中,一个进程最多同时监听1024个套接字描述符

  • select返回的时候,并不知道具体是哪个套接字描述符已经就绪,所以需要遍历所有套接字来判断哪个已经就绪,可以继续进行读写

(2)poll

poll解决了select带来的套接字描述符的最大数量限制问题

poll的fds参数集合了select的read、write和exception套接字数组,合三为一。poll中的fds没有了1024个的数量限制。当有些描述符状态发生变化并就绪之后,poll同select一样会返回。但是遗憾的是,我们同样不知道具体是哪个或哪些套接字已经就绪,我们仍需要遍历套接字集合去判断究竟是哪个套接字已经就绪,这一点并没有解决刚才提到select的第二个问题。

(3)epoll

epoll是最先进的套接字的管理员,解决了上述select和poll中所存在的问题。它将一个阻塞的select、poll系统调用拆分成了三个步骤。一次select或poll可以看作是由一次 epoll_create、若干次 epoll_ctl、若干次 epoll_wait构成:

// 创建一个epoll实例
int epoll_create(int size);
// 对套接字描述符集合进行增删改操作,并告诉内核需要监听套接字描述符的什么事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 等待监听列表中的连接事件(监听套接字描述符才会发生)或读写事件(连接套接字描述符才会发生)。如果有某个或某些套接字事件已经准备就绪,就会返回这些已就绪的套接字们
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

Redis的多路复用源码分析

Redis基于原有的select、pool和epoll机制,结合自己独特的业务需求,封装了一套事件处理函数,称之为ae(a simple event-driven programming library)。redis具备使用select、epoll还是mac上的kqueue技术,redis首先会判断,选择最优的那个。

select函数是作为POSIX标准中的系统调用,在不同的操作系统上都有实现,一般会作为备份方案。

Redis服务器主线程的执行流程在Redis.c的main函数中体现,而关于处理文件事件的主要的有这几行:

int main(int argc, char **argv) {

...
// 初始化存储服务端信息的结构体
initServerConfig();

...
// 建立各个事件处理器
initServer();

...
// 执行事件处理循环
aeMain();

...
// 关闭停止事件处理循环
aeDeleteEventLoop(server.el);

return 0;

}

initServerConfig()

Redis服务端所有的信息都存储在一个RedisServer结构体中,这个方法是对结构体的所有字段进行初始。

initServer()

初始化完服务器信息后,进行套接字的创建、绑定、监听并与客户端建立连接。

void initServer(void) {
  ...
   // 创建aeEventLoop,用来管理所有相关的事件描述字段、存储已注册的事件、已就绪的事件。包含创建epoll_create()
   server.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);
  ...
// 创建socket、绑定和监听
   if (server.port != 0 && listenToPort(server.port,server.ipfd,&server.ipfd_count) == C_ERR)
       exit(1);
  ...
// 客户端连接事件、读写事件统称为文件事件.将监听描述符添加到epoll的监听列表,以监听客户端的连接事件,同时指定了它的事件处理函数acceptTcpHandler()来实现对客户端连接事件的处理。
   for (j = 0; j < server.ipfd_count; j++) {
       if (aeCreateFileEvent(server.el, server.ipfd[j], AE_READABLE, acceptTcpHandler,NULL) == AE_ERR){
               serverPanic("Unrecoverable error creating server.ipfd file event.");
      }
  }
  ...
}

aeMain()

while(1)循环,等待客户端连接事件的到来

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);
  }
}


总结

多路 I/O 复用模型是利用select、poll、epoll可以同时检查多个流的 I/O 事件的能力,在空闲的时候,会把当前线程阻塞掉,当有一个或多个流有I/O事件时,就从阻塞态中唤醒,于是程序就会轮询一遍所有的流(epoll是只轮询那些真正发出了事件的流),并且只依次顺序的处理就绪的流,这种做法就避免了大量的无用操作。

这里“多路”指的是多个网络连接,“复用”指的是复用同一个线程。采用多路 I/O 复用技术可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),且Redis在内存中操作数据的速度非常快(内存内的操作不会成为这里的性能瓶颈),主要以上两点造就了Redis具有很高的吞吐量。

巨人肩膀

redis百度百科

https://baike.baidu.com/item/Redis/6549233?fr=aladdin

浅析redis中的IO多路复用与事件机制

https://segmentfault.com/a/1190000020252203

浅析服务器并发IO性能提升之路 — 从网络编程基础到epoll

https://segmentfault.com/a/1190000020194471

Redis的事件驱动(IO多路复用)

https://blog.csdn.net/u014590757/article/details/79860766

「Redis源码解读」-事件(一)IO多路复用

https://www.jianshu.com/p/c8f462827499


以上是关于Redis底层的I/O模型的主要内容,如果未能解决你的问题,请参考以下文章

Java NIO预备知识:I/O底层原理与网络I/O模型

详述 Redis 选择单线程模型的原因以及 I/O 多路复用

从理解Linux操作系统的网络IO模型来理解NettyRedis...

Redis篇:单线程I/O模型

Redis 和 I/O 多路复用

Redis 和 I/O 多路复用