From 5d93d90cbc1e8eca1075753510e6df3fab0e4d5a Mon Sep 17 00:00:00 2001 From: "j.fu" Date: Fri, 31 May 2024 16:47:36 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=9A=E5=A2=9E=E5=8A=A0=E5=AF=B9config=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=A7=A3=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tutorial/README.adoc | 169 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 161 insertions(+), 8 deletions(-) diff --git a/tutorial/README.adoc b/tutorial/README.adoc index bf2c5ec..8536bdb 100644 --- a/tutorial/README.adoc +++ b/tutorial/README.adoc @@ -139,17 +139,170 @@ image::./imgs/debug-nginx.png[调试nginx] == 三. 网关原理 -参考: +网关可以执行多种功能,包括路由、负载均衡、认证、授权、限流、监控和安全等。负载均衡是网关最基本的功能之一。负载均衡器中的主动探测和被动探测是两种不同的健康检查机制,用于确定后端服务器或服务实例是否能够正常处理请求。以下是它们的主要区别: -1. 负载均衡解释:link:https://www.nginx.org.cn/article/detail/440[] -2. 高可用配置示例:link:https://blog.csdn.net/IT_10/article/details/89365436[] -3. nginx开发参考1:link:https://tengine.taobao.org/book/[] -4. nginx开发参考2:link:https://www.nginx.org.cn/article/detail/443[] -5. nginx中文配置手册:link:https://wizardforcel.gitbooks.io/nginx-doc/content/index.html[] -6. nginx开发参考3:link:https://www.kancloud.cn/kancloud/master-nginx-develop/51798[] +[%autowidth] +|=== +| |主动探测(Active Probing) | 被动探测(Passive Probing) + +|定义| 负载均衡器定期向后端服务器发送探测请求(如HTTP请求、TCP连接或ICMP消息),以检查它们是否健康|负载均衡器通过监控经过它的实际用户请求来检测后端服务器的健康状况 +| 依赖| 不依赖于用户请求 | 依赖于用户请求的响应来评估后端服务器的状态,不会主动发送额外的探测请求 +|类型|可以是简单的TCP连接尝试,或者更复杂的应用层探测,如发送HTTP HEAD请求并检查HTTP状态码|取决于用户请求 +|频率|根据需要设置探测的时间间隔|取决于用户请求频率 +|响应|如果后端服务器在指定的时间内响应了探测请求并且返回了健康的响应,那么它被认为是健康的|如果用户请求得到了成功的响应(如HTTP 200 OK),则认为后端服务器是健康的;如果响应表明错误(如HTTP 5xx错误),则认为服务器可能不健康 +|优点|及时发现不健康的服务器,在用户请求到达之前将其从负载均衡池中移除|仅依赖于实际的用户请求,不会给后端服务器带来额外的负载 +|缺点|频繁的探测请求可能给后端服务器带来额外的负载|只有在用户请求到达不健康的服务器时才会被检测到,可能无法及时发现不健康的服务器 +|=== + +本文档以link:https://github.com/yaoweibin/nginx_upstream_check_module[nginx_upstream_check_module]为例,深入理解nginx的主动探测原理。 + +=== 3.1 源码解读 + +==== 3.1.1 配置与编译 + +ngx_http_upstream_check_module模块的 `config` 文件内容如下: + +[source,shell,numbered] +---- +ngx_feature="ngx_http_upstream_check_module" +ngx_feature_name= +ngx_feature_run=no +ngx_feature_incs= +ngx_feature_libs="" +ngx_feature_path="$ngx_addon_dir" +ngx_feature_deps="$ngx_addon_dir/ngx_http_upstream_check_module.h" +ngx_check_src="$ngx_addon_dir/ngx_http_upstream_check_module.c" +ngx_feature_test="int a;" +. auto/feature + +if [ $ngx_found = yes ]; then + have=NGX_HTTP_UPSTREAM_CHECK . auto/have + CORE_INCS="$CORE_INCS $ngx_feature_path" + ngx_addon_name=ngx_http_upstream_check_module + HTTP_MODULES="$HTTP_MODULES ngx_http_upstream_check_module" + NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_feature_deps" + NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_check_src" +else + cat << END + $0: error: the ngx_http_upstream_check_module addon error. +END + exit 1 +fi +---- + +**解读如下**: + +`auto/feature` 是 Nginx 源码树中的一个脚本文件,位于 auto 目录下。这个脚本的作用是自动检测当前编译环境是否支持某些特定的系统特性(features),并据此设置编译配置。这对于确保 Nginx 能够在不同操作系统和硬件平台上以最优方式运行至关重要。该脚本主要功能是: + +. **检测特性**: 检查编译环境中是否存在 Nginx 所需的系统调用、库函数或编译器特性。 +. **设置宏定义**: 如果检测到特性存在,则在 Nginx 的配置头文件中定义相应的宏,以便编译时启用相关的特性代码。 +. **生成配置**: 根据检测结果,生成或更新配置文件,如 `nginx.conf` 和 `ngx_auto_config.h`(低版本Nginx该文件名为 `auto_config.h`)。 + +`auto/feature` 脚本通过命令行参数指定要检测的特性。以下是一些常见的参数: + +* `ngx_feature`: 特性的名称,用于日志输出。 +* `ngx_feature_name`: 这个参数预期是一个宏名称字符串,它用于在 Nginx 的配置头文件(通常是 `ngx_auto_config.h`)中定义一个宏,以指示编译器和运行时环境该特性是可用的。 +* `ngx_feature_run`: 这个参数的值通常为 `yes` 或者不设置(空)。如果设置为 `yes`,则表示编译后的测试程序需要被执行,以确保不仅编译通过了,而且程序在运行时也能正确工作。如果不设置或者留空,编译后的程序将不会被执行,仅仅编译成功即认为特性支持。。 +* `ngx_feature_incs`: 这个参数预期是一个或多个头文件的列表,这些头文件对于编译测试程序来说是必需的,它们通常用于声明用于检测特定系统特性的函数、结构或宏。 +* `ngx_feature_path`: 头文件的搜索路径。 +* `ngx_feature_libs`: 编译测试程序时需要链接的库。 +* `ngx_feature_test`: 这个参数是一个字符串,包含了用于检测特性的 C 语言代码片段。这段代码通常包括函数调用、结构体定义、宏检查或其他编译时的检查,旨在验证系统是否提供了所需的功能。 + +注意,`ngx_check_src` 和 `ngx_feature_deps` 并不是feature脚本内置参数,而是用户自定义参数。 + +`auto/feature` 脚本的工作流程: + +. **生成测试代码**: 根据参数 `ngx_feature_test` 提供的参数,脚本生成一个 C 语言的测试程序。 +. **编译测试程序**: 使用参数 `ngx_feature_incs` 和 `ngx_feature_libs` 指定的编译器选项、头文件路径和库链接选项编译测试程序。 +. **运行测试**: 如果 `ngx_feature_run` 设置为 `yes` ,则执行编译后的测试程序。 +. **分析结果**: 根据测试程序的编译和运行结果,确定特性是否存在。 +. **更新配置**: 如果特性存在,更新配置文件和头文件,使用 `ngx_feature_name` 指定的名称定义相应的宏。 + +其他变量: + +* `NGX_ADDON_DEPS` : 主要用于指定第三方模块(被编译为 addon 模块)所依赖的其他模块或文件。这个变量在 Nginx 的 `auto/modules` 脚本中被处理,负责设置模块依赖. +* `CORE_INCS` : 是一个变量,用于定义 Nginx 编译所需的头文件的搜索路径。在编译过程中,`CORE_INCS` 变量的值会被用作编译器的 `-I` 参数,这是指定头文件搜索目录的标准编译器选项。在 Nginx 的 `auto/configure` 脚本或 `auto/modules` 脚本中,`CORE_INCS` 会根据检测到的模块和特性动态地设置和更新。 +* `ngx_addon_name`` :指定第三方模块的名称,是必须定义的变量, `ngx_addon_name` 被添加到模块列表中,这样在构建过程中,构建脚本就知道需要编译哪些模块 +* `HTTP_MODULES` : 定义了所有编译进 Nginx 的 HTTP 模块的列表,包括 Nginx 核心 HTTP 功能以及任何第三方 HTTP 模块。 +* `NGX_ADDON_DEPS` : 指定第三方模块(也称为 addon 模块)在编译时所依赖的其他文件或模块,在 Nginx 的构建脚本 auto/modules 中被使用。 +* `NGX_ADDON_SRCS` : 指定构成第三方模块的所有源代码文件,在 Nginx 的构建脚本 auto/modules 中被使用。 + +[小技巧]: 使用kimi对config文件解读如下: + +---- +这段 Nginx 第三方模块的 `config` 文件是一个脚本,用于配置和检测模块 `ngx_http_upstream_check_module`。下面是对脚本中每行的详细解释: + +1. `ngx_feature="ngx_http_upstream_check_module"` + 定义了正在配置的 Nginx 模块的名称。 + +2. `ngx_feature_name=` + 通常用于定义一个宏名称,但这里留空,表示可能不需要定义特定的宏。 + +3. `ngx_feature_run=no` + 表示不需要执行编译后的测试程序来验证特性。这可能是因为模块的存在与否不依赖于运行时检测。 + +4. `ngx_feature_incs=` + 留空,表示没有特定的头文件需要包含。 + +5. `ngx_feature_libs=""` + 留空,表示没有特定的库需要链接。 + +6. `ngx_feature_path="$ngx_addon_dir"` + 设置模块的路径为 `ngx_addon_dir` 变量的值,这通常是模块的安装目录。 + +7. `ngx_feature_deps="$ngx_addon_dir/ngx_http_upstream_check_module.h"` + 定义模块的依赖头文件,这里是模块的头文件。 + +8. `ngx_check_src="$ngx_addon_dir/ngx_http_upstream_check_module.c"` + 指定模块的源文件路径。 + +9. `ngx_feature_test="int a;"` + 提供一个非常简单的测试代码,这里仅仅声明了一个整型变量,实际上这个测试可能不会对模块的检测产生实际作用。 + +10. `. auto/feature` + 执行 Nginx 的 `auto/feature` 脚本来处理配置。 + +11. `if [ $ngx_found = yes ]; then` + 如果 `auto/feature` 脚本执行后 `ngx_found` 变量被设置为 `yes`,则继续执行以下命令。 + +12. `have=NGX_HTTP_UPSTREAM_CHECK . auto/have` + 使用 `auto/have` 脚本定义一个宏,表示检测到 `ngx_http_upstream_check_module`。 + +13. `CORE_INCS="$CORE_INCS $ngx_feature_path"` + 将模块的路径添加到 Nginx 核心的头文件搜索路径中。 + +14. `ngx_addon_name=ngx_http_upstream_check_module` + 设置第三方模块的名称。 + +15. `HTTP_MODULES="$HTTP_MODULES ngx_http_upstream_check_module"` + 将模块名称添加到 HTTP 模块列表中。 + +16. `NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_feature_deps"` + 将模块的依赖项添加到第三方模块的依赖列表中。 + +17. `NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_check_src"` + 将模块的源文件添加到第三方模块的源文件列表中。 + +18. `else` + 如果 `ngx_found` 不是 `yes`,执行错误处理。 + +19. `cat << END ... END` + 输出错误信息,指出 `ngx_http_upstream_check_module` 模块的添加出现错误。 + +20. `exit 1` + 退出脚本并返回状态码 1,表示配置过程中出现错误。 + +整体来看,这个 `config` 文件的作用是检测 `ngx_http_upstream_check_module` 是否可以被添加到 Nginx 中,并根据检测结果更新 Nginx 的配置,以便在编译时包含这个模块。如果检测失败,则输出错误信息并退出配置过程。 +---- == 四. 开发流程 == 五. 其他 -== 六. 参考资料 \ No newline at end of file +== 六. 参考资料 +1. 负载均衡解释:link:https://www.nginx.org.cn/article/detail/440[] +2. nginx中文配置手册:link:https://wizardforcel.gitbooks.io/nginx-doc/content/index.html[] +3. 高可用配置示例:link:https://blog.csdn.net/IT_10/article/details/89365436[] +4. nginx开发参考1:link:https://tengine.taobao.org/book/[] +5. nginx开发参考2:link:https://www.nginx.org.cn/article/detail/443[] +6. nginx开发参考3:link:https://www.kancloud.cn/kancloud/master-nginx-develop/51798[] \ No newline at end of file From 8fa73c40f603659365f0f5332749e0c52e49d6d5 Mon Sep 17 00:00:00 2001 From: "j.fu" Date: Mon, 3 Jun 2024 19:34:49 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= =?UTF-8?q?=EF=BC=8C=E4=BB=A3=E7=A0=81=E6=B3=A8=E9=87=8A=EF=BC=8C=E4=BB=A5?= =?UTF-8?q?=E5=8F=8A=E4=B8=8A=E4=BC=A0=E6=96=AD=E7=82=B9=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nginx-demo/nginx-src/nginx-1.26.0/gdb.cfg | 16 ++ .../ngx_http_upstream_check_module.c | 94 ++++----- tutorial/README.adoc | 180 +++++++++++++++--- 3 files changed, 220 insertions(+), 70 deletions(-) create mode 100644 nginx-demo/nginx-src/nginx-1.26.0/gdb.cfg diff --git a/nginx-demo/nginx-src/nginx-1.26.0/gdb.cfg b/nginx-demo/nginx-src/nginx-1.26.0/gdb.cfg new file mode 100644 index 0000000..3764352 --- /dev/null +++ b/nginx-demo/nginx-src/nginx-1.26.0/gdb.cfg @@ -0,0 +1,16 @@ +break ngx_http_upstream_check_timeout_handler +break ngx_http_upstream_check_http_reinit +disable $bpnum +break ngx_http_upstream_check_http_parse +break ngx_http_upstream_check_http_init +disable $bpnum +break ngx_http_upstream_check_recv_handler +break ngx_http_upstream_check_send_handler +break /workspaces/cpp-5/nginx_upstream_check_module/ngx_http_upstream_check_module.c:1332 +disable $bpnum +watch check_peers_ctx +break /workspaces/cpp-5/nginx_upstream_check_module/ngx_http_upstream_check_module.c:3757 +watch check_peers_ctx +watch check_peers_ctx +break ngx_http_upstream_check_begin_handler +break ngx_http_upstream_check_connect_handler diff --git a/nginx-demo/nginx_upstream_check_module/ngx_http_upstream_check_module.c b/nginx-demo/nginx_upstream_check_module/ngx_http_upstream_check_module.c index 67bce96..640bd3f 100644 --- a/nginx-demo/nginx_upstream_check_module/ngx_http_upstream_check_module.c +++ b/nginx-demo/nginx_upstream_check_module/ngx_http_upstream_check_module.c @@ -117,7 +117,7 @@ typedef ngx_int_t (*ngx_http_upstream_check_packet_parse_pt) typedef void (*ngx_http_upstream_check_packet_clean_pt) (ngx_http_upstream_check_peer_t *peer); -struct ngx_http_upstream_check_peer_s { /*peer关键配置类:包括一组回调函数*/ +struct ngx_http_upstream_check_peer_s { ngx_flag_t state; ngx_pool_t *pool; ngx_uint_t index; @@ -125,17 +125,17 @@ struct ngx_http_upstream_check_peer_s { /*peer关键配置类:包括一组回 ngx_str_t *upstream_name; ngx_addr_t *check_peer_addr; ngx_addr_t *peer_addr; - ngx_event_t check_ev; /*关键数据结构:check event*/ - ngx_event_t check_timeout_ev; /*关键数据结构: check timeout event*/ - ngx_peer_connection_t pc; + ngx_event_t check_ev; /* 主动探测事件 */ + ngx_event_t check_timeout_ev; /* 主动探测超时事件 */ + ngx_peer_connection_t pc; /* 保存与后端服务器的连接 */ void *check_data; - ngx_event_handler_pt send_handler; /*TODO: ???*/ - ngx_event_handler_pt recv_handler; /*TODO: ???*/ + ngx_event_handler_pt send_handler; /* ngx_connection_t中的write事件handler:向后端服务器发送探测数据 */ + ngx_event_handler_pt recv_handler; /* ngx_connection_t中的read事件handler:从后端服务器接收探测结果数据 */ - ngx_http_upstream_check_packet_init_pt init; /*TODO: ???*/ - ngx_http_upstream_check_packet_parse_pt parse; /*TODO: ???*/ - ngx_http_upstream_check_packet_clean_pt reinit; /*TODO: ???*/ + ngx_http_upstream_check_packet_init_pt init; + ngx_http_upstream_check_packet_parse_pt parse; + ngx_http_upstream_check_packet_clean_pt reinit; ngx_http_upstream_check_peer_shm_t *shm; ngx_http_upstream_check_srv_conf_t *conf; @@ -163,7 +163,7 @@ typedef struct { #define NGX_CHECK_HTTP_5XX 0x0010 #define NGX_CHECK_HTTP_ERR 0x8000 -typedef struct {/*核心配置:包括了check相关的回调函数*/ +typedef struct { ngx_uint_t type; ngx_str_t name; @@ -173,12 +173,12 @@ typedef struct {/*核心配置:包括了check相关的回调函数*/ /* HTTP */ ngx_uint_t default_status_alive; - ngx_event_handler_pt send_handler; /*TODO: ???*/ - ngx_event_handler_pt recv_handler; /*TODO: ???*/ + ngx_event_handler_pt send_handler; + ngx_event_handler_pt recv_handler; - ngx_http_upstream_check_packet_init_pt init; /*TODO: ???*/ - ngx_http_upstream_check_packet_parse_pt parse; /*TODO: ???*/ - ngx_http_upstream_check_packet_clean_pt reinit; /*TODO: ???*/ + ngx_http_upstream_check_packet_init_pt init; + ngx_http_upstream_check_packet_parse_pt parse; + ngx_http_upstream_check_packet_clean_pt reinit; unsigned need_pool; unsigned need_keepalive; @@ -941,9 +941,9 @@ ngx_http_upstream_check_free_peer(ngx_uint_t index) ngx_shmtx_unlock(&peer[index].shm->mutex); } - +/*核心函数:根据当前配置,加载peer对象,为各个peer对象注册回调函数*/ static ngx_int_t -ngx_http_upstream_check_add_timers(ngx_cycle_t *cycle) +ngx_http_upstream_check_add_timers(ngx_cycle_t *cycle) /*此函数被调用的时间是:master进程创建出worker进程时,在初始化当前module的cycle对象时,被调用*/ { ngx_uint_t i; ngx_msec_t t, delay; @@ -978,7 +978,7 @@ ngx_http_upstream_check_add_timers(ngx_cycle_t *cycle) for (i = 0; i < peers->peers.nelts; i++) { peer[i].shm = &peer_shm[i]; - peer[i].check_ev.handler = ngx_http_upstream_check_begin_handler; /* TODO 整个回调函数处理什么事件event?*/ + peer[i].check_ev.handler = ngx_http_upstream_check_begin_handler; /* 回调函数:处理定时check后端服务器的事件*/ peer[i].check_ev.log = cycle->log; peer[i].check_ev.data = &peer[i]; peer[i].check_ev.timer_set = 0; @@ -999,8 +999,8 @@ ngx_http_upstream_check_add_timers(ngx_cycle_t *cycle) } } - peer[i].send_handler = cf->send_handler; - peer[i].recv_handler = cf->recv_handler; + peer[i].send_handler = cf->send_handler; /* send event handler*/ + peer[i].recv_handler = cf->recv_handler; /* recv event handler*/ peer[i].init = cf->init; peer[i].parse = cf->parse; @@ -1013,13 +1013,13 @@ ngx_http_upstream_check_add_timers(ngx_cycle_t *cycle) delay = ucscf->check_interval > 1000 ? ucscf->check_interval : 1000; t = ngx_random() % delay; - ngx_add_timer(&peer[i].check_ev, t); + ngx_add_timer(&peer[i].check_ev, t); /*为每个后端服务器(即peer)添加timer:nginx后端会计时,时间到就触发事件的回调函数*/ } return NGX_OK; } -/* 处理check相关event*/ +/* timer event:定期check的事件*/ static void ngx_http_upstream_check_begin_handler(ngx_event_t *event) { @@ -1055,7 +1055,7 @@ ngx_http_upstream_check_begin_handler(ngx_event_t *event) return; } - interval = ngx_current_msec - peer->shm->access_time; + interval = ngx_current_msec - peer->shm->access_time; /* 距离上一次检测过去了多少毫秒 */ ngx_log_debug5(NGX_LOG_DEBUG_HTTP, event->log, 0, "http check begin handler index: %ui, owner: %P, " "ngx_pid: %P, interval: %M, check_interval: %M", @@ -1111,7 +1111,7 @@ ngx_http_upstream_check_connect_handler(ngx_event_t *event) peer = event->data; ucscf = peer->conf; - if (peer->pc.connection != NULL) { + if (peer->pc.connection != NULL) { /* 已与后端服务器建立连接 */ c = peer->pc.connection; if ((rc = ngx_http_upstream_check_peek_one_byte(c)) == NGX_OK) { goto upstream_check_connect_done; @@ -1122,20 +1122,20 @@ ngx_http_upstream_check_connect_handler(ngx_event_t *event) } ngx_memzero(&peer->pc, sizeof(ngx_peer_connection_t)); - peer->pc.sockaddr = peer->check_peer_addr->sockaddr; /*TODO 挑选后端服务器*/ + peer->pc.sockaddr = peer->check_peer_addr->sockaddr; peer->pc.socklen = peer->check_peer_addr->socklen; peer->pc.name = &peer->check_peer_addr->name; - peer->pc.get = ngx_event_get_peer; + peer->pc.get = ngx_event_get_peer; /* 如何挑选后端服务器?由Nginx内置的轮询(round robin)和IP哈希(ip hash)模块提供! */ peer->pc.log = event->log; peer->pc.log_error = NGX_ERROR_ERR; peer->pc.cached = 0; peer->pc.connection = NULL; - rc = ngx_event_connect_peer(&peer->pc); + rc = ngx_event_connect_peer(&peer->pc); /* 连接后端服务器,等待触发write和send事件的回调函数 */ - if (rc == NGX_ERROR || rc == NGX_DECLINED) { + if (rc == NGX_ERROR || rc == NGX_DECLINED) { /* 检测后端服务器失败 */ ngx_http_upstream_check_status_update(peer, 0); ngx_http_upstream_check_clean_event(peer); return; @@ -1171,7 +1171,7 @@ ngx_http_upstream_check_peek_one_byte(ngx_connection_t *c) ngx_int_t n; ngx_err_t err; - n = recv(c->fd, buf, 1, MSG_PEEK); + n = recv(c->fd, buf, 1, MSG_PEEK); /* 系统调用:从指定socket接收数据 */ err = ngx_socket_errno; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, err, @@ -1315,7 +1315,7 @@ ngx_http_upstream_check_send_handler(ngx_event_t *event) goto check_send_fail; } - if (peer->init == NULL || peer->init(peer) != NGX_OK) { + if (peer->init == NULL || peer->init(peer) != NGX_OK) { /* 初始化peer:调用ngx_http_upstream_check_http_init */ ngx_log_error(NGX_LOG_ERR, event->log, 0, "check init error with peer: %V ", @@ -1329,7 +1329,7 @@ ngx_http_upstream_check_send_handler(ngx_event_t *event) while (ctx->send.pos < ctx->send.last) { - size = c->send(c, ctx->send.pos, ctx->send.last - ctx->send.pos); + size = c->send(c, ctx->send.pos, ctx->send.last - ctx->send.pos); /* 向后端服务器发送探测数据 */ #if (NGX_DEBUG) { @@ -1360,14 +1360,14 @@ ngx_http_upstream_check_send_handler(ngx_event_t *event) return; -check_send_fail: +check_send_fail: /*心跳数据发送失败*/ ngx_http_upstream_check_status_update(peer, 0); ngx_http_upstream_check_clean_event(peer); } static void -ngx_http_upstream_check_recv_handler(ngx_event_t *event) +ngx_http_upstream_check_recv_handler(ngx_event_t *event) /*处理后端服务器返回的响应数据*/ { u_char *new_buf; ssize_t size, n; @@ -1380,8 +1380,8 @@ ngx_http_upstream_check_recv_handler(ngx_event_t *event) return; } - c = event->data; - peer = c->data; + c = event->data; + peer = c->data; /*响应数据被nginx封装在event中:rev event的data中,是connection类型,connection的data中是自定义的peer数据(如何关联上的呢?在ngx_http_upstream_check_begin_handler处理check事件时),peer中有check上下文数据,其中就包含了send和recv数据*/ if (peer->state != NGX_HTTP_CHECK_SEND_DONE) { @@ -1392,7 +1392,7 @@ ngx_http_upstream_check_recv_handler(ngx_event_t *event) return; } - ctx = peer->check_data; + ctx = peer->check_data; /*健康检测上下文*/ if (ctx->recv.start == NULL) { /* 1/2 of the page_size, is it enough? */ @@ -1449,7 +1449,7 @@ ngx_http_upstream_check_recv_handler(ngx_event_t *event) } } - rc = peer->parse(peer); + rc = peer->parse(peer); /*解析响应数据:调用ngx_http_upstream_check_http_parse*/ ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http check parse rc: %i, peer: %V ", @@ -2516,19 +2516,19 @@ ngx_http_upstream_check_status_update(ngx_http_upstream_check_peer_t *peer, ucscf = peer->conf; - if (result) { + if (result) { /* 1 检测成功 */ peer->shm->rise_count++; peer->shm->fall_count = 0; - if (peer->shm->down && peer->shm->rise_count >= ucscf->rise_count) { + if (peer->shm->down && peer->shm->rise_count >= ucscf->rise_count) { /* 成功次数达到复活阈值,复活后端服务器 */ peer->shm->down = 0; ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "enable check peer: %V ", &peer->check_peer_addr->name); } - } else { + } else { /* 0 检测失败 */ peer->shm->rise_count = 0; peer->shm->fall_count++; - if (!peer->shm->down && peer->shm->fall_count >= ucscf->fall_count) { + if (!peer->shm->down && peer->shm->fall_count >= ucscf->fall_count) { /* 失败次数达到关停阈值,关停后端服务器 */ peer->shm->down = 1; ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "disable check peer: %V ", @@ -2536,7 +2536,7 @@ ngx_http_upstream_check_status_update(ngx_http_upstream_check_peer_t *peer, } } - peer->shm->access_time = ngx_current_msec; + peer->shm->access_time = ngx_current_msec; /* 记录检测时间,以毫秒为单位*/ } @@ -2574,7 +2574,7 @@ ngx_http_upstream_check_clean_event(ngx_http_upstream_check_peer_t *peer) peer->state = NGX_HTTP_CHECK_ALL_DONE; if (peer->check_data != NULL && peer->reinit) { - peer->reinit(peer); + peer->reinit(peer); /*重新初始化peer:调用ngx_http_upstream_check_http_reinit函数*/ } peer->shm->owner = NGX_INVALID_PID; @@ -2614,7 +2614,7 @@ ngx_http_upstream_check_finish_handler(ngx_event_t *event) static ngx_int_t ngx_http_upstream_check_need_exit() { - if (ngx_terminate || ngx_exiting || ngx_quit) { + if (ngx_terminate || ngx_exiting || ngx_quit) { /* 当nginx正在终止时,清空所有相关event */ ngx_http_upstream_check_clear_all_events(); return 1; } @@ -2624,7 +2624,7 @@ ngx_http_upstream_check_need_exit() static void -ngx_http_upstream_check_clear_all_events() +ngx_http_upstream_check_clear_all_events() /* 删除timer,关闭connection,销毁peer的内存pool */ { ngx_uint_t i; ngx_connection_t *c; @@ -3089,7 +3089,7 @@ ngx_http_upstream_check(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) value = cf->args->elts;/*数组指针,指向:{{len = 5, data = 0x555555706795 "check"}, {len = 13, data = 0x55555570679b "interval=1000"}, {len = 6, data = 0x5555557067a9 "rise=1"}, {len = 6, data = 0x5555557067b0 "fall=3"}, {len = 12, data = 0x5555557067b7 "timeout=2000"}, {len = 9, data = 0x5555557067c4 "type=http"}}*/ - /* 获取已经注册的自定义configuration结构体, TODO: 那么,在哪里注册的configuration结构体呢?*/ + /* 获取已经注册的自定义configuration结构体, 那么,在哪里注册的configuration结构体呢?在ngx_http_upstream_check_module_ctx变量中*/ ucscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_check_module); if (ucscf == NULL) { @@ -3754,7 +3754,7 @@ ngx_http_upstream_check_init_shm(ngx_conf_t *cf, void *conf) shm_name, shm_size); shm_zone->data = cf->pool; - check_peers_ctx = ucmcf->peers; + check_peers_ctx = ucmcf->peers; /*获取后端服务器列表*/ shm_zone->init = ngx_http_upstream_check_init_shm_zone; } diff --git a/tutorial/README.adoc b/tutorial/README.adoc index 8536bdb..973d4cf 100644 --- a/tutorial/README.adoc +++ b/tutorial/README.adoc @@ -77,7 +77,15 @@ image::./imgs/nginx-src-structure.png[Nginx源码结构] === 3. 一个开源实例模块 开源地址:link:https://github.com/yaoweibin/nginx_upstream_check_module[nginx_upstream_check_module] -=== 4. 编译开源组件 +=== 4. 编译开源模块 + +在编译之前,需要参照开源模块的README文件,对nginx源码打补丁,否则主动探测功能会失败: +[source,shell] +---- +cd /path/to/nginx_source_code/nginx-1.26.0/ +patch -p1 < /path/to/nginx_http_upstream_check_module/check_1.20.1+.patch +---- + 为了便于编译,我们将编译命令和参数写成shell脚本(`configure-health-check.sh`): [source,shell] ---- @@ -158,6 +166,7 @@ image::./imgs/debug-nginx.png[调试nginx] === 3.1 源码解读 + ==== 3.1.1 配置与编译 ngx_http_upstream_check_module模块的 `config` 文件内容如下: @@ -234,72 +243,197 @@ fi 1. `ngx_feature="ngx_http_upstream_check_module"` 定义了正在配置的 Nginx 模块的名称。 - 2. `ngx_feature_name=` 通常用于定义一个宏名称,但这里留空,表示可能不需要定义特定的宏。 - 3. `ngx_feature_run=no` 表示不需要执行编译后的测试程序来验证特性。这可能是因为模块的存在与否不依赖于运行时检测。 - 4. `ngx_feature_incs=` 留空,表示没有特定的头文件需要包含。 - 5. `ngx_feature_libs=""` 留空,表示没有特定的库需要链接。 - 6. `ngx_feature_path="$ngx_addon_dir"` 设置模块的路径为 `ngx_addon_dir` 变量的值,这通常是模块的安装目录。 - 7. `ngx_feature_deps="$ngx_addon_dir/ngx_http_upstream_check_module.h"` 定义模块的依赖头文件,这里是模块的头文件。 - 8. `ngx_check_src="$ngx_addon_dir/ngx_http_upstream_check_module.c"` 指定模块的源文件路径。 - 9. `ngx_feature_test="int a;"` 提供一个非常简单的测试代码,这里仅仅声明了一个整型变量,实际上这个测试可能不会对模块的检测产生实际作用。 - 10. `. auto/feature` 执行 Nginx 的 `auto/feature` 脚本来处理配置。 - 11. `if [ $ngx_found = yes ]; then` 如果 `auto/feature` 脚本执行后 `ngx_found` 变量被设置为 `yes`,则继续执行以下命令。 - 12. `have=NGX_HTTP_UPSTREAM_CHECK . auto/have` 使用 `auto/have` 脚本定义一个宏,表示检测到 `ngx_http_upstream_check_module`。 - 13. `CORE_INCS="$CORE_INCS $ngx_feature_path"` 将模块的路径添加到 Nginx 核心的头文件搜索路径中。 - 14. `ngx_addon_name=ngx_http_upstream_check_module` 设置第三方模块的名称。 - 15. `HTTP_MODULES="$HTTP_MODULES ngx_http_upstream_check_module"` 将模块名称添加到 HTTP 模块列表中。 - 16. `NGX_ADDON_DEPS="$NGX_ADDON_DEPS $ngx_feature_deps"` 将模块的依赖项添加到第三方模块的依赖列表中。 - 17. `NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_check_src"` 将模块的源文件添加到第三方模块的源文件列表中。 - 18. `else` 如果 `ngx_found` 不是 `yes`,执行错误处理。 - 19. `cat << END ... END` 输出错误信息,指出 `ngx_http_upstream_check_module` 模块的添加出现错误。 - 20. `exit 1` 退出脚本并返回状态码 1,表示配置过程中出现错误。 整体来看,这个 `config` 文件的作用是检测 `ngx_http_upstream_check_module` 是否可以被添加到 Nginx 中,并根据检测结果更新 Nginx 的配置,以便在编译时包含这个模块。如果检测失败,则输出错误信息并退出配置过程。 ---- -== 四. 开发流程 +==== 3.1.2 代码调试与跟踪 + +首先,使用gdb对编译后的nginx进行debugging +[source,shell] +---- +# 调式nginx +gdb /opt/nginx/sbin/nginx + +# 载入事先准备好的断点 +source gdb.cfg + +# 开启子进程调试 +set follow-fork-mode child +set set detach-on-fork on -== 五. 其他 +# 终端显示更加友好 +set print pretty + +# 开始运行nginx +run +---- + +【此处忽略阅读代码过程...】 + +==== 3.1.3 代码总结 + +===== 3.1.3.1 关键数据结构 + +结构体: `ngx_module_t` 结构体,名为 `ngx_http_upstream_check_module` 的变量 +[source,c] +---- +ngx_module_t ngx_http_upstream_check_module = { + NGX_MODULE_V1, + &ngx_http_upstream_check_module_ctx, /* module context */ + ngx_http_upstream_check_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + ngx_http_upstream_check_init_process, /* init process: 在master进程fork出workder进程的时候调用 */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; +---- +注意: **其中的回调函数 `ngx_http_upstream_check_init_process`是在Nginx master进程创建worker进程后开始执行的,是`ngx_http_uptstream_check_module`模块的入口** ,我们看看这个函数做了什么? + +===== 3.1.3.2 模块入口 + +[source,c] +---- +static ngx_int_t +ngx_http_upstream_check_init_process(ngx_cycle_t *cycle) +{ + ngx_http_upstream_check_main_conf_t *ucmcf; + + if (ngx_process != NGX_PROCESS_WORKER) { + return NGX_OK; + } + + ucmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_upstream_check_module); + if (ucmcf == NULL) { + return NGX_OK; + } + + return ngx_http_upstream_check_add_timers(cycle); +} +---- + +1. 首先判断当前进程是不是worker进程,如果不是worker进程,则直接返回; +2. 然后,获取当前module的、已经**事先装载的配置数据**(“事先”的意思是:在创建worker进程之前、在初始化和装载配置数据阶段,此处暂略,后面再讲); +3. 最后进入 `ngx_http_upstream_check_add_timers` 函数,这是核心函数,为当前模块注册timer:定时向后端服务器发送健康检测请求,并统计检测结果。关键代码如下: + [source,c] + ---- + static ngx_int_t ngx_http_upstream_check_add_timers(ngx_cycle_t *cycle) + { + ... + for (i = 0; i < peers->peers.nelts; i++) { + peer[i].shm = &peer_shm[i]; + + peer[i].check_ev.handler = ngx_http_upstream_check_begin_handler; /* 注册check事件的回调函数 */ + ... + + peer[i].check_timeout_ev.handler = ngx_http_upstream_check_timeout_handler; /* 注册check事件的超时回调函数 */ + ... + cf = ucscf->check_type_conf; /* check_type_conf的赋值来自于全局变量ngx_check_types[] */ + ... + peer[i].send_handler = cf->send_handler; /* 发送check数据的回调函数 */ + peer[i].recv_handler = cf->recv_handler; /* 接收check响应数据的回调函数 */ + + ... + ngx_add_timer(&peer[i].check_ev, t); /* 为check事件添加定时器 */ + } + + return NGX_OK; + } + ---- +需要注意几点: + +- 首先,Nginx的高性能秘诀是其Event模型和Event Loop机制(略,读者可自行查阅资料),同样地,这个模块为主动探测也创建了两个event变量(`ngx_http_upstream_check_peer_s` 中的 `check_ev` 和 `check_timeout_ev` 变量),当前函数就为这两个事件各注册一个回调函数(handler); +- 除此以外,还有nginx内置的两个事件:`ngx_connection_t` 中的 `write` 和 `read` 事件,主要完成网络连接的IO操作,Nginx规定每个event都必须注册一个回调函数,当前函数也为 `write` 和 `read` 事件注册回调函数,回调函数的指派来自全局变量数组: `ngx_check_types` (细节藏在 `ngx_http_upstream_check_begin_handler` 函数中),我们暂时只关注通过http协议的主动探测; +[source,c] +---- +static ngx_check_conf_t ngx_check_types[] = { + ... + { NGX_HTTP_CHECK_HTTP, + ngx_string("http"), + ngx_string("GET / HTTP/1.0\r\n\r\n"), + NGX_CONF_BITMASK_SET | NGX_CHECK_HTTP_2XX | NGX_CHECK_HTTP_3XX, + ngx_http_upstream_check_send_handler, /* ngx_connection_t结构体中 write 事件的回调函数 */ + ngx_http_upstream_check_recv_handler, /* ngx_connection_t结构体中 send 事件的回调函数 */ + ... + 1, + 1 }, + ... +---- +- 最后,调用 `ngx_add_timer()` 为 `check_ev` 事件注册定时器,Nginx会定时调用 `ngx_http_upstream_check_begin_handler()` 函数,一旦发现超时,则会调用 `ngx_http_upstream_check_timeout_handler`。 + +===== 3.1.3.3 主动检测逻辑 + + 继续跟踪 `ngx_http_upstream_check_begin_handler()` 函数即可,注意其中一个点: + +1. 如何保证多个worker进程之间的互斥操作的? +2. 如何挑选后端服务器(peer)的? 在此文中,peer的选择主要由nginx内置负载均衡模块完成,例如, `ngx_http_upstream_round_robin` 模块或ip hash模块, `ngx_http_upstream_check` 模块只完成地后端服务器的主动探测和探测结果汇总。 通过给nginx源码打补丁, `ngx_http_upstream_check` 模块将 `ngx_http_upstream_check_add_peer()` 的调用插入到nginx原生round_robin模块中 `ngx_http_upstream_round_robin.c` 的初始化函数 `ngx_http_upstream_init_round_robin()` 中。 +3. 如何解析后端服务器的响应数据的? 跟踪 `ngx_http_upstream_check_recv_handler()` 函数。 + +===== 3.1.3.4 检测超时逻辑 + + 跟踪 `ngx_http_upstream_check_timeout_handler()` 函数即可 + +===== 3.1.3.5 配置初始化逻辑 + +一个数据结构:注册模块配置指令的数据接口 +[source,c] +---- +static ngx_command_t ngx_http_upstream_check_commands[] = { + + { ngx_string("check"), + NGX_HTTP_UPS_CONF|NGX_CONF_1MORE, + ngx_http_upstream_check, /* 从配置文件中解析check指令的参数,即主动探测的配置*/ + 0, + 0, + NULL }, + ... +---- +接下来,跟踪 `ngx_http_upstream_check()` 函数即可。 -== 六. 参考资料 +== 四. 参考资料 1. 负载均衡解释:link:https://www.nginx.org.cn/article/detail/440[] 2. nginx中文配置手册:link:https://wizardforcel.gitbooks.io/nginx-doc/content/index.html[] 3. 高可用配置示例:link:https://blog.csdn.net/IT_10/article/details/89365436[]