当您的系统在用户中变得越来越受欢迎时,扩展性将成为系统的必要部分。

有两种类型的扩展:

  • 垂直扩展 – 向单个服务器添加更多资源(CPU、RAM、存储)。
  • 水平扩展 – 启动更多服务器并在它们之间分配负载,这被称为负载均衡。

在本文中,我们将讨论什么是负载均衡以及如何使用Nginx和Docker实现负载均衡器。现在让我们开始吧!

什么是负载平衡?

负载均衡是将传入的网络流量分布到多个服务器、应用程序或网络资源上的过程,以确保没有单个资源被过多的流量压垮。负载均衡的目标是优化资源使用、最大化吞吐量、最小化响应时间并确保系统高可用性和可靠性。

在典型的负载均衡场景中,传入的请求首先被定向到负载均衡器,负载均衡器充当系统的前端。然后,根据一组预定义的规则、策略或算法,负载均衡器将请求转发到适当的服务器、应用程序或资源上,以避免请求过度集中到某个资源上。负载均衡器可有效地:

防止请求发送到失效或故障的服务器。 防止过度集中资源。 帮助消除单点故障。

有几种不同类型的负载均衡可供选择,具体取决于系统的特定要求和约束:

1、轮询

这是最简单、最常见的负载均衡类型,其中传入的请求按照循环顺序分发给服务器。每个服务器都有平等的机会处理请求,不考虑其当前的负载或性能。

servers = [server1, server2, server3]
next_server_index = 0

def handle_request(request):
    server = servers[next_server_index]
    next_server_index = (next_server_index + 1) % len(servers)
    server.process_request(request)

2、加权轮询

这是轮询负载均衡的一种变体,其中每个服务器根据其处理能力分配一个权重,然后按比例分配请求。分配更高权重的服务器会获得更高比例的传入流量。

servers = [{'server': server1, 'weight': 2},
           {'server': server2, 'weight': 3},
           {'server': server3, 'weight': 1}]
next_server_index = 0

def handle_request(request):
    server_info = servers[next_server_index]
    server = server_info['server']
    server.process_request(request)
    server_info['weight'] -= 1
    if server_info['weight'] == 0:
        next_server_index = (next_server_index + 1) % len(servers)
        for info in servers:
            info['weight'] = info['weight'] + 1

3、最少连接

在这种类型的负载均衡中,传入的请求被发送到具有最少活动连接的服务器,以避免过载任何一个服务器。这对于涉及长连接的应用程序特别有用,例如流媒体或游戏。

servers = [server1, server2, server3]

def handle_request(request):
    min_connections = float('inf')
    min_server = None
    for server in servers:
        if server.connections < min_connections:
            min_connections = server.connections
            min_server = server
    min_server.process_request(request)

4、IP 哈希负载均衡

该方法使用客户端的 IP 地址来确定发送请求的服务器。将 IP 地址进行哈希处理,然后使用生成的值选择服务器。这可以确保来自同一客户端的请求始终发送到同一台服务器上,这对于维护会话状态或其他应用程序特定要求非常有用。

servers = [server1, server2, server3]

def handle_request(request):
    client_ip = request.get_client_ip()
    hashed_ip = hash(client_ip)
    server_index = hashed_ip % len(servers)
    server = servers[server_index]
    server.process_request(request)

 

5、四层负载均衡

这种类型的负载均衡在网络栈的传输层(TCP/UDP)操作,并使用源IP、目标IP、源端口和目标端口等信息来分发流量到不同的服务器。

servers = [server1, server2, server3]

def handle_request(request):
    dest_ip = request.get_dest_ip()
    dest_port = request.get_dest_port()
    server_index = hash(dest_ip + ':' + dest_port) % len(servers)
    server = servers[server_index]
    server.process_request(request)

6、七层负载均衡

这种负载均衡是在网络堆栈的应用层操作,并利用HTTP头、cookie和URL路径等信息来分发流量到服务器。这样可以根据特定应用的标准进行更智能的路由,例如会话亲和性,基于内容的路由或SSL卸载。

servers = [{'server': server1, 'route': '/api/*'},
           {'server': server2, 'route': '/auth/*'},
           {'server': server3, 'route': '/*'}]

def handle_request(request):
    for info in servers:
        if request.matches_route(info['route']):
            server = info['server']
            server.process_request(request)
            break

实现 Nginx 负载均衡器

假设我们有 3 个 Python 服务器,每个服务器都部署在一个容器中。然后,我们将使用 Nginx 作为这 3 个服务器的负载均衡器。

这是我们的文件结构:

nginx-load-balancer
    |
    |---app1
    |     |-- app1.py
    |     |-- Dockerfile
    |     |-- requirements.txt
    |
    |---app2
    |     |-- app2.py
    |     |-- Dockerfile
    |     |-- requirements.txt
    |
    |---nginx
    |     |-- nginx.conf
    |     |-- Dockerfile
    |
    |------ docker-compose.yml

我们有两个基本的Flask服务器,它们返回一个文本来说明我们连接到哪个服务器:

app1/app1.py

from flask import request, Flask
app1 = Flask(__name__)

@app1.route('/')
def hello_world():
    return '<h1>Hello from server 1</h2>'


if __name__ == '__main__':
   app1.run(debug=True, host='0.0.0.0')

每个Flask服务器都部署到一个Docker容器中:

app1/Dockerfile

FROM python:3

COPY ./requirements.txt /requirements.txt

WORKDIR /

RUN pip install -r requirements.txt

COPY . /

ENTRYPOINT [ "python3" ]

CMD [ "app1.py" ]

在上面的代码中,我们告诉docker安装Python3镜像并复制要求文件。之后,我们更改docker的工作目录并安装依赖项。最后,我们复制代码并运行服务器。

我们将为Nginx执行相同的操作:

nginx/nginx.conf

upstream loadbalancer {
    server 172.17.0.1:5001 weight=5;
    server 172.17.0.1:5002 weight=5;
}

server {
    location / {
        proxy_pass http://loadbalancer;
    }
}

在这里,我们使用轮询负载均衡算法。使用weight参数指定路由标准。在本例中,我们希望为两个服务器均衡负载。

之后,让我们将 Nginx 配置 Docker 化。当启动 Docker 时,它将在相关路径内复制上述配置文件。

nginx/Dockerfile

FROM nginx
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf

最后让我们创建一个docker-compose文件,它将作为一个包装器启动整个架构:

docker-compose.yml

version: '3'
services:
  app1:
    build: ./app1
    ports:
    - "5001:5000"
  app2:
    build: ./app2
    ports:
    - "5002:5000"
  nginx:
    build: ./nginx 
    ports:
    - "8080:80"
    depends_on:
      - app1
      - app2

这个文件的作用如下:

  • 它将基于我们的 Dockerfile 为 app1、app2 和 Nginx 构建镜像,然后从这些镜像中创建容器。
  • app1 和 app2 容器内部打开的端口为 5000(Flask 使用的默认端口),这些端口将映射到 5001 和 5002。
  • 负载均衡器将根据该端口路由流量到适当的应用程序。
  • 负载均衡器(Nginx)将其内部的 80 端口公开到 8080,这样我们就可以从 http://localhost:8080 访问应用程序。

最后,您可以使用基本目录中的以下命令启动架构:

docker-compose up

该请求将被平等地路由到两个服务器:

 

发表回复