[nginx] nginx源碼分析--健康檢查模塊鎖分析
健康檢查模塊
見前文:[nginx] nginx源碼分析--健康檢查模塊 其中有一張框架圖,
接下來的內容,將會利用到這個圖中的內容。
[classic_tong @ https:////www.rzrgm.cn/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()
調用該代碼的地方是本輪健康檢查結束的地方(成功或失?。蕰尫舘wner,等待下一次重新
搶鎖。
綜上,我們目前已經知道了,owner的定義,以及它的三個op,初始化,加鎖,解鎖。
當, 我繼續閱讀這部分代碼時,發現在很多退出函數 ngx_http_upstream_check_need_exit()離開的
地方,并沒有進行解鎖。那么因為使用了共享內存,是否有一中可能,當舊nginx程序退出后,由于其
沒有解鎖,而導致新的nginx程序再也搶不到鎖了呢?
我們知道,nginx在每次更新配置的時候,會使用啟動新work退出舊work的方式進行。
[classic_tong @ https:////www.rzrgm.cn/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.rzrgm.cn/hugetong/p/12274125.html ]
-----
完
浙公網安備 33010602011771號