軟負載與nginx那些強大的不可不說的功能

http://tech.hexun.com/2014-08-05/167270036.html

當我們打開手機訪問點評客戶端的時候,訪問商戶的請求是如何到達對應某臺應用服務器的?

當有很多XX寬帶的用戶投訴說我大點評某某域名無法打開但是我們卻找不出任何問題的時候,我們就想到會不會是寬帶運營商的問題。

今天與大家分享的話題,主要是跟我們的軟負載集羣和Nginx這個強大的開源應用有關係。

當我們準備上線一個新的業務,或者新的功能時候,除了把代碼發佈的線上生產環境的應用服務器外,還需要做什麼工作才能讓我們的資深吃貨的用戶們可以訪問到我們高端大氣上檔次的服務呢?

用戶是不可能直接跑到我們的IDC機房插根網線就來訪問我們的內部服務器的,我們答應,電信管理IDC的怪叔叔們也不會答應啊。

首先,我們很清楚用戶是通過dianping.com的域名來訪問我們的站點,同時通過我們對外開放的url鏈接來訪問一些新站點或者新功能的頁面。然後,用戶訪問的域名會通過DNS服務被替換爲我們對外的IP地址,這樣才能被網絡設備識別,然後將用戶的請求按照一個一個網絡包發給我們的網絡設備,最後網絡設備收到這些網絡數據包後,會將這些數據整理後轉化爲應用程序可以理解的數據。

數據到了我們的核心網絡設備被轉換爲應用層的數據後,是如何到我們具體某一臺應用服務器來處理呢,這就牽涉到我們要講的負載均衡器了。負載均衡器如果是硬件設備的話,那就是我們經常提到的負載均衡設備,如果是linux服務器上運行的負載均衡軟件,那就是軟負載了,如果是集羣而不是單機的話,那就是傳說中的負載均衡集羣了。

如下圖是我們 線上生產環境的用戶請求走向圖:

當一個吃貨通過瀏覽器或手機APP訪問我們網站的時候,無論是訪問商戶,添加點評,購買團購,還是在社區通過私信功能與妹子聊天,所有請求都會經過我們的F5負載均衡設備按照設定的轉發策略(隨機,權重,最小連接等)轉發到特定的某臺應用服務器來處理,然後再將處理結果返回給用戶。

好吧,當我們說了這個硬件設備時候,是不是要談談以軟件實現的負載均衡功能呢,其實目前在我們PPE環境(xx機房,以後的雙IDC運行後另一個生產環境)運行着這樣一套軟負載集羣來處理用戶的請求(當然,現在都是僞造的用戶請求)。

網絡設備和Nginx負載均衡集羣中間的F5作爲流量管理設備,做4層(連接層,tcp)流量分發。

軟負載實質上是一組nginx集羣以及允許用戶管理nginx配置文件的一個web端。

Nginx ("engine x") 是一個高性能的 HTTP 和 反向代理 服務器,也是一個 IMAP/POP3/SMTP 代理服務器。 Nginx因穩定性、豐富的功能集和低系統資源的消耗而聞名。

從中我們可以看出,nginx至少可以做web服務器,同時可以做反向代理服務器,同時又可以做郵件代理服務器,功能還是非常豐富的,稍後我們會對nginx的功能模塊做簡要的介紹。

作爲web服務器,nginx由於自身的優勢,在處理靜態文件上有着絕對的優勢,所以也是天然的優秀web服務器軟件。

什麼是反向代理服務器,和我們平時說的用代理服務器上國外網站又有什麼區別?

有圖有真相,看圖說話

代理服務器呢,就是當我想訪問某個網站的時候因爲各種原因不能直接訪問,我可以主動或者被動用一臺可以訪問目標站點的服務器做代理去訪問我想要訪問的站點。

當我主動去找代理服務器去訪問是一種情況,還有一種比較悲催的情況,當我們使用了某些無良寬帶運營商提供的物美價廉,縮水嚴重,還不斷搞各種潛規則的寬帶時候,就會碰到我們這些吃貨去訪問點評網站的時候,首先是去訪問寬帶運營商局域網的代理服務器,然後代理服務器去訪問點評的網站。這樣做對於寬帶運營商來說,可以緩存一些數據,這樣就能節省點帶寬,但是對於我們這些使用寬帶的用戶而言,一則數據不安全,運營商的代理服務器上可能有我們的豔照也說不定,二則,當點評站點可正常訪問,寬帶運營商代理服務器出現問題的時候,就會收到各種用戶投訴,點評又跪了,這讓吃貨怎麼活?問題是點評活的好好的,用戶卻訪問不到。

反向代理(Reverse Proxy)是指以代理服務器來接受internet上的連接請求,然後將請求轉發給內部網絡上的服務器,並將從服務器上得到的結果返回給internet上請求連接的客戶端,此時代理服務器對外就表現爲一個服務器。

反向代理服務器可以看下面圖,nginx就是反向代理的角色,用戶的請求是先發到nginx,然後轉發到後端的tomcat。

當然,代理服務器和反向代理服務器的類型不只web服務一種。

如下是一個簡單的反向代理的配置:

server_name   qunying.dianping.com;
location / {
proxy_pass test.qunying.liu.dianping.com; //反向代理站。
index index.html index.htm;
}

當用戶訪問qunying.dianping.com/helloword.jsp的時候,這臺服務器上的nginx就會將用戶的請求轉發到qunying.liu.dianping.com/helloword.jsp,當然proxy_pass轉向的地址也可以是內部地址,比如127.0.0.1:8080
當然對於我們線上的環境,nginx不是作爲典型的反向代理在使用,目前點評java相關web業務服務器上採用的是 nginx (緩存和壓縮,日誌)+tomcat(java容器),充分利用了nginx低系統佔用以及高併發處理的優勢。

很多人會有疑問,tomcat也可以做web容器的啊,改個端口不就可以直接給用戶提供服務了,而且tomcat也能記錄日誌,沒必要再放一個nginx啊。

tomcat 前面有沒有必要放一個nginx呢?

術業有專攻,tomcat做web服務器是兼職,做java容器是專職。nginx服務器是專職做web服務器,支持高併發,響應快,擅長處理靜態內容,而且可以把動態內容交給tomcat處理。用tomcat做web容器響應用戶請求,有可能1分鐘只能處理10個請求,但是用nginx+tomcat一分鐘就可能可以處理100個請求。

nginx爲什麼可以這麼快處理用戶的請求呢?

nginx的進程模型以及系統事件機制

nginx啓動後的進程,如圖所示:

我們可以看到master進程,是以root身份啓動,執行內容爲:/usr/local/nginx/sbin/nginx -c

/usr/local/nginx/conf/nginx.conf

worker進程都是以我們指定的nobody身份運行,其中有一個worker進程是舊的worker進程奔潰後,自動重新創建的,你能找到他嗎?

nginx在啓動後,會有一個master進程和多個worker進程。master進程主要用來管理worker進程,包含:接收來自外界的信號,向各個worker進程發送信號,監控worker進程的運行狀態,當worker進程退出後(異常情況下),會自動重新啓動新的worker進程。基本的網絡事件,則是放在worker進程中來處理了。多個worker進程之間是對等的,他們同等競爭來自客戶端的請求,各進程互相之間是獨立的。一個請求,只可能在一個worker進程中處理,一個worker進程,不可能處理其它進程的請求。worker進程的個數是可以設置的,一般我們會設置與機器cpu核數一致。

我們要控制nginx,只需要通過kill向master進程發送信號就行了。比如kill -HUP pid,則是告訴nginx,從容地重啓nginx,我們一般用這個信號來重啓nginx,或重新加載配置,因爲是從容地重啓,因此服務是不中斷的。master進程在接收到HUP信號後是怎麼做的呢?首先master進程在接到信號後,會先重新加載配置文件,然後再啓動新的進程,並向所有老的進程發送信號,告訴他們可以光榮退休了。新的進程在啓動後,就開始接收新的請求,而老的進程在收到來自master的信號後,就不再接收新的請求,並且在當前進程中的所有未處理完的請求處理完成後,再退出。當然,直接給master進程發送信號,這是比較老的操作方式,nginx在0.8版本之後,引入了一系列命令行參數,來方便我們管理。比如,./nginx -s reload,就是來重啓nginx,./nginx -s stop,就是來停止nginx的運行。如何做到的呢?我們還是拿reload來說,我們看到,執行命令時,我們是啓動一個新的nginx進程,而新的nginx進程在解析到reload參數後,就知道我們的目的是控制nginx來重新加載配置文件了,它會向master進程發送信號,然後接下來的動作,就和我們直接向master進程發送信號一樣了。

worker進程是如何處理我們的http請求的?

master(master進程會先建立好需要listen的socket)--------fork生成子進程workers,繼承socket(此時workers子進程們都繼承了父進程master的所有屬性,當然也包括已經建立好的socket,當然不是同一個socket,只是每個進程的這個socket會監控在同一個ip地址與端口,這個在網絡協議裏面是允許的)------當一個連接進入,產生驚羣現象(驚羣現象:指一個fd的事件被觸發後,等候這個fd的所有線程/進程都被喚醒。雖然都被喚醒,但是隻有一個會去響應。)。

Nginx對驚羣現象的處理:共享鎖

nginx提供了一個accept_mutex這個東西,從名字上,我們可以看這是一個加在accept上的一把共享鎖。有了這把鎖之後,同一時刻,就只會有一個進程在accpet連接,這樣就不會有驚羣問題了。accept_mutex是一個可控選項,我們可以顯示地關掉,默認是打開的。

worker進程工作:

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

採用這種方式的好處:

1)節省鎖帶來的開銷。對於每個worker進程來說,獨立的進程,不需要加鎖,所以省掉了鎖帶來的開銷,同時在編程以及問題查上時,也會方便很多

2)獨立進程,減少風險。採用獨立的進程,可以讓互相之間不會影響,一個進程退出後,其它進程還在工作,服務不會中斷, master進程則很快重新啓動新的worker進程。當然,worker進程的異常退出,肯定是程序有bug了,異常退出,會導致當前worker上的所有請求失敗,不過不會影響到所有請求,所以降低了風險。

Nginx的事件處理機制,採用異步非阻塞事件處理機制,一個worker進程只有一個主線程,通過異步非阻塞的事件處理機制,實現了循環處理多個準備好的事件,從而實現輕量級和高併發。

異步非阻塞事件處理機制:

同步和異步的概念,這兩個概念與消息的通知機制有關.同步的情況下,是由處理消息者自己去等待消息是否被觸發,而異步的情況下是由觸發機制來通知處理消息者。

阻塞和非阻塞,這兩個概念與程序等待消息(無所謂同步或者異步)時的狀態有關.

當讀寫事件沒有準備好時,就放入epoll裏面。如果有事件準備好了,那麼就去處理;如果事件返回的是EAGAIN,那麼繼續將其放入epoll裏面。從而,只要有事件準備好了,我們就去處理,只有當所有時間都沒有準備好時,纔在epoll裏面等着。這樣,我們就可以併發處理大量的併發了,當然,這裏的併發請求,是指未處理完的請求,線程只有一個,所以同時能處理的請求當然只有一個了,只是在請求間進行不斷地切換而已,切換也是因爲異步事件未準備好,而主動讓出的。這裏的切換是沒有任何代價,你可以理解爲循環處理多個準備好的事件。

與多線程相比,這種事件處理方式是有很大的優勢的,不需要創建線程,每個請求佔用的內存也很少,沒有上下文切換,事件處理非常的輕量級。併發數再多也不會導致無謂的資源浪費(上下文切換)。更多的併發數,只是會佔用更多的內存而已.
之前我們提到nginx的負載均衡功能,那麼和LVS的負載均衡有什麼區別呢?

負載均衡分爲:

L4 switch(四層交換),即在OSI第4層工作,就是TCP層啦。此種Load Balance不理解應用協議(如HTTP/FTP/MySQL等等)。例子:LVS,F5

L7 switch(七層交換),OSI的最高層,應用層。此時,該Load Balancer能理解應用協議。例子:haproxy,MySQL Proxy
很多Load Balancer(例如F5)既可以做四層交換,也可以做七層交換。

LVS 工作在網絡4層僅做請求分發之用沒有流量,可配置性低,幾乎可對所有應用做負載均衡,對網絡依賴大,沒有健康檢查機制。

nginx的7層(應用層),所以它可以針對http應用本身來做分流策略,比如針對域名、目錄結構等,對網絡依賴小,可檢測服務器內部錯誤。

nginx可以根據URL進行負載均衡的請求轉發,而LVS只能根據ip:port進行請求轉發

一般情況下,LVS會被放在最前端做負載均衡,nginx可作爲lvs的節點服務器。

前面我們也提到過nginx實現郵件代理服務器的功能,一般使用nginx做郵件代理服務器的場景不多。

很不幸,nginx最早也是被當作郵件代理服務器來開發的。

--with-mail - 啓用 IMAP4/POP3/SMTP 代理模塊

安裝時需要注意的庫依賴:

gzip模塊需要 zlib 庫

rewrite模塊需要 pcre 庫

ssl 功能需要openssl庫

我們nginx一般安裝在:/usr/local/nginx 目錄,nginx的安裝目錄結構如下圖所示

/usr/local/nginx

├── conf(配置文件目錄)
│   ├── fastcgi.conf
│   ├── fastcgi.conf.default
│   ├── fastcgi_params
│   ├── fastcgi_params.default
│   ├── hosts
│   ├── koi-utf
│   ├── koi-win
│   ├── mime.types
│   ├── mime.types.default
│   ├── nginx_app.conf(應用相關配置段 server段)
│   ├── nginx.conf(nginx公用配置信息 events,http段)
│   ├── nginx.conf.default
│   ├── nginx_status.conf
│   ├── proxy.conf
│   ├── scgi_params
│   ├── scgi_params.default
│   ├── uwsgi_params
│   ├── uwsgi_params.default
│   └── win-utf
├── html
│   ├── 50x.html
│   └── index.html
├── logs -> /data/applogs/nginx
├── sbin(nginx程序目錄)
│   └── nginx
└── tmpdir
   ├── client_body_temp
   ├── fastcgi_temp
   ├── proxy_temp
   │   ├── 0
   │   │   └── 01
   │   ├── 1
   │   │   ├── 00
   │   │   └── 01
   │   ├── 2
   │   │   ├── 00
   │   │   └── 01
   │   ├── 3
   │   │   ├── 00
   │   │   └── 01
   │   ├── 4
   │   │   ├── 00
   │   │   └── 01
   │   ├── 5
   │   │   ├── 00
   │   │   └── 01
   │   ├── 6
   │   │   ├── 00
   │   │   └── 01
   │   ├── 7
   │   │   ├── 00
   │   │   └── 01
   │   ├── 8
   │   │   ├── 00
   │   │   └── 01
   │   └── 9
   │       └── 00
   ├── scgi_temp
   └── uwsgi_temp

nginx基本配置文件:

#運行用戶(worker進程屬主)
user nobody;
#啓動進程,設置成和cpu的數量相等

過多的worker數,只會導致進程相互競爭cpu資源,從而帶來不必要的上下文切換

worker_processes  4;
#全局錯誤日誌及PID文件
#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;
#pid        logs/nginx.pid;
#工作模式及連接數上限
events {
    #epoll是多路複用IO(I/O Multiplexing)中的一種方式,
    #僅用於linux2.6以上內核,可以大大提高nginx的性能
    use   epoll;
    #單個後臺worker process進程的最大併發鏈接數  
    worker_connections  1024;
    # 併發總數是 worker_processes 和 worker_connections 的乘積
    # 即 max_clients = worker_processes * worker_connections
    # worker_connections 值的設置跟物理內存大小有關
    # 因爲併發受IO約束,max_clients的值須小於系統可以打開的最大文件數
    # 系統可以打開的最大文件數和內存大小成正比
    # $ cat /proc/sys/fs/file-max
    # 併發連接總數小於系統可以打開的文件句柄總數,這樣就在操作系統可以承受的範圍之內
    # worker_connections 的值需根據 worker_processes 
進程數目和系統可以打開的最大文件總數進行適當地進行設置
    # 根據主機的物理可用CPU和可用內存進行配置
}
http {
    #設定mime類型,類型由mime.type文件定義
    include    mime.types;
    default_type  application/octet-stream;
    #設定日誌格式
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log  logs/access.log  main;
    #sendfile 指令指定 nginx 是否調用 sendfile 函數(zero copy 方式)來輸出文件,
    #對於普通應用,必須設爲 on,
    #如果用來進行下載等應用磁盤IO重負載應用,
可設置爲 off,以平衡磁盤與網絡I/O處理速度,降低系統的uptime.
    sendfile     on;
    #tcp_nopush     on;
    #連接超時時間
    #keepalive_timeout  0;
    keepalive_timeout  65;
    tcp_nodelay     on;
    #開啓gzip壓縮
    gzip  on;
    gzip_disable "MSIE [1-6].";
    #設定請求緩衝
    client_header_buffer_size    128k;
    large_client_header_buffers  4 128k;
    #設定虛擬主機配置
    server {
        #偵聽80端口
        listen    80;
        #定義使用 www.nginx.cn訪問
        server_name  www.nginx.cn;
        #定義服務器的默認網站根目錄位置
        root html;
        #設定本虛擬主機的訪問日誌
        access_log  logs/nginx.access.log  main;
        #默認請求
        location / {
                     
            #定義首頁索引文件的名稱
            index index.php index.html index.htm; 
        }
        # 定義錯誤提示頁面
        error_page   500 502 503 504 /50x.html;
        location = /50x.html {
        }
        #靜態文件,nginx自己處理
        location ~ ^/(images|javascript|js|css|flash|media|static)/ {
                     
            #過期30天,靜態文件不怎麼更新,過期可以設大一點,
            #如果頻繁更新,則可以設置得小一點。
            expires 30d;
        }
        #PHP 腳本請求全部轉發到 FastCGI處理. 使用FastCGI默認配置.
        location ~ .php$ {
            fastcgi_pass 127.0.0.1:9000;
            fastcgi_index index.php;
            fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
        #禁止訪問 .htxxx 文件
            location ~ /.ht {
            deny all;
        }
    }
}

線上的一個應用的配置文件:

user                              nobody nobody;
worker_processes                  4;
worker_rlimit_nofile              51200;
error_log                         logs/error.log  notice;
pid                               /var/run/nginx.pid;
events {
  use                             epoll;
  worker_connections              51200;
}
http {
  server_tokens                   off;
  include                         mime.types;
  include                         proxy.conf;
  default_type                    application/octet-stream;
  charset                         utf-8;
  client_body_temp_path           /usr/local/nginx/tmpdir/client_body_temp 1 2;
  proxy_temp_path                 /usr/local/nginx/tmpdir/proxy_temp 1 2;
  fastcgi_temp_path               /usr/local/nginx/tmpdir/fastcgi_temp 1 2;
  uwsgi_temp_path                 /usr/local/nginx/tmpdir/uwsgi_temp 1 2;
  scgi_temp_path                  /usr/local/nginx/tmpdir/scgi_temp 1 2;
  ignore_invalid_headers          on;
  server_names_hash_max_size      256;
  server_names_hash_bucket_size   64;
  client_header_buffer_size       8k;
  large_client_header_buffers     4 32k;
  connection_pool_size            256;
  request_pool_size               64k;
  output_buffers                  2 128k;
  postpone_output                 1460;
  client_header_timeout           1m;
  client_body_timeout             3m;
  send_timeout                    3m;
  sendfile                        on;
  tcp_nodelay                     on;
  tcp_nopush                      off;
  reset_timedout_connection       on;
  keepalive_timeout               20 5;
  keepalive_requests              100;
  gzip                            on;
  gzip_http_version               1.1;
  gzip_vary                       on;
  gzip_proxied                    any;
  gzip_min_length                 1024;
  gzip_comp_level                 6;
  gzip_buffers                    16 8k;
  gzip_proxied                    expired no-cache no-store private auth no_last_modified no_etag;
  gzip_types                      text/plain application/x-javascript text/css application/xml application/json;
  gzip_disable                    "MSIE [1-6]\.(?!.*SV1)";
  include                         nginx_app.conf; #與應用有關的server配置
  include                         nginx_status.conf; #單機nginx訪問統計配置

Nginx配置文件分爲好多塊,常見的從外到內依次是「http」、「server」、「location」等等,缺省的繼承關係是從外到內,也就是說內層塊會自動獲取外層塊的值作爲缺省值。

root和alias的區別

當用戶訪問http://ip:port/nginx/qunying/helloword.jsp時,nginx上可以進行如下配置:

location  /nginx/qunying/ {
       alias  /home/qunying.liu/;
       }

實際訪問的文件是:/home/qunying.liu/helloword.jsp

location /nginx/qunying/ {
       root  /home/;
       }

實際訪問的文件是:/home/qunying/helloword.jsp

在location /中配置root,在location /other中配置alias,推薦如此配置

nginx的目錄瀏覽功能:

在server里加上如下三行即可。

autoindex on;  
autoindex_localtime on;  
autoindex_exact_size off;

nginx登錄驗證:

在location段內添加:

auth_basic "phoenix slb admin ";  
auth_basic_user_file /data/appdatas/phoenix-slb-passwd;

最後說下:

nginx與tengine

官網介紹:http://tengine.taobao.org/

Tengine是由淘寶網發起的Web服務器項目。它在Nginx的基礎上,針對大訪問量網站的需求,添加了很多高級功能和特性。Tengine的性能和穩定性已經在大型的網站如淘寶網,天貓商城等得到了很好的檢驗。它的最終目標是打造一個高效、穩定、安全、易用的Web平臺。

我們的軟負載實際上是基於tengine 開發的,個人認爲 tengine實際上就是nginx,只不過功能比nginx多,屬於Nginx的超集。

當然,我們的科普之路纔剛剛開始,更多的還需要大家自行研究,快樂往往都是自找的。

發佈了0 篇原創文章 · 獲贊 2 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章