背景
基于Springboot應用以war包的形式運行在Tomcat容器中,當更新war包時會有一段時間服務返回404,這對于線上服務是不可接受的。4層的負載均衡可以自動將80端口關閉的節點下線,但由于內網服務器位于堡壘機后方,根據公司規定不能自行配置SSH服務,所以無法執行遠程腳本。所以只能通過別的方式實現。
實驗素材
- Nginx 作為web server和7層負載均衡
- tomcat * 2 作為應用后端
- gitlab-ce 代碼版本控制
- jenkins 發布平臺
基本原理
基本的原理就是讓Nginx后方有3個Tomcat容器,其中1個是active,1個是standby,正常情況下不會訪問到standby的容器,但可以通過額外的手段保證standby的容器是可以提供服務的,在發布前先更新所有的standby節點,驗證沒問題之后更新active的容器,來保證服務不會中斷。
實際操作
創建springboot項目
參考Springboot使用內置和獨立tomcat以及其他思考。
編寫同一個接口的不同版本
// tag v1
@RestControllerpublic class HelloController { @GetMApping("/hello")
public String hello() { return "V1";
}}
// tag v2
@RestControllerpublic class HelloController { @GetMapping("/hello")
public String hello() { return "V2";
}}
打包
mvn clean package -Dmaven.test.skip=true
創建兩個tomcat容器
Docker run -itd --name tomcat-active -v /tmp/tomcat/active:/usr/local/tomcat/webapps -p 32771:8080 tomcat
docker run -itd --name tomcat-standby -v /tmp/tomcat/standby:/usr/local/tomcat/webapps -p 32772:8080 tomcat
將war包拷貝到容器中
可能是docker toolbox的問題,無法掛載目錄,所以只好把war包手動拷貝進去。
docker cp ~/workspace/spring-demo/target/spring-demo-0.0.1-SNAPSHOT.war tomcat-active:/usr/local/tomcat/webapps/
docker cp ~/workspace/spring-demo/target/spring-demo-0.0.1-SNAPSHOT.war tomcat-standby:/usr/local/tomcat/webapps/
訪問兩個容器中的服務
稍等片刻兩個容器中的服務會自動部署,就可以分別通過相應的端口訪問了,簡單壓測一下QPS可以達到2000+且沒有報錯。
$ wrk -c 20 -d 10 -t 4 http://192.168.99.100:32771/spring-demo-0.0.1-SNAPSHOT/hello
Running 10s test @ http://192.168.99.100:32771/spring-demo-0.0.1-SNAPSHOT/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 10.20ms 8.70ms 122.66ms 81.20%
Req/Sec 554.18 167.66 1.04k 63.25%
22088 requests in 10.02s, 2.43MB read
Requests/sec: 2203.76
Transfer/sec: 247.89KB
$ wrk -c 20 -d 10 -t 4 http://192.168.99.100:32772/spring-demo-0.0.1-SNAPSHOT/hello
Running 10s test @ http://192.168.99.100:32772/spring-demo-0.0.1-SNAPSHOT/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 11.30ms 14.24ms 186.52ms 92.95%
Req/Sec 557.54 207.91 1.24k 67.17%
22025 requests in 10.03s, 2.42MB read
Requests/sec: 2196.36
Transfer/sec: 247.05KB
配置Nginx
upstream ha {
server 192.168.99.100:32771;
server 192.168.99.100:32772 backup;
}server {
listen 80;
server_name _;
location / {
proxy_next_upstream http_502 http_504 http_404 error timeout invalid_header;
proxy_pass http://ha/spring-demo-0.0.1-SNAPSHOT/;
}}
注意:默認情況下只會轉發GET/HEAD/PUT/DELETE/OPTIONS這種冪等的請求,而不會轉發POST請求,如果需要對POST請求也做轉發,就需要加上non_idempotent配置,整體配置如下
upstream ha {
server 192.168.99.100:32771;
server 192.168.99.100:32772 backup;
}server {
listen 80;
server_name _;
location / {
proxy_next_upstream http_502 http_504 http_404 error timeout invalid_header non_idempotent;
proxy_pass http://ha/spring-demo-0.0.1-SNAPSHOT/;
}}
注意proxy_next_upstream http_502 http_504 http_404 error timeout invalid_header;這行,這里就是表示把訪問當前的upstream返回了這些狀態碼的請求轉發到upstream中的下一臺機器,在我們現在的應用場景下,當war包發布時,正在更新war包的tomcat會返回404,也就是對應http_404,如果不配置這行,是不會做轉發的。
但這樣簡單的配置還會有一個問題,那就是Nginx不會把出問題的后端從upstream中摘除,也就是說請求還會訪問到這個正在更新中的realserver,只是Nginx會再把請求轉發到下一臺好的realserver上,這樣會增加一些耗時。目前有三種方式可以實現對Nginx負載均衡的后端節點服務器進行健康檢查,具體參考Nginx負載均衡
通過Nginx壓測
基本測試
- 兩個tomcat節點均正常的情況下壓測
$ wrk -c 20 -d 10 -t 4 http://192.168.99.100:32778/hello
Running 10s test @ http://192.168.99.100:32778/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 57.36ms 32.06ms 335.36ms 71.29%
Req/Sec 89.29 48.20 390.00 85.25%
3577 requests in 10.05s, 562.30KB read
Requests/sec: 355.77
Transfer/sec: 55.93KB
和上面沒有經過Nginx的壓測相比,最明顯的變化就是QPS下降了84%,平均響應時間增加了5倍,猜測可能是因為Nginx使用的默認配置中worker_processes 1;的問題。
- 在開始壓測后立即刪除tomcat-active容器中的war包和目錄,結果如下
$ wrk -c 20 -d 10 -t 4 http://192.168.99.100:32778/hello
Running 10s test @ http://192.168.99.100:32778/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 57.29ms 28.69ms 181.88ms 67.38%
Req/Sec 87.93 39.51 240.00 75.25%
3521 requests in 10.05s, 553.50KB read
Requests/sec: 350.22
Transfer/sec: 55.05KB
同樣沒有非200的響應,而且整體和正常情況相當。
- 只有standby節點工作的情況下壓測
$ wrk -c 20 -d 10 -t 4 http://192.168.99.100:32778/hello
Running 10s test @ http://192.168.99.100:32778/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 72.12ms 35.99ms 240.89ms 68.34%
Req/Sec 70.04 31.84 180.00 76.50%
2810 requests in 10.05s, 441.71KB read
Requests/sec: 279.48
Transfer/sec: 43.93KB
可以看到,響應時間有明顯的增加,QPS也有明顯的下降,也驗證了上面說的響應是404的請求會被轉發到正常工作的節點,但有問題的節點不會被摘除導致的響應時間變長的問題。
進一步測試
為了消除上面測試中可能存在war包刪除后對服務的影響還沒有生效,壓測就已經結束的可能,將壓測時間調長,增加至60s。
- 兩個節點都正常的情況
$ wrk -c 20 -d 60 -t 4 http://192.168.99.100:32778/hello
Running 1m test @ http://192.168.99.100:32778/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 55.53ms 28.10ms 306.58ms 70.07%
Req/Sec 91.52 39.35 300.00 69.23%
21906 requests in 1.00m, 3.36MB read
Requests/sec: 364.66
Transfer/sec: 57.32KB
整體情況和上面10s的測試相同。查看日志發現backup節點沒有接收到任何請求。為了驗證是否是worker_processes配置導致的,把這個值改成4之后重新測試,結果如下
$ wrk -c 20 -d 60 -t 4 http://192.168.99.100:32778/hello
Running 1m test @ http://192.168.99.100:32778/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 41.55ms 24.92ms 227.15ms 72.21%
Req/Sec 125.06 46.88 373.00 71.76%
29922 requests in 1.00m, 4.59MB read
Requests/sec: 498.11
Transfer/sec: 78.29KB
可以看到,有了將近20%的提升,但還是不太符合預期。
- 開始測試后立即更新active節點的war包
$ wrk -c 20 -d 60 -t 4 http://192.168.99.100:32778/hello
Running 1m test @ http://192.168.99.100:32778/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 54.40ms 33.76ms 329.73ms 70.53%
Req/Sec 95.85 56.28 420.00 81.60%
22914 requests in 1.00m, 3.52MB read
Requests/sec: 381.42
Transfer/sec: 59.95KB
沒有明顯變化,測試開始后有一段時間standby節點收到請求,后面請求又全部指向了active節點。可能是因為服務太簡單,重新加載的太快,只有很少量(5750)的請求轉發到了standby節點,所以對整體結果影響不大。 3. 開始測試后立即刪除active節點的war包
$ wrk -c 20 -d 60 -t 4 http://192.168.99.100:32778/hello
Running 1m test @ http://192.168.99.100:32778/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 72.11ms 34.33ms 346.24ms 69.54%
Req/Sec 70.16 29.78 191.00 67.23%
16813 requests in 1.00m, 2.58MB read
Requests/sec: 279.84
Transfer/sec: 43.99KB
刪除節點后,所有的請求都會先請求active,然后被Nginx轉發至standby,所以吞吐量有明顯下降,延遲也有明顯的提升。
效果測試
- 直接訪問active
$ wrk -c 20 -d 60 -t 4 http://10.75.1.42:28080/web-0.0.1-SNAPSHOT/hello
Running 1m test @ http://10.75.1.42:28080/web-0.0.1-SNAPSHOT/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 5.56ms 25.16ms 203.83ms 95.82%
Req/Sec 7.54k 0.91k 8.31k 84.44%
1803421 requests in 1.00m, 217.03MB read
Requests/sec: 30006.18
Transfer/sec: 3.61MB
服務器的性能果然還是比本地強太多。
- 在進行性能壓測期間發布新版本
$ wrk -c 20 -d 60 -t 4 http://10.75.1.42:28080/web-0.0.1-SNAPSHOT/hello
Running 1m test @ http://10.75.1.42:28080/web-0.0.1-SNAPSHOT/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.47ms 22.31ms 401.95ms 96.67%
Req/Sec 7.58k 0.88k 8.26k 87.12%
1811240 requests in 1.00m, 285.84MB read
Non-2xx or 3xx responses: 72742
Requests/sec: 30181.93
Transfer/sec: 4.76MB
發布新版本導致4%的請求失敗。
- 通過Nginx訪問服務
$ wrk -c 20 -d 60 -t 4 http://10.75.1.42:28010/web/hello
Running 1m test @ http://10.75.1.42:28010/web/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 2.94ms 16.21ms 248.18ms 98.01%
Req/Sec 6.02k 551.52 6.92k 83.38%
1437098 requests in 1.00m, 260.33MB read
Requests/sec: 23948.20
Transfer/sec: 4.34MB
雖然服務器配置的worker_processes auto,實際上開了40個進程,但仍然達不到直接訪問JAVA服務的吞吐量。
- 通過Nginx壓測期間發布新版本
$ wrk -c 20 -d 60 -t 4 http://10.75.1.42:28010/web/hello
Running 1m test @ http://10.75.1.42:28010/web/hello
4 threads and 20 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 4.09ms 20.50ms 402.11ms 97.12%
Req/Sec 5.89k 733.62 6.86k 84.85%
1404463 requests in 1.00m, 253.67MB read
Requests/sec: 23401.54
Transfer/sec: 4.23MB
可以看到,延遲明顯變大了,但總體的QPS沒有明顯下降,還是因為存在一些轉發。
思考
原來是一臺機器上運行一個tomcat容器,現在要運行兩個,那么會對機器的負載造成多大的影響呢?可以通過visualvm連接上遠程tomcat來觀察對內存和CPU的占用
可以看到正常情況下,backup容器對服務器的負載基本可以忽略不計。即便是在發布期間,backup容器也只是在active容器重新載入期間承擔職責,之后馬上就恢復了。
新版本在線上正式運行之后為保證下一次發布新版本時backup版本是最新的,需要再發布一下backup版本,當然這時流量都在active節點上,對backup節點的發布更新操作不會對負載有什么影響。
總結
可以通過Nginx的backup機制可以保證服務不中斷的情況下發布新版本。總體的發布流程如下:
- 發布新版本到active容器
- 確認發布的新版本穩定后發布新版本到backup容器
優勢
- 任意一臺機器上在任意時刻都保證有一個tomcat容器是可用的,保證服務不中斷
- 從直觀上的分機器上線改為直接全量上線,并且保證如果上線的新版本有問題時也不會影響線上服務
劣勢
- 需要上線兩次
- 需要在tomcat容器所在的機器上安裝Nginx和作為backup的tomcat的容器
- backup容器在“待機”時的消耗
鏈接:https://juejin.im/post/6867088509245603854
來源:掘金