Nginx 负载均衡详解 x Shell 脚本实战

Nginx 不仅仅是反向代理服务器,它也是搭建负载均衡的利器。

在正确的配置和设计下,即使某些服务器发生故障,只要还有正常运行的服务器可用,负载均衡器就能将流量转发到这些可用服务器上,从而确保服务的连续性。这是负载均衡的一个关键优势,它通过以下机制实现高可用性:

  1. 流量重定向:当检测到某个服务器故障时,负载均衡器会自动将流量重定向到其他正常工作的服务器。这一过程通常是对用户透明的。

  2. 健康检查:负载均衡器定期对每个服务器进行健康检查,以判断它们的状态。一旦某个服务器恢复可用,负载均衡器可以重新开始将流量发送给它。

  3. 冗余性和扩展性:通过在系统中引入冗余,即使在突然的负载高峰或多个服务器故障情况下,系统仍然可以处理流量而不发生中断。

确保这一机制能有效发挥作用的前提是服务器集群中有足够的冗余资源。也就是说,正常运行的服务器必须能够承接所有流量而不至于过载。

此外,在日常运维中 Shell 脚本不可或缺,本篇介绍 Nginx 的负载均衡功能,并结合 Shell 自动化脚本,高效管理服务。废话不多说,直接看实战。

Nginx 负载均衡

假设我们在 localhost 上运行了两个服务实例,分别监听 1972920729 端口。我们希望通过 Nginx 实现负载均衡,将请求分发给这两个实例。

Nginx 配置策略

我们使用 upstream 指令来定义一组后端服务器。在具体的负载均衡配置中,还可以利用一些高级选项来实现更加细致的流量管控,比如权重、最大连接数等。

以下是几个常用的高级负载均衡配置示例:

权重用于控制请求分配到每个服务器的比例,让性能更好或资源更多的服务器承担更多的请求:

conf 复制代码
upstream myapp {
    server localhost:19729 weight=3;
    server localhost:20729 weight=1;
}

这里,localhost:19729 拥有比 localhost:20729 更高的权重设置,意味着三倍于另一个服务器的请求将被分配给它。

设置每个服务器的最大并发连接数,旨在防止过载。

conf 复制代码
upstream myapp {
    server localhost:19729 max_conns=100;
    server localhost:20729 max_conns=100;
}

其他选项:

  • backup:指定备用服务器,只有当所有非备用服务器都不可用时,才会被选用。
  • down:将服务器标记为不可用。
  • fail_timeoutmax_fails:配置失败响应的条件和时间限制。

除了 轮询(默认) ,Nginx 还支持其它负载均衡策略,如 ip_hashleast_conn

conf 复制代码
upstream myapp {
    least_conn;
    server localhost:19729;
    server localhost:20729;
}

least_conn 策略将请求分发给连接数最少的服务器,这在请求处理时间差异较大的情况下能提供更平衡的负载。

完整配置示例

整合上述选项,我们可以得到一个更复杂的 Nginx 配置例子:

conf 复制代码
upstream myapp {
    least_conn;  # 使用最少连接策略
    server localhost:19729 weight=2 max_conns=100; # 设置权重和最大连接数
    server localhost:20729 max_conns=100 backup;   # 设为备用服务器
}

server {
    listen 80;
    location / {
        proxy_pass http://myapp;
    }
}

Shell 自动化脚本

我们先编写一个简单而实用的脚本函数 start_instances,用于批量启动服务实例。

这是预期的函数输入,完整代码将在后面解析:

bash 复制代码
# 写入文件 common.sh
start_instances() {
    local description=$1
    local log_dir=$2
    local command=$3
    shift 3
    local json_env_configurations="$(echo "$@" | jq -c '.[]')"

参数说明:

  • description:用于标识任务的描述性字符串,这将帮助日志文件的分辨与管理。
  • log_dir:日志存储目录,方便集中管理各个服务实例的输出日志。
  • command:启动服务实例所需的实际命令,这里面可能包含 Shell 任何可执行程序。
  • json_env_configurations:以 JSON 格式给出的一系列环境变量配置,便利于为每个实例配备不同的启动参数。

现在,假设我们有一个 Python 服务 myapp.py,通过环境变量 CUDA_VISIBLE_DEVICESSERVER_PORT 来配置 GPU 和端口。

bash 复制代码
#!/bin/bash
# 1. 导入函数 start_instances
source common.sh

# 2. 定义服务启动命令
CMD="nohup python myapp.py"

# 3. 定义 JSON 格式的配置文件
configs='
[
    {"CUDA_VISIBLE_DEVICES":"6", "SERVER_PORT":"19729"},
    {"CUDA_VISIBLE_DEVICES":"7", "SERVER_PORT":"20729"}
]'

# 批量启动服务实例
start_instances "示例服务" "logs" "$CMD" $configs

演示了启动两个服务实例,configs 的每一项代表一个独立的实例启动配置,其绑定不同的 GPU 和端口,并将日志输出到 logs 目录下。

下面我们来逐步解析脚本的实现逻辑和过程。

1. 解析与读取输入参数

bash 复制代码
start_instances() {
    local description=$1
    local log_dir=$2
    local command=$3
    shift 3
    local json_env_configurations="$(echo "$@" | jq -c '.[]')"

将需要的描述性字符串、日志目录、启动命令等输入信息传递给 start_instances 函数。使用 shift 将注意力集中在约定的 JSON 配置输入上。随后,用 jq 打开 JSON 列表。

2. 解析 JSON 环境配置

使用 jq 来解析每个 JSON 字符串,将其转换为 Bash 对能用的键值对:

bash 复制代码
    for json_config in ${json_env_configurations[@]}
    do
        local keys=()
        local values=()
        while IFS= read -r key && IFS= read -r value; do
            keys+=("$key")
            values+=("$value")
        done < <(echo $json_config | jq -r 'to_entries[] | "\(.key)\n\(.value)"')

通过 jqto_entries[] 方法,将 JSON 字符串解析为键值对,以便后续过程使用。

3. 日志文件路径

根据每个实例的配置构建独特的日志文件名,以便于区分不同实例的日志:

bash 复制代码
        local log_file="$log_dir/$description"
        declare -A env_vars
        for ((i=0; i<${#keys[@]}; i++)); do
            key="${keys[$i]}"
            value="${values[$i]}"
            env_vars["$key"]="$value"
            log_file+="_${value}"
        done
        log_file+=".log"

4. 检查并关闭已有进程

确定进程占用的端口并关闭,以便为新实例的正常启动腾出资源:

bash 复制代码
        for key in "${!env_vars[@]}"; do
            if [[ "$key" == *_PORT ]]; then
                port=${env_vars[$key]}
                pid=$(lsof -i:$port -t)
                if [ -n "$pid" ]; then
                    kill -9 $pid
                    echo "$description instance on port $port is down"
                fi
            fi
        done

通过lsof命令,可以识别出程序占用的端口并终止相关进程。

5. 启动服务实例

最后,针对每个环境变量启动服务实例:

bash 复制代码
        for key in "${!env_vars[@]}"; do
            export "$key"="${env_vars[$key]}"
        done

        # Start the instance
        eval $command > $log_file 2>&1 &

        echo "$description with config $json_config is up"
    done

执行命令,将输出重定向到构建的日志文件中。这里有个踩坑点,最后要用 eval 运行而不能直接用 $command 运行,否则对于多指令代码会有问题。

举个例子,假设 command="echo 1 && pwd",使用 $command 会输出字符串 1 && pwd,而只有用 eval $command 才能把后边的 pwd 也执行。

完整脚本

汇总上边内容,编写文件 common.sh

bash 复制代码
#!/bin/bash

start_instances() {
    local description=$1
    local log_dir=$2
    local command=$3
    shift 3
    local json_env_configurations="$(echo "$@" | jq -c '.[]')"

    # Process each JSON configuration
    for json_config in ${json_env_configurations[@]}
    do
        # Use jq to parse the JSON string into key-value pairs
        local keys=()
        local values=()
        while IFS= read -r key && IFS= read -r value; do
            keys+=("$key")
            values+=("$value")
        done < <(echo $json_config | jq -r 'to_entries[] | "\(.key)\n\(.value)"')

        # Prepare environment variables and log file name
        local log_file="$log_dir/$description"
        declare -A env_vars
        for ((i=0; i<${#keys[@]}; i++)); do
            key="${keys[$i]}"
            value="${values[$i]}"
            env_vars["$key"]="$value"
            log_file+="_${value}"
        done
        log_file+=".log"

        # Identify and kill process based on ports
        for key in "${!env_vars[@]}"; do
            if [[ "$key" == *_PORT ]]; then
                port=${env_vars[$key]}
                pid=$(lsof -i:$port -t)
                if [ -n "$pid" ]; then
                    kill -9 $pid
                    echo "$description instance on port $port is down"
                fi
            fi
        done

        # Export environment variables
        for key in "${!env_vars[@]}"; do
            export "$key"="${env_vars[$key]}"
        done

        # Start the instance
        eval $command > $log_file 2>&1 &

        echo "$description with config $json_config is up"
    done
}

总结

以上,我们介绍了 Nginx 负载均衡的基本配置和 Shell 脚本的实际应用。通过结合两者,我们可以实现服务的自动化管理与负载均衡,提高服务的稳定性和可靠性。

相关推荐
培根芝士2 小时前
如何设置浏览器不缓存网页
nginx·缓存
YonJ2 小时前
CentOS7源码编译安装nginx+php+mysql
mysql·nginx·php
kaoa0002 小时前
Linux入门攻坚——42、Nginx及web站点架构模式
linux·前端·nginx
文浩(楠搏万)6 小时前
Java Spring Boot 项目中嵌入前端静态资源:完整教程与实战案例
java·服务器·前端·spring boot·后端·nginx·github
恩爸编程7 小时前
Nginx 在不同操作系统下的安装指南
运维·nginx·nginx安装·nginx安装流程·nginx linux安装·nginx window安装·nginx安装使用
Allen Bright9 小时前
【Nginx-4】Nginx负载均衡策略详解
运维·nginx·负载均衡
qq_4489410810 小时前
1、lvs介绍和模式
负载均衡·lvs
烟草的香味.13 小时前
nginx 记录完整的 request 及 response
java·nginx·junit
ks胤墨13 小时前
Nginx Proxy Manager如何管理与配置反向代理服务并实现远程访问
运维·nginx
gaog2zh18 小时前
0101多级nginx代理websocket配置-nginx-web服务器
websocket·nginx