Linux / 惊群效应
一、簡介
當你往一群鴿子中間扔一塊食物,雖然最終只有一個鴿子搶到食物,但所有鴿子都會被驚動來爭奪,沒有搶到食物的鴿子只好回去繼續(xù)睡覺, 等待下一塊食物到來。這樣,每扔一塊食物,都會驚動所有的鴿子,即為驚群。
二、OS驚群簡介
在多進程/多線程等待同一資源時,也會出現(xiàn)驚群。即當某一資源可用時,多個進程/線程會驚醒,競爭資源。這就是操作系統(tǒng)中的驚群。
三、壞處
四、常見場景
在高并發(fā)(多線程/多進程/多連接)中,會產生驚群的情況有:
4.1 accept 驚群
以多進程為例,在主進程創(chuàng)建監(jiān)聽描述符 listenfd 后,fork() 多個子進程,多個進程共享 listenfd,accept 是在每個子進程中,當一個新連接來的時候,會發(fā)生驚群。
在內核2.6之前,所有進程accept都會驚醒,但只有一個可以accept成功,其他返回EGAIN。
在內核2.6及之后,解決了驚群問題,辦法是在內核中增加了一個互斥等待變量。一個互斥等待的行為與睡眠基本類似,主要的不同點在于:
????????1)當一個等待隊列入口有 WQ_FLAG_EXCLUSEVE 標志置位, 它被添加到等待隊列的尾部。若沒有這個標志的入口項,則添加到隊首。
????????2)當 wake up 被在一個等待隊列上調用時, 它在喚醒第一個有 WQ_FLAG_EXCLUSIVE 標志的進程后停止。
????????對于互斥等待的行為,比如對一個 listen 后的socket描述符,多線程阻塞 accept 時,系統(tǒng)內核只會喚醒所有正在等待此時間的隊列的第一個,隊列中的其他人則繼續(xù)等待下一次事件的發(fā)生。這樣就避免的多個線程同時監(jiān)聽同一個socket描述符時的驚群問題。
4.2?epoll 驚群
epoll驚群分兩種:
1、是在fork之前創(chuàng)建 epollfd,所有進程共用一個epoll。
2、是在fork之后創(chuàng)建 epollfd,每個進程獨用一個epoll。
4.2.1?fork之前創(chuàng)建epollfd(新版內核已解決)
這里的epoll驚群跟 accept 驚群是類似的,共享一個 epollfd,加鎖或標記解決,在新版本的epoll中已解決,但在內核2.6及之前是存在的。
4.2.2 fork之后創(chuàng)建epollfd(內核未解決)
因為每個子進程的 epoll 是不同的epoll, 雖然 listenfd 是同一個,但新連接過來時, accept 會觸發(fā)驚群。因為內核不知道該發(fā)給哪個監(jiān)聽進程,因為不是同一個 epoll 。所以這種驚群內核并沒有處理,驚群還是會出現(xiàn)。
4.3 nginx驚群
這里說的nginx驚群,其實就是上面的問題(fork之后創(chuàng)建epollfd),下面看看 nginx 是怎么處理驚群的。
在nginx中使用的epoll,是在創(chuàng)建進程后創(chuàng)建的 epollfd 。因些會出現(xiàn)上面的驚群問題。即每個子進程worker都會驚醒。
在nginx中,流程。
分析:
nginx采用互斥鎖和主動的方法,避免了驚群,使得nginx中并無驚群。
4.4 線程池驚群
在多線程設計中,經常會用到互斥和條件變量的問題。當一個線程解鎖并通知其他線程的時候,就會出現(xiàn)驚群的現(xiàn)象。
pthread_mutex_lock / pthread_mutex_unlock
線程互斥鎖的加鎖及解鎖函數。
pthread_cond_wait
線程池中的消費者線程等待線程條件變量被通知;
pthread_cond_signal / pthread_cond_broadcast
生產者線程通知線程池中的某個或一些消費者線程池,接收處理任務;
這里的驚群現(xiàn)象出現(xiàn)在 3 里,pthread_cond_signal,語義上看是通知一個線程。調用此函數后,系統(tǒng)會喚醒在相同條件變量上等待的一個或多個線程(可參看手冊)。如果通知了多個線程,則發(fā)生了驚群。
正常的用法:
解決驚群的方法:
五、高并發(fā)設計
以多線程為例,進程同理
| 栗子 | 主線程 | 子線程 epoll | 是否有驚群 | 新版本是否已經解決 | 參考 |
| 1 | listenfd epollfd | 共用 listenfd 和 epollfd 子線程accept | epoll 驚群 | 已解決 | 被動 |
| 2 | listenfd | 共用 listenfd, 每個線程創(chuàng)建 epollfd listenfd 加入 epoll | epoll 驚群 | 未解決 | 被動 |
| 3 | listenfd 主線程accept并分發(fā)connfd | 每個線程創(chuàng)建 epollfd 接收主線程分發(fā)的 connfd | 無驚群 accept 瓶頸? | (無用) | 被動 |
| 4 | listenfd | 共用 listenfd, 每個線程創(chuàng)建 epollfd 互斥鎖決定加入 / 移出 epoll | 無驚群 | nginx |
5.1 栗1
分析
主線程創(chuàng)建 listenfd 和 epollfd,子線程共享并把 listenfd 加入到epoll中,舊版中會出現(xiàn)驚群,新版中已解決了驚群。
缺點
應用層并不知道內核會把新連接分給哪個線程,可能平均,也可能不平均如果某個線程已經最大負載了,還分過來,會增加此線程壓力甚至崩潰。
總結
因為例1并不是最好的方法,因為沒有解決負載和分配問題。
5.2 栗2
分析
主線程創(chuàng)建 listenfd,子線程創(chuàng)建 epollfd,,把 listenfd 加入到 epoll 中, 這種方法是無法避免驚群的問題。每次有新連接時,都會喚醒所有的accept線程,但只有一個 accept 成功,其他的線程 accept 失敗 EAGAIN 。
總結
栗 2 解決不了驚群的問題,如果線程超多,驚群越明顯。如果真正開發(fā)中,可忽略驚群,或者需要用驚群,那么使用此種設計也是可行的。
5.3 栗3
分析
主線程創(chuàng)建 listenfd,每個子線程創(chuàng)建 epollfd,主線程負責accept,并發(fā)分新connfd給負載最低的一個線程,然后線程再把connfd 加入到 epoll 中。無驚群現(xiàn)象。
總結:
主線程只用 accept 用,可能會主線程沒干,或連接太多處理不過來,accept 瓶頸(一般情況不會產生)。主線程可以很好地根據子線程的連接來分配新連接,有比較好的負載并發(fā)量也比較大,自測(單進程十萬并發(fā)連接QPS十萬,四核四G內存,很穩(wěn)定)
5.4 栗4
這是 nginx 的設計,無疑是目前最優(yōu)的一種高并發(fā)設計,無驚群。
nginx本質
同一時刻只允許一個 nginx worker 在自己的 epoll 中處理監(jiān)聽句柄。它的負載均衡也很簡單,當達到最大 connection 的 7/8時,本 worker 不會去試圖拿 accept 鎖,也不會去處理新連接。這樣其他 nginx worker 進程就更有機會去處理監(jiān)聽句柄,建立新連接了。而且,由于 timeout 的設定,使得沒有拿到鎖的worker進程,去拿鎖的頻繁更高。
總結
nginx的設計非常巧妙,很好的解決了驚群的產生,所以沒有驚群。同時也根據各進程的負載主動去決定要不要接受新連接,負載比較優(yōu)。
六、總結
研究高并發(fā)有一段時間了,總結下我自已的理解,怎么樣才算是高并發(fā)呢?單進程百萬連接,單進程百萬 QPS ?
先說說基本概念
6.1 高并發(fā)連接
指的是連接的數量,對服務端來說,一個套接字對就是一個連接,連接和本地文件描述符無關,不受本地文件描述符限制,只跟內存有關,假設一個套接字對占用服 務器 8k 內存,那么1G內存=1024*1024/8 = 131072。因此連接數跟內存有關。1G = 10萬左右連接,當然這是理論,實際要去除內核占用,其他進程占用,和本進程其他占用。假哪一個機器 32G 內存,那個撐個100萬個連接是沒有問題的。如果是單個進程100萬連,那就更牛B了,但一般都不會這么做,因為如果此進程宕了,那么,所有業(yè)務都影響了。所以一般都會分布到不同進程,不同機器,一個進程出問題了,不會影響其他進程的處理。(這也是nginx原理)
6.2 PV?
每天的總訪問量 pave view, PV = QPS * (24*0.2) * 3600 (二八原則)
6.3 QPS
每秒請求量。假如每秒請求量10萬,假如機器為16核,那么啟16個線程同時工作, 那么每個線程同時的請求量 = 10萬/ 16核 = ?6250QPS。
按照二八原則,一天24小時,忙時=24*0.2 = 4.8小時。
則平均一天總請求量 = 4.8 * 3600 *10萬QPS = 172億8千萬。
那么每秒請求10萬并發(fā)量,每天就能達到172億的PV。這算高并發(fā)嗎?
6.4 丟包率
如果客端端發(fā)10萬請求,服務端只處理了8萬,那么就丟了2萬。丟包率=2/10 = 20%。丟包率是越小越好,最好是沒有。去除網絡丟包,那么就要考慮內核里的丟包問題,因此要考慮網卡的吞吐量,同一時間發(fā)大多請求過來,內核會不會處理不過來, 導致丟包。
6.5 穩(wěn)定性
一個高并發(fā)服務,除了高并發(fā)外,最重要的就是穩(wěn)定了,這是所有服務都必須的。 一千 QPS 能處理,一萬QPS 也能處理,十萬 QPS 也能處理,當然越多越好。不要因為業(yè)務驟增導致業(yè)務癱瘓,那失敗是不可估量的。因為,要有個度,當業(yè)務增加到一定程 度,為了保證現(xiàn)有業(yè)務的處理,不處理新請求業(yè)務,延時處理等,同時保證代碼的可靠。
因此,說到高并發(fā),其實跟機器有并,內存,網卡,CPU核數等有關,一個強大的服務器,比如:32核,64G內存,網卡吞吐很大,那么單個進程,開32個線程,做一個百萬連接,百萬QPS的服務,是可行的。
本身按栗 3 去做了個高并發(fā)的設計,做到了四核4G內存的虛擬機里,十萬連接,十萬QPS,很穩(wěn)定,沒加業(yè)務,每核CPU %sys 15左右 %usr 5%左右。如果加了業(yè)務,應該也是比較穩(wěn)定的,有待測試。當然例3是有自已的缺點的。
同進,也希望研究高并發(fā)的同學,一起來討論高并發(fā)服務設計思想。(加微:luoying140131)
?
(SAW:Game Over!)
總結
以上是生活随笔為你收集整理的Linux / 惊群效应的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: shared_ptr 循环引用问题以及解
- 下一篇: OS / Linux / 进程的虚拟地址