Nginx中的设计模式

参考书籍《nginx完全开发指南:使用C、C#和OpenResty》

PS:这里的分类只是依据模式在Nginx里的应用的层次,模式本身应用并无限制。即使这样,这种划分也不一定精准,有的模式可能跨越两个层次。所以这样的分类只是方便理解和阅读。另外,GoF设计模式原本是用来描述面向对象开发中遇到的场景的,这里nginx开发语言为c,不是面向对象,但是其蕴含的设计模式思想仍然具有讨论意义与学习价值。

一、框架级别的设计模式

1. 反应器(Reactor)

反应器模式是一种I/O事件处理的设计模式,它管理多个事件源,并通过多路分离器把就绪事件分发到相应的handler,极大的提高了并发处理能力。Linux、FreeBSD里的epoll、kqueue等系统调用都是反应器模式的具体应用,是高性能Web服务器的实现基础。Nginx也正是利用了epoll、kqueue才能无阻塞地处理海量并发连接。所以反应器是nginx快速高效的根本秘密。(redis也是反应器模式)。

关于反应器和主动器,可以看我的这篇介绍简单了解下反应器Reactor和主动器Proactor的区别 - 方山客 - 博客园 (cnblogs.com)

关于select、poll、epoll的区别,参考这篇文章Linux IO模式及 select、poll、epoll详解 - SegmentFault 思否

2. 外观模式(Facade)

外观模式整理底层的系统API,分类、重命名或者简单包装,最后给出一个统一易用的接口,它可以屏蔽不同操作系统之间的差异,增强软件的可移植性。nginx为了能过跨平台运行,大量应用了外观模式,使用宏、函数等手段,重定义了许多系统函数,减少了UNIX平台实现差异产生的影响。

3. 桥接(Bridge)

桥接模式分离了架构设计与实现,架构是稳定的,而实现可以任意变化,增强了系统的灵活性。Nginx的模块架构就应用了桥接模式,它使用了nginx_module_t定义模块,结构体里有若干函数指针和扩展字段,然后桥接实现了丰富多彩的core、conf、event、stream、http等功能模块,搭建起整个Nginx框架。

4. 模板方法(Template)

模板方法模式确定了操作的主要步骤和流程,并在关键节点定义了回调函数,允许外界实现回调函数来扩展或增强原操作的功能,是框架设计中最常用最基本的模式。作为开发框架,Nginx在配置解析阶段、请求处理阶段都定义了数量繁多的回调函数,模块可以根据自身的需求实现特定的回调函数。

5. 策略(Strategy)

最明显的,load-balance模块,封装了不同的负载均衡算法。另外,各种handler/filter/upstream模块,其实也可以算是策略模式,在配置文件替换不同的模块,就可以改变Nginx的行为。

二、业务级别的模式

1. 对象池

其实就是一个池化的概念。Nginx里的内存池是对象池的典型应用。另外nginx_pool_t/nginx_http_request_body_t使用链表保存闲置的nginx_chain_t,当需要使用缓冲区时直接取出来使用。

2. 职责链

Nginx的Stream/Http框架实现了处理引擎和过滤引擎,是职责链模式的具体应用,它们把stream/http模块组织成链表,逐个地加工处理客户端请求。其中处理引擎本身是二维职责链,分多个阶段,每个阶段里的模块也组织成职责链,而且在处理过程中由于URL重写的原因还可以在链表里跳转,导致职责链从头执行。

3. 命令模式

命令模式把请求封装成一个对象,让对象携带尽可能多的相关信息,可以简化后续的处理操作,通常配合职责链模式一起使用。显然,Nginx在处理http请求时的nginx_http_request_t结构体就是命令对象,它存储了非常多的信息,如连接、配置、数据、状态、变量、子请求等,在各个模块之间反复传递,由模块职责链进行处理。

4. 备忘录模式(Memento)

备忘录模式捕获一个对象的内部状态,并在对象之外保存这个对象为“备忘”,这样当对象再次启动时就可以通过备忘录恢复原状态,无差错地继续执行。

Nginx在请求结构体里设计的ctx成员就是备忘录,它为每个模块提供了保存运行时数据的空间,模块可以把任何数据作为“备忘”存储在ctx里,不会因为框架的异步机制导致运行状态不一致。

5. 中介者模式(Mediator)

中介者扮演“中间人”的角色,系统里的每个对象只与中介者通信,简化对象之间的多对多联系,协调保证它们共同工作。中介者模式的缺点是它必须维护所有的连接关系,很容易造成自身过度复杂。

nginx_http_upstream_module里定义的upstream框架就是中介者模式(同时也应用了模板模式),它协调load-balance模块和upstream模块共同工作,获取上游服务器的地址,然后转发下游的请求和上游的响应数据。

三、代码级别的模式

1. 组合模式

组合模式描述的是整体-部分的关系,使客户端可以一致地使用组合结构或其中单个对象,这里的“组合”和类关系那个“组合”是两个不同的概念。Nginx里的子请求设计就是组合模式,请求结构体使用main、parent、posted_requests等指针组织成了一个请求数,根是主请求,下面的分支是各个子请求。但从外部来看,一个主请求和一个子请求是没有任何区别的,可以一致处理。

2. 观察者

观察者模式定义了对象间一对多的联系,当被观察的对象状态变化时,观察者能够立即得到通知。

Nginx里父请求与子请求的通信就应用了观察者模式。子请求设置父请求的处理函数,子请求处理结束时在nginx_http_finalize_request()中发送通知,这样父请求能够及时被唤醒继续执行。

3. 适配器(Adapter)

Nginx在event模块里使用了适配器模式,把epoll、kqueue、select等不同的异步IO接口统一适配为ngx_event_actions_t结构体。

4. 原型(prototype)

Nginx在创建子请求时使用了原型模式,它从父请求里拷贝了大部分字段,创建了一个基本相同的新请求对象。

5. 访问者(visitor)

访问者解耦了数据和访问数据的操作,可以在不改变数字的前提下任意增加访问它们的新操作,两者也可以独立变化。Nginx的变量机制应用了访问者模式,外界不能直接操作模块内部数据,只能通过变量提供的get/set函数来间接访问。所以可以很容易地增加变量来暴露更多信息,也可以随时变动模块的内部实现,而对外导出的变量则不受影响。

6. 工厂模式

Nginx创建模块配置数据结构的函数指针create_main/srv/loc_conf是工厂方法模式,每个模块都可以实现自己的工厂函数,创建出模块专属的配置数据结构。

7. 代理

ngx_str_t 和 ngx_buf_t 代理了一块内存空间,表示一个字符串或者数据块,前者是只读字符串,而后者允许对数据块做更多操作。

8. 装饰

装饰是另一种形式的包装,它不适配接口,而是在原接口的基础上增加新的功能,允许动态组合。装饰与适配器、代理很像,但目的、用法截然不同。Nginx用来连接后端服务的结构体ngx_peer_connection_t就是装饰模式,它“装饰”复用连接对象nginx_connection_t,添加了后端服务器的地址、连接时间等相关信息。

 

posted @ 2022-03-22 11:30  方山客  阅读(370)  评论(0编辑  收藏  举报