Nginx + Tomcat實現動靜分離、負載均衡
什麼是動靜分離
為了提高網站的響應速度,減輕程式伺服器(Tomcat,Jboss等)的負載,對於靜態資源比如圖片,js,css等檔案,我們可以在反向代理伺服器中進行快取,這樣瀏覽器在請求一個靜態資源時,代理伺服器就可以直接處理,而不用將請求轉發給後端伺服器。使用者請求的動態檔案比如servlet,jsp則轉發給Tomcat,Jboss伺服器處理,這就是動靜分離。這也是反向代理伺服器的一個重要的作用。
軟體安裝
使用centos環境。安裝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天 } } }
基本配置這個檔案,就可以實現負載了。
如下自己配置成功的動態分離
user nginx; worker_processes 1; error_log /var/log/nginx/error.log warn; pid /var/run/nginx.pid; events { worker_connections 1024; } http { server { listen 80; location / { root /usr/share/nginx/html; index index.html; } location ~\.(gif|jpg|jpeg|png|bmp|swf)$ { root /usr/share/nginx/html1; } #所有動態請求都轉發給tomcat處理 location ~\.jsp { proxy_pass http://192.168.0.189:8080; proxy_set_header Host $host; } #所有html請求都轉發給httpd處理 location ~\.html { proxy_pass http://192.168.0.189:80; proxy_set_header Host $host; } } include /etc/nginx/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 /var/log/nginx/access.log main; sendfile on; #tcp_nopush on; keepalive_timeout 65; #gzip on; include /etc/nginx/conf.d/*.conf; }
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; } } }
修改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段增加下面配置即可。
1 gzip on;
2 gzip_min_length 1k; #最小1K
3 gzip_buffers 16 64K;
4 gzip_http_version 1.1;
5 gzip_comp_level 6;
6 gzip_types text/plain application/x-javascript text/css application/xml application/javascript;
7 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;
1 location ~ .*\.(js|css|ico|png|jpg|eot|svg|ttf|woff) {
2 proxy_cache cache_one;
3 proxy_cache_valid 200 304 302 5d;
4 proxy_cache_valid any 5d;
5 proxy_cache_key '$host:$server_port$request_uri';
6 add_header X-Cache '$upstream_cache_status from $host';
7 proxy_pass http://192.168.8.203:808;
8
9 expires 30d; #快取30天
10 }
經過這兩個的配置就基本能實現了,這裡說幾個注意項,也是困擾我很久的問題。上面第一段程式碼第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,然後分配對應的伺服器。具體配置如下:
1 upstream dynamic{
2 ip_hash;
3 server 127.0.0.1:8080;
4 server 192.168.0.203:8080;
5 }
這樣就可以實現一個使用者對應一個伺服器節點。這樣就不會有重複登入的問題。另一種治本的辦法是,利用快取系統進行session的統一儲存管理。具體的做法我還沒有試驗過,參考資料有相關的文章,可以瞭解一下。
--- 2015-12-25 16:19:16 更新---
Nginx增加SSL功能,同樣的Nginx預設是有SSL模組功能,我們不用額外安裝,只需要簡單的配置就可以了。首先我們先來生成一些必要的證書。製作的過程還是比較簡單的。
1 #製作CA證書
2 openssl genrsa -des3 -out ca.key 2048
3 openssl req -new -x509 -days 7305 -key ca.key -out ca.crt
4
5 #生成Nginx伺服器所需證書,並使用CA簽名
6 openssl genrsa -des3 -out client.key 1024
7 openssl req -new -key client.key -out client.csr
8 openssl x509 -req -in client.csr -out client.pem -signkey client.key -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650
9
10 #取消證書密碼
11 openssl rsa -in client.key -out client.key.unsecure
下面就是配置Nginx了,我們可以把需要用到的client.pem, client.pem, client.key,unsecure這三個檔案放到Nginx的一個目錄下,剩下的Nginx配置如下:
1 server{
2 server_name localhost;
3 listen 443 ssl;
4 root html;
5 location / {
6 index index.html index.html;
7 }
8 ssl on;
9 ssl_certificate keys/client.pem;
10 ssl_certificate_key keys/client.key.unsecure;
11 }
重啟Nginx,我們就可以訪問Https網站了。 但是他喵的出現這個
這個是沒有什麼問題,具體原因是這個CA證書要得到認可。所以我們上面自己生成的https證書,只是自己生成的,如果要變成下面這種,就需要花錢購買了,剩下的這個自己上網解決。(雖然自己生成的證書可以用,但是還是抵擋不了DNS欺騙,所以這種不安全證書,跟沒有其實是一樣的。不過據說這樣可以阻止運營商劫持。)
增加一個,就是在我們輸入http連線時自動跳轉到安全的https連線。這個還是比較實用的。方法還是有多種的,具體可以看參考資料裡面的部落格。我是使用下面這一種,我覺得是比較簡單的,程式碼改動比較少的。就是對80埠進行代理轉發。
1 server{
2 server_name localhost;
3 listen 80;
4 rewrite ^(.*)$ https://$host$1 permanent;
5 }
好了,暫時到這裡。
參考資料:
http://www.ha97.com/5194.html Nginx配置檔案nginx.conf中文詳解
http://saiyaren.iteye.com/blog/1956692 nginx cache靜態化+tmpfs 高效能cdn方案
http://www.oschina.net/question/35243_180072 Nginx配置了proxy_cache為什麼不能產生快取檔案
http://www.ttlsa.com/nginx/nginx-enforce-cache/ nginx強制快取
http://www.cnblogs.com/dudu/p/4597351.html 解決nginx反向代理快取不起作用的問題
http://blog.csdn.net/akon_vm/article/details/8494620 快取nginx伺服器的靜態檔案
http://www.cnblogs.com/lengfo/p/4260363.html 基於nginx tomcat redis分散式web應用的session共享配置
http://my.oschina.net/zijian1315/blog/207311 生成證書
http://my.oschina.net/u/1186777/blog/147962 證書配置
http://www.cnblogs.com/yun007/p/3739182.html Nginx強制跳轉到https
https://blog.csdn.net/u010028869/article/details/50522033?utm_source=blogxgwz1 Nginx+Tomcat實現動靜分離、負載均衡