Nginx 负载均衡配置、限流配置、Https 配置详解
2023年12月07日
120
Nginx 负载均衡配置、限流配置、Https 配置详解

一. 负载均衡

1. 用法

通过 proxy_pass 可以把请求代理至后端服务,但是为了实现更高的负载及性能, 我们的后端服务通常是多个, 这个是时候可以通过 upstream 模块实现负载均衡。


使用的模块为:【ngx_http_upstream_module】,具体配置可以根据模块名去查找文档。


负载均衡的算法有:


  • ll:轮询

  • ll+weight: 轮询加权重

  • ip_hash : 基于 Hash 计算,用于保持 session 一至性      该算法下权重失效

  • url_hash: 静态资源缓存,节约存储,加快速度(第三方)  该算法下权重配置失效

  • least_conn :最小链接数

  • least_time  :最小的响应时间,计算节点平均响应时间,然后取响应最快的那个,分配更高权重


image.png


2. 参数

upstream 相关参数如下:


  • server 反向服务地址加端口

  • weight  权重,默认是 1,越大权重就越大

  • max_fails  失败多少次认为主机已挂掉则,踢出 (默认配置 10s,即服务器宕掉,会自动剔除)

  • fail_timeout  踢出后重新探测时间

  • backup  备用服务,当其他非 backup 的机器全部宕机或者繁忙的时候,才会启动这台机器。

  • down  表示当前 Server 不参与负载

  • max_conns 允许最大连接数

  • slow_start 当节点恢复,不立即加入,而是等待 slow_start 后加入服务对列。


upstream myApiTest {          server localhost:9001 weight=10;          server localhost:9002 weight=5;          server localhost:9003  max_fails=3 fail_timeout=30s;          server localhost:9004 backup;          server localhost:9005 down;    }

复制代码

3. 案例

事先准备:


有三个同样的 api 服务,分别部署在 9001、9002、9003 端口下,比如:访问 http://localhost:9001/Home/GetMsg,会返回 【 获取成功,当前端口为:9001】,其它端口类似。


要求:


Nginx 监听 8080 端口,接收到 8080 端口的请求,按照响应的算法进行转发到 9001-9003 端口。


(1). 轮询


访问地址:http://localhost:8080/Home/GetMsg ,会依次转发到 9001、9002、9003 端口上。【最新版本测试,轮询的时候一个服务器连续沦陷两次,才到下一个服务器,继续连续两次】??


配置如下:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    upstream myApiTest {          server localhost:9001;          server localhost:9002;          server localhost:9003;    }    server {        listen       8080;        server_name   xxx;    #随意配置一个即可,优先走代理地址        location / {            proxy_pass http://myApiTest;        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }    }}

复制代码


补充其他参数说明:


下面配置,当请求 http://localhost:8080/Home/GetMsg 时候,只会被转发到 9003 端口上,此时把 9003 端口的服务关掉,再次请求,则会被转发到 9001 端口上,其中 9002 端口,全程不参与负载。


upstream myApiTest {    server localhost:9001 backup;    server localhost:9002 down;    server localhost:9003;}

复制代码


(2).轮询+权重


下面配置,被转发到 9001 9002 端口的概率要大于 9003 端口。


配置如下:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    upstream myApiTest {          server localhost:9001 weight=10;          server localhost:9002 weight=5;          server localhost:9003;    }    server {        listen       8080;        server_name   xxx;    #随意配置一个即可,优先走代理地址        location / {            proxy_pass http://myApiTest;        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }    }}

复制代码


(3). ip_hash


同一个 ip 永远会被分配到同一个 Server 上,主要用来解决 Session 不一致的问题,但该策略也有弊端,weight 权重无效,所以该方案会导致某个 Server 压力可能过大,请求分配不均匀问题。


配置如下:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    upstream myApiTest {          ip_hash;   #开启ip_hash策略          server localhost:9001;          server localhost:9002;          server localhost:9003;    }    server {        listen       8080;        server_name   xxx;    #随意配置一个即可,优先走代理地址        location / {            proxy_pass http://myApiTest;        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }    }}

复制代码


(4). url_hash


主要用于节省空间,比如我有 9G 的图片资源,服务器集群有三台,因为不确定请求会被转发到哪一台上,所以每台服务器都存放 9G,显然这样是不合理的。


我们可以在存储的时候,将图片进行 urlhash 算法,分别存放到三台服务器上,这样请求的时候也是用 urlhash,去指定服务器请求即可,节省了服务器空间,也就是 3 台服务器总共用了 9G。


PS:上面只是举例方便理解,生产中,大量图片资源存放 cdn 第三方,然后在自己的服务器上做一层临时缓存,为了提高缓存的命中率,通常用 urlhash 算法。


更多 C++后台开发技术点知识内容包括 C/C++,Linux,Nginx,ZeroMQ,MySQL,Redis,MongoDB,ZK,流媒体,音视频开发,Linux 内核,TCP/IP,协程,DPDK 多个高级知识点。


二. 限流配置

1.  说明

(1). 限流的作用


限流主要用作安全目的,比如可以减慢暴力密码破解的速率。


通过将传入请求的速率限制为真实用户的典型值,并标识目标 URL 地址(通过日志),


还可以用来抵御 DDOS 攻击。更常见的情况,该功能被用来保护上游应用服务器不被同时太多用户请求所压垮。


(2). 原理


令牌桶算法


漏桶算法


(3). 涉及到的模块


A.  用来限制同一时间连接数,即并发限制  【不常用】


【ngx_http_limit_conn_module】  对应文档:http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html


B.  用来限制单位时间内的请求数,即速率限制,采用的漏桶算法   【推荐使用】


【ngx_http_limit_req_module】    对应文档:http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

2. 环境准备

(1). Api 接口


启动指令如下:


【dotnet NginxTest.dll --urls="http://*:7061" --ip="127.0.0.1" --port=7061】


【dotnet NginxTest.dll --urls="http://*:7062" --ip="127.0.0.1" --port=7062】


接口地址为: http://localhost:7061/api/Home/GetNowTime      【Post 请求】


接口代码如下:


[Route("api/[controller]/[action]")]    [ApiController]    public class HomeController : ControllerBase    {        [HttpPost]        public string GetNowTime()        {            string nowTime = DateTime.Now.ToString();            Console.WriteLine($"当前时间为:{nowTime}");            return $"当前时间为:{nowTime}";        }    }

复制代码


(2). nginx 服务


使用到的指令


启动服务:【start nginx】


强制关闭服务:【nginx -s stop】


重载服务:【nginx -s reload】


nginx 监听 7000 端口,然后进行代理配置,这里重点测试的是限流,只用 7061 一个 api 端口即可。


worker_processes  1;events {    worker_connections  1024;}
http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;        server {        listen       7000;        #监听端口        server_name  xxx;         #随意配置一个地址即可,优先走代理        location / {             proxy_pass http://localhost:7061;   #代理地址        }            error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }}

复制代码


通过 post 请求访问:http://localhost:7000/api/Home/GetNowTime ,返回当前时间,表示配置成功。



(3). jmeter 测试工具


添加线程组,然后在线程组的基础上添加:http 请求、察看结果树、聚合报告、用表格查结果。


配置请求的并发数、请求地址。


image.png




测试结果:10 个请求全部成功。


image.png

3. 限流-限制并发连接数【不常用】

声明格式:


limit_conn_zone $server_name zone=myLimit0:10m;limit_conn_zone $binary_remote_addr zone=myLimit1:10m;

复制代码


(1). $server_name:表示虚拟主机(server) 同时能处理并发连接的总数。 (数量在启用时配置)


(2). $binary_remote_addr:表示限制每个客户端 IP(单个 ip)连接到服务器的链接数量。  (数量在启用时配置)


(3).  zone=myLimit1:10m :表示内存区域名称 和 空间大小。


调用格式:


limit_conn myLimit1 2;   #启用限流

复制代码


(1).  myLimit1 :表示用上述声明的哪个配置进行限制,myLimit1 与上述声明的名称相对应。


(2).  2:  表示配置的数量限制。


(1). 限制-虚拟主机同时能处理的并发链接总数


分析:


虚拟主机(server) 同时能处理并发连接的总数为 5.


测试条件:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;        # 限流配置声明    limit_conn_zone $server_name zone=myLimit0:10m;    server {        listen       7000;        #监听端口        server_name  xxx;         #随意配置一个地址即可,优先走代理        location / {             limit_conn myLimit0 5;  #启用限流             proxy_pass http://localhost:7061;   #代理地址        }            error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }}

复制代码


测试结果:


忽略


(2). 限制-单个 ip 链接到服务器的数量


剖析:


单个 IP 同时最多能持有 8 个连接


测试条件:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    # 限流配置声明    limit_conn_zone $binary_remote_addr zone=myLimit1:10m;    server {        listen       7000;        #监听端口        server_name  xxx;         #随意配置一个地址即可,优先走代理        location / {             limit_conn myLimit1 8;   #启用限流             proxy_pass http://localhost:7061;   #代理地址        }            error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }}

复制代码


测试结果:忽略

4. 限流-限制速率【推荐使用】

声明格式:


limit_req_zone $binary_remote_addr zone=myLimit2:10m rate=5r/s;

复制代码


(1). $binary_remote_addr :  表示限制同一客户端 ip 地址,即限制速率是以 ip 为分类的,限制每个 ip 的速度


(2). zone=myLimit2:10m:  myLimit2 表示内存区域的名称,10m 表示内存空间的大小。


(3).  rate=5r/s : 表示 1s 允许 5 个请求, 注意这里需要拆分理解,即 200ms 允许 1 个请求,当第一个请求处理完成,如果 200ms 内又进来一个请求,该请求将被拒绝处理,只有过了 200ms 后,才会处理第 2 个请求,一次类推。


调用格式 :


image.png


limit_req zone=myLimit2 burst=5 nodelay;

复制代码


(1). zone=myLimit2:表示用上述声明的哪个配置进行限制,myLimit2 与上述声明的名称相对应。


(2). burst=5 :设置一个大小为 5 的缓冲区,当有大量请求(瞬间爆发)过来时,超过了上述配置的访问频次限制的请求,可以先放到这个缓冲区内。


注:burst 的作用是让多余的请求可以先放到队列里,慢慢处理。如果不加 nodelay 参数,队列里的请求不会立即处理,而是按照 rate 设置的速度,以毫秒级精确的速度慢慢处理。


(3). nodelay : 设置后,burst 缓冲区中排队的请求立即被处理,超过频次限制 并且 缓冲区满了的情况下,直接返回 503 状态码;如不设置,那么额外的请求将进入等待排队的状态


注:通过设置 burst 参数,我们可以允许 Nginx 缓存处理一定程度的突发,多余的请求可以先放到队列里,慢慢处理,不报错,这起到了平滑流量的作用。


但是如果队列设置的比较大,请求排队的时间就会比较长,从用户角度看来就是响应变长了,这对用户很不友好,所以引入 nodelay 参数。


nodelay 参数允许请求在排队的时候就立即被处理,也就是说只要请求能够进入 burst 队列,就会立即被后台处理,请注意,这意味着 burst 设置了 nodelay 时,系统瞬间的 QPS 可能会超过 rate 设置的阈值。


所以:nodelay 参数要跟 burst 一起使用才有作用。


(1). 实操 1-限制速率


分析:


使用 jmeter 发送 10 个请求进行测试,nginx 的限制速率设置为 2r/s,意味着第 1 个请求处理完后,500ms 内接收的请求都将拒绝,过了 500ms 后,才能处理下一个请求。 详见下面的测试结果。


测试条件:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    # 限流配置声明    limit_req_zone $binary_remote_addr zone=myLimit2:10m rate=2r/s;    server {        listen       7000;        #监听端口        server_name  xxx;         #随意配置一个地址即可,优先走代理        location / {             limit_req zone=myLimit2;   #启用限流                proxy_pass http://localhost:7061;   #代理地址        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }}

复制代码


测试结果:


第 1 个成功的请求的是 008ms,接下来第 2-6 个请求,由于是在 500ms 内,所有都请求失败;  第 2 个成功的请求为 608ms,正好过了 500ms 了,所以成功了。


image.png


(2). 限制速率+设置缓冲区


分析:


使用 jmeter 发送 10 个请求进行测试,nginx 的限制速率设置为 2r/s,burst=5,意味着第 1 个请求处理完后,接下来的 5 个请求都是存放到缓存中,第 7 个请求如果在第 1 个的 500ms 后,则请求成功,反之失败。


测试条件:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    # 限流配置声明    limit_req_zone $binary_remote_addr zone=myLimit2:10m rate=2r/s;    server {        listen       7000;        #监听端口        server_name  xxx;         #随意配置一个地址即可,优先走代理        location / {             limit_req zone=myLimit2 burst=5;    #启用限流             proxy_pass http://localhost:7061;   #代理地址        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }}

复制代码


测试结果:


第 1 个成功的请求为 343ms,接下来第 2-6 个加入到缓存区,依次执行成功; 第 7 个请求为 944,与第一个成功的请求相比,已经超过了 500ms,所以执行成功,接下来的 8-10 个请求,均在第 7 个成功后的 500ms 内,所以均失败。


image.png


(3). 限制速率+设置缓冲区+立即处理


分析:


使用 jmeter 发送 10 个请求进行测试,nginx 的限制速率设置为 2r/s,burst=5  nodelay,意味着第 1 个请求处理完后,接下来的 5 个请求立即执行,第 7 个请求如果在第 1 个的 500ms 后,则请求成功,反之失败。


测试条件:


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;    # 限流配置声明    limit_req_zone $binary_remote_addr zone=myLimit2:10m rate=1r/s;    server {        listen       7000;        #监听端口        server_name  xxx;         #随意配置一个地址即可,优先走代理        location / {             limit_req zone=myLimit2 burst=5 nodelay;  #启用限流                proxy_pass http://localhost:7061;   #代理地址        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }}

复制代码


测试结果:


第 1 个成功的请求为 278ms,接下来 2-6 个立即执行,第 7 个请求为 879,距离第一个成功的已经超过 500ms,所以执行成功,接下来的 8-10 个请求,均在 500ms 内,所以执行失败。


image.png

三. Https 配置

1. 准备

(1). 生成证书


OpenSSL 工具下载地址:http://slproweb.com/products/Win32OpenSSL.html      【这里以 3.0.5 为例】


image.png


OpenSSL 生成证书步骤:https://jingyan.baidu.com/article/6c67b1d6be538c2787bb1e06.html


(2). 相关模块


【ngx_http_rewrite_module】     参考文档:http://nginx.org/en/docs/http/ngx_http_rewrite_module.html

2. 实操

(1). 配置 https 的 Server


开启一个新的虚拟主机,用来配置 https 监听 8000 端口,配置证书的物理地址即可,就可以通过 https://localhost:8000/api/Home/GetNowTime ,访问代理地址下 7061 的 api 了。


(PS:下面配置同时开启了 http 的主机,所以通过 http://localhost:7000/api/Home/GetNowTime,也可以访问)


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;
   #http主机    server {        listen       7000;          #监听端口        server_name  test1;         #随意配置一个地址即可,优先走代理        location / {              proxy_pass http://localhost:7061;   #代理地址        }        error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }      #https主机    server {        listen       8000 ssl;        server_name  test2;        #证书目录        ssl_certificate      D:/cert/server-cert.pem;        ssl_certificate_key  D:/cert/server-key.pem;
       ssl_session_cache    shared:SSL:1m;        ssl_session_timeout  5m;        location / {            proxy_pass http://localhost:7061;        }    }}

复制代码


(2). 将 http 跳转到 https


上述 http 监听的 7000 端口,https 监听的 8000 端口,如何让 http 请求自动跳转到 https 请求上呢?


加个 return 301 xxxxx  跳转即可。


worker_processes  1;events {    worker_connections  1024;}http {    include       mime.types;    default_type  application/octet-stream;    sendfile        on;    keepalive_timeout  65;  #http主机    server {        listen       7000;          #监听端口        server_name  test1;         #随意配置一个地址即可,优先走代理        location / {               proxy_pass http://localhost:7061;   #代理地址            #跳转到https (test2是https主机的server_name)      return 301 https://test2$request_uri;            #或者      #return 301 https://$host:8000$request_uri;        }            error_page   500 502 503 504  /50x.html;        location = /50x.html {            root   html;        }      }    #https主机  server {        listen       8000 ssl;        server_name  test2;
   #证书目录    ssl_certificate      D:/cert/server-cert.pem;        ssl_certificate_key  D:/cert/server-key.pem;        ssl_session_cache    shared:SSL:1m;        ssl_session_timeout  5m;        location / {            proxy_pass http://localhost:7061;        }    }}