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