[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 ]  

-----

 

posted on 2020-02-07 20:12  toong  阅读(681)  评论(0编辑  收藏  举报