系列文章目录
第四章 网络设计与redis、memcached、nginx组件(一)
第五章 网络设计与redis、memcached、nginx组件(二)
前言
此文章主要是是介绍一下reactor 模型和典型的reactor模型开源软件介绍分析。
一、reactor模型?
Reactor 模型的核心就是把对网络IO的处理转变成对事件的处理。 把网络IO检测功能交由IO多路复用(epoll select poll)实现检测fd 状态,针对时间的处理进行IO操作,不同职能事件通过不同的事件函数处理。
IO多路复用的主要功能是检测多条链路的状态(可读 可写 错误 断开等),但不具备具体IO操作的功能(比如读写数据)。IO多路复用都是同步网络IO ,常见的IO多路复用器有select、poll,epoll,他们是对IO的管理,检测接入的IO,触发IO事件;注意这三个都是同步IO。
之所以把IO就绪检测的功能交由IO多路复用器,是因为对于服务端而言,某一时刻只有少量的连接有数据交互,如果让IO函数自己检测,在阻塞IO时,每个连接都需要一个线程;非阻塞IO时,每个连接都需要通过while在应用层进行检测。
二、Reactor 开发
代码如下(示例):
//1.服务端作为被动建立连接 // listenfd 注册监听listenfd 的事件 struct epoll_event ev; ev.events = EPOLLIN; ev.date.fd = listenfd; epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
// 触发listenfd 的读事件,调用accept 接受新的连接 clientfd = accept(listen_fd,(struct sockaddr *)&addr, sizeof(addr)) ev.events = EPOLLIN; ev.date.fd = clientfd; epoll_ctl(epfd, EPOLL_CTL_ADD,clientfd, &ev); .//服务端主动 // 1. 创建 socket 建立连接 int connectfd = socket(AF_INET, SOCK_STREAM, 0); connect(connectfd, (struct sockaddr *)&addr, sizeof(addr)); // 2. 注册监听 connectfd 的写事件 struct epoll_event ev; ev.events = EPOLLOUT; ev.data.fd = connetfd epoll_ctl(efd, EPOLL_CTL_ADD, connectfd, &ev); // 3. 当 connectfd 写事件被触发,连接建立成功 if (status == e_connecting && e->events & EPOLLOUT) { status == e_connected; // 这里需要把写事件关闭 epoll_ctl(epfd, EPOLL_CTL_DEL, connectfd, NULL); }
三、典型reactor 模型
典型 readis
redis命令行采用了单reactor 网络事件模型,在启动时会创建一个I/O多路复用的事件处理器(即reactor)用于监听链路的状态,然后将所有客户端请求都注册到该事件处理器中。当有新连接到达或者已有连接有数据可读写时,事件处理器就会触发相应的回调函数进行处理。
由于redis是单线程运行的,因此只需要一个reactor即可满足其并发处理需求。通过合理地利用CPU资源和异步非阻塞IO技术,redis可以实现高效地处理大量并发请求。
radis 中优化
需要注意的是,当Redis遇到某些长时间执行的命令时,可能会导致其他请求被阻塞而影响整个系统性能。比如三个客户端同时向服务端发送数据,服务端是按先后顺序接收处理。这种就会出现如果先到的数据处理比较耗时,会导致后续客户端出现饥饿现象。优化方式是:
1、如果IO耗时,把IO操作放到IO线程池处理,主线程处理computer。read 、write、encode 、decode等丢入线程池处理
2、如果computer耗时,采用分治思想或者不同时间复杂度的算法
多reactor 模型 多线程(one eventloop per thread)
模型图
典型 memcached
memcached是一个基于事件驱动的内存缓存服务器,它使用多个reactor来处理并发连接。开启多个线程,每个线程中都有一个独立的reactor 对象。
为什么支持多线程 多reactor
memcached 也是kv类型的数据库,但是value 支持数据结构比较单一不支持多种数据结构, 对于锁的处理比较简单。
具体来说,memcached使用一个主线程和多个工作线程。每个工作线程都有自己的reactor,用于处理客户端连接和请求。当有新连接到达时,主线程(master epoll )会将新的fd分配给某个工作线程,并且该工作线程的reactor会负责处理这个连接的所有事件。
多reactor 模型 多进程 (one eventloop per process)
模型
典型引用 nginx
nginx是一个基于事件驱动的服务器,它通过多个reactor处理并发连接,它采用多进程的方式(master、slave worker)模型
具体来说,Nginx使用一个主进程(master)和多个worker进程。每个worker进程都有自己的reactor队形,用于处理客户端连接和请求。当有新连接到达时,主进程会将其分配给某个worker进程,并且该worker进程的reactor会负责处理这个连接的所有事件。
在默认情况下,Nginx的worker进程数等于CPU核心数。
nginx流程
Master process 创建的时候会监听listenfd, 然后在fork 出work 进程, work 进程当中都会有一个listenfd 的备份。每个work 进程会创建自己的epoll 对象。并且把listenfd交割epoll 对象管理. 多个进程都监听了listenfd 的读事件,如果listenfd 的全链接队列当中,加入节点的时候,他就会给每一个epoll 对象发送信号,每个epoll 对象都会去触发读事件。这样就产生了惊群现象。在用户层解决这个问题,通过在work 进程当中添加共享锁的方式解决。
总结
单线程单reactor模型,典型是radis 可以把IO放入线程池,也可以把业务计算部分放入线程池。通过回调的方式处理
多线程多reactor模型 如果业务之间交互比较多,但加锁简单,可以用多线程memcached
多进程多reactor模型 如果业务之间交互比较少,可以用多进程ngnix
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接,详细查看详细的服务器课程