Nginx 作为 多进程网络编程的 巅峰之作(那么请问 多线程巅峰、单进程巅峰是?),也是当前最流行的 HTTP Server,其优秀之处毋容置疑;一直到 2011 年左右,Nginx 还仅仅是 反向代理的 首选,后来随着 HTTP 核心的成熟 慢慢用来 替代 Apache 单独作为 HTTP Server;当然这中间也离不开 第三方扩展模块 的日益丰富;从最初的 nginx_lua_module 支持 lua 嵌入,到后来 春来大神 的 OpenResty,集成 LuaJIT,使得 Nginx 生态再次丰富起来
说到第三方扩展,就不得不说一下 Nginx 的 高扩展特性,它从设计上完全就由多个不同功能、不同层次、不同类型且耦合度极低的模块构成,因此,当对某一个模块修复 Bug 或 进行升级的时候,可以专注于模块本身,无需在意其他。而且在 HTTP 模块中,还设计了 HTTP 过滤器模块,也就是说一个正常的 HTTP 模块在处理完成 request 后,会有一串 HTTP 过滤器模块对请求结果进行再处理,这样,当我们开发一个新的 HTTP 模块时,当然也可以复用诸如 HTTP 核心模块、events 模块、log 模块 等不同层次不同类型的模块,这种低耦合度的优秀设计,也给我们 定制模块开发 提供了很大便利
模块/扩展/插件
广义上 Nginx 模块开发 分为 两种
- 基于原生 的 C Module 开发( Redis / Memcached / Mongo / Elasticsearch client 端,HTTP Healthcheck 等)
- 基于 OpenResty 的 Lua Module 开发
前者偏 基础工具,而 后者因为 Lua 的存在让很多想法成为可能,就更加 偏业务,比如之前的 Kong 网关
我们这次从 原生扩展模块 的角度去实现一个简单的 熔断模块
ngx_http_fuse_module
,为了便于展示,所以 示例功能 的逻辑上没有太过复杂,不过完全理解以后,万变不离其宗,始终不会逃脱下面这一套流程。。。
模块工作原理
Nginx 本身做的工作实际很少,当它接到一个 HTTP 请求时,它仅仅是通过查找配置文件将此次请求映射到一个 location block,而此 location 中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做 Nginx 真正的劳动工作者。通常一个 location 中的指令会涉及一个 handler 模块和多个 filter 模块(当然,多个 location 可以复用同一个模块)
- handler 模块负责处理请求,完成响应内容的生成
- 而 filter 模块对响应内容进行处理
因此 Nginx 模块开发分为 handler 开发和 filter 开发(我们不考虑load-balancer负载均衡模块),下图展示了一次常规请求和响应的时序图
这里需要注意的是 根据处理模块的逻辑决定是否执行 POST 请求
这句,意思为 nginx core
模块 本身不会主动去读取请求体,他认为这是个很糟糕很损耗性能的做法,所以这个一般是交给具体的请求处理模块,比如说 ngx_http_fastcgi_module cgi
模块,它会在 ngx_http_fastcgi_handler
请求处理逻辑中根据请求类型去确定是否需要读取 request body
,如果需要就调用 ngx_http_read_client_request_body
接口单独读取 请求体,所以在执行的整个链路中,这块是被单独拿出来的:http://nginx.org/en/docs/dev/development_guide.html
熔断模块
模块定义:ngx_http_fuse_module
熔断功能实现逻辑
- 熔断能力
- 检测不健康状态码
- 根据不健康度进行强行熔断,阻隔请求
- 自愈能力
- 定期开启 半开通道,放行部分流量
- 痊愈打开熔断
- or 不健康保持熔断
流行熔断器可以根据各个精度进行控制,比如 域名级别
、路由级别
等,使用 hashtable
分别记录不同级别对应的 不健康统计数据、熔断状态、通道半开标识、放行流量 等数据,但是我们此次示例以 容易理解为主,只设计 location 级别
根据功能我们对这个模块进行配置项设计
而根据 Nginx 模块精细化的处理阶段,我们来确定我们模块的挂载阶段
一般情况下,我们自定义的模块,大多数是挂载在 NGX_HTTP_CONTENT_PHASE
阶段的,默认也就是属于这个模块。挂载的动作一般是在模块上下文调用的 postconfiguration
函数中
注意:有几个阶段是特例,它不调用挂载地任何的handler,也就是你就不用挂载到这几个阶段了:
NGX_HTTP_FIND_CONFIG_PHASE
NGX_HTTP_POST_ACCESS_PHASE
NGX_HTTP_POST_REWRITE_PHASE
NGX_HTTP_TRY_FILES_PHASE
所以根据我们的功能设计,需要对 response 进行健康度检测,应该挂载在 NGX_HTTP_CONTENT_PHASE
阶段
但是,我们还有一个 熔断的功能,是需要在 request 处理前进行熔断,所以一定要在 NGX_HTTP_TRY_FILES_PHASE
之前进行处理,而根据模块开发的约定习俗,一般挂载在 NGX_HTTP_PREACCESS_PHASE
阶段
所以我们的插件实际上要在不同的 执行阶段 分别进行 逻辑运算处理,这里要注意~~~
定义模块配置结构
首先我们需要一个结构用于存储从配置文件中读进来的相关指令参数,即模块配置信息结构。根据Nginx模块开发规则,这个结构的命名规则为 ngx_http_[module-name]_conf_t
,我们这里需要的配置为
由于我们涉及数据存储(不健康个数、时间节点等)逻辑,所以使用了 slab共享内存池
(slab 部分我们可以抽个时间单独分享一下),以下为内存存储数据结构
|
|
定义指令
一个 Nginx 模块往往接收一至多个指令,此次熔断模块接收两个指令,Nginx模块使用一个ngx_command_t数组表示模块所能接收的所有模块,其中每一个元素表示一个条指令
|
|
创建合并配置信息
这里首先需要定义一个 ngx_http_module_t
类型的结构体变量,命名规则为 ngx_http_[module-name]_module_ctx
,这个结构主要用于定义各个 Hook 函数
一共有 8 个 Hook 注入点,,分别会在不同时刻被 Nginx 调用,由于我们的模块仅仅用于 location 域,这里将不需要的注入点设为 NULL 即可。其中 create_loc_conf
用于初始化一个配置结构体,如为配置结构体分配内存等工作;merge_loc_conf
用于将其父 block 的配置信息合并到此结构体中,也就是实现配置的继承。这两个函数会被 Nginx 自动调用。注意这里的命名规则:ngx_http_[module-name]_[create|merge]_conf
|
|
|
|
|
|
|
|
编写 Handler
handler 我们分为两个部分,header 过滤功能(ngx_http_fuse_header_filter)
和 request 预处理功能(ngx_http_fuse_handler)
|
|
上面我们已经展示了 熔断模块的各个组成部分,接下来我们对各个部分进行组装,形成 Nginx 可识别的 模块入口
组合Nginx Module
|
|
安装调试
安装
为了调试,这里特地打开了 debug,并且使用了 -g -O0
跳过编译优化
调试
我们对 nginx.conf
进行特殊设置
|
|
测试
PHP 测试程序中实现逻辑为,根据传递的参数强行执行 header 返回状态
….直到半开通道开启,如果检测到 200 状态码,则打开熔断阻隔,如果检测依然为 不健康状态码,依然熔断
成功实现我们最初设计的简单熔断功能…
优秀的 第三方模块
https://www.nginx.com/resources/wiki/modules/index.html
本文作者: wettper
本文链接: http://www.web-lovers.com/redis-module-fuse.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议。转载请注明出处!