淺談Nginx是如何解決驚群效應(yīng)的
什么是驚群效應(yīng)?
第一次聽到的這個(gè)名詞的時(shí)候覺得很是有趣,不知道是個(gè)什么意思,總覺得又是奇怪的中文翻譯導(dǎo)致的。
復(fù)雜的說(來源于網(wǎng)絡(luò))TLDR;
驚群效應(yīng)(thundering herd)是指多進(jìn)程(多線程)在同時(shí)阻塞等待同一個(gè)事件的時(shí)候(休眠狀態(tài)),如果等待的這個(gè)事件發(fā)生,那么他就會(huì)喚醒等待的所有進(jìn)程(或者線程),但是最終卻只能有一個(gè)進(jìn)程(線程)獲得這個(gè)時(shí)間的“控制權(quán)”,對(duì)該事件進(jìn)行處理,而其他進(jìn)程(線程)獲取“控制權(quán)”失敗,只能重新進(jìn)入休眠狀態(tài),這種現(xiàn)象和性能浪費(fèi)就叫做驚群效應(yīng)。
簡單的講(我的大白話)
有一道雷打下來,把很多人都吵醒了,但只有其中一個(gè)人去收衣服了。也就是:有一個(gè)請(qǐng)求過來了,把很多進(jìn)程都喚醒了,但只有其中一個(gè)能最終處理。
原因&問題
說起來其實(shí)也簡單,多數(shù)時(shí)候?yàn)榱颂岣邞?yīng)用的請(qǐng)求處理能力,會(huì)使用多進(jìn)程(多線程)去監(jiān)聽請(qǐng)求,當(dāng)請(qǐng)求來時(shí),因?yàn)槎加心芰μ幚?,所以就都被喚醒了?/p>
而問題就是,最終還是只能有一個(gè)進(jìn)程能來處理。當(dāng)請(qǐng)求多了,不停地喚醒、休眠、喚醒、休眠,做了很多的無用功,上下文切換又累,對(duì)吧。那怎么解決這個(gè)問題呢?下面就是今天要看的重點(diǎn),我們看看 nginx 是如何解決這個(gè)問題的。
Nginx 架構(gòu)
第一點(diǎn)我們需要了解 nginx 大致的架構(gòu)是怎么樣的。nginx 將進(jìn)程分為 master 和 worker 兩類,非常常見的一種 M-S 策略,也就是 master 負(fù)責(zé)統(tǒng)籌管理 worker,當(dāng)然它也負(fù)責(zé)如:啟動(dòng)、讀取配置文件,監(jiān)聽處理各種信號(hào)等工作。
但是,第一個(gè)要注意的問題就出現(xiàn)了,master 的工作有且只有這些,對(duì)于請(qǐng)求來說它是不管的,就如同圖中所示,請(qǐng)求是直接被 worker 處理的。如此一來,請(qǐng)求應(yīng)該被哪個(gè) worker 處理呢?worker 內(nèi)部又是如何處理請(qǐng)求的呢?
nginx 使用 epoll
接下來我們就要知道 nginx 是如何使用 epoll 來處理請(qǐng)求的。下面可能會(huì)涉及到一些源碼的內(nèi)容,但不用擔(dān)心,你不需要全部理解,只需要知道它們的作用就可以了。順便我會(huì)簡單描述一下我是如何去找到這些源碼的位置的。
master 的工作
其實(shí) master 并不是毫無作為,至少端口是它來占的。
ngx_open_listening_sockets(ngx_cycle_t *cycle) { ..... for (i = 0; i < cycle->listening.nelts; i++) { ..... if (bind(s, ls[i].sockaddr, ls[i].socklen) == -1) { if (listen(s, ls[i].backlog) == -1) { }
那么,根據(jù)我們 nginx.conf
的配置文件,看需要監(jiān)聽哪個(gè)端口,于是就去 bind 的了,這里沒問題。
【發(fā)現(xiàn)源碼】這里我是直接在代碼里面搜 bind 方法去找的,因?yàn)槲抑?,不管你怎么樣,你總是要綁定端口?/p>
然后是創(chuàng)建 worker 的,雖不起眼,但很關(guān)鍵。
ngx_spawn_process(ngx_cycle_t *cycle, ngx_spawn_proc_pt proc, void *data, char *name, ngx_int_t respawn) { .... pid = fork();
【發(fā)現(xiàn)源碼】這里我直接搜 fork,整個(gè)項(xiàng)目里面需要 fork 的情況只有兩個(gè)地方,很快就找到了 worker
由于是 fork 創(chuàng)建的,也就是復(fù)制了一份 task_struct
結(jié)構(gòu)。所以 master 的幾乎全部它都有。
worker 的工作
nginx 有一個(gè)分模塊的思想,它將不同功能分成了不同的模塊,而 epoll 自然就是在 ngx_epoll_module.c
中了
ngx_epoll_init(ngx_cycle_t *cycle, ngx_msec_t timer) { ngx_epoll_conf_t *epcf; epcf = ngx_event_get_conf(cycle->conf_ctx, ngx_epoll_module); if (ep == -1) { ep = epoll_create(cycle->connection_n / 2);
其他不重要,就連 epoll_ctl
和 epoll_wait
也不重要了,這里你需要知道的就是,從調(diào)用鏈路來看,是 worker 創(chuàng)建的 epoll 對(duì)象,也就是每個(gè) worker 都有自己的 epoll 對(duì)象,而監(jiān)聽的sokcet 是一樣的!
【發(fā)現(xiàn)源碼】這里更加直接,搜索 epoll_create 肯定就能找到
問題的關(guān)鍵
此時(shí)問題的關(guān)鍵基本就能了解了,每個(gè) worker 都有處理能力,請(qǐng)求來了此時(shí)應(yīng)該喚醒誰呢?講道理那不是所有 epoll 都會(huì)有事件,所有 worker 都 accept 請(qǐng)求?顯然這樣是不行的。那么 nginx 是如何解決的呢?
如何解決
解決方式一共有三種,下面我們一個(gè)個(gè)來看:
accept_mutex
(應(yīng)用層的解決方案)EPOLLEXCLUSIVE
(內(nèi)核層的解決方案)SO_REUSEPORT
(內(nèi)核層的解決方案)
accept_mutex
看到 mutex 可能你就知道了,鎖嘛!這也是對(duì)于高并發(fā)處理的 ”基操“ 遇事不決加鎖,沒錯(cuò),加鎖肯定能解決問題。
具體代碼就不展示了,其中細(xì)節(jié)很多,但本質(zhì)很容易理解,就是當(dāng)請(qǐng)求來了,誰拿到了這個(gè)鎖,誰就去處理。沒拿到的就不管了。鎖的問題很直接,除了慢沒啥不好的,但至少很公平。
EPOLLEXCLUSIVE
EPOLLEXCLUSIVE
是 2016 年 4.5+ 內(nèi)核新添加的一個(gè) epoll 的標(biāo)識(shí)。它降低了多個(gè)進(jìn)程/線程通過 epoll_ctl 添加共享 fd 引發(fā)的驚群概率,使得一個(gè)事件發(fā)生時(shí),只喚醒一個(gè)正在 epoll_wait
阻塞等待喚醒的進(jìn)程(而不是全部喚醒)。
關(guān)鍵是:每次內(nèi)核只喚醒一個(gè)睡眠的進(jìn)程處理資源
但,這個(gè)方案不是完美的解決了,它僅是降低了概率哦。為什么這樣說呢?相比于原來全部喚醒,那肯定是好了不少,降低了沖突。但由于本質(zhì)來說 socket 是共享的,當(dāng)前進(jìn)程處理完成的時(shí)間不確定,在后面被喚醒的進(jìn)程可能會(huì)發(fā)現(xiàn)當(dāng)前的 socket 已經(jīng)被之前喚醒的進(jìn)程處理掉了。
SO_REUSEPORT
nginx 在 1.9.1 版本加入了這個(gè)功能
https://www.nginx.com/blog/socket-sharding-nginx-release-1-9-1/
其本質(zhì)是利用了 Linux 的 reuseport 的特性,使用 reuseport 內(nèi)核允許多個(gè)進(jìn)程 listening socket
到同一個(gè)端口上,而從內(nèi)核層面做了負(fù)載均衡,每次喚醒其中一個(gè)進(jìn)程。
反應(yīng)到 nginx 上就是,每個(gè) worker 進(jìn)程都創(chuàng)建獨(dú)立的 listening socket
,監(jiān)聽相同的端口,accept 時(shí)只有一個(gè)進(jìn)程會(huì)獲得連接。效果就和下圖所示一樣。
而使用方式則是:
http { server { listen 80 reuseport; server_name localhost; # ... } }
從官方的測(cè)試情況來看確實(shí)是厲害
當(dāng)然,正所謂:完事無絕對(duì),技術(shù)無銀彈。這個(gè)方案的問題在于內(nèi)核是不知道你忙還是不忙的。只會(huì)無腦的丟給你。與之前的搶鎖對(duì)比,搶鎖的進(jìn)程一定是不忙的,現(xiàn)在手上的工作都已經(jīng)忙不過來了,沒機(jī)會(huì)去搶鎖了;而這個(gè)方案可能導(dǎo)致,如果當(dāng)前進(jìn)程忙不過來了,還是會(huì)只要根據(jù) reuseport
的負(fù)載規(guī)則輪到你了就會(huì)發(fā)送給你,所以會(huì)導(dǎo)致有的請(qǐng)求被前面慢的請(qǐng)求卡住了。
總結(jié)
本文,從了解什么 ”驚群效應(yīng)“ 到 nginx 架構(gòu)和 epoll 處理的原理,最終分析三種不同的處理 “驚群效應(yīng)” 的方案。分析到這里,我想你應(yīng)該明白其實(shí) nginx 這個(gè)多隊(duì)列服務(wù)模型是所存在的一些問題,只不過絕大多數(shù)場(chǎng)景已經(jīng)完完全全夠用了。
到此這篇關(guān)于淺談Nginx是如何解決驚群效應(yīng)的的文章就介紹到這了,更多相關(guān)Nginx 驚群效應(yīng)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
配置ab來為Nginx服務(wù)器做壓力測(cè)試的方法
這篇文章主要介紹了配置ab來為Nginx服務(wù)器做壓力測(cè)試的方法,ab是針對(duì)Apache的測(cè)試工具但本文講解其測(cè)試Nginx的過程,需要的朋友可以參考下2016-01-01Nginx服務(wù)器下配置個(gè)性二級(jí)域名及多個(gè)域名的實(shí)例講解
這篇文章主要介紹了Nginx服務(wù)器下配置個(gè)性二級(jí)域名及多個(gè)域名的實(shí)例講解,注意一下rewrite的寫法規(guī)則,需要的朋友可以參考下2016-01-01Nginx?Proxy?Manager配置Web?WAF應(yīng)用防火墻
Nginx?Proxy?Manager是一款功能強(qiáng)大的開源軟件,配置Web應(yīng)用防火墻,可以防止常見的web攻擊,本文就來介紹一下Nginx?Proxy?Manager配置Web?WAF應(yīng)用防火墻,感興趣的可以了解一下2025-02-02如何配置Nginx每個(gè)進(jìn)程最多打開的文件數(shù)量
這篇文章主要介紹了配置Nginx每個(gè)進(jìn)程最多打開的文件數(shù)量,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06nginx日志導(dǎo)入elasticsearch的方法示例
這篇文章主要介紹了nginx日志導(dǎo)入elasticsearch的方法示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05Nginx的偽靜態(tài)配置中使用rewrite來實(shí)現(xiàn)自動(dòng)補(bǔ)全的實(shí)例
這篇文章主要介紹了Nginx的偽靜態(tài)配置中使用rewrite來實(shí)現(xiàn)自動(dòng)補(bǔ)全的實(shí)例,文中對(duì)rewrite的相關(guān)參數(shù)和正則表達(dá)使用也做了介紹,需要的朋友可以參考下2015-12-12Nexus使用nginx代理實(shí)現(xiàn)支持HTTPS協(xié)議
這篇文章主要介紹了Nexus使用nginx代理實(shí)現(xiàn)支持HTTPS協(xié)議,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05