初步探索Nginx高併發原理

Nginx

首先要明白,Nginx採用的是多進程(單線程)&多路IO複用模型。使用了I/O多路複用技術的Nginx,就成了”併發事件驅動“的服務器。

這裏寫圖片描述

多進程的工作模式

1、Nginx在啓動後,master進程fork()多個相互獨立的worker進程。
2、接收來自外界的信號,然向各worker進程發送信號,每個進程都有可能來處理這個連接。
3、 master進程能監控worker進程的運行狀態,當worker進程退出後(異常情況下),會自動再fork()新worker進程。

注意worker進程數,一般會設置成機器cpu核數。因爲更多的worker只會導致進程之間相互競爭cpu,從而帶來不必要的上下文切換。

使用多進程模式,不僅能提高併發率,而且進程之間是相互獨立的,一 個worker進程掛了不會影響到其他worker進程。

驚羣現象

master進程首先通過socket()來創建一個監聽描述符,然後fork()若干個worker,子進程將繼承父進程的監聽描述符,之後子進程在該監聽描述符上accept()創建已連接描述符(connected descriptor),然後通過已連接描述符來與客戶端通信。

那麼,由於所有子進程都繼承了父進程的 sockfd,那麼當連接進來時,所有子進程都將收到通知並“爭着”與它建立連接,這就叫“驚羣現象”。大量的進程被激活又掛起,只有一個進程可以accept() 到這個連接,這當然會消耗系統資源。

Nginx對驚羣現象的處理:

Nginx提供了一個accept_mutex這個東西,這是一個加在accept上的一把互斥鎖。即每個worker進程在執行accept()之前都需要先獲取鎖,accept()成功之後再解鎖。有了這把鎖,同一時刻,只會有一個進程執行accpet(),這樣就不會有驚羣問題了。accept_mutex是一個可控選項,我們可以顯示地關掉,默認是打開的。

worker進程工作流程

當一個 worker 進程在 accept() 這個連接之後,就開始讀取請求,解析請求,處理請求,產生數據後,再返回給客戶端,最後才斷開連接,一個完整的請求。一個請求,完全由worker進程來處理,而且只會在一個worker進程中處理。

這樣做帶來的好處:

1、節省鎖帶來的開銷。每個worker進程都彼此獨立地工作,不共享任何資源,因此不需要鎖。同時在編程以及問題排查上時,也會方便很多。

2、獨立進程,減少風險。採用獨立的進程,可以讓互相之間不會影響,一個進程退出後,其它進程還在工作,服務不會中斷,master進程則很快重新啓動新的worker進程。當然,worker進程自己也能發生意外退出。

核心:Nginx採用的 IO多路複用模型epoll

多路複用,允許我們只在事件發生時纔將控制返回給程序,而其他時候內核都掛起進程,隨時待命。

epoll通過在Linux內核中申請一個簡易的文件系統(文件系統一般用B+樹數據結構來實現),其工作流程分爲三部分:

1、調用 int epoll_create(int size)建立一個epoll對象,內核會創建一個eventpoll結構體,用於存放通過epoll_ctl()向epoll對象中添加進來的事件,這些事件都會掛載在紅黑樹中。
2、調用 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) 在 epoll 對象中爲 fd 註冊事件,所有添加到epoll中的事件都會與設備驅動程序建立回調關係,也就是說,當相應的事件發生時會調用這個sockfd的回調方法,將sockfd添加到eventpoll 中的雙鏈表。
3、調用 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) 來等待事件的發生,timeout 爲 -1 時,該調用會阻塞知道有事件發生

這樣,註冊好事件之後,只要有fd上事件發生,epoll_wait()就能檢測到並返回給用戶,用戶執行阻塞函數時就不會發生阻塞了。

epoll()在中內核維護一個鏈表,epoll_wait直接檢查鏈表是不是空就知道是否有文件描述符準備好了。順便提一提,epoll與select、poll相比最大的優點是不會隨着sockfd數目增長而降低效率,使用select()時,內核採用輪訓的方法來查看是否有fd準備好,其中的保存sockfd的是類似數組的數據結構fd_set,key 爲 fd,value爲0或者1(發生時間)。

能達到這種效果,是因爲在內核實現中epoll是根據每 sockfd 上面的與設備驅動程序建立起來的回調函數實現的。那麼,某個sockfd上的事件發生時,與它對應的回調函數就會被調用,將這個sockfd加入鏈表,其他處於“空閒的”狀態的則不會。在這點上,epoll 實現了一個"僞"AIO。

可以看出,因爲一個進程裏只有一個線程,所以一個進程同時只能做一件事,但是可以通過不斷地切換來“同時”處理多個請求。

例子:Nginx 會註冊一個事件:“如果來自一個新客戶端的連接請求到來了,再通知我”,此後只有連接請求到來,服務器纔會執行 accept() 來接收請求。又比如向上遊服務器(比如 PHP-FPM)轉發請求,並等待請求返回時,這個處理的 worker 不會在這阻塞,它會在發送完請求後,註冊一個事件:“如果緩衝區接收到數據了,告訴我一聲,我再將它讀進來”,於是進程就空閒下來等待事件發生。

這樣,基於 多進程+epoll, Nginx 便能實現高併發。

使用 epoll 處理事件的一個框架,代碼轉自:http://www.cnblogs.com/fnlingnzb-learner/p/5835573.html

for( ; ; )  //  無限循環
      {
          nfds = epoll_wait(epfd,events,20,500);  //  最長阻塞 500s
          for(i=0;i<nfds;++i)
          {
              if(events[i].data.fd==listenfd) //有新的連接
              {
                  connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個連接
                  ev.data.fd=connfd;
                 ev.events=EPOLLIN|EPOLLET;
                 epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監聽隊列中
             }
             else if( events[i].events&EPOLLIN ) //接收到數據,讀socket
             {
                 n = read(sockfd, line, MAXLINE)) < 0    //讀
                 ev.data.ptr = md;     //md爲自定義類型,添加數據
                 ev.events=EPOLLOUT|EPOLLET;
                 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓
             }
             else if(events[i].events&EPOLLOUT) //有數據待發送,寫socket
             {
                 struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取數據
                 sockfd = md->fd;
                 send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //發送數據
                 ev.data.fd=sockfd;
                 ev.events=EPOLLIN|EPOLLET;
                 epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標識符,等待下一個循環時接收數據
             }
             else
             {
                 //其他的處理
             }
         }
     }

Nginx 與 多進程模式 Apache 的比較:

事件驅動適合於I/O密集型服務,多進程或線程適合於CPU密集型服務:
1、Nginx能夠大量作爲反向代理使用。
2、事件驅動服務器,最適合做的就是這種I/O密集型工作,如反向代理,它在客戶端與WEB服務器之間起一個數據中轉作用,純粹是I/O操作,自身並不涉及到複雜計算。因爲進程在一個地方進行計算時,那麼這個進程就不能處理其他事件了。
3、Nginx只需要少量進程配合事件驅動,起幾個進程跑libevent,不像 Apache傳統多進程模型那樣動輒數百的進程數。
4、Nginx處理靜態文件效果也很好,那是因爲讀寫文件和網絡通信其實都是I/O操作,處理過程是一樣的。

這裏僅將Nginx與傳統多進程工作模式下的Apache做比較,Apache也有事件驅動的工作模式。

參考 http://codinglife.sinaapp.com/?p=40

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章