1. 程式人生 > >軟負載與nginx強大功能

軟負載與nginx強大功能

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

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

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

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

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

首先,我們很清楚使用者是通過dianping.com的域名來訪問我們的站點,同時通過我們對外開放的url連結來訪問一些新站點或者新功能的頁面。然後,使用者訪問的域名會通過DNS服務被替換為我們對外的IP地址,這樣才能被網路裝置識別,然後將使用者的請求按照一個一個網路包發給我們的網路裝置,最後網路裝置收到這些網路資料包後,會將這些資料整理後轉化為應用程式可以理解的資料。

資料到了我們的核心網路裝置被轉換為應用層的資料後,是如何到我們具體某一臺應用伺服器來處理呢,這就牽涉到我們要講的負載均衡器了。負載均衡器如果是硬體裝置的話,那就是我們經常提到的負載均衡裝置,如果是linux伺服器上執行的負載均衡軟體,那就是軟負載了,如果是叢集而不是單機的話,那就是傳說中的負載均衡叢集了。

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

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

wKiom1NhAT3xIqdpAAD43rtCzhQ827.jpg

好吧,當我們說了這個硬體裝置時候,是不是要談談以軟體實現的負載均衡功能呢,其實目前在我們PPE環境(xx機房,以後的雙IDC執行後另一個生產環境)執行著這樣一套軟負載叢集來處理使用者的請求(當然,現在都是偽造的使用者請求)。

wKioL1NhASOBUE4oAAEaWwRzzCQ111.jpg

網路裝置和Nginx負載均衡叢集中間的F5作為流量管理裝置,做4層(連線層,tcp)流量分發。

wKiom1NhAWqBgZGNAAE70_ipu7w168.jpg

軟負載實質上是一組nginx叢集以及允許使用者管理nginx配置檔案的一個web端。

wKiom1NhAXrBLo_1AAEhYUrHQmM672.jpg

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

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

作為web伺服器,nginx由於自身的優勢,在處理靜態檔案上有著絕對的優勢,所以也是天然的優秀web伺服器軟體。

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

有圖有真相,看圖說話

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

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

wKioL1NhAV7jqiPLAACXJkGloDQ338.jpg

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

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

wKiom1NhAZbSut7FAACZukjMSpM461.jpg

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

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

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

當然對於我們線上的環境,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啟動後的程序,如圖所示:

wKioL1NhAYuDpxoQAAFduYmboQ4730.jpg

我們可以看到master程序,是以root身份啟動,執行內容為:/usr/local/nginx/sbin/nginx -c /usr/local/nginx/conf/nginx.conf

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

wKioL1NhAZmwwXNPAADogK6xWTc871.jpg

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資源,從而帶來不必要的上下文切換

  1. worker_processes 4;  
  2. #全域性錯誤日誌及PID檔案  
  3. #error_log logs/error.log;  
  4. #error_log logs/error.log notice;  
  5. #error_log logs/error.log info;  
  6. #pid logs/nginx.pid;  
  7. #工作模式及連線數上限  
  8. events {  
  9. #epoll是多路複用IO(I/O Multiplexing)中的一種方式,  
  10. #僅用於linux2.6以上核心,可以大大提高nginx的效能  
  11. use epoll;  
  12. #單個後臺worker process程序的最大併發連結數   
  13. worker_connections 1024;  
  14. # 併發總數是 worker_processes 和 worker_connections 的乘積  
  15. # 即 max_clients = worker_processes * worker_connections  
  16. # worker_connections 值的設定跟實體記憶體大小有關  
  17. # 因為併發受IO約束,max_clients的值須小於系統可以開啟的最大檔案數  
  18. # 系統可以開啟的最大檔案數和記憶體大小成正比  
  19. # $ cat /proc/sys/fs/file-max  
  20. # 併發連線總數小於系統可以開啟的檔案控制代碼總數,這樣就在作業系統可以承受的範圍之內  
  21. # worker_connections 的值需根據 worker_processes 程序數目和系統可以開啟的最大檔案總數進行適當地進行設定  
  22. # 根據主機的物理可用CPU和可用記憶體進行配置  
  23. }  
  24. http {  
  25. #設定mime型別,型別由mime.type檔案定義  
  26. include mime.types;  
  27. default_type application/octet-stream;  
  28. #設定日誌格式  
  29. log_format main '$remote_addr - $remote_user [$time_local] "$request" '  
  30. '$status $body_bytes_sent "$http_referer" '  
  31. '"$http_user_agent" "$http_x_forwarded_for"';  
  32. access_log logs/access.log main;  
  33. #sendfile 指令指定 nginx 是否呼叫 sendfile 函式(zero copy 方式)來輸出檔案,  
  34. #對於普通應用,必須設為 on,  
  35. #如果用來進行下載等應用磁碟IO重負載應用,可設定為 off,以平衡磁碟與網路I/O處理速度,降低系統的uptime.  
  36. sendfile on;  
  37. #tcp_nopush on;  
  38. #連線超時時間  
  39. #keepalive_timeout 0;  
  40. keepalive_timeout 65;  
  41. tcp_nodelay on;  
  42. #開啟gzip壓縮  
  43. gzip on;  
  44. gzip_disable "MSIE [1-6].";  
  45. #設定請求緩衝  
  46. client_header_buffer_size 128k;  
  47. large_client_header_buffers 4 128k;  
  48. #設定虛擬主機配置  
  49. server {  
  50. #偵聽80埠  
  51. listen 80;  
  52. #定義使用 www.nginx.cn訪問  
  53. server_name www.nginx.cn;  
  54. #定義伺服器的預設網站根目錄位置  
  55. root html;  
  56. #設定本虛擬主機的訪問日誌  
  57. access_log logs/nginx.access.log main;  
  58. #預設請求  
  59. location / {  
  60. #定義首頁索引檔案的名稱  
  61. index index.php index.html index.htm;   
  62. }  
  63. # 定義錯誤提示頁面  
  64. error_page 500 502 503 504 /50x.html;  
  65. location = /50x.html {  
  66. }  
  67. #靜態檔案,nginx自己處理  
  68. location ~ ^/(images|javascript|js|css|flash|media|static)/ {  
  69. #過期30天,靜態檔案不怎麼更新,過期可以設大一點,  
  70. #如果頻繁更新,則可以設定得小一點。  
  71. expires 30d;  
  72. }  
  73. #PHP 指令碼請求全部轉發到 FastCGI處理. 使用FastCGI預設配置.  
  74. location ~ .php$ {  
  75. fastcgi_pass 127.0.0.1:9000;  
  76. fastcgi_index index.php;  
  77. fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  
  78. include fastcgi_params;  
  79. }  
  80. #禁止訪問 .htxxx 檔案  
  81. location ~ /.ht {  
  82. deny all;  
  83. }  
  84. }  

線上的一個應用的配置檔案:

  1. user nobody nobody;  
  2. worker_processes 4;  
  3. worker_rlimit_nofile 51200;  
  4. error_log logs/error.log notice;  
  5. pid /var/run/nginx.pid;  
  6. events {  
  7. use epoll;  
  8. worker_connections 51200;  
  9. }  
  10. http {  
  11. server_tokens off;  
  12. include mime.types;  
  13. include proxy.conf;  
  14. default_type application/octet-stream;  
  15. charset utf-8;  
  16. client_body_temp_path /usr/local/nginx/tmpdir/client_body_temp 1 2;  
  17. proxy_temp_path /usr/local/nginx/tmpdir/proxy_temp 1 2;  
  18. fastcgi_temp_path /usr/local/nginx/tmpdir/fastcgi_temp 1 2;  
  19. uwsgi_temp_path /usr/local/nginx/tmpdir/uwsgi_temp 1 2;  
  20. scgi_temp_path /usr/local/nginx/tmpdir/scgi_temp 1 2;  
  21. ignore_invalid_headers on;  
  22. server_names_hash_max_size 256;  
  23. server_names_hash_bucket_size 64;  
  24. client_header_buffer_size 8k;  
  25. large_client_header_buffers 4 32k;  
  26. connection_pool_size 256;  
  27. request_pool_size 64k;  
  28. output_buffers 2 128k;  
  29. postpone_output 1460;  
  30. client_header_timeout 1m;  
  31. client_body_timeout 3m;  
  32. send_timeout 3m;  
  33. sendfile on;  
  34. tcp_nodelay on;  
  35. tcp_nopush off;  
  36. reset_timedout_connection on;  
  37. keepalive_timeout 20 5;  
  38. keepalive_requests 100;  
  39. gzip on;  
  40. gzip_http_version 1.1;  
  41. gzip_vary on;  
  42. gzip_proxied any;  
  43. gzip_min_length 1024;  
  44. gzip_comp_level 6;  
  45. gzip_buffers 16 8k;  
  46. gzip_proxied expired no-cache no-store private auth no_last_modified no_etag;  
  47. gzip_types text/plain application/x-javascript text/css application/xml application/json;  
  48. gzip_disable "MSIE [1-6]\.(?!.*SV1)";  
  49. include nginx_app.conf; #與應用有關的server配置  
  50. include nginx_status.conf; #單機nginx訪問統計配置 

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

root和alias的區別

  1. location /nginx/qunying/ {  
  2. alias /home/qunying.liu/;  
  3. }  

  1. location /nginx/qunying/ {  
  2. root /home/;  
  3. }  

實際訪問的檔案是:/home/qunying/helloword.jsp

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