Nginx + Tomcat 動靜分離實現負載均衡

無腦仔的小明發表於2015-12-01

0.前期準備

使用Debian環境。安裝Nginx(預設安裝),一個web專案,安裝tomcat(預設安裝)等。

1.一份Nginx.conf配置檔案

# 定義Nginx執行的使用者 和 使用者組 如果對應伺服器暴露在外面的話建議使用許可權較小的使用者 防止被入侵
# user www www;
#Nginx程式數, 建議設定為等於CPU總核心數
worker_processes 8;
#開啟全域性錯誤日誌型別
error_log /var/log/nginx/error.log info;
#程式檔案
pid /var/run/nginx.pid;
#一個Nginx程式開啟的最多檔案描述數目 建議與ulimit -n一致
#如果面對高併發時 注意修改該值 ulimit -n 還有部分系統引數 而並非這個單獨確定
worker_rlimit_nofile 65535;
events{
 #使用epoll模型提高效能
 use epoll;
 #單個程式最大連線數
 worker_connections 65535;
}
http{
 #副檔名與檔案型別對映表
 include mime.types;
 #預設型別
 default_type application/octet-stream;
 sendfile on;
 tcp_nopush on;
 tcp_nodelay on;
 keepalive_timeout 65;
 types_hash_max_size 2048;
 #日誌
 access_log /var/log/nginx/access.log;
 error_log /var/log/nginx/error.log;
 #gzip 壓縮傳輸
 gzip on;
 gzip_min_length 1k; #最小1K
 gzip_buffers 16 64K;
 gzip_http_version 1.1;
 gzip_comp_level 6;
 gzip_types text/plain application/x-javascript text/css application/xml application/javascript;
 gzip_vary on;
 #負載均衡組
 #靜態伺服器組
 upstream static.zh-jieli.com {
 server 127.0.0.1:808 weight=1;
 }
 #動態伺服器組
 upstream zh-jieli.com {
 server 127.0.0.1:8080;
 #server 192.168.8.203:8080;
 }
 #配置代理引數
 proxy_redirect off;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 client_max_body_size 10m;
 client_body_buffer_size 128k;
 proxy_connect_timeout 65;
 proxy_send_timeout 65;
 proxy_read_timeout 65;
 proxy_buffer_size 4k;
 proxy_buffers 4 32k;
 proxy_busy_buffers_size 64k;
 #快取配置
 proxy_cache_key '$host:$server_port$request_uri';
 proxy_temp_file_write_size 64k;
 proxy_temp_path /dev/shm/JieLiERP/proxy_temp_path;
 proxy_cache_path /dev/shm/JieLiERP/proxy_cache_path levels=1:2 keys_zone=cache_one:200m inactive=5d max_size=1g;
 proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie;
server{
 listen 80;
 server_name erp.zh-jieli.com;
 location / {
 index index; #預設主頁為 /index
 #proxy_pass http://jieli;
 }
 location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
 proxy_cache cache_one;
 proxy_cache_valid 200 304 302 5d;
 proxy_cache_valid any 5d;
 proxy_cache_key '$host:$server_port$request_uri';
 add_header X-Cache '$upstream_cache_status from $host';
 proxy_pass http://static.zh-jieli.com;
 #所有靜態檔案直接讀取硬碟
 # root /var/lib/tomcat7/webapps/JieLiERP/WEB-INF ;
 expires 30d; #快取30天
 }
 #其他頁面反向代理到tomcat容器
 location ~ .*$ {
 index index;
 proxy_pass http://zh-jieli.com;
 }
 }
 server{
 listen 808;
 server_name static;
 location / {
}
 location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
 #所有靜態檔案直接讀取硬碟
 root /var/lib/tomcat7/webapps/JieLiERP/WEB-INF ;
 expires 30d; #快取30天
 }
 }
}

基本配置這個檔案,就可以實現負載了。但是裡面的各種關係要了解就比較麻煩了。這篇部落格,也不是教學篇,是記錄一下,方便以後自己看了。

2.基礎講解

現在假使有一臺電腦192.168.8.203這臺電腦,上面部署了Tomcat,裡面8080埠有J2EE的服務,通過瀏覽器可以正常瀏覽網頁。現在有一個問題tomcat是一個比較全面的web容器,對靜態網頁的處理,應該是比較費資源的,特別是每次都要從磁碟讀取靜態頁面,然後返回。這中間會消耗Tomcat的資源,可能會使那些動態頁面解析效能影響。秉承Linux哲學,一個軟體只做一件事的原則。Tomcat就應該只處理JSP動態頁面。這裡就用到以前瞭解的Nginx來進行反向代理。第一步代理,實現動靜網頁分離。這個很簡單的。

worker_processes 8;

 pid /var/run/nginx.pid;

 worker_rlimit_nofile 65535;

 events{
 use epoll;
 worker_connections 65535;
 }

 http{
 include mime.types;
 default_type application/octet-stream;
 sendfile on;
 tcp_nopush on;
 tcp_nodelay on;
 keepalive_timeout 65;
 types_hash_max_size 2048;
proxy_redirect off;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 client_max_body_size 10m;
 client_body_buffer_size 128k;
 proxy_connect_timeout 65;
 proxy_send_timeout 65;
 proxy_read_timeout 65;
 proxy_buffer_size 4k;
 proxy_buffers 4 32k;
 proxy_busy_buffers_size 64k;

 server{
 listen 80;
 server_name xxx.com;
 location / {
 index index; 
 }
 location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
 proxy_pass http://192.168.8.203:8080;
 expires 30d; 
 }
 location ~ .*$ {
 index index;
 proxy_pass http://192.168.8.203:8080;
 }
 }
 }
worker_processes 8;
pid /var/run/nginx.pid;
worker_rlimit_nofile 65535;
events{
 use epoll;
 worker_connections 65535;
 }
http{
 include mime.types;
 default_type application/octet-stream;
 sendfile on;
 tcp_nopush on;
 tcp_nodelay on;
 keepalive_timeout 65;
 types_hash_max_size 2048;
proxy_redirect off;
 proxy_set_header Host $host;
 proxy_set_header X-Real-IP $remote_addr;
 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 client_max_body_size 10m;
 client_body_buffer_size 128k;
 proxy_connect_timeout 65;
 proxy_send_timeout 65;
 proxy_read_timeout 65;
 proxy_buffer_size 4k;
 proxy_buffers 4 32k;
 proxy_busy_buffers_size 64k;
server{
 listen 80;
 server_name xxx.com;
 location / {
 index index;
 }
 location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
 proxy_pass http://192.168.8.203:8080;
 expires 30d;
 }
 location ~ .*$ {
 index index;
 proxy_pass http://192.168.8.203:8080;
 }
 }
 }

修改nginx的配置檔案 /etc/nginx/nginx.conf 預設有個配置檔案的。其實大部分都差不多,關鍵還是server段的設定。這裡我設定server段如上所示,其他段複製就可以了。server段裡面的解釋如下:第35行為監聽本機80埠。37-39行表示預設主頁,這裡的預設主頁我是index.jsp 對應到我專案中是一個index。 這裡根據需要可以改為

index index.jsp index.html index.htm index.php

具體可參考其他文章。 關鍵的第40行,這個是正則匹配,網上也有很多介紹。這裡匹配我專案中用到的所有靜態網頁字尾。第41行是代理地址。這裡我代理到我的web應用中。expires 30d快取為30天,這裡的快取是對應到前端頁面,使用者的Cache-Control欄位,

第44行中那個正則是匹配無字尾的頁面。我專案中jsp頁面是無字尾的。這裡可以根據需要進行修改。同樣代理到192.168.8.203:8080這裡。到這裡你可能會問,我艹,這有毛意思啊?當然不是這樣了。簡單的實現靜動分離,我們可以把第41行進行修改,改為

root   /var/lib/tomcat7/webapps/JieLiERP/WEB-INF

表示不代理,直接從本地磁碟拿。通過查tomcat日誌可以看到靜態頁面是沒有訪問到的。但這樣又有一個問題。這樣的靈活性不好,對下面要講到的記憶體快取和叢集部署來說都是不友好的,所以又有了下面的這種寫法。再寫一個server段。

server{
 listen 808;
 server_name static;
 location / {
}
location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
 #所有靜態檔案直接讀取硬碟
 root /var/lib/tomcat7/webapps/JieLiERP/WEB-INF ;
 expires 30d; #快取30天
 }
 }

這次監聽808埠,然後上上面的程式碼41行就可以修改為 proxy_pass http://192.168.8.203:808了,到這裡就實現了動靜分離了。如果多臺伺服器,就修改對應的ip就可以了。如果發現連線不上的,要檢查一下防火牆,許可權等外部問題,這個配置是這樣的。

如果單純這樣的話,我們會發現頁面直接傳輸過於佔用頻寬。對應web的優化,這裡想到的是通過對頁面進行gzip壓縮,然後傳到使用者那裡,再解壓,這樣可以有效的減少頻寬。這裡就會用到Nginx 的gzip模組了。預設的Nginx是整合有gzip模組的。只需在http段增加下面配置即可。

gzip on;
 gzip_min_length 1k; #最小1K
 gzip_buffers 16 64K;
 gzip_http_version 1.1;
 gzip_comp_level 6;
 gzip_types text/plain application/x-javascript text/css application/xml application/javascript;
 gzip_vary on;

給個首頁看看效果

不要在意請求數不一樣,那兩個請求是谷歌外掛來的。不用覺得我在騙你。

作為假使有很多人訪問的網站來說,快取肯定是很重要的東西了。一開始是想通過外掛,讓Nginx和Redis進行合成,然後Nginx使用Redis來快取的,但是發現配置起來很麻煩,還要自己下載外掛,重新編譯Nginx,比較麻煩,所以這裡覺得用Nginx自帶的快取也是不錯的選擇。雖然效率比不上redis,但是有還是比沒有好。Nginx預設的快取是磁碟檔案系統的快取,而不是像Redis那樣的記憶體級別的快取。一開始我以為Nginx就只有這樣。後來查了寫資料,才知道是我太天真了,對Linux不是很瞭解導致的。Linux的一切皆檔案。原來我們可以把檔案快取到記憶體對應的Linux檔案系統中。我說的可能比較難以理解,請自行搜尋/dev/shm 這個檔案目錄。我們把檔案快取到這個檔案目錄裡,其實就相當與記憶體的快取了。只不過還是靠檔案系統管理。所以比不上自定義格式的Redis那樣的記憶體快取。

在http段進行基本配置

1 #快取配置
 2 proxy_cache_key '$host:$server_port$request_uri';
 3 proxy_temp_file_write_size 64k;
 4 proxy_temp_path /dev/shm/JieLiERP/proxy_temp_path;
 5 proxy_cache_path /dev/shm/JieLiERP/proxy_cache_path levels=1:2 keys_zone=cache_one:200m inactive=5d max_size=1g;
 6 proxy_ignore_headers X-Accel-Expires Expires Cache-Control Set-Cookie;
location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
 proxy_cache cache_one;
 proxy_cache_valid 200 304 302 5d;
 proxy_cache_valid any 5d;
 proxy_cache_key '$host:$server_port$request_uri';
 add_header X-Cache '$upstream_cache_status from $host';
 proxy_pass http://192.168.8.203:808;
expires 30d; #快取30天
 }

經過這兩個的配置就基本能實現了,這裡說幾個注意項,也是困擾我很久的問題。上面第一段程式碼第6行,proxy_ignore_headers 如果web專案中的html的head頭裡面指定

1 <meta http-equiv="pragma" content="no-cache">
 2 <meta http-equiv="cache-control" content="no-cache">
 3 <meta http-equiv="expires" content="0">

這些不快取的話,就要加上proxy_ignore_headers的配置項了。還有一點就是/dev/shm下面的檔案系統許可權預設只給root使用者,所以要chmod 777 -R /dev/shm 這樣不是很安全的做法,如果實際上線可以給定某個使用者組,關於使用者組的設定是配置的第一行

user www www;

上面第二段程式碼的第6行是增加一個header欄位方便檢視是否擊中快取。

我們rm -rf /dev/shm/JieLiERP/proxy_* 下面的所有檔案(注意這裡如果是進行多次測試的話要nginx -s reload 重新讀取配置或重啟服務,因為你rm -rf只是刪除了快取檔案,但是快取的結構資訊還在nginx程式裡面,結構還在,如果不重啟的話,是會出現訪問不到的)

所以要記得重啟哦。下面是執行效果

第一次訪問

第二次訪問,在瀏覽器中Ctrl+Shift+R 強制重新整理

到這裡就可以看到效果了。我們檢視一下/dev/shm這個裡面

到這裡已經快結束了。最後也是比較關鍵的一個技術點,就是叢集,叢集,叢集。這個就要用到upstream了,看到最開頭的配置檔案了嗎,就是那個

#負載均衡組
#靜態伺服器組
upstream static {
 server 127.0.0.1:808 weight=1;
 server 192.168.8.203:808 weight=1;
}
#動態伺服器組
upstream dynamic {
 server 127.0.0.1:8080;
 #server 192.168.8.203:8080;
}

上面那個就是叢集組了。upstream是關鍵字,static 和 dynamic是兩個伺服器叢集組的名稱。以第一個為例,server 127.0.0.1:808 是伺服器地址,後面的weight=1 是權重。有多個就寫多個。親測試過,叢集中的一個壞了,不影響系統執行。至於更多的輪詢規則,可以參考網上更多的資料。這裡不多說。至於怎麼使用呢? proxy_pass http://192.168.8.203:808 改為 proxy_pass http://static; 這樣即可實現均衡。

到這裡就結束了。把上面各個部分根據自己需求配置起來就可以實現單機房負載均衡了。 上面這種做法有一個缺點就是在前面的那一臺nginx如果當機,後面所以機器就失去了被訪問的能力了,所以需要在前面實現多個nginx多機房的負載。關於這個就是另外一個話題了。目前還沒有研究。以後有機會再說了。

上面動態伺服器組如果是那種需要儲存使用者狀態的話,會有問題,就是session問題,比如我在server1進行登入後,下一次動態伺服器組進行輪詢後可能分配到server2,就會造成要重新登入。治標的辦法是,配置輪詢規則,根據使用者請求的IP進行Hash,然後分配對應的伺服器。具體配置如下:

 upstream dynamic{
 ip_hash;
 server 127.0.0.1:8080;
 server 192.168.0.203:8080;
 }

這樣就可以實現一個使用者對應一個伺服器節點。這樣就不會有重複登入的問題。另一種治本的辦法是,利用快取系統進行session的統一儲存管理。具體的做法我還沒有試驗過,參考資料有相關的文章,可以瞭解一下。

相關文章