1. 程式人生 > >深入理解Nginx及使用Nginx實現負載均衡

深入理解Nginx及使用Nginx實現負載均衡

前言:

  最近在部署專案時要求實現負載均衡,有趣的是發現網上一搜全部都是以下類似的配置檔案

upstream localhost{
    server 127.0.0.1:8080 weight=1;
    server 127.0.0.1:8081 weight=1;
    }
    server {
        listen       80;
        server_name  localhost;

        location / {
            proxy_pass http://localhost;
            index  index.html index.htm index.jsp;
        
        }
    }

  所以打算來看看Nginx內部原理,這篇部落格主要介紹Nginx如何實現反向代理以及在Nginx中負載均衡的引數使用

一、正向代理與反向代理

  正向代理是代理客戶端,也就是客戶端能真正接觸到的,比如訪問外網時需要使用VPN軟體,在這個軟體中使用者可以選擇連線哪裡的伺服器。

  反向代理則是代理服務端,使用者感知不到,只是客戶端把請求發到服務端的埠時,Nginx監聽到了便把該埠的請求轉發到不同的伺服器上。就以上面配置檔案來講解,當在網址中輸入http://localhost:80/時(不加80一樣時預設進入80埠,這裡為了表示清楚),而後Nginx監聽到80埠的請求之後,就會查詢對應的location來執行。由上面的配置檔案我們可以看出是將請求轉發到了不同的埠。這是在伺服器中執行的,使用者不可見。

  而服務端中我們最常使用的反向代理的工具就是Nginx。

二、Nginx內部基本架構

  nginx在啟動後以daemon的方式在後臺執行,會有一個master程序和多個worker程序。

  master程序:主要用來管理worker程序,包含:接收來自外界的訊號,向各worker程序傳送訊號,監控worker程序的執行狀態,當worker程序退出後(異常情況下),會自動重新啟動新的worker程序。

  worker程序:處理基本的網路事件了。多個worker程序之間是對等的,他們同等競爭來自客戶端的請求,各程序互相之間是獨立的。一個請求,只可能在一個worker程序中處理,一個worker程序,不可能處理其它程序的請求。worker程序的個數是可以設定的,一般我們會設定與機器cpu核數一致,或者直接設定引數worker_processes  auto;

  所以Nginx基本的架構就如下:

  當我們輸入./nginx -s reload,就是來重啟nginx,./nginx -s stop,就是來停止nginx的執行,這裡面是如何做到的?執行命令時,我們是啟動一個新的nginx程序,而新的nginx程序在解析到reload引數後,就知道我們的目的是控制nginx來重新載入配置檔案了,它會向master程序傳送訊號。master程序在接到訊號後,會先重新載入配置檔案,然後再啟動新的worker程序,並向所有老的worker程序傳送訊號,告訴他們可以光榮退休了。新的worker在啟動後,就開始接收新的請求,而老的worker在收到來自master的訊號後,就不再接收新的請求,並且在當前程序中的所有未處理完的請求處理完成後,再退出。所以使用上面命令重啟Nginx的時候服務是不中斷的。

三、Nginx如何處理客戶端請求 

  首先來解釋一下上面的架構圖:每個worker程序都是從master程序分支過來的,在master程序裡面,先建立好需要監聽的socket之後,然後再分支出多個worker程序。所有worker程序的listenfd(socket中listenfd是指客戶端連線本機時的fd,是用來和客戶端通訊用的)會在新連線到來時變得可讀,為保證只有一個程序處理該連線,所有worker程序在註冊listenfd讀事件前搶accept_mutex,搶到互斥鎖的那個程序註冊listenfd讀事件,在讀事件裡呼叫accept接受該連線。

  在Nginx中worker程序之間是平等的,每個程序,處理請求的機會也是一樣的。當Nginx監聽80埠時,一個客戶端的連線請求過來,每個程序都有可能處理這個連線,上面說到是每個worker程序都會去搶注listenfd讀事件。當一個worker程序在accept這個連線之後,就開始讀取請求,解析請求,處理請求,產生資料後,再返回給客戶端,最後才斷開連線,這樣一個完整的請求就是這樣的了。這裡需要注意的是一個請求,完全由worker程序來處理,而且只在一個worker程序中處理。

  下面兩幅流程圖能很好的幫我們理解

四、Nginx如何處理事件並且實現高併發

  Nginx內部採用了非同步非阻塞的方式來處理請求,也就是說,Nginx是可以同時處理成千上萬個請求的。

  非同步非阻塞:當一個網路請求過來時,我們並不依賴於這個請求才能做後續操作,那麼這個請求就是非同步操作,也就是呼叫者在沒有得到結果之前同樣可以執行後續的操作。非阻塞就是當前程序/執行緒沒有得到請求呼叫的結果時也不會妨礙到程序/執行緒後續的操作。可以看出非同步和非阻塞的物件是不同的。

  回到Nginx中,首先,請求過來,要建立連線,然後再接收資料,接收資料後,再發送資料。具體到系統底層,就是讀寫事件,而當讀寫事件沒有準備好時,必然不可操作,如果不用非阻塞的方式來呼叫,那就得阻塞呼叫了,事件沒有準備好,那就只能等了,等事件準備好了,再繼續。而阻塞呼叫會進入核心等待,cpu就會讓出去給別人用了,對單執行緒的worker來說,顯然不合適,當網路事件越多時,大家都在等待,cpu空閒下來沒人用,cpu利用率自然上不去了,更別談高併發了。而非阻塞就是,事件沒有準備好,馬上返回EAGAIN,告訴呼叫者,事件還沒準備好,過會再來。過一會,再來檢查一下事件,直到事件準備好了為止,在這期間,Nginx可以處理其他呼叫者的讀寫事件。但是雖然不阻塞了,但Nginx得不時地過來檢查一下事件的狀態,Nginx可以處理更多請求了,但帶來的開銷也是不小的。所以,才會有了非同步非阻塞的事件處理機制,具體到系統呼叫就是像select/poll/epoll/kqueue這樣的系統呼叫。它們提供了一種機制,讓你可以同時監控多個事件,以epoll為例子,當事件沒準備好時,放到epoll裡面,事件準備好了,Nginx就去讀寫,當讀寫返回EAGAIN時,就將它再次加入到epoll裡面。這樣,只要有事件準備好了,Nginx就可以去處理它,只有當所有事件都沒準備好時,才在epoll裡面等著。這樣便實現了所謂的併發處理請求,但是執行緒只有一個,所以同時能處理的請求當然只有一個了,只是在請求間進行不斷地切換而已,切換也是因為非同步事件未準備好,而主動讓出的。這裡的切換是沒有任何代價,你可以理解為迴圈處理多個準備好的事件,事實上就是這樣的。與多執行緒相比,這種事件處理方式是有很大的優勢的,不需要建立執行緒,每個請求佔用的記憶體也很少,沒有上下文切換,事件處理非常的輕量級。併發數再多也不會導致無謂的資源浪費(上下文切換)。更多的併發數,只是會佔用更多的記憶體而已。現在的網路伺服器基本都採用這種方式,這也是nginx效能高效的主要原因。

五、Nginx負載均衡的演算法及引數

  round robin(預設):輪詢方式,依次將請求分配到後臺各個伺服器中,適用於後臺機器效能一致的情況,若伺服器掛掉,可以自動從服務列表中剔除

  weight:根據權重來分發請求到不同伺服器中,可以理解為比例分發,效能較高伺服器分多點請求,較低的則分少點請求

  IP_hash:根據請求者ip的hash值將請求傳送到後臺伺服器中,保證來自同一ip的請求被轉發到固定的伺服器上,解決session問題

upstream localhost {    
ip_hash;    
server 127.0.0.1:8080;    
server 127.0.0.1:8080;    
}  

  上面是最基本的三種演算法,我們還可以通過改變引數來自行配置負載均衡

upstream localhost{   
ip_hash;    
server 127.0.0.1:9090 down;    
server 127.0.0.1:8080 weight=2;    
server 127.0.0.1:6060;    
server 127.0.0.1:7070 backup;    
}

引數列表如下:

down 表示單前的server暫時不參與負載
weight 預設為1.weight越大,負載的權重就越大
max_fails 允許請求失敗的次數預設為1.當超過最大次數時,返回proxy_next_upstream 模組定義的錯誤
fail_timeout max_fails次失敗後,暫停的時間
backup 其它所有的非backup機器down或者忙的時候,請求backup機器。所以這臺機器壓力會最輕

參考:

理解Nginx工作原理

Nginx從入門到精通

&n