日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

[框架]高并发中的惊群效应

發布時間:2023/12/20 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 [框架]高并发中的惊群效应 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

高并發中的驚群效應

second60 20180726

目錄

高并發中的驚群效應

1.驚群效應簡介

2. 操作系統的驚群

3. 驚群的壞處

3.1 壞處

3.2 其他

4 驚群的幾種情況

4.1 accept驚群(新版內核已解決)

4.2 epoll驚群

4.2.1 fork之前創建epollfd(新版內核已解決)

4.2.2 fork之后創建epollfd(內核未解決)

4.3 nginx驚群的解決

4.4 線程池驚群

5 高并發設計

5.1 例1

5.2 例2

5.3 例3

5.4 例4

6 總結


1.驚群效應簡介

當你往一群鴿子中間扔一塊食物,雖然最終只有一個鴿子搶到食物,但所有鴿子都會被驚動來爭奪,沒有搶到食物的鴿子只好回去繼續睡覺, 等待下一塊食物到來。這樣,每扔一塊食物,都會驚動所有的鴿子,即為驚群。

?

簡單地說:就是扔一塊食物,所有鴿子來搶,但最終只一個鴿子搶到了食物。

語義分析:食物只有一塊,最終只有一個鴿子搶到,但是驚動了所有鴿子,每個鴿子都跑過來,消耗了每個鴿子的能量。(這個很符合達爾文的進化論,物種之間的競爭,適者生存。)

?

2. 操作系統的驚群

在多進程/多線程等待同一資源時,也會出現驚群。即當某一資源可用時,多個進程/線程會驚醒,競爭資源。這就是操作系統中的驚群。

?

3. 驚群的壞處

3.1 壞處

  • 驚醒所有進程/線程,導致n-1個進程/線程做了無效的調度,上下文切換,cpu瞬時增高
  • 多個進程/線程爭搶資源,所以涉及到同步問題,需對資源進行加鎖保護,加解鎖加大系統CPU開銷
  • 3.2 其他

    1. 在某些情況:驚群次數少/進(線)程負載不高,驚群可以忽略不計

    ?

    4 驚群的幾種情況

    在高并發(多線程/多進程/多連接)中,會產生驚群的情況有:

  • accept驚群
  • epoll驚群
  • nginx驚群
  • 線程池驚群
  • 4.1 accept驚群(新版內核已解決)

    以多進程為例,在主進程創建監聽描述符listenfd后,fork()多個子進程,多個進程共享listenfd,accept是在每個子進程中,當一個新連接來的時候,會發生驚群。

    ?

    由上圖所示:

  • 主線程創建了監聽描述符listenfd = 3
  • 主線程fork 三個子進程共享listenfd=3
  • 當有新連接進來時,內核進行處理
  • 在內核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(新版內核已解決)

    1. 主進程創建listenfd, 創建epollfd

    2. 主進程fork多個子進程

    3. 每個子進程把listenfd,加到epollfd中

    4. 當一個連接進來時,會觸發epoll驚群,多個子進程的epoll同時會觸發

    ?

    分析:

    這里的epoll驚群跟accept驚群是類似的,共享一個epollfd, 加鎖或標記解決。在新版本的epoll中已解決。但在內核2.6及之前是存在的。

    ?

    4.2.2 fork之后創建epollfd(內核未解決)

    1. 主進程創建listendfd

    2. 主進程創建多個子進程

    3. 每個子進程創建自已的epollfd

    4. 每個子進程把listenfd加入到epollfd中

    5. 當一個連接進來時,會觸發epoll驚群,多個子進程epoll同時會觸發

    ?

    分析:

    因為每個子進程的epoll是不同的epoll, 雖然listenfd是同一個,但新連接過來時, accept會觸發驚群,但內核不知道該發給哪個監聽進程,因為不是同一個epoll。所以這種驚群內核并沒有處理。驚群還是會出現。

    ?

    4.3 nginx驚群的解決

    這里說的nginx驚群,其實就是上面的問題(fork之后創建epollfd),下面看看nginx是怎么處理驚群的。

    ?

    在nginx中使用的epoll,是在創建進程后創建的epollfd。因些會出現上面的驚群問題。即每個子進程worker都會驚醒。

    在nginx中,流程。

    1

    主線程創建listenfd

    ?

    2

    主線程fork多個子進程(根據配置)

    ?

    3

    子進程創建epollfd

    ?

    4

    獲到accept鎖,只有一個子進程把listenfd加到epollfd中

    同一時間只有一個進程會把監聽描述符加到epoll中

    5

    循環監聽

    ?

    在nginx中,解決驚群的方法,使用了互斥鎖還解決。

    void ngx_process_events_and_timers(ngx_cycle_t *cycle){// 忽略....//ngx_use_accept_mutex表示是否需要通過對accept加鎖來解決驚群問題。//當nginx worker進程數>1時且配置文件中打開accept_mutex時,這個標志置為1if (ngx_use_accept_mutex) {//ngx_accept_disabled表示此時滿負荷,沒必要再處理新連接了,//我們在nginx.conf曾經配置了每一個nginx worker進程能夠處理的最大連接數,//當達到最大數的7/8時,ngx_accept_disabled為正,說明本nginx worker進程非常繁忙,//將不再去處理新連接,這也是個簡單的負載均衡if (ngx_accept_disabled > 0) {ngx_accept_disabled--;} else {//獲得accept鎖,多個worker僅有一個可以得到這把鎖。//獲得鎖不是阻塞過程,都是立刻返回,獲取成功的話ngx_accept_mutex_held被置為1。//拿到鎖,意味著監聽句柄被放到本進程的epoll中了,//如果沒有拿到鎖,則監聽句柄會被從epoll中取出。if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {return;}//拿到鎖的話,置flag為NGX_POST_EVENTS,這意味著ngx_process_events函數中,//任何事件都將延后處理,會把accept事件都放到ngx_posted_accept_events鏈表中,// epollin|epollout事件都放到ngx_posted_events鏈表中if (ngx_accept_mutex_held) {flags |= NGX_POST_EVENTS;} else {//拿不到鎖,也就不會處理監聽的句柄,//這個timer實際是傳給epoll_wait的超時時間,//修改為最大ngx_accept_mutex_delay意味著epoll_wait更短的超時返回,//以免新連接長時間沒有得到處理if (timer == NGX_TIMER_INFINITE|| timer > ngx_accept_mutex_delay){timer = ngx_accept_mutex_delay;}}}}// 忽略....//linux下,調用ngx_epoll_process_events函數開始處理(void) ngx_process_events(cycle, timer, flags);// 忽略....//如果ngx_posted_accept_events鏈表有數據,就開始accept建立新連接if (ngx_posted_accept_events) {ngx_event_process_posted(cycle, &ngx_posted_accept_events);}//釋放鎖后再處理下面的EPOLLIN EPOLLOUT請求if (ngx_accept_mutex_held) {ngx_shmtx_unlock(&ngx_accept_mutex);}if (delta) {ngx_event_expire_timers();}ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,"posted events %p", ngx_posted_events);//然后再處理正常的數據讀寫請求。因為這些請求耗時久,//所以在ngx_process_events里NGX_POST_EVENTS標志將事件//都放入ngx_posted_events鏈表中,延遲到鎖釋放了再處理。if (ngx_posted_events) {if (ngx_threaded) {ngx_wakeup_worker_thread(cycle);} else {ngx_event_process_posted(cycle, &ngx_posted_events);}}}

    ?

    步驟

    nginx主動解決驚群流程

    1

    子進程loop

    2

    判斷是否使用accept加鎖

    3

    判斷是否滿負荷最大連接數的7/8(是不處理)

    4

    多個worker競爭accept_mutex鎖(主動精髓)

    5

    獲得鎖成功

    獲得鎖失敗

    6

    (監聽句柄加到本進程的epoll)

    監聽句柄會被從epoll中取出

    7

    事件加入到鏈表中

    (accept事件放到ngx_posted_accept_events鏈表

    epollin|out事件放到ngx_posted_events鏈表)

    修改epoll_wait的超時時間

    (為了下次更早搶鎖)

    8

    如果有accept_event就處理新連接

    ?

    9

    釋放鎖accept_mutex

    ?

    10

    處理正常的數據讀寫請求

    ?

    11

    子進程繼續loop

    12

    ?

    (注:上面的總結如果有不對的地方,麻煩大牛提出來,謝謝)

    ?

    分析:

  • nginx里采用了主動的方法去把監聽描述符放到epoll中或從epoll移出(這個是nginx的精髓所在,因為大部份的并發架構都是被動的)
  • nginx中用采互斥鎖去解決誰來accept問題,保證了同一時刻,只有一個worker接收新連接(所以nginx并沒有驚群問題)
  • nginx根據自已的載負(最大連接的7/8)情況,決定去不去搶鎖,簡單方便地解決負載,防止進程因業務太多而導致所有業務都不及時處理
  • ?

    總結: nginx采用互斥鎖和主動的方法,避免了驚群,使得nginx中并無驚群

    4.4 線程池驚群

    在多線程設計中,經常會用到互斥和條件變量的問題。當一個線程解鎖并通知其他線程的時候,就會出現驚群的現象。

  • pthread_mutex_lock/pthread_mutex_unlock:線程互斥鎖的加鎖及解鎖函數。
  • pthread_cond_wait:線程池中的消費者線程等待線程條件變量被通知;
  • pthread_cond_signal/pthread_cond_broadcast:生產者線程通知線程池中的某個或一些消費者線程池,接收處理任務;
  • 這里的驚群現象出現在3里,pthread_cond_signal,語義上看,是通知一個線程。調用此函數后,系統會喚醒在相同條件變量上等待的一個或多個線程(可參看手冊)。如果通知了多個線程,則發生了驚群。

    ?

    正常的用法:

  • 所有線程共用一個鎖,共用一個條件變量
  • pthread_cond_signal通知時,就可能會出現驚群
  • 解決驚群的方法:

  • 所有線程共用一個鎖,每個線程有自已的條件變量
  • pthread_cond_signal通知時,定向通知某個線程的條件變量,不會出現驚群
  • ?

    5 高并發設計

    以多線程為例,進程同理

    線程

    線程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的設計非常巧妙,很好的解決了驚群的產生,所以沒有驚群,同時也根據各進程的負載主動去決定要不要接受新連接,負載比較優。

    6 總結

    高并發設計,仁者見仁,智者見智,如果要求不高,隨便拿個常用的開源庫,就可能支撐。如果對業務有特殊要求,那么根據業務去選擇,如網關服(可用高并發連接的開源庫libevent/libev),消息隊列(zmq/RabbitMQ/ActiveMQ/Kafka),數據緩存(redis/memcached),分布式等。

    ?

    研究高并發有一段時間了,總結下我自已的理解,怎么樣才算是高并發呢?單進程百萬連接,單進程百萬QPS?

    ?

    先說說基本概念

    高并發連接:指的是連接的數量,對服務端來說,一個套接字對就是一個連接,連接和本地 文件描述符無關,不受本地文件描述符限制,只跟內存有關,假設一個套接字對占用服 務器8k內存,那么1G內存=1024*1024/8 = 131072。因此連接數跟內存有關。

    1G = 10萬左右連接,當然這是理論,實際要去除內核占用,其他進程占用,和本進程其他占用。

    假哪一個機器32G內存,那個撐個100萬個連接是沒有問題的。

    如果是單個進程100萬連,那就更牛B了,但一般都不會這么做,因為如果此進程宕了,那么,所有業務都影響了。所以一般都會分布到不同進程,不同機器,一個進程出問題了,不會影響其他進程的處理。(這也是nginx原理)

    ?

    PV : 每天的總訪問量pave view, PV = QPS * (24*0.2) * 3600 (二八原則)

    ?

    QPS: 每秒請求量。假如每秒請求量10萬,假如機器為16核,那么啟16個線程同時工作, 那么每個線程同時的請求量= 10萬/ 16核 = ?6250QPS。

    按照二八原則,一天24小時,忙時=24*0.2 = 4.8小時。

    則平均一天總請求量=4.8 * 3600 *10萬QPS = 172億8千萬。

    那么每秒請求10萬并發量,每天就能達到172億的PV。這算高并發嗎?

    ?

    丟包率: 如果客端端發10萬請求,服務端只處理了8萬,那么就丟了2萬。丟包率=2/10 = 20%。丟包率是越小越好,最好是沒有。去除,網絡丟包,那么就要考慮內核里的丟包 問題,因此要考慮網卡的吞吐量,同一時間發大多請求過來,內核會不會處理不過來, 導致丟包。

    ?

    穩定性:一個高并發服務,除了高并發外,最重要的就是穩定了,這是所有服務都必須的。 一千QPS能處理,一萬QPS也能處理,十萬QPS也能處理,當然越多越好。不要因為 業務驟增導致業務癱瘓,那失敗是不可估量的。因為,要有個度,當業務增加到一定程 度,為了保證現有業務的處理,不處理新請求業務,延時處理等。同時保證代碼的可靠。

    ?

    因此,說到高并發,其實跟機器有并,內存,網卡,CPU核數等有關,一個強大的服務器,比如:32核,64G內存,網卡吞吐很大,那么單個進程,開32個線程,做一個百萬連接,百萬QPS的服務,是可行的。

    ?

    本身 按例3去做了個高并發的設計,做到了四核4G內存的虛擬機里,十萬連接,十萬QPS,很穩定,沒加業務,每核CPU %sys 15左右 %usr 5%左右。如果加了業務,應該也是比較穩定的。有待測試。當然例3是有自已的缺點的。

    ?

    同進,也希望研究高并發的同學,一起來討論高并發服務設計思想。(加微:luoying140131)

    總結

    以上是生活随笔為你收集整理的[框架]高并发中的惊群效应的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。