通過我們會用Nginx的upstream做基於http/https埠的7層負載均衡,由於Nginx老版本不支援tcp協議,所以基於tcp/udp埠的四層負載均衡一般用LVS或Haproxy來做。至於4層負載均衡和7層負載均衡的區別,可以參考:http://www.cnblogs.com/kevingrace/p/6137881.html。然而Nginx從1.9.0版本開始,新增加了一個stream模組,用來實現四層協議的轉發、代理或者負載均衡等,鑑於Nginx在7層負載均衡和web service上的成功,和Nginx良好的框架,stream模組前景一片光明。官方文件:http://nginx.org/en/docs/stream/ngx_stream_core_module.html
Nginx的stream模組預設不會自帶安裝,需要編譯安裝的時候手動新增上這個模組。廢話不多說了,下面介紹下一個自己使用stream做四層負載均衡(socket協議代理)的案例:
在此之前已經使用Ningx+Keepalived(主從模式)做了7層負載均衡的LB環境,之前編譯的時候,沒有加上這個stream模組,所以需要後續手動新增該模組。 由於Nginx的LB已經有業務跑在上面,可以選擇平滑新增stream模組,並不會對線上業務造成多大影響。步驟如下: 1)先在LB的slave從機上進行平滑新增,然後再將vip切換到從機上,隨即在對master主機進行平滑新增該模組。 2)平滑新增即是重新configure編譯的時候加上--with-stream,接著make。 3)千萬注意,make之後,不要make install,否則會覆蓋掉之前的配置!!! -------------------------------------------------------------------------------------------------- 由於本人的LB環境升級了openssl版本,再新增--with-stream重新編譯的時候報了錯誤,具體可參考: http://www.cnblogs.com/kevingrace/p/8058535.html -------------------------------------------------------------------------------------------------- 檢查下,發現nginx沒有安裝stream模組 [root@external-lb01 ~]# /data/nginx/sbin/nginx -V nginx version: nginx/1.12.2 built by gcc 4.4.7 20120313 (Red Hat 4.4.7-18) (GCC) built with OpenSSL 1.1.0g 2 Nov 2017 TLS SNI support enabled configure arguments: --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre 操作之前,一定要備份一下之前的nginx安裝目錄,防止操作失敗進行回滾! [root@external-lb01 ~]# cp -r /data/nginx /mnt/nginx.bak 之前的編譯命令是: [root@external-lb01 vhosts]# cd /data/software/nginx-1.12.2 [root@external-lb01 nginx-1.12.2]# ./configure --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre 現在需要手動新增stream,編譯命令如下: [root@external-lb01 vhosts]# cd /data/software/nginx-1.12.2 [root@external-lb01 nginx-1.12.2]# ./configure --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream [root@external-lb01 nginx-1.12.2]# make [root@external-lb01 nginx-1.12.2]# cp -f /data/software/nginx-1.12.2/objs/nginx /data/nginx/sbin/nginx [root@external-lb01 nginx-1.12.2]# pkill -9 nginx [root@external-lb01 nginx-1.12.2]# /data/nginx/sbin/nginx 檢查下,發現nginx已經安裝了stream模組了 [root@external-lb01 nginx-1.12.2]# /data/nginx/sbin/nginx -V nginx version: nginx/1.12.2 built by gcc 4.4.7 20120313 (Red Hat 4.4.7-18) (GCC) built with OpenSSL 1.1.0g 2 Nov 2017 TLS SNI support enabled configure arguments: --prefix=/data/nginx --user=www --group=www --with-http_ssl_module --with-http_flv_module --with-http_stub_status_module --with-http_gzip_static_module --with-pcre --with-stream --with-openssl=/usr/local/ssl ====================================================================================================================== stream的4層負載均衡和upstream的7層負載均衡可以共同配置在nginx中,stream模組用法和http模組差不多,關鍵的是語法幾乎一致。 具體如下: [root@external-lb01 ~]# cat /data/nginx/conf/nginx.conf user www; worker_processes 8; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { worker_connections 65535; } stream { upstream kk5 { server 10.0.58.22:5100; server 10.0.58.23:5100; } upstream kk5http { server 10.0.58.22:8000; server 10.0.58.23:8000; } upstream kk5https { server 10.0.58.22:8443; server 10.0.58.23:8443; } server { listen 5100; proxy_connect_timeout 1s; proxy_pass kk5; } server { listen 8000; proxy_connect_timeout 1s; proxy_pass kk5http; } server { listen 8443; proxy_connect_timeout 1s; proxy_pass kk5https; } } http { include mime.types; default_type application/octet-stream; charset utf-8; ###### ## set access log format ###### log_format main '$remote_addr $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '$http_user_agent $http_x_forwarded_for $request_time $upstream_response_time $upstream_addr $upstream_status'; ####### ## http setting ####### sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; proxy_cache_path /var/www/cache levels=1:2 keys_zone=mycache:20m max_size=2048m inactive=60m; proxy_temp_path /var/www/cache/tmp; fastcgi_connect_timeout 3000; fastcgi_send_timeout 3000; fastcgi_read_timeout 3000; fastcgi_buffer_size 256k; fastcgi_buffers 8 256k; fastcgi_busy_buffers_size 256k; fastcgi_temp_file_write_size 256k; fastcgi_intercept_errors on; # client_header_timeout 600s; client_body_timeout 600s; # client_max_body_size 50m; client_max_body_size 100m; client_body_buffer_size 256k; gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_http_version 1.1; gzip_comp_level 9; gzip_types text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php; gzip_vary on; ## includes vhosts include vhosts/*.conf; } [root@external-lb01 ~]# cd /data/nginx/conf/vhosts/ [root@external-lb01 vhosts]# ls -rw-r-xr-- 1 root root 889 12月 26 15:18 bpm.kevin.com.conf -rw-r-xr-- 1 root root 724 12月 26 14:38 mobi.kevin.com.conf [root@external-lb01 vhosts]# cat bpm.kevin.com.conf upstream os-8080 { ip_hash; server 10.0.58.20:8080 max_fails=3 fail_timeout=15s; server 10.0.58.21:8080 max_fails=3 fail_timeout=15s; } server { listen 80; server_name bpm.kevin.com; access_log /data/nginx/logs/bpm.kevin.com-access.log main; error_log /data/nginx/logs/bpm.kevin.com-error.log; location / { proxy_pass http://os-8080; proxy_set_header Host $host; proxy_redirect http://os-8080/ http://bpm.kevin.com/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_next_upstream error timeout invalid_header http_502 http_503 http_504; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } [root@external-lb01 vhosts]# cat mobi.kevin.com.conf upstream mobi_cluster{ server 10.0.54.20:8080; } server { listen 80; server_name mobi.kevin.com; access_log /data/nginx/logs/mobi.kevin.com-access.log main; error_log /data/nginx/logs/mobi.kevin.com-error.log; location / { proxy_pass http://mobi_cluster; proxy_set_header Host $host; proxy_redirect http://mobi_cluster/ http://mobi.kevin.com/; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } 關閉防火牆,否則要依次開啟如上配置中的埠! [root@external-lb01 vhosts]# /data/nginx/sbin/nginx -s reload 重啟nginx後,發現http埠80、8080、8000、8443都起來了(lsof命令可以檢視到),而tcp/udp埠5100沒有起來,這是正常的。
==========順便總結下Nginx的四大模組——proxy、headers、upstream、stream模組梳理=========
一、ngx_http_proxy_module模組
1)proxy_pass URL; Context: location, if in location, limit_except 注意:proxy_pass後面的路徑不帶uri時,其會將location的uri傳遞給後端主機 server { … server_name HOSTNAME; location /uri/ { proxy http://hos[:port]; } … } http://HOSTNAME/uri –> http://host/uri proxy_pass後面的路徑是一個uri時,其會將location的uri替換為proxy_pass的uri server { … server_name HOSTNAME; location /uri/ { proxy http://host/new_uri/; } … } http://HOSTNAME/uri/ –> http://host/new_uri/ 如果location定義其uri時使用了正規表示式的模式,則proxy_pass之後必須不能使用uri; 使用者請求時傳遞的uri將直接附加代理到的服務的之後 server { … server_name HOSTNAME; location ~|~* /uri/ { proxy http://host; } … } http://HOSTNAME/uri/ –> http://host/uri/ 2)proxy_set_header field value; 設定發往後端主機的請求報文的請求首部的值;Context: http, server, location proxy_set_header X-Real-IP $remote_addr; $remote_addr:記錄的是上一臺主機的IP,而上一臺主機有可能也是代理伺服器 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; $proxy_add_x_forwarded_for:記錄的是源IP地址 在http客戶端還有修改/etc/httpd/conf/httpd.conf檔案 LogFormat "%{X-Real-IP}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined 通過上述方法則可以在後端主機上記錄真實的httpd資源請求者,而不再是隻記錄前端代理伺服器的IP地址 3)proxy_cache_path 定義可用於proxy功能的快取;Context: http proxy_cache_path path [levels=levels] [use_temp_path=on|off] keys_zone=name:size [inactive=time] [max_size=size] [manager_files=number] [manager_sleep=time] [manager_threshold=time] [loader_files=number] [loader_sleep=time] [loader_threshold=time] [purger=on|off] [purger_files=number] [purger_sleep=time] [purger_threshold=time]; proxy_cache_path /var/cache/nginx/proxy_cache levels=1:2:1 keys_zone=gmtest:20M max_size=1G; 4)proxy_cache zone | off; 指明要呼叫的快取,或關閉快取機制;Context: http, server, location proxy_cache gmtest; 5)proxy_cache_key string; 快取中用於“鍵”的內容; 預設值:proxy_cache_key $scheme$proxy_host$request_uri; 建議定義成方法和url 6)proxy_cache_valid [code …] time; 定義對特定響應碼的響應內容的快取時長; 定義在http{…}中; proxy_cache_path /var/cache/nginx/proxy_cache levels=1:1:1 keys_zone=gmtest:20m max_size=1g; 定義在需要呼叫快取功能的配置段,例如server{…},或者location中; proxy_cache gmtest; proxy_cache_key $request_uri; proxy_cache_valid 200 302 301 1h; proxy_cache_valid any 1m; 7)proxy_cache_use_stale proxy_cache_use_stale error | timeout | invalid_header | updating | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | off …; Determines in which cases a stale cached response can be used when an error occurs during communication with the proxied server. 後端伺服器的故障在那種情況下,就使用快取的功能對客戶的進行返回 8)proxy_cache_methods GET | HEAD | POST …; If the client request method is listed in this directive then the response will be cached. “GET” and “HEAD” methods are always added to the list, though it is recommended to specify them explicitly. 預設方法就是GET HEAD方法 9)proxy_hide_header field; By default, nginx does not pass the header fields “Date”, “Server”, “X-Pad”, and “X-Accel-…” from the response of a proxied server to a client. The proxy_hide_header directive sets additional fields that will not be passed. 10)proxy_connect_timeout time; Defines a timeout for establishing a connection with a proxied server. It should be noted that this timeout cannot usually exceed 75 seconds. 預設為60s 11)buffer相關的配置 a:proxy_buffer_size size; Sets the size of the buffer used for reading the first part of the response received from the proxied server. This part usually contains a small response header. By default, the buffer size is equal to one memory page. 預設為4k|8k b:proxy_buffering on | off; Enables or disables buffering of responses from the proxied server. 預設為on c:proxy_buffers number size; Sets the number and size of the buffers used for reading a response from the proxied server, for a single connection. By default, the buffer size is equal to one memory page. 預設為8 4k|8k d:proxy_busy_buffers_size size; When buffering of responses from the proxied server is enabled, limits the total size of buffers that can be busy sending a response to the client while the response is not yet fully read. 預設為8k|16k
二、ngx_http_headers_module模組
The ngx_http_headers_module module allows adding the “Expires” and “Cache-Control” header fields, and arbitrary fields, to a response header. 向由代理伺服器響應給客戶端的響應報文新增自定義首部,或修改指定首部的值; 1)add_header name value [always]; 新增自定義首部; add_header X-Via $server_addr; 經由的代理伺服器地址 add_header X-Accel $server_name; 2)expires [modified] time; expires epoch | max | off; 用於定義Expire或Cache-Control首部的值; 可以把伺服器定義的快取時長修改了;
三、ngx_http_upstream_module模組
The ngx_http_upstream_module module is used to define groups of servers that can be referenced by the proxy_pass, fastcgi_pass, uwsgi_pass, scgi_pass, and memcached_pass directives. 1)upstream name { … } 定義後端伺服器組,會引入一個新的上下文;Context: http upstream httpdsrvs { server … server… … } 2)server address [parameters]; 在upstream上下文中server成員,以及相關的引數;Context: upstream address的表示格式: unix:/PATH/TO/SOME_SOCK_FILE IP[:PORT] HOSTNAME[:PORT] parameters: weight=number 權重,預設為1;預設演算法是wrr max_fails=number 失敗嘗試最大次數;超出此處指定的次數時,server將被標記為不可用 fail_timeout=time 設定將伺服器標記為不可用狀態的超時時長 max_conns 當前的伺服器的最大併發連線數 backup 將伺服器標記為“備用”,即所有伺服器均不可用時此伺服器才啟用 down 標記為“不可用” 先在nginx前端配置down,然後在下架後端伺服器,上架新的web程式,然後上架,在修改配置檔案立馬的down 3)least_conn; 最少連線排程演算法,當server擁有不同的權重時其為wlc 要在後端伺服器是長連線時,效果才好,比如mysql 4)ip_hash; 源地址hash排程方法 5)hash key [consistent]; 基於指定的key的hash表來實現對請求的排程,此處的key可以直接文字、變數或二者的組合 作用:將請求分類,同一類請求將發往同一個upstream server If the consistent parameter is specified the ketama consistent hashing method will be used instead. 示例: hash $request_uri consistent; hash $remote_addr; hash $cookie_name; 對同一瀏覽器的請求,發往同一個upstream server 6)keepalive connections; 為每個worker程式保留的空閒的長連線數量 nginx的其它的二次發行版: tengine OpenResty 1.9版本之後可以反代tcp/udp的協議,基於stream模組,工作與傳輸層
四、ngx_stream_core_module模組(實現Nginx的TCP負載均衡)
HTTP負載均衡,也就是我們通常所有"七層負載均衡",工作在第七層"應用層"。而TCP負載均衡,就是我們通常所說的"四層負載均衡",工作在"網路層"和"傳輸層"。例如,LVS(Linux Virtual Server,Linux虛擬服務)和F5(一種硬體負載均衡裝置),也是屬於"四層負載均衡"
nginx-1.9.0 已釋出,該版本增加了stream 模組用於一般的TCP 代理和負載均衡,ngx_stream_core_module 這個模組在1.90版本後將被啟用。但是並不會預設安裝, 需要在編譯時通過指定 --with-stream 引數來啟用這個模組。 1)配置Nginx編譯檔案引數 ./configure --with-http_stub_status_module --with-stream ------------------------------------------------------------------ 2)編譯、安裝,make && make install ------------------------------------------------------------------ 3)配置nginx.conf檔案 stream { upstream kevin { server 192.168.10.10:8080; #這裡配置成要訪問的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的埠,在這裡我代理一一個kevin模組的介面8081 } server { listen 8081; #需要監聽的埠 proxy_timeout 20s; proxy_pass kevin; } } 建立最高階別的stream(與http同一級別),定義一個upstream組 名稱為kevin,由多個服務組成達到負載均衡 定義一個服務用來監聽TCP連線(如:8081埠), 並且把他們代理到一個upstream組的kevin中,配置負載均衡的方法和引數為每個server;配置些如:連線數、權重等等。 首先建立一個server組,用來作為TCP負載均衡組。定義一個upstream塊在stream上下文中,在這個塊裡面新增由server命令定義的server,指定他的IP地址和 主機名(能夠被解析成多地址的主機名)和埠號。下面的例子是建立一個被稱之為kevin組,兩個監聽1395埠的server ,一個監聽8080埠的server。 upstream kevin { server 192.168.10.10:8080; #這裡配置成要訪問的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的埠,在這裡我代理一一個kevin模組的介面8081 } 需要特別注意的是: 你不能為每個server定義協議,因為這個stream命令建立TCP作為整個 server的協議了。 配置反向代理使Nginx能夠把TCP請求從一個客戶端轉發到負載均衡組中(如:kevin組)。在每個server配置塊中 通過每個虛擬server的server的配置資訊和在 每個server中定義的監聽埠(客戶端需求的代理埠號,如我推流的的是kevin協議,則埠號為:8081)的配置資訊和proxy_passs 命令把TCP通訊傳送到 upstream的哪個server中去。下面我們將TCP通訊傳送到kevin 組中去。 server { listen 8081; #需要監聽的埠 proxy_timeout 20s; proxy_pass kevin; } 當然我們也可以採用單一的代理方式: server { listen 8081; #需要監聽的埠 proxy_timeout 20s; proxy_pass 192.168.10.30:8081; #需要代理的埠,在這裡我代理一一個kevin模組的介面8081 } ------------------------------------------------------------------ 4)改變負載均衡的方法: 預設nginx是通過輪詢演算法來進行負載均衡的通訊的。引導這個請求迴圈的到配置在upstream組中server埠上去。 因為他是預設的方法,這裡沒有輪詢命令, 只是簡單的建立一個upstream配置組在這兒stream山下文中,而且在其中新增server。 a)least-connected :對於每個請求,nginx plus選擇當前連線數最少的server來處理: upstream kevin { least_conn; server 192.168.10.10:8080; #這裡配置成要訪問的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的埠,在這裡我代理一一個kevin模組的介面8081 } b)least time :對於每個連結,nginx pluns 通過幾點來選擇server的: 最底平均延時:通過包含在least_time命令中指定的引數計算出來的: connect:連線到一個server所花的時間 first_byte:接收到第一個位元組的時間 last_byte:全部接收完了的時間 最少活躍的連線數: upstream kevin { least_time first_byte; server 192.168.10.10:8080; #這裡配置成要訪問的地址 server 192.168.10.20:8081; server 192.168.10.30:8081; #需要代理的埠,在這裡我代理一一個kevin模組的介面8081 } c)普通的hash演算法:nginx plus選擇這個server是通過user_defined 關鍵字,就是IP地址:$remote_addr; upstream kevin { hash $remote_addr consistent; server 192.168.10.10:8080 weight=5; #這裡配置成要訪問的地址 server 192.168.10.20:8081 max_fails=2 fail_timeout=30s; server 192.168.10.30:8081 max_conns=3; #需要代理的埠,在這裡我代理一一個kevin模組的介面8081 }
=================Nginx的TCP負載均衡====================
Nginx的TCP負載均衡的執行原理
當Nginx從監聽埠收到一個新的客戶端連結時,立刻執行路由排程演算法,獲得指定需要連線的服務IP,然後建立一個新的上游連線,連線到指定伺服器。
TCP負載均衡支援Nginx原有的排程演算法,包括Round Robin(預設,輪詢排程),雜湊(選擇一致)等。同時,排程資訊資料也會和健壯性檢測模組一起協作,為每個連線選擇適當的目標上游伺服器。如果使用Hash負載均衡的排程方法,你可以使用$remote_addr(客戶端IP)來達成簡單持久化會話(同一個客戶端IP的連線,總是落到同一個服務server上)。
和其他upstream模組一樣,TCP的stream模組也支援自定義負載均和的轉發權重(配置“weight=2”),還有backup和down的引數,用於踢掉失效的上游伺服器。max_conns引數可以限制一臺伺服器的TCP連線數量,根據伺服器的容量來設定恰當的配置數值,尤其在高併發的場景下,可以達到過載保護的目的。
Nginx監控客戶端連線和上游連線,一旦接收到資料,則Nginx會立刻讀取並且推送到上游連線,不會做TCP連線內的資料檢測。Nginx維護一份記憶體緩衝區,用於客戶端和上游資料的寫入。如果客戶端或者服務端傳輸了量很大的資料,緩衝區會適當增加記憶體的大小。
當Nginx收到任意一方的關閉連線通知,或者TCP連線被閒置超過了proxy_timeout配置的時間,連線將會被關閉。對於TCP長連線,我們更應該選擇適當的proxy_timeout的時間,同時,關注監聽socke的so_keepalive引數,防止過早地斷開連線。
Nginx的TCP負載均衡服務健壯性監控
TCP負載均衡模組支援內建健壯性檢測,一臺上游伺服器如果拒絕TCP連線超過proxy_connect_timeout配置的時間,將會被認為已經失效。在這種情況下,Nginx立刻嘗試連線upstream組內的另一臺正常的伺服器。連線失敗資訊將會記錄到Nginx的錯誤日誌中。
如果一臺伺服器,反覆失敗(超過了max_fails或者fail_timeout配置的引數),Nginx也會踢掉這臺伺服器。伺服器被踢掉60秒後,Nginx會偶爾嘗試重連它,檢測它是否恢復正常。如果伺服器恢復正常,Nginx將它加回到upstream組內,緩慢加大連線請求的比例。
之所"緩慢加大",因為通常一個服務都有"熱點資料",也就是說,80%以上甚至更多的請求,實際都會被阻擋在"熱點資料快取"中,真正執行處理的請求只有很少的一部分。在機器剛剛啟動的時候,"熱點資料快取"實際上還沒有建立,這個時候爆發性地轉發大量請求過來,很可能導致機器無法"承受"而再次掛掉。以mysql為例子,我們的mysql查詢,通常95%以上都是落在了記憶體cache中,真正執行查詢的並不多。
其實,無論是單臺機器或者一個叢集,在高併發請求場景下,重啟或者切換,都存在這個風險,解決的途徑主要是兩種:
1)請求逐步增加,從少到多,逐步積累熱點資料,最終達到正常服務狀態。
2)提前準備好"常用"的資料,主動對服務做"預熱",預熱完成之後,再開放伺服器的訪問。
TCP負載均衡原理上和LVS等是一致的,工作在更為底層,效能會高於原來HTTP負載均衡不少。但是,不會比LVS更為出色,LVS被置於核心模組,而Nginx工作在使用者態,而且,Nginx相對比較重。另外一點,令人感到非常可惜,這個模組竟然是個付費功能。