基于Springboot应用以war包的形式运行在Tomcat容器中,当更新war包时会有一段时间服务返回404,这对于线上服务是不可接受的。4层的负载均衡可以自动将80端口关闭的节点下线,但由于内网服务器位于堡垒机后方,根据公司规定不能自行配置SSH服务,所以无法执行远程脚本。所以只能通过别的方式实现。
基本的原理就是让Nginx后方有3个Tomcat容器,其中1个是active,1个是standby,正常情况下不会访问到standby的容器,但可以通过额外的手段保证standby的容器是可以提供服务的,在发布前先更新所有的standby节点,验证没问题之后更新active的容器,来保证服务不会中断。
参考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
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
可能是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
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负载均衡
$ 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;的问题。
$ 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的响应,而且整体和正常情况相当。
$ 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%的提升,但还是不太符合预期。
$ 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,所以吞吐量有明显下降,延迟也有明显的提升。
$ 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%的请求失败。
$ 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服务的吞吐量。
$ 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机制可以保证服务不中断的情况下发布新版本。总体的发布流程如下:
链接:https://juejin.im/post/6867088509245603854
来源:掘金