要点:
- 事件驱动架构的关键点
- 多阶段划分的一些方法
来自 《深入理解 Nginx》 ——陶辉 著
事件驱动架构,简单来说包含三部分:
- 事件产生者,由一些事件发生源产生事件(对于 Web 服务器来说一般是网卡、磁盘产生事件);
- 事件收集、分发者,由一个或多个收集器来收集和分发事件;
- 事件消费者,消费者注册自己感兴趣的时间并消费这些事件。
传统 Web 服务器的事件驱动往往局限在 TCP 连接建立、关闭事件上,一个连接建立之后,在其关闭之前的所有操作就不再是事件驱动,这是会退化成按需执行的批处理模式,这样每个请求在连接建立后都会始终占用着系统资源,直到连接关闭才会释放。即用进程或线程作为事件消费者, 而 Nginx 的事件消费者不是整个进程或线程,而是某个模块(模块是一段逻辑调用),而模块内不会有阻塞行为。
事件驱动架构的关键在于请求的多阶段异步处理:把请求的处理过程按事件的触发方式划分为多个阶段,每个阶段由事件收集,由分发器触发。
如将一个获取静态文件的 HTTP 请求切分为如下几个阶段(阶段处理方式:触发事件):
- 建立 TCP 连接:收到 TCP SYN 包
- 开始接收用户请求: 收到 TCP 的 ACK 三次握手完成
- 接收到用户请求并分析请求是否完整:在接收到用户的数据包
- 接收到完整的用户请求后开始处理请求:接收到用户的数据包
- 读取静态文件的部分内容并发送:接收到用户数据包,或 ACK 包
- 对于非 keep-alive 请求主动关闭连接:接收到最后数据的 ACK 包
- 由于用户关闭连接而结束请求:接收到 FIN 包
总的来说是找到请求处理流程中造成阻塞的部分,将其拆解,有以下原则:
- 按触发事件拆分
- 数据分片为小块数据(按时间)
- 定时器划分
- 用新的进程单独处理
- 按触发事件拆分
一个可能导致进程休眠的方法或系统调用一般都能分解为多个更小的方法或系统调用,这些调用可以按事件触发关联起来。大多数情况下,一个阻塞的系统调用都可以划分为两个阶段:改为异步的调用阶段和异步调用结束后获取结果阶段。
如 send 方法使用非阻塞方式产生调用并得到 socket 句柄加入到事件收集器中,等到响应的事件触发下一个阶段获取发送结果。
- 数据分片
如读取文件的调用,如果读取 10MB 的文件可能需要多次硬盘寻址,在寻址过程中进程多半会进入休眠或等待,如果Nginx 的事件模块没打开异步IO时就不能用上述做法(这个条件暂且搁下)。这时可以将 10MB 均分成 1000 份来分解读取文件的调用
- 定时器划分
如在“无所事事”且必须等待系统的响应时,用定时器循环来判断标志位。如果标志位不满足,则立即归还进程控制权。
- 用新的进程执行
如果阻塞方法完全没有开放非阻塞方法,且完全无法继续划分,则使用独立的进程执行这个阻塞方法。