[nginx] nginx源码分析--健康检查模块锁分析

摘要:
关于健康检查模块,请参阅上一篇文章:[nginx]nginx源代码分析——健康检查模块包含一个框架图。以下内容将使用此图中的内容。重复健康检查。所有进程都设置了计时器,以激活每个后端服务器的运行状况检查。当达到计时器时间时,所有进程将同时获取锁,获取锁的进程将有权对实体进行此健康检查。ngx_httpupstream_check_clean_Event()在本轮健康检查结束时调用代码,因此它将释放所有者并等待下一次锁抓取。
健康检查模块

见前文:[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 ]  

-----

免责声明:文章转载自《[nginx] nginx源码分析--健康检查模块锁分析》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇分布式下Session一致性问题JDK自带方法实现AES对称加密下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

使用 Consul + Docker + Registrator + Consul-template 搭建可伸缩服务发现平台

背景介绍 Docker 的出现,改变了软件的交付方式,使得开发、测试、运维都能在一个完全统一的环境中进行。在服务容器化的网络中,需要添加服务发现功能。每个服务可能对应多个示例以容器运行在多个机器上,并且提供自动注册和失败检测机制。目前服务发现已经有很多成熟的解决方案,例如 Spring Cloud中的 Eureka 注册中心,Hystrix 断路器,zuu...

为Nginx服务器配置黑(白)名单的防火墙

处在黑名单中的ip与网络,将无法访问web服务。 处在白名单中的ip,访问web服务时,将不受nginx所有安全模块的限制。 支持动态黑名单(需要与ngx_http_limit_req 配合) 具体详见下面的说明   文件配置方法说明   一、定义黑名单或白名单方法: 在Nginx的conf目录下面建立blockip.conf文件,把想要屏蔽的IP只要加...

向大神学习

一、关注前端前沿知识,具体做法是加入一些前端的微信公众号,每天推送一点前沿知识新鲜玩意让你了解,每天去逛逛知名的社区和论坛,如掘金、css88、csdn; 尤其推荐使用掘金chrome插件版。这样每天打开页面就可以看到最新整理的文章和github项目了。 二、关注基本功,html、css、js。尤其是js;这没什么好说的,除了html,其他两个深似海。学...

详解封装源码包成RPM包

源码编译安装是最常用安装软件方式,可是面对工作量巨大时候就需要我们的RPM包上场了,统一的模块,一键安装。在面对一定数量的服务器上,RPM就可以为我们节省大量的时间。 RPM可以在网上下载,但是当我们需要用到特殊模块时,这些网上的RPM就显得那么的苍白无力了。所以自行封装打包成了一和需求。现在就介绍如何封装打包。 打包流程 1)准备源码软件 2)安装r...

centos 系统下安装FastDFS+nginx+fastdfs-nginx-module安装配置

前言: 以前的项目上传的文件都是保存到本地或者是局域网内的共享文件夹下,由于数据量,服务器的负载均衡(分机的某些图片无法访问的问题处理)等因素的情况下,就想到用fastdfs来文件管理,花了几天时间硬着头皮去学习怎样安装配置,由于linux基础不好,安装配置起来感觉有点费力,不得不随时去查找一些资料,好在经过这几天的努力安装配置fastdfs最终还是搞定了...

实验 1 : Mininet 源码安装 和可视化拓扑工具

实验 1 : Mininet 源码安装 和可视化拓扑工具 一、 实验 目的 掌握 Mininet 的源码安装方法和 miniedit 可视化拓扑生成工具。 二 、实验 任务 使用源码安装 Mininet 的 2.3.0d6 版本,并使用可视化拓扑工具生成一个最简拓扑(1 台交换机连接 2 台主机)。 三 、 实验步骤 1. 实验环境 安装了...