軟負載與nginx那些強大的不可不說的功能
當我們打開手機訪問點評客戶端的時候,訪問商戶的請求是如何到達對應某臺應用服務器的?
當有很多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進程發送信號一樣了。
#p#
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
#p#
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的超集。
當然,我們的科普之路才剛剛開始,更多的還需要大家自行研究,快樂往往都是自找的。
書籍推薦:
《深入理解nginx:模塊開發與架構解析》 作者:陶輝 阿里巴巴資深nginx技術專家
參考資料:
http://blog.csdn.net/syhd142/article/details/8440667
http://blog.csdn.net/yankai0219/article/details/8018275
http://blog.csdn.net/yankai0219/article/details/8018232
http://www.cppblog.com/converse/archive/2009/05/13/82879.html
http://www.alidata.org/archives/1208