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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux / 惊群效应

發布時間:2024/10/14 linux 75 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux / 惊群效应 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、簡介

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

二、OS驚群簡介

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

三、壞處

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

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

  • accept 驚群
  • epoll 驚群
  • nginx 驚群
  • 線程池驚群
  • 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(新版內核已解決)

  • 主進程創建 listenfd,創建 epollfd。
  • 主進程 fork 多個子進程
  • 每個子進程把 listenfd,加到 epollfd 中。
  • 當一個連接進來時,會觸發 epoll 驚群,多個子進程的 epoll 同時會觸發。
  • 這里的epoll驚群跟 accept 驚群是類似的,共享一個 epollfd,加鎖或標記解決,在新版本的epoll中已解決,但在內核2.6及之前是存在的。

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

  • 主進程創建 listendfd 。
  • 主進程創建多個子進程
  • 每個子進程創建自已的 epollfd。
  • 每個子進程把 listenfd 加入到 epollfd 中。
  • 當一個連接進來時,會觸發 epoll 驚群,多個子進程 epoll 同時會觸發。
  • 因為每個子進程的 epoll 是不同的epoll, 雖然 listenfd 是同一個,但新連接過來時, accept 會觸發驚群。因為內核不知道該發給哪個監聽進程,因為不是同一個 epoll 。所以這種驚群內核并沒有處理,驚群還是會出現。

    4.3 nginx驚群

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

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

    在nginx中,流程。

  • 主線程創建 listenfd 。
  • 主線程fork多個子進程(根據配置)。
  • 子進程創建 epollfd 。
  • 獲得 accept 鎖,只有一個子進程把 listenfd 加到epollfd中。(同一時間只有一個進程會把監聽描述符加到 epoll 中)
  • 循環監聽。
  • 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里采用了主動的方法去把監聽描述符放到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 通知時,定向通知某個線程的條件變量,不會出現驚群
  • 五、高并發設計

    以多線程為例,進程同理

    栗子

    主線程

    子線程 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 / 惊群效应的全部內容,希望文章能夠幫你解決所遇到的問題。

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