【nginx】- 通过ip_hash实现不停机发布

实现不停机发布

有一个后台项目由于并发量不高所以只部署了一台机器,但是如果要升级的话其他人就用不了了。为了解决不影响其他同事正常使用,我想做一个不停机发布的功能。

具体原理就是通过nginx负载均衡来实现,当停了一台还有另外一台可以提供服务,这样就做到了不停机发布。

我修改nginx.conf文件,修改点如下:

location /xx {
            proxy_read_timeout 600;
            #proxy_pass http://localhost:9080;
            proxy_pass http://xx_manage; //这个地方配置了负载均衡
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_next_upstream error timeout http_500 http_404 http_502 http_503  non_idempotent;
        }
		
		
	upstream xx_manage {
     ip_hash; //为了解决session问题使用了ip_hash
     server 127.0.0.1:9080;
     server 127.0.0.1:9082;
    }

配置后,经过测试发现如果一台服务停机了可以进行自动切换。但是实际使用过程中有同事反映经常有跳转到登录页面的情况。

排查自动登出的情况

问题描述

场景是这样的,我登录网站,从日志发现是访问到了A服务器。当我在网站上点击其他页面的时候发现跳转到登录页面让我重新登录。

我的分析

  1. nginx层前面没有其他负载均衡服务器意味着到达nginx请求的ip是固定的,也就是经过ip_hash会路由到同一各服务。
  2. nginx跟应用服务器之间没有其他负载均衡服务器,即经过nginx处理后直接分发到服务器不会在路由了。
  3. 检查了nginx的配置,发现有如下的配置,意味着如果A服务器执行请求出现500\404\502这些状态码,就会重定向到另一个服务,而另一个服务器是没有session的,那么就会跳转到重新登录页面。
proxy_next_upstream error timeout http_500 http_404 http_502 http_503  non_idempotent;

我的目标是,如果一台服务挂掉之后自动failover到另一台机器,但是如果某个页面出现异常不需要重定向(另一台机器代码也是一样的,没必要重定向,重定向还可以导致重新登录的情况)。所以我需要在proxy_next_upstream那里把这两种情况区分开来,即要弄明白服务挂掉和业务有异常返回的状态码分别是什么?

我做的操作

区分业务异常和停机时nginx的返回码

为了区分业务异常和服务挂掉nginx的返回码的区别,我在虚拟机里启动了nginx,并且部署了两个springboot项目。springboot项目代码参考 后面的参考->spring boot后台请求代码,这里nginx配置参考参考->虚拟机里nginx.conf配置

经过测试查看对应nginx日志:发现业务异常throw new RuntimeException()nginx返回500错误,如果服务停机的话返回的502错误。

对应nginx日志如下:

image-20200620154001639

502结果图如下:

![image-20200620153825464](【nginx】- ipHash负载均衡.4.assets/image-20200620153825464.png)

测试业务异常和停机时是否有failOver

要测试是否有failover先在nginx.conf上配置proxy_next_upstream

我们对upstream为默认的负载均衡策略和ip_hash策略分别进行测试:

情况1:默认负载均衡策略

配置的结果如下:

image-20200620170532275

在浏览器中输入url进行测试:

http://192.168.233.118/test500  //这个请求后台会抛出throw new RuntimeException();

image-20200620170849143

分析执行结果:

  1. 第一次有failover 从8080 failover到8089了。

  2. 第二次从8089 failover到8080了。

  3. 第三次发现做了一个轮回都有错,所以直接从8080 failover到 demoupstream 502

  4. 隔了一段时间(2分钟)重新执行又恢复到500 failover了。

    相关nginx配置参考:“ 虚拟机里nginx.conf配置”

情况2:IP_hash负载均衡方式

先把负载均衡改成ip_hash的方式:

 upstream demoupstream{
        ip_hash;
        server 127.0.0.1:8080;
        server 127.0.0.1:8089;   
    }

在浏览器中输入http://192.168.233.118/test500 结果如下:发现做了failvoer从8080转为80989

image-20200620180052847

从上面两个情况知道,业务异常有failover。那么怎么让业务异常不再有failover呢?

让业务异常的时候不再failvoer

根据前面了解到有failover是由于配置了proxy_next_upstream,所以我把proxy_next_upstream后面的http_500去掉了。

server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

	set $web "/mnt/web_manager";

        location / {
            # proxy_pass http://localhost:8080;
            proxy_pass http://demoupstream;
            root   $web;
            index  index.html index.htm;
            proxy_next_upstream error timeout http_502  non_idempotent; ## 这里没有http_500
        }
}

 upstream demoupstream{
        server 127.0.0.1:8080;
        server 127.0.0.1:8089;   
}

修改后重新在浏览器输入http://192.168.233.118/test500 结果如下图所示,发现没有做failover了。

image-20200620172331297

但是注意,虽然没有failover但是后一次访问跟前面一次访问upstream sever不一样,具体体现就是8080和8089轮流被访问。注意:upstream配置的是默认负载均衡策略

我现在在把负载均衡方式改成ip_hash,发现不管请求多少次请求的后端服务器地址都是8080:

image-20200620180400710

让停机的场景可以failover

经过前面操作,让业务异常不failover搞定了。停机的时候响应码为http_502,为了让停机的情况能够failvoer,所以只需要配置proxy_next_upstream http_502

备注:upstream 路由方式为ip_hash

upstream 

我先杀掉8080进程再杀8089看看有没有failvoer,(因为ip_hash方式下服务默认先请求到8080):

image-20200620181231800

执行结果如下:

image-20200620181607085

我们分析一下结果:

第一次:当把8080进程杀掉之后进行了failover,请求从8080转到8089,因为8089服务还在所以返回的是500的返回码。

注意:这里500是8089返回的,是当访问8080出现异常可能是502路由到8089在执行的返回,由这里也可以看出failover是没有经过客户端的,而是先8080转到8089再返回给客户端。

第二次:我再把8089进程杀掉,请求同样从8080转到8089,但是由于8089服务也停了所以返回502的错误。

第三次:由于前面两次知道8080和8089都502了,所以后面返回结果demoupstream 502

总结

要实现不停机发布,就需要做到服务器挂掉的时候failover同时当有业务异常的时候不进行failover,只需要在proxy_next_upstream那里配置http_502,http_500不要配置,如下代码所示:

location /xx {
            proxy_read_timeout 600;
            #proxy_pass http://localhost:9080;
            proxy_pass http://xx_manage; //这个地方配置了负载均衡
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_next_upstream http_502 non_idempotent;
        }

我具体做的就是把以前http_500从proxy_next_upstream去掉了,改动虽然只有几个代码但是整个调试过程和场景值得保留下来。

参考

nginx.conf的配置

http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 100M;
    log_format  main  '$remote_addr - $remote_user [$time_local] "requst:$request" '
                      'upstream_addr:$upstream_addr '
                      'ups_res_time:$upstream_response_time '
                      'request_time:$request_time '
                      'status:$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" x-forwarded-for: "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;
	
	location / {
	    proxy_pass http://localhost:8081;
	}

    ## 当请求链接为http://120.78.154.33/hnxx/index,那么会到这里而不是上面的location,所以是贪婪匹配
	location /xx {
            proxy_read_timeout 600;
            #proxy_pass http://localhost:9080;
            proxy_pass http://xx_manage; //这个地方配置了负载均衡
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_next_upstream error timeout http_500 http_404 http_502 http_503  non_idempotent;
        }
		
	location /vipRegister {
	    proxy_pass http://localhost:9081;	
	  }
    }
    
    //这是配置的负载均衡
    upstream xx_manage {
     ip_hash;
     server 127.0.0.1:9080;
     server 127.0.0.1:9082;
    }

虚拟机里nginx.conf配置

#user  nobody;
worker_processes  1;

#error_log  logs/error.log;
#error_log  logs/error.log  notice;
#error_log  logs/error.log  info;

#pid        logs/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$upstream_addr'
                      '- $status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       80;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;

	set $web "/mnt/web_manager";

        location / {
            # proxy_pass http://localhost:8080;
            proxy_pass http://demoupstream;
            root   $web;
            index  index.html index.htm;
            proxy_next_upstream error timeout http_502 http_500  non_idempotent;
        }

        #error_page  404              /404.html;

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    upstream demoupstream{
        # ip_hash;
        server 127.0.0.1:8080;
        server 127.0.0.1:8089;   
    }
}

spring boot后台请求代码

@RestController
public class DockerController {

    @RequestMapping("/")
    public String index() {
        System.out.println(">>>Hello Docker!>>>");
        return "Hello Docker!";
    }

    @RequestMapping("/test500")
    public String test500() {
        System.out.println(">>>test500>>>");
        throw new RuntimeException(">>>test500>>>"); //抛出异常服务器就会出现http_500的错误
    }
}

猜你喜欢

转载自www.cnblogs.com/codetalking/p/13176203.html
今日推荐