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

    -----

  • 相关阅读:
    【转】内部Handler类引起内存泄露
    检测是否存在相机硬件代码
    asp.net 过滤器
    iis 中经典和集成模式对应webconfig节点
    事务
    C# Excel操作
    一步一步部署SSIS包图解教程
    js和.net操作Cookie遇到的问题
    File,FileInfo,Directory,DirectoryInfo
    C#文件Copy
  • 原文地址:https://www.cnblogs.com/hugetong/p/12274125.html
Copyright © 2020-2023  润新知