Nginx 负载均衡和反向代理

发布于:2024-12-06 ⋅ 阅读:(23) ⋅ 点赞:(0)

Nginx 是一个高性能的 HTTP 服务器和反向代理服务器,广泛应用于负载均衡中。它的负载均衡功能支持多种策略,可以有效分配流量到后端服务器,提升系统的可靠性和可用性。

负载均衡

首先,Nginx 负载均衡配置是通过在 Nginx 配置文件中定义 upstream 块和对应的 server 块来实现的。Nginx 安装过程就不多说了,如果没有安装,可以参考安装链接:Nginx 安装和访问-CSDN博客

负载均衡配置

我这儿以我的示例进行的配置。先在 192.168.128.139 机器上修改 nginx.conf (记得先将原 nginx.conf 备份)。

events {
    worker_connections  1024;
}

http {
    upstream backend {
        server 192.168.128.139:5001 weight=1;
        server 192.168.128.138:5001 weight=2;
    }

    server {
        listen 5000;
        location / {
            proxy_pass http://backend;
        }
    }
}

在上面的例子中,upstream backend 定义了一个名为 backend 的服务器组,包含了两个后端服务器,并设置权重分别为 1 和 2。

server 监听指定端口 5000(监听并转发来自 5000 端口的请求), proxy_pass 指令则将请求代理到定义的服务器组 backend 中。

配置好以后,执行 sbin/nginx 启动服务(后续修改 nginx.conf 配置以后,可使用 sbin/nginx -s reload 命令重新加载配置即可)。

root@master /u/l/nginx# pwd
/usr/local/nginx
root@master /u/l/nginx# sbin/nginx

为了配合验证 nginx 的负载均衡,我分别在两个虚拟机 192.168.128.139 和 192.168.128.138 上分别搭建了一个 flask 服务。

服务示例

如果仅仅是测试,为了避免访问被挡住的问题,可以将测试虚拟机的防火墙关闭。

[root@master ~]# service iptables stop
Redirecting to /bin/systemctl stop iptables.service
root@slave ~# service firewalld stop
Redirecting to /bin/systemctl stop firewalld.service

192.168.128.139

from flask import Flask

app = Flask(__name__)


@app.route('/test', methods=['POST', 'GET'])
def test():
    result = {
        "result": "from 192.168.128.139: I am backend1"
    }
    return result


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5001)

192.168.128.138 

from flask import Flask

app = Flask(__name__)


@app.route('/test', methods=['POST', 'GET'])
def test():
    result = {
        "result": "from 192.168.128.138: I am backend2"
    }
    return result


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5001)

请求示例

可以看到,我模拟请求的是 192.168.128.139 的 5000 端口,但请求最后还是可以被转发到 flask 服务的  5001 端口。

模拟请求 10 次,可以看到 192.168.128.139 和 192.168.128.138 的请求比 是 3:7,和我们配置的权重  1: 2 很接近(多次请求基本上就和配置权重一致了)。

import requests


def test_request(url):
    res = requests.get(url)
    print(res.json())


if __name__ == '__main__':
    url = "http://192.168.128.139:5000/test"
    for i in range(10):
        test_request(url)
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}

指定路由转发

上面的示例是统一将 根路径 下的所有请求进行转发。虽然我的请求使用的 /test 路径,但也会命中基础匹配,因此也会被转发。

但是,有时候我可能希望特定路由的请求被转发到特定的服务器。

如果涉及到指定多个 URL 路径(如  根路径 / 和路径 /test),则可以配置多个 location 分配不同路径到不同的后端服务器组。

events {
    worker_connections  1024;
}

http {
    upstream backend {
        server 192.168.128.139:5001 weight=1;
        server 192.168.128.138:5001 weight=2;
    }

    upstream backend_test {
        server 192.168.128.138:5001;
    }

    server {
        listen 5000;
        location / {
            proxy_pass http://backend;
        }

        location /test {
            proxy_pass http://backend_test;
        }
    }
}

这样,满足 /test 前缀的会被转发到 backend_test 服务器组,其它不满足的则命中基础匹配被转发到 backend 服务器组。

匹配规则

基础匹配

location / { ... }:这是最基本的匹配,匹配所有请求。因为它没有指定具体的文件或目录,所以通常作为后备选项出现。

精确匹配

location = /exact/path { ... }:精确匹配这个路径。如果请求的 URI 完全等于 /exact/path,则使用这个 location 块的配置处理此请求。这具有最高的优先级。

前缀匹配

location /prefix/ { ... }:前缀匹配请求的 URI 的开始部分。如果请求的 URI 以 /prefix/ 开始,这个 location 块将被用来处理请求。

location ^~ /prefix/ { ... }:前缀匹配,但它会停止正则表达式匹配,即使正则表达式可能会更具体匹配。如果该 location 匹配,Nginx 不会考虑之后的正则 location 块。

正则表达式匹配

location ~ /regex/ { ... }:大小写敏感的正则匹配。
location ~* /regex/ { ... }:大小写不敏感的正则匹配。
location / { ... } 正则表达式匹配会在普通字符串前缀匹配后进行。如果有多个正则表达式 location 都匹配请求,则使用第一个匹配的 location 块。

匹配优先级

Nginx 处理请求时 location 匹配的优先级顺序如下:

  • 首先进行精确匹配 location =
  • 其次按文件中出现顺序匹配所有正则表达式 location ~ 和 location ~*
  • 然后进行最长的前缀匹配 location ^~
  • 最后是通常的前缀匹配 location /prefix/
  • 如果前面的匹配都没有找到,就使用默认的 location /

负载均衡算法

Nginx 支持多种负载均衡算法,常见的包括:

  • 轮询(Round Robin):默认算法,按照顺序将请求依次分发到后端服务器。
  • 权重(Weighted Round Robin):为每个后端服务器设置权重,权重高的服务器分配的请求会更多。
  • IP 哈希(IP Hash):根据客户端 IP 的哈希值决定分发到哪台后端服务器,适用于需要保持会话一致性的场景。
  • 最少连接(Least Connections):将请求分配给当前连接数最少的后端服务器。
  • Hash(指定字段哈希):基于指定的请求字段(如 URL、Cookie 等)来分配请求。

轮询配置

默认的配置,按顺序把请求依次发到列表的服务器。

events {
    worker_connections  1024;
}

http {
    upstream backend {
        server 192.168.128.139:5001;
        server 192.168.128.138:5001;
    }

    server {
        listen 5000;
        location / {
            proxy_pass http://backend;
        }
    }
}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}

权重配置

为每个后端服务器设置权重,会尽可能将请求按照设定的权重比例进行转发。

upstream backend {
    server 192.168.128.139:5001 weight=1;
    server 192.168.128.138:5001 weight=2;
}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}

IP哈希配置

根据客户端 IP 的哈希值决定分发到哪台后端服务器,适用于需要保持会话一致性的场景。

简单理解,也即客户端的请求第一次被转发到的那台服务器,那么后续这个客户端的请求,都会被转发到之前的同一台服务器。

events {
    worker_connections  1024;
}

http {
    upstream backend {
        ip_hash;
        server 192.168.128.139:5001;
        server 192.168.128.138:5001;
    }

    server {
        listen 5000;
        location / {
            proxy_pass http://backend;
        }
    }
}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}

最少连接配置

将请求分配给当前连接数最少的后端服务器,也即哪台请求服务器的压力比较小,就将请求转发到哪台服务器。

events {
    worker_connections  1024;
}

http {
    upstream backend {
        least_conn;
        server 192.168.128.139:5001;
        server 192.168.128.138:5001;
    }

    server {
        listen 5000;
        location / {
            proxy_pass http://backend;
        }
    }
}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.139: I am backend1'}
{'result': 'from 192.168.128.138: I am backend2'}

健康检查配置

手动将某个服务器标记为不可用,那么负载均衡进行请求转发的时候,就会忽略掉 "不可用" 的服务器,从而不会将请求转发到这个失效的服务器上。

events {
    worker_connections  1024;
}

http {
    upstream backend {
        server 192.168.128.139:5001 down;
        server 192.168.128.138:5001;
    }

    server {
        listen 5000;
        location / {
            proxy_pass http://backend;
        }
    }
}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}

反向代理

简单理解,反向代理就是服务端的代理,上面一节的 负载均衡 也用到了反向代理。

upstream

我们把 192.168.128.139 上的 nginx.conf 配置从负载均衡稍微改一下。

events {
    worker_connections  1024;
}

http {
    upstream backend {
        server 192.168.128.138:5001;
    }

    server {
        listen 5000;
        location / {
            proxy_pass http://backend;
        }
    }
}

这样的话,请求到 192.168.128.139:5000 的请求,都被转发到了 192.168.128.138:5001 上去了。

再次用相同的请求运行,可以看到,请求都被转发到另外一个机器的另外一个端口上去了。

import requests


def test_request(url):
    res = requests.get(url)
    print(res.json())


if __name__ == '__main__':
    url = "http://192.168.128.139:5000/test"
    for i in range(10):
        test_request(url)
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}
{'result': 'from 192.168.128.138: I am backend2'}

proxy_pass

当然,如果只是想做单一的反向代理,并不考虑负载均衡的话,我们也可以不用 upstream 模块。

events {
    worker_connections  1024;
}

http {
    server {
        listen 5000;
        location / {
            proxy_pass http://192.168.128.138:5001;
        }
    }
}

上面这种,也可以达到反向代理的作用。但这种情况具有局限性,并不能做到负载均衡,只限于一个被代理服务器的情况下。

还有个小的知识点。proxy_pass 结尾带 / 和不带 / 是有区别的

我们可以测试一下。在 192.168.128.138 上部署如下服务。

192.168.128.138 

from flask import Flask

app = Flask(__name__)


@app.route('/test', methods=['POST', 'GET'])
def test():
    result = {
        "result": "from 192.168.128.138: I am backend2"
    }
    return result


@app.route('/image', methods=['POST', 'GET'])
def test2():
    result = {
        "result": "from 192.168.128.138: I am backend2"
    }
    return result


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5001)

配置 nginx 为 :

events {
    worker_connections  1024;
}

http {
    server {
        listen 5000;
        location /test {
            proxy_pass http://192.168.128.138:5001;
        }
    }
}

请求 http://192.168.128.139:5000/test 可以正常调通。

import requests

if __name__ == '__main__':
    url = "http://192.168.128.139:5000/test"
    res = requests.get(url)
    print(res.json())  # {'result': 'from 192.168.128.138: I am backend2'}

我们修改 nginx 为(proxy_pass 中的 url 末尾多了一个斜杠 /):

events {
    worker_connections  1024;
}

http {
    server {
        listen 5000;
        location /test {
            proxy_pass http://192.168.128.138:5001/;
        }
    }
}

此时,请求 http://192.168.128.139:5000/test 无法正常调通。但是请求 http://192.168.128.139:5000/test/image 却是可以正常调通的。

import requests

if __name__ == '__main__':
    url = "http://192.168.128.139:5000/test/image"
    res = requests.get(url)
    print(res.json())  # {'result': 'from 192.168.128.138: I am backend2'}
这是因为, nginx 匹配到路由中的 /test 并将其阶段,最终转发的 url 是 http://192.168.128.139:5000/image,因此能正常调通。

下面直接给出结论:

proxy_pass 结尾带/的场景中,会截断匹配成功的 location 规则,转发 proxy_pass + 剩余的 URI

如果是下面这种 nginx 配置(localtion 和 proxy_pass 后缀一致):

events {
    worker_connections  1024;
}

http {
    server {
        listen 5000;
        location /test {
            proxy_pass http://192.168.128.138:5001/test/;
        }
    }
}

假设请求 url 是 http://192.168.128.139:5000/test/test_image,则虽然 /test/test_image 被截断 /test 后是 /test_image,但是转发时拼接上 proxy_pass 的 ​​​​​http://192.168.128.138:5001/test ,最后真正转发的仍然是 http://192.168.128.138:5001/test/test_image 。


网站公告

今日签到

点亮在社区的每一天
去签到