15、负载均衡、优化与云环境下的 NGINX 应用

负载均衡、优化与云环境下的 NGINX 应用

1. 负载均衡概念理解

负载均衡,简单来说,就是将工作负载(如 CPU 负载、硬盘负载等)以对访客完全透明的方式分配到多个服务器上。在单服务器架构中,客户端请求由一台机器接收和处理,而机器的处理能力是有限的。例如,一台 Web 服务器每秒能响应 1000 个 HTTP 请求,如果每秒收到超过 1000 个请求,第 1001 个请求就无法及时处理,页面响应速度会变慢,访客体验变差。

将负载分配到多个服务器可以提高整体请求处理能力。理论上,两台服务器每秒可处理 2000 个 HTTP 请求,三台则可处理 3000 个,以此类推。实现负载均衡有多种技术,DNS 负载均衡是最常用的技术之一。当用户访问网站时,浏览器会将域名解析为 IP 地址。要实现 DNS 负载均衡,只需将多个 IP 地址关联到域名。访客的操作系统会按照简单的轮询算法选择其中一个 IP 地址,从而确保所有服务器收到的流量大致相同。

然而,这种负载均衡方法存在一些问题,不适用于高流量网站:
- 访客操作系统选择的 IP 地址指向的服务器可能暂时不可用。
- 架构中的服务器处理能力可能不同。
- 访客登录特定服务器后,可能会在一段时间后被切换到其他服务器,导致会话数据丢失,即会话亲和性问题。

2. 会话亲和性

会话亲和性指的是在负载均衡架构中,将客户端持久地分配到特定服务器。当访客浏览网站时,从登录账户到添加商品到购物车再到结账等一系列操作,直到关闭浏览器或标签页,这些操作都属于一个会话,通常是有状态的,服务器会保存相关操作的数据。如果在会话期间访客切换到其他服务器,就会丢失原服务器上的会话信息,如购物车内容和登录凭证。因此,保持会话亲和性非常重要,而 DNS 负载均衡方法无法保证这一点,但 NGINX 可以帮助实现。

3. NGINX 中的 upstream 模块

NGINX 实现负载均衡的方式非常巧妙,它可以在基础设施的多个层面分配负载,不仅可以跨后端服务器代理 HTTP 请求,还能将请求分配到 FastCGI 后端、Memcached 服务器等。以 _pass 结尾的指令(如 proxy_pass、fastcgi_pass、memcached_pass)可以引用一组服务器。

第一步是使用 upstream 块声明服务器组,该块必须放在 http 块内,在 upstream 块中使用 server 指令声明一个或多个服务器,示例如下:

http {
    upstream MyUpstream {
        server 10.0.0.201;
        server 10.0.0.202;
        server 10.0.0.203;
    }
    ...
}

也可以在 upstream 块中使用 include 指令从外部文件加载服务器:

http {
    upstream MyUpstream {
        include myUpstreamServers.txt
    }
    ...
}

声明服务器组后,可在虚拟主机配置中引用它,例如通过代理将传入的 HTTP 请求分配到服务器组:

server {
    server_name example.com;
    listen 80;
    root /home/example.com/www;
    # Proxy all requests to the MyUpstream server group
    proxy_pass http://MyUpstream;
    ...
}

在这种基本配置下,请求按照简单的轮询算法分配到 MyUpstream 组的三个服务器,不保持会话亲和性。

4. 请求分配机制

NGINX 提供了多种解决上述问题的方法。最简单的是 weight 标志,可在服务器组定义中启用:

upstream MyUpstream {
    server 10.0.0.201 weight=3;
    server 10.0.0.202 weight=2;
    server 10.0.0.203;
}

默认情况下,服务器权重为 1。权重越高,服务器接收的请求越多。例如,每收到 6 个 HTTP 请求,NGINX 会分配 3 个给 10.0.0.201 服务器,2 个给 10.0.0.202 服务器,1 个给 10.0.0.203 服务器。

NGINX 还会检查服务器组中服务器的状态。如果服务器未能及时响应,请求会被重新发送到下一个服务器。upstream 块中的服务器可以使用以下标志更好地控制该机制:
| 标志 | 说明 |
| ---- | ---- |
| fail_timeout=N | N 为请求被视为失败前的秒数 |
| max_fails=N | N 为在放弃该服务器并切换到下一个服务器之前的尝试次数,默认尝试 1 次 |
| max_conns=N | N 为可发送到该服务器的最大并发连接数,默认不限制 |
| backup | 将服务器标记为备份服务器,仅在其他服务器失败时使用 |
| down | 将服务器标记为永久不可用,不再使用 |

5. 实现会话亲和性

NGINX 提供了多种实现会话亲和性的选项,可在 upstream 块中插入相关指令。最简单的是 ip_hash 指令,它根据客户端 IPv4 地址的前 3 个字节(或完整的 IPv6 地址)计算哈希值,并根据该哈希值将客户端分配到特定服务器。只要客户端 IP 地址不变,NGINX 就会将请求转发到上游组中的同一服务器:

upstream {
    server 10.0.0.201 weight=3;
    server 10.0.0.202 weight=2;
    server 10.0.0.203;
    ip_hash;
}

不过,由于大多数互联网服务提供商提供的是动态 IP 地址,这种方法可能不太可靠。可以使用 hash 指令根据自定义标准(如 cookie 值)来分配请求:

upstream {
    server 10.0.0.201;
    server 10.0.0.202;
    hash $cookie_username;
}
6. 使用 NGINX 作为 TCP/UDP 负载均衡器

直到 2015 年 5 月,开源版的 NGINX 仅支持 HTTP 请求的负载均衡,而商业版的 NGINX Plus 则进一步支持将 NGINX 用作 TCP/UDP 负载均衡器。从 NGINX 1.9.0 开始,开源版也包含了 stream 模块,可用于实现 TCP/UDP 负载均衡。

使用 stream 模块进行 TCP/UDP 负载均衡与 HTTP 负载均衡类似,但需要在编译程序前使用 --with-stream 标志运行 configure 命令。stream 模块提供了一个新的 stream 块,必须放在配置文件的根目录(http 块之外)。在该块中,需要声明两组指令:
- server:声明一个监听特定端口的 TCP/UDP 服务器,可选择指定网络接口,支持 SSL。
- upstream:定义服务器组,与之前的方式类似。

在 server 块中,使用 proxy_pass 指令将请求发送到服务器组。以下是一个 MySQL 负载均衡的示例:

stream {
    upstream MyGroup {
        # use IP address-based distribution
        hash $remote_addr;
        server 10.0.0.201 weight=2;
        server 10.0.0.202;
        server 10.0.0.203 backup; # use as backup only
    }
    server {
        # listen on the default MySQL port
        listen 3306;
        proxy_pass MyGroup; # forward requests to upstream
    }
}
7. 线程池和 I/O 机制

对于需要大量 I/O 操作(如文件上传或下载)的网站,NGINX 的异步架构可能存在劣势。主进程可以异步处理传入连接,但工作进程可能会因某些任务(如从硬盘或网络驱动器读取数据)而长时间阻塞。例如,在一个简化的配置中,两个工作进程处理请求,如果文件存储在有 100ms 延迟的网络驱动器上,两个工作进程大部分时间都会等待文件,服务器每秒只能处理 18 - 20 个请求。

从 NGINX 1.7.11 开始,引入了线程池的解决方案。其基本原理是将文件读取操作委托给线程,从而释放工作进程,使其可以处理下一个请求。线程完成操作后,工作进程再完成响应并发送给客户端。

要启用线程池支持,NGINX 必须使用 --with-threads 参数进行编译。配置的第一步是在配置文件的根目录使用 thread_pool 指令定义线程池:

thread_pool MyPool threads=64;

在需要的 location 块中,插入 aio 指令并指定线程池名称:

location /downloads/ {
    aio threads=MyPool;
}

也可以使用默认线程池:

location /downloads/ {
    aio threads;
    directio 8k;
    sendfile on;
}

如果客户端请求的文件超过 8k(directio 指令指定的值),将使用 aio;否则,使用 sendfile 发送文件。

8. 云基础设施中的 NGINX

在当今的网络基础设施中,从传统服务器配置向云架构的转变是不可避免的。传统部署方式中,服务器需要手动配置来托管多个站点或应用程序,而现在更灵活、可扩展的云基方法逐渐取代了这种方式。本章将重点介绍在云基础设施中使用 Docker 平台部署 NGINX 的实践,展示如何无缝编排多个服务,创建一个有弹性和适应性的环境。

9. 理解云基础设施

云服务已经成为我们在线生活的重要组成部分。移动应用与桌面应用无缝同步,传统服务器已演变成如今的云概念,这既简化了用户的生活,也使网络基础设施更加复杂。

传统方法

以 WordPress 为例,它运行在 Web 服务器、PHP 服务器和数据库服务器上。传统方法是将每个组件安装在一台机器上,这种方法适合托管单个博客。但当需要扩展时,就会暴露出局限性。不同软件可能有不同的依赖版本,甚至相互冲突,而且随着服务的增加,在单台服务器上管理这些服务会变得越来越复杂。

云方法

在单台服务器上托管多个服务时,软件依赖冲突是常见问题。例如,一个应用程序需要 PHP 7.4,另一个需要 PHP 8.3。Docker 通过将应用程序封装在容器中来解决这个问题,容器是独立的环境,与底层 Linux 发行版的软件版本无关。

使用 Docker 容器有以下优点:
- 版本管理 :简化了软件版本的维护和更改,必要时可以轻松回滚。
- 隔离和效率 :容器隔离应用程序,提高了可移植性,便于迁移,同时确保一个应用程序的资源消耗不会影响其他应用程序。
- 部署简化 :克服 Docker 的学习曲线后,部署和扩展变得更容易管理,提供了传统配置所没有的灵活性。

例如,在运行 WordPress 和 Nextcloud 时,服务器运行 Linux 发行版,上面的两个独立容器作为最小的 Linux 环境,各自只包含运行相应应用程序所需的内容。这种配置允许多个具有不同软件版本的 Docker 镜像在隔离环境中同时运行。如果一个容器出现安全问题,其影响将被限制在该容器内,保护了系统的其他部分。

虽然本章不涉及 Docker Swarm 高可用性或 Kubernetes 可扩展性等高级主题,但这些是云基础设施发展中值得探索的重要领域。

综上所述,在适应高流量之前,应先利用现有工具进行优化。如果服务器因阻塞操作(如缓慢的磁盘读取)而无响应,可以尝试使用线程池。如果线程池无法满足需求,负载均衡是更好的选择。NGINX 使实现负载均衡架构变得简单,还可用于分配其他服务器应用程序(如 MySQL、电子邮件等)的负载。通过本文的介绍,我们对如何管理 NGINX 服务器资源有了更深入的了解,希望能帮助你更好地应对实际应用中的挑战。

负载均衡、优化与云环境下的 NGINX 应用

10. 使用 Docker 部署 NGINX

在云基础设施中,使用 Docker 部署 NGINX 是一种高效且灵活的方式。以下将详细介绍如何在 Docker 中设置 NGINX,并使用它来代理主机应用程序。

10.1 拉取 NGINX 镜像

首先,需要从 Docker Hub 拉取 NGINX 官方镜像。打开终端,运行以下命令:

docker pull nginx

这个命令会从 Docker Hub 下载最新版本的 NGINX 镜像到本地。

10.2 运行 NGINX 容器

拉取镜像后,可以使用以下命令运行一个 NGINX 容器:

docker run -d -p 80:80 --name my-nginx nginx
  • -d :表示以守护进程模式运行容器,即容器在后台运行。
  • -p 80:80 :将容器的 80 端口映射到主机的 80 端口,这样就可以通过主机的 IP 地址访问 NGINX 服务。
  • --name my-nginx :为容器指定一个名称,方便后续管理。
  • nginx :指定要运行的镜像名称。
10.3 配置 NGINX 容器

可以通过挂载配置文件的方式对 NGINX 容器进行自定义配置。创建一个本地的 NGINX 配置文件,例如 nginx.conf ,内容如下:

worker_processes 1;

events {
    worker_connections 1024;
}

http {
    server {
        listen 80;
        server_name localhost;

        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;
        }
    }
}

然后使用以下命令运行容器,并挂载配置文件:

docker run -d -p 80:80 -v /path/to/nginx.conf:/etc/nginx/nginx.conf --name my-nginx nginx
  • -v /path/to/nginx.conf:/etc/nginx/nginx.conf :将本地的 nginx.conf 文件挂载到容器的 /etc/nginx/nginx.conf 路径,这样容器就会使用我们自定义的配置文件。
11. 使用 NGINX 代理主机应用程序

在 Docker 中设置好 NGINX 后,可以使用它来代理主机上的其他应用程序。以下是一个简单的示例,假设主机上运行着一个 Node.js 应用程序,监听在 3000 端口。

11.1 修改 NGINX 配置文件

在之前创建的 nginx.conf 文件中添加以下内容:

http {
    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass http://host.docker.internal:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
}
  • proxy_pass http://host.docker.internal:3000 :将所有请求代理到主机上的 3000 端口, host.docker.internal 是 Docker 提供的一个特殊域名,用于指向主机的 IP 地址。
  • proxy_set_header Host $host proxy_set_header X-Real-IP $remote_addr :设置请求头,确保代理请求的正确性。
11.2 重启 NGINX 容器

修改配置文件后,需要重启 NGINX 容器使配置生效:

docker restart my-nginx
12. 总结与最佳实践

在云基础设施中使用 NGINX 和 Docker 可以带来许多好处,如提高可扩展性、简化部署和管理等。以下是一些总结和最佳实践:

12.1 负载均衡
  • 当流量增加导致服务器响应缓慢时,先尝试使用线程池优化资源管理。如果仍然无法满足需求,再考虑使用负载均衡。
  • NGINX 提供了多种负载均衡算法和机制,如 weight 标志、ip_hash 指令等,可以根据实际需求选择合适的方法。
12.2 会话亲和性
  • 对于需要保持会话状态的应用程序,确保使用支持会话亲和性的负载均衡方法,如 NGINX 的 ip_hash 或 hash 指令。
12.3 云基础设施与 Docker
  • Docker 容器提供了隔离和版本管理的优势,适合在云环境中部署多个应用程序。
  • 使用 Docker 时,注意合理配置容器的资源限制,避免资源过度使用。
12.4 NGINX 配置
  • 定期审查和优化 NGINX 配置文件,确保其性能和安全性。
  • 使用模块化的配置方式,将不同的配置逻辑分离到不同的文件中,便于管理和维护。
13. 流程图:NGINX 负载均衡与云部署流程
graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px

    A([开始]):::startend --> B{流量是否导致服务器无响应?}:::decision
    B -->|是| C(尝试使用线程池优化):::process
    B -->|否| D(正常运行):::process
    C --> E{线程池是否满足需求?}:::decision
    E -->|是| D
    E -->|否| F(使用负载均衡):::process
    F --> G{是否需要会话亲和性?}:::decision
    G -->|是| H(使用支持会话亲和性的方法):::process
    G -->|否| I(选择合适的负载均衡算法):::process
    H --> J(配置 NGINX 负载均衡):::process
    I --> J
    J --> K(部署到云基础设施):::process
    K --> L{是否使用 Docker?}:::decision
    L -->|是| M(拉取 NGINX 镜像):::process
    L -->|否| N(传统部署方式):::process
    M --> O(运行 NGINX 容器):::process
    O --> P(配置 NGINX 代理主机应用程序):::process
    P --> Q([结束]):::startend
    N --> Q
14. 表格:NGINX 配置指令总结
指令 说明 示例
upstream 定义服务器组 upstream MyUpstream { server 10.0.0.201; }
proxy_pass 将请求代理到指定的服务器组 proxy_pass http://MyUpstream;
ip_hash 实现会话亲和性,根据客户端 IP 地址分配请求 upstream { ip_hash; }
hash 根据自定义标准分配请求 hash $cookie_username;
thread_pool 定义线程池 thread_pool MyPool threads=64;
aio 使用异步 I/O aio threads=MyPool;
sendfile 直接从磁盘发送文件 sendfile on;
directio 指定文件大小阈值,超过该值使用 aio directio 8k;

通过以上内容,我们全面了解了负载均衡、优化以及在云环境下使用 NGINX 和 Docker 的相关知识和实践方法。希望这些信息能帮助你更好地构建和管理高效、稳定的网络基础设施。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值