首頁 運維 Nginx nginx驚群問題如何解決

nginx驚群問題如何解決

May 17, 2023 pm 08:49 PM
nginx

1. 解決方式

每個worker進程被建立的時候,都會呼叫ngx_worker_process_init()方法初始化目前worker進程,這個過程中有一個非常重要的步驟,也就是每個worker程序都會呼叫epoll_create()方法為自己建立一個獨特的epoll句柄。對於每一個需要監聽的端口,都有一個文件描述符與之對應,而worker進程只有將該文件描述符通過epoll_ctl()方法添加到當前進程的epoll句柄中,並且監聽accept事件,此時​​才會被客戶端的連線建立事件觸發,從而處理該事件。從這裡也可以看出,worker進程如果沒有將所需要監聽的連接埠對應的檔案描述子加入到該進程的epoll句柄中,那麼其是無法被觸發對應的事件的。基於這個原理,nginx就使用了一個共享鎖定來控制當前進程是否有權限將需要監聽的連接埠加入到目前進程的epoll句柄中,也就是說,只有取得鎖定的進程才會監聽目標連接埠。透過這種方式,就確保了每次事件發生時,只有一個worker進程會被觸發。如下圖所示為worker進程工作循環的示意圖:

nginx驚群問題如何解決

這裡關於圖中的流程,需要說明的一點是,每個worker進程在進入循環之後就會嘗試取得共享鎖,如果沒有取得到,就會將所監聽的連接埠的檔案描述符從目前進程的epoll句柄中移除(即使不存在也會移除),這麼做的主要目的是防止遺失客戶端連線事件,即使這可能造成少量的驚群問題,但並不嚴重。試想一下,如果按照理論,在當前進程釋放鎖的時候就將監聽的端口的文件描述符從epoll句柄中移除,那麼在下一個worker進程獲取鎖之前,這段時間各個端口對應的文件描述符是沒有任何epoll句柄進行監聽的,此時就會造成事件的遺失。如果反過來,按照圖中的在獲取鎖失敗的時候才移除監聽的文件描述符,由於獲取鎖失敗,則說明當前一定有一個進程已經監聽了這些文件描述符,因而此時移除是安全的。但是這樣會造成的一個問題是,按照上圖,當前進程在一個循環執行完畢的時候,會釋放鎖,然後處理其他的事件,注意這個過程中其是沒有釋放所監聽的文件描述符的。此時,如果另一個進程獲取到了鎖,並且監聽了文件描述符,那麼這個時候就有兩個進程監聽了文件描述符,因而此時如果客戶端發生連接建立事件,那麼就會觸發兩個worker進程。這個問題是可以容忍的,主要原因有兩點:

  1. 這種驚群現像只會觸發較少數量的工作進程,相比每次都驚醒所有工作進程要好得多

  2. 會發生這種驚群問題的主要原因是,當前進程釋放了鎖,但是沒有釋放所監聽的檔案描述符,但是worker進程在釋放鎖之後主要是處理客戶端連線的讀寫事件和檢查標誌位,這個過程是非常短的,在處理完之後,其就會嘗試獲取鎖,這個時候就會釋放所監聽的文件描述符了,而相較而言,取得鎖的worker進程在等待處理客戶端的連線建立事件的事件就更長了,因而會發生驚群問題的機率還是比較小的。

2. 原始碼講解

worker進程初始事件的方法主要是在ngx_process_events_and_timers()方法中進行的,下面我們就來看看該方法是如何處理整個流程的,如下是該方法的源碼:

void ngx_process_events_and_timers(ngx_cycle_t *cycle) {
 ngx_uint_t flags;
 ngx_msec_t timer, delta;

 if (ngx_trylock_accept_mutex(cycle) == ngx_error) {
  return;
 }

 // 这里开始处理事件,对于kqueue模型,其指向的是ngx_kqueue_process_events()方法,
 // 而对于epoll模型,其指向的是ngx_epoll_process_events()方法
 // 这个方法的主要作用是,在对应的事件模型中获取事件列表,然后将事件添加到ngx_posted_accept_events
 // 队列或者ngx_posted_events队列中
 (void) ngx_process_events(cycle, timer, flags);

 // 这里开始处理accept事件,将其交由ngx_event_accept.c的ngx_event_accept()方法处理;
 ngx_event_process_posted(cycle, &ngx_posted_accept_events);

 // 开始释放锁
 if (ngx_accept_mutex_held) {
  ngx_shmtx_unlock(&ngx_accept_mutex);
 }

 // 如果不需要在事件队列中进行处理,则直接处理该事件
 // 对于事件的处理,如果是accept事件,则将其交由ngx_event_accept.c的ngx_event_accept()方法处理;
 // 如果是读事件,则将其交由ngx_http_request.c的ngx_http_wait_request_handler()方法处理;
 // 对于处理完成的事件,最后会交由ngx_http_request.c的ngx_http_keepalive_handler()方法处理。

 // 这里开始处理除accept事件外的其他事件
 ngx_event_process_posted(cycle, &ngx_posted_events);
}
登入後複製

上面的程式碼中,我們省略了大部分的檢查工作,只留下了骨架程式碼。首先,worker程序會呼叫ngx_trylock_accept_mutex()方法取得鎖,這其中如果取得到了鎖就會監聽各個連接埠對應的檔案描述子。然後會呼叫ngx_process_events()方法處理epoll句柄中監聽到的事件。接著會釋放共享鎖,最後就是處理已建立連線的客戶端的讀寫事件。下面我們來看看ngx_trylock_accept_mutex()方法是如何取得共享鎖的:

ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle) {
 // 尝试使用cas算法获取共享锁
 if (ngx_shmtx_trylock(&ngx_accept_mutex)) {

  // ngx_accept_mutex_held为1表示当前进程已经获取到了锁
  if (ngx_accept_mutex_held && ngx_accept_events == 0) {
   return ngx_ok;
  }

  // 这里主要是将当前连接的文件描述符注册到对应事件的队列中,比如kqueue模型的change_list数组
  // nginx在启用各个worker进程的时候,默认情况下,worker进程是会继承master进程所监听的socket句柄的,
  // 这就导致一个问题,就是当某个端口有客户端事件时,就会把监听该端口的进程都给唤醒,
  // 但是只有一个worker进程能够成功处理该事件,而其他的进程被唤醒之后发现事件已经过期,
  // 因而会继续进入等待状态,这种现象称为"惊群"现象。
  // nginx解决惊群现象的方式一方面是通过这里的共享锁的方式,即只有获取到锁的worker进程才能处理
  // 客户端事件,但实际上,worker进程是通过在获取锁的过程中,为当前worker进程重新添加各个端口的监听事件,
  // 而其他worker进程则不会监听。也就是说同一时间只有一个worker进程会监听各个端口,
  // 这样就避免了"惊群"问题。
  // 这里的ngx_enable_accept_events()方法就是为当前进程重新添加各个端口的监听事件的。
  if (ngx_enable_accept_events(cycle) == ngx_error) {
   ngx_shmtx_unlock(&ngx_accept_mutex);
   return ngx_error;
  }

  // 标志当前已经成功获取到了锁
  ngx_accept_events = 0;
  ngx_accept_mutex_held = 1;

  return ngx_ok;
 }

 // 前面获取锁失败了,因而这里需要重置ngx_accept_mutex_held的状态,并且将当前连接的事件给清除掉
 if (ngx_accept_mutex_held) {
  // 如果当前进程的ngx_accept_mutex_held为1,则将其重置为0,并且将当前进程在各个端口上的监听
  // 事件给删除掉
  if (ngx_disable_accept_events(cycle, 0) == ngx_error) {
   return ngx_error;
  }

  ngx_accept_mutex_held = 0;
 }

 return ngx_ok;
}
登入後複製

上面的程式碼中,本質上主要做了三件事:

  1. 透過ngx_shmtx_trylock()方法嘗試使用cas方法取得共用鎖定;

  2. #取得鎖定之後則呼叫ngx_enable_accept_events()方法監聽目標連接埠對應的檔案描述子;

  3. ##如果沒有取得到鎖,則呼叫ngx_disable_accept_events()方法釋放所監聽的檔案描述子。
#######

以上是nginx驚群問題如何解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

nginx在windows中怎麼配置 nginx在windows中怎麼配置 Apr 14, 2025 pm 12:57 PM

如何在 Windows 中配置 Nginx?安裝 Nginx 並創建虛擬主機配置。修改主配置文件並包含虛擬主機配置。啟動或重新加載 Nginx。測試配置並查看網站。選擇性啟用 SSL 並配置 SSL 證書。選擇性設置防火牆允許 80 和 443 端口流量。

docker容器名稱怎麼查 docker容器名稱怎麼查 Apr 15, 2025 pm 12:21 PM

可以通過以下步驟查詢 Docker 容器名稱:列出所有容器(docker ps)。篩選容器列表(使用 grep 命令)。獲取容器名稱(位於 "NAMES" 列中)。

docker怎麼啟動容器 docker怎麼啟動容器 Apr 15, 2025 pm 12:27 PM

Docker 容器啟動步驟:拉取容器鏡像:運行 "docker pull [鏡像名稱]"。創建容器:使用 "docker create [選項] [鏡像名稱] [命令和參數]"。啟動容器:執行 "docker start [容器名稱或 ID]"。檢查容器狀態:通過 "docker ps" 驗證容器是否正在運行。

怎麼查看nginx是否啟動 怎麼查看nginx是否啟動 Apr 14, 2025 pm 01:03 PM

確認 Nginx 是否啟動的方法:1. 使用命令行:systemctl status nginx(Linux/Unix)、netstat -ano | findstr 80(Windows);2. 檢查端口 80 是否開放;3. 查看系統日誌中 Nginx 啟動消息;4. 使用第三方工具,如 Nagios、Zabbix、Icinga。

nginx怎麼查版本 nginx怎麼查版本 Apr 14, 2025 am 11:57 AM

可以查詢 Nginx 版本的方法有:使用 nginx -v 命令;查看 nginx.conf 文件中的 version 指令;打開 Nginx 錯誤頁,查看頁面的標題。

docker怎麼創建容器 docker怎麼創建容器 Apr 15, 2025 pm 12:18 PM

在 Docker 中創建容器: 1. 拉取鏡像: docker pull [鏡像名] 2. 創建容器: docker run [選項] [鏡像名] [命令] 3. 啟動容器: docker start [容器名]

nginx怎麼配置雲服務器域名 nginx怎麼配置雲服務器域名 Apr 14, 2025 pm 12:18 PM

在雲服務器上配置 Nginx 域名的方法:創建 A 記錄,指向雲服務器的公共 IP 地址。在 Nginx 配置文件中添加虛擬主機塊,指定偵聽端口、域名和網站根目錄。重啟 Nginx 以應用更改。訪問域名測試配置。其他注意事項:安裝 SSL 證書啟用 HTTPS、確保防火牆允許 80 端口流量、等待 DNS 解析生效。

怎麼啟動nginx服務器 怎麼啟動nginx服務器 Apr 14, 2025 pm 12:27 PM

啟動 Nginx 服務器需要按照不同操作系統採取不同的步驟:Linux/Unix 系統:安裝 Nginx 軟件包(例如使用 apt-get 或 yum)。使用 systemctl 啟動 Nginx 服務(例如 sudo systemctl start nginx)。 Windows 系統:下載並安裝 Windows 二進製文件。使用 nginx.exe 可執行文件啟動 Nginx(例如 nginx.exe -c conf\nginx.conf)。無論使用哪種操作系統,您都可以通過訪問服務器 IP

See all articles