当您的系统在用户中变得越来越受欢迎时,扩展性将成为系统的必要部分。
有两种类型的扩展:
- 垂直扩展 – 向单个服务器添加更多资源(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
该请求将被平等地路由到两个服务器: