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

歡迎訪問 生活随笔!

生活随笔

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

Nginx

Nginx 多进程连接请求/事件分发流程分析

發布時間:2023/11/29 Nginx 92 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Nginx 多进程连接请求/事件分发流程分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Nginx使用多進程的方法進行任務處理,每個worker進程只有一個線程,單線程循環處理全部監聽的事件。本文重點分析一下多進程間的負載均衡問題以及Nginx多進程事件處理流程,方便大家自己寫程序的時候借鑒。

?

一、監聽建立流程

整個建立監聽socket到accept的過程如下圖:

?

說明:

1.main里面調用ngx_init_cycle(src/core/ngx_cycle.c),ngx_init_cycle里面完成很多基本的配置,如文件,共享內存,socket等。

2.上圖左上角是ngx_init_cycle里面調用的ngx_open_listening_sockets(src/core/ngx_connection.c)主要完成的工作,包括基本的創建socket,setsockopt,bind和listen等。

3.然后是正常的子進程生成過程。在每個子worker進程的ngx_worker_process_cycle中,在調用ngx_worker_process_init里面調用各模塊的初始化操作init_process。一epoll module為例,這里調用ngx_event_process_init,里面初始化多個NGX_EVENT_MODULE類型的module.NGX_EVENT_MODULE類型的只有ngx_event_core_module和ngx_epoll_module。前一個module的actions部分為空。ngx_epoll_module里面的init函數就是ngx_epoll_init。ngx_epoll_init函數主要完成epoll部分相關的初始化,包括epoll_create,設置ngx_event_actions等。

4.初始化完ngx_epoll_module,繼續ngx_event_process_init,然后循環設置每個listening socket的read handler為ngx_event_accept.最后將每個listening socket的READ事件添加到epoll進行等待。

5.ngx_event_process_init初始化完成后,每個worker process開始循環處理events&timers。最終調用的是epoll_wait。由于之前listening socket以及加入到epoll,所以如果監聽字有read消息,那么久調用rev->handler進行處理,監聽字的handler之前已經設置為ngx_event_accept。ngx_event_accept主要是調用accept函數來接受新的客戶端套接字client socket。

下面是監聽字的處理函數ngx_event_accept流程圖:

?

說明:

1.前半部分主要是通過accept接受新連接字,生成并設置相關結構,然后添加到epoll中。

2.后半部分調用connection中的listening對應的handler,即ngx_xxx_init_connection,其中xxx可以是mail,http和stream。顧名思義,該函數主要是做新的accepted連接字的初始化工作。上圖以http module為例,初始化設置了連接字的read handler等。

?

二、負載均衡問題

?Nginx里面通過一個變量ngx_accept_disabled來實施進程間獲取客戶端連接請求的負載均衡策略。ngx_accept_disabled使用流程圖:

?

說明:

1.ngx_process_events_and_timers函數中,通過ngx_accept_disabled的正負判斷當前進程負載高低(大于0,高負載;小于0,低負載)。如果低負載時,不做處理,進程去申請accept鎖,監聽并接受新的連接。

2.如果是高負載時,ngx_accept_disabled就發揮作用了。這時,不去申請accept鎖,讓出監聽和接受新連接的機會。同時ngx_accept_disabled減1,表示通過讓出一次accept申請的機會,該進程的負載將會稍微減輕,直到ngx_accept_disabled最后小于0,重新進入低負載的狀態,開始新的accept鎖競爭。

?

參考鏈接:http://www.jb51.net/article/52177.htm

?

三、“驚群”問題

“驚群”問題:多個進程同時監聽一個套接字,當有新連接到來時,會同時喚醒全部進程,但只能有一個進程與客戶端連接成功,造成資源的浪費。

Nginx通過進程間共享互斥鎖ngx_accept_mutex來控制多個worker進程對公共監聽套接字的互斥訪問,獲取鎖后調用accept取出與客戶端已經建立的連接加入epoll,然后釋放互斥鎖。

Nginx處理流程示意圖:

說明:

1.ngx_accept_disabled作為單個進程負載較高(最大允許連接數的7/8)的標記,計算公式:

ngx_accept_disabled = ngx_cycle->connection_n/8?- ngx_cycle->free_connection_n;

即進程可用連接數free_connection_n小于總連接數connection_n的1/8時ngx_accept_disabled大于0;否則小于0.或者說ngx_accept_disabled小于0時,表示可用連接數較多,負載較低;ngx_accept_disabled大于0時,說明可用連接數較少,負載較高。

2.如果進程負載較低時,即ngx_accept_disabled 小于0,進程允許競爭accept鎖。

3.如果進程負載較高時,放棄競爭accept鎖,同時ngx_accept_disabled 減1,即認為由于讓出一次競爭accept鎖的機會,負載稍微減輕(ngx_accept_disabled 小于0可用)。由于負載較高時(ngx_accept_disabled >0)只是將ngx_accept_disabled 減1,這里不申請accept鎖,所以后續的accept函數會遭遇“驚群”問題,返回錯誤errno=EAGAIN,直接返回(個人覺得這里有改進的空間,見補充部分)。

ngx_process_events_and_timers函數部分代碼如下:

1 if (ngx_use_accept_mutex) { 2 if (ngx_accept_disabled > 0) { 3 ngx_accept_disabled--; 4 5 } else { 6 if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) { 7 return; 8 } 9 10 if (ngx_accept_mutex_held) { 11 flags |= NGX_POST_EVENTS; 12 13 } else { 14 if (timer == NGX_TIMER_INFINITE 15 || timer > ngx_accept_mutex_delay) 16 { 17 timer = ngx_accept_mutex_delay; 18 } 19 } 20 } 21 }

4.如果競爭加鎖失敗(6-7行),直接返回,返回到ngx_worker_process_cycle的for循環里面,此次不參與事件處理,進行下一次循環。

5.如果競爭加鎖成功,設置NGX_POST_EVENTS標記,表示將事件先放入隊列中,稍后處理,優先釋放ngx_accept_mutex,防止單個進程過多占用鎖時間,影響事件處理效率。ngx_epoll_process_events函數有如下部分(寫事件wev部分也一樣):

1 if (flags & NGX_POST_EVENTS) { 2 queue = rev->accept ? &ngx_posted_accept_events 3 : &ngx_posted_events; 4 5 ngx_post_event(rev, queue);//先將event放入隊列,稍后處理 6 7 } else { 8 rev->handler(rev); 9 }

6.從ngx_epoll_process_events返回ngx_process_events_and_timers,然后是處理accept事件(下面代碼10行);處理完accept事件,馬上釋放鎖(下面代碼13-15行),給其他進程機會去監聽連接事件。最后處理一般的連接事件。

1 delta = ngx_current_msec; 2 3 (void) ngx_process_events(cycle, timer, flags); 4 5 delta = ngx_current_msec - delta; 6 7 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, 8 "timer delta: %M", delta); 9 10 ngx_event_process_posted(cycle, &ngx_posted_accept_events);//這里處理ngx_process_events 里面post的accept事件 11 12 //處理完accept事件,馬上釋放鎖 13 if (ngx_accept_mutex_held) { 14 ngx_shmtx_unlock(&ngx_accept_mutex); 15 } 16 17 //在處理一般的connection事件之前,先處理超時。 18 if (delta) { 19 ngx_event_expire_timers(); 20 } 21 22 //處理普通的connection事件請求 23 ngx_event_process_posted(cycle, &ngx_posted_events);

7.在處理accept事件時,handler是ngx_event_accept(src/event/ngx_event_accept.c),在這個函數里面,每accept一個新的連接,就更新ngx_accept_disabled。

1 do { 2 ... 3 //接受新連接 4 accept(); 5 ... 6 //更新ngx_accept_disabled 7 ngx_accept_disabled = ngx_cycle->connection_n / 8 8 - ngx_cycle->free_connection_n; 9 10 ... 11 12 }while(ev->available)

?補充:

ngx_accept_disabled 減1這條路徑很明顯沒有申請accept鎖,所以后面的epoll_wait和accept函數會出現“驚群”問題。建議按如下圖改進:

?

說明:

添加紅色框步驟,在負載過高時,ngx_accept_disabled 減1進行均衡操作同時,將accept事件從當前進程epoll中清除。這樣epoll當前循環只處理自己的普通connection事件。當然,左側路徑可能執行多次,ngx_disable_accept_events操作只需要執行一次即可。

如果過了一段時間,該進程負載降低,進入右側路徑,在申請accept鎖的函數中ngx_trylock_accept_mutex中,申請加鎖成功后,會調用ngx_enable_accept_events將accept事件再次加入到epoll中,這樣就可以監聽accept事件和普通connection事件了。

以上補充部分為個人理解,有錯誤之處,歡迎指正。

?

四、多進程(每個進程單線程)高效的原因

?一點思考:

1.master/worker多進程模式,保證了系統的穩定。master對多個worker子進程和其他子進程的管理比較方便。由于一般worker進程數與cpu內核數一致,所以不存在大量的子進程生成和管理任務,避免了大量子進程的數據IPC共享開銷和切換競爭開銷。各worker進程之間也只是重復拷貝了監聽字,除了父子進程間傳遞控制消息,基本沒有IPC需求。

2.每個worker單線程,不存在大量線程的生成和同步開銷。

以上兩個方面都使Nginx避免了過多的同步、競爭、切換和IPC數據傳遞,即盡可能把cpu從不必要的計算開銷中解放出來,只專注于業務計算和流程處理。

解放了CPU之后,就是內存的高效操作了。像cache_manager_process,內存池ngx_pool_t等等。還有可以設置進程的affinity來綁定cpu單個內核等。

這樣的模型更簡單,大連接量擴展性更好。

?

“偉大的東西,總是簡單的”,此言不虛。

?

?

注:引用本人文章請注明出處,謝謝。

轉載于:https://www.cnblogs.com/NerdWill/p/4992345.html

總結

以上是生活随笔為你收集整理的Nginx 多进程连接请求/事件分发流程分析的全部內容,希望文章能夠幫你解決所遇到的問題。

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