[nginx] nginx源码分析--健康检查模块锁分析
健康检查模块
见前文:[nginx] nginx源码分析--健康检查模块 其中有一张框架图,
接下来的内容,将会利用到这个图中的内容。
[classic_tong @ https:////www.cnblogs.com/hugetong/p/12274125.html ]
描述
我们知道nginx是多进程的,每个进程都保存了相同的配置。但是实际上,
并不需要每一个进程对每一个后端服务器进行。
于是健康检查模块在这里需要一个进程间同步机制,用来协商哪一个进程对
哪一个后端服务器进行检查。
接下来,我们将描述这个机制。
分析
该模块采用了owner的概念。在nginx的全局,对每一个后端服务器抽象了一个
“后端服务器检测”的实体,该实体有一个owner属性。该属性的写操作有一个用来
保护临界区的互斥锁。
健康检测是一次次的,所有进程都是设置了timer来激活对每一个后端服务器的健康
检查。timer时间达到后,所有进程会同时来抢锁,抢到锁的进程变得到了对该实体
的本次健康检查权限。随后将自己的PID信息写入owner内。
该实体的数据结构和它的owner放置在共享内存中,由所有进程共享。
数据结构
该锁的数据结构如下:
typedef struct { ngx_shmtx_t mutex; ngx_atomic_t lock; ... ... ngx_pid_t owner; ... ... } ngx_http_upstream_check_peer_shm_t;
以上结构存储在共享内存中,共享内存初始化的时候会调用下边的函数完成具体值的初始化:
ngx_http_upstream_check_init_shm_zone()
并将owner的值赋为未使用
peer_shm->owner = NGX_INVALID_PID;
代码逻辑
一 现在你要回到前文那张图里去,然后,我们会知道如下信息:
1. health check模块的入口是通过event机制调用的函数:
ngx_http_upstream_check_begin_handler()
2. 在begin_handler()里,会根据不同的检查类型分别调用如下,不同的函数:
ngx_http_upstream_check_peek_handler()
ngx_http_upstream_check_send_handler()
ngx_http_upstream_check_recv_handler()
... ...
3. health check会通过如下函数作为出口,完成优雅退出:
ngx_http_upstream_check_need_exit()
该函数内,会检测进程退出的标记,并清理资源,注销已经注册的资源。
需要强调的是,在这里,并没有对上文中已经抢到的owner进行清除。
二 接下来的内容,在本文中新增,之前的框架图中并不包含。
1. 加锁
加锁是在入口函数 ngx_http_upstream_check_begin_handler() 做了如下内容。
a 检测是否已经有人抢到了owner, 没有则设置owner为自己,否则返回。
b 调研其他的handler()
c 其他的大部分handler会在一开始的时候调用 ngx_http_upstream_check_need_exit()
如果时机合适变回直接退出。
2. 解锁
解锁在一个专门的函数里完成,该函数没有被异步注册,而是在recv完成或逻辑异常结束(健康
检测失败)时在代码里显示的调用。
ngx_http_upstream_check_clean_event()
调用该代码的地方是本轮健康检查结束的地方(成功或失败),故会释放owner,等待下一次重新
抢锁。
综上,我们目前已经知道了,owner的定义,以及它的三个op,初始化,加锁,解锁。
当, 我继续阅读这部分代码时,发现在很多退出函数 ngx_http_upstream_check_need_exit()离开的
地方,并没有进行解锁。那么因为使用了共享内存,是否有一中可能,当旧nginx程序退出后,由于其
没有解锁,而导致新的nginx程序再也抢不到锁了呢?
我们知道,nginx在每次更新配置的时候,会使用启动新work退出旧work的方式进行。
[classic_tong @ https:////www.cnblogs.com/hugetong/p/12274125.html ]
共享内存
为了回答这个问题,我又对共享内存部分做了如下的简单分析。
master的主循环函数:
ngx_master_process_cycle()
收到更新配置的信号后,会走进如下代码:
if (ngx_reconfigure) { ngx_reconfigure = 0; if (ngx_new_binary) { ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_RESPAWN); ngx_start_cache_manager_processes(cycle, 0); ngx_noaccepting = 0; continue; } ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring"); cycle = ngx_init_cycle(cycle); if (cycle == NULL) { cycle = (ngx_cycle_t *) ngx_cycle; continue; } ngx_cycle = cycle; ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); ngx_start_worker_processes(cycle, ccf->worker_processes, NGX_PROCESS_JUST_RESPAWN); ngx_start_cache_manager_processes(cycle, 1); /* allow new processes to start */ ngx_msleep(100); live = 1; ngx_signal_worker_processes(cycle, ngx_signal_value(NGX_SHUTDOWN_SIGNAL)); }
其中,ngx_init_cycle(cycle); 会初始化共享内存,并存放在cycle结构体中,然后,在
函数 ngx_start_worker_processes() 中会fork出一批新的worker,公用这部分共享内存。
cycle的结构中的shared_memory保存这这个共享内存。
struct ngx_cycle_s { void ****conf_ctx; ... ... ngx_list_t shared_memory; ... ... ngx_cycle_t *old_cycle; ... ... };
通过阅读函数ngx_init_cycle(cycle);可以发现这份共享内存并不与旧的共享内存复用,
旧的共享内存保存在old_cycle中,并被释放。
ngx_cycle_t * ngx_init_cycle(ngx_cycle_t *old_cycle) { ... for (i = 0; /* void */ ; i++) { if (ngx_shm_alloc(&shm_zone[i].shm) != NGX_OK) { goto failed; } ... if (shm_zone[i].init(&shm_zone[i], NULL) != NGX_OK) { goto failed; } ... } for (i = 0; /* void */ ; i++) { ... ngx_shm_free(&oshm_zone[i].shm); ... } }
所以,即使在退出函数ngx_http_upstream_check_need_exit()离开的时候没有情况owner也没用关系,
因为所有新的woker进程都使用新的共享内存来同步。
只有一种特殊情况会导致这个锁出现问题,就是某一个worker异常死掉了,那么他的锁便得不到释放,
导致其他进程不能正常健康检查。
[classic_tong @ https:////www.cnblogs.com/hugetong/p/12274125.html ]
-----
完