nginx+keepalived负载均衡可用框架(二 nginx负载均衡-ip_hash)

本文详细介绍了nginx使用ip_hash进行负载均衡的初始化过程和算法原理,包括server weight的作用以及如何根据权重分配请求。ip_hash能够确保同一客户端的请求被定向到同一台后端服务器,适合实现会话持久化。但同时也存在可能导致服务器间负载不均的问题。

ip哈希初始化

ip哈希的初始化函数ngx_http_upstream_init_ip_hash(ngx_http_upstream_ip_hash_module.c)

    static ngx_int_t  
    ngx_http_upstream_init_ip_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)  
    {  
        //调用了加权轮询  
        if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {  
            return NGX_ERROR;  
        }  
      
        //修改了针对单个请求进行初始化的回调函数  
        us->peer.init = ngx_http_upstream_init_ip_hash_peer;  
      
        return NGX_OK;  
    }  
选择后端服务器

当客户端请求过来之后,将会执行初始化函数ngx_http_upstream_init_ip_hash_peer。其中调用了轮询算法中的初始化函数。

    static ngx_int_t  
    ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t *r,  
        ngx_http_upstream_srv_conf_t *us)  
    {  
        struct sockaddr_in                     *sin;  
        //针对IPv6的支持  
    #if (NGX_HAVE_INET6)  
        struct sockaddr_in6                    *sin6;  
    #endif  
        ngx_http_upstream_ip_hash_peer_data_t  *iphp;  
      
        iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));  
        if (iphp == NULL) {  
            return NGX_ERROR;  
        }  
      
        r->upstream->peer.data = &iphp->rrp;  
      
        //调用了RR算法中的初始化函数  
        if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {  
            return NGX_ERROR;  
        }  
      
        //回调函数设置,具体做选择的回调函数  
        r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;  
      
        switch (r->connection->sockaddr->sa_family) {  
      
        //保存客户端地址  
        case AF_INET:  
            sin = (struct sockaddr_in *) r->connection->sockaddr;  
            iphp->addr = (u_char *) &sin->sin_addr.s_addr;  
            //转储IPv4只用到了前3个字节,因为在后面的hash计算过程中只用到了3个字节  
            iphp->addrlen = 3;  
            break;  
      
    #if (NGX_HAVE_INET6)  
        case AF_INET6:  
            sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;  
            iphp->addr = (u_char *) &sin6->sin6_addr.s6_addr;  
            iphp->addrlen = 16;  
            break;  
    #endif  
      
        default:  
            iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr;  
            iphp->addrlen = 3;  
        }  
      
        //初始化hash种子  
        iphp->hash = 89;  
        //初始化尝试失败次数  
        iphp->tries = 0;  
        //做RR选择的函数  
        iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;  
      
        return NGX_OK;  
    }  
其中结构体ngx_http_upstream_ip_hash_peer_data_t:

    typedef struct {  
        /* the round robin data must be first */  
        ngx_http_upstream_rr_peer_data_t   rrp;  
        //hash种子值  
        ngx_uint_t                         hash;  
        //IP地址  
        u_char                             addrlen;  
        u_char                            *addr;  
        //尝试连接的次数  
        u_char                             tries;  
      
        ngx_event_get_peer_pt              get_rr_peer;  
    } ngx_http_upstream_ip_hash_peer_data_t;  
      
      
    typedef struct {  
        //指向所有服务器的指针  
        ngx_http_upstream_rr_peers_t   *peers;  
        //当前服务器  
        ngx_uint_t                      current;  
        //指向位图的指针  
        uintptr_t                      *tried;  
        //位图的实际存储位置  
        uintptr_t                       data;  
    } ngx_http_upstream_rr_peer_data_t;  
      
    typedef struct ngx_http_upstream_rr_peers_s  ngx_http_upstream_rr_peers_t;  
      
    struct ngx_http_upstream_rr_peers_s {  
        ngx_uint_t                      number;//所有服务器地址总数  
      
     /* ngx_mutex_t                    *mutex; */  
      
        ngx_uint_t                      total_weight;//所有服务总权重  
      
        unsigned                        single:1;//是否只有一个后端服务  
        unsigned                        weighted:1;//number != total_weight ?  
        ngx_str_t                      *name;  
      
        ngx_http_upstream_rr_peers_t   *next;  
      
        ngx_http_upstream_rr_peer_t     peer[1];  
    };  
具体做选择的函数是ngx_http_upstream_get_ip_hash_peer:

    static ngx_int_t  
    ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data)  
    {  
        ngx_http_upstream_ip_hash_peer_data_t  *iphp = data;  
      
        time_t                        now;  
        ngx_int_t                     w;  
        uintptr_t                     m;  
        ngx_uint_t                    i, n, p, hash;  
        ngx_http_upstream_rr_peer_t  *peer;  
      
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,  
                       "get ip hash peer, try: %ui", pc->tries);  
      
        /* TODO: cached */  
        //如果失败次数太多,或者只有一个后端服务,那么直接做RR选择  
        if (iphp->tries > 20 || iphp->rrp.peers->single) {  
            return iphp->get_rr_peer(pc, &iphp->rrp);  
        }  
      
        now = ngx_time();  
      
        pc->cached = 0;  
        pc->connection = NULL;  
      
        hash = iphp->hash;  
      
        for ( ;; ) {  
            //计算IP的hash值  
            for (i = 0; i < iphp->addrlen; i++) {  
                //113质数,可以让哈希结果更散列  
                hash = (hash * 113 + iphp->addr[i]) % 6271;  
            }  
      
            //根据哈希结果得到被选中的后端服务器  
            if (!iphp->rrp.peers->weighted) {  
                p = hash % iphp->rrp.peers->number;  
      
            } else {  
                w = hash % iphp->rrp.peers->total_weight;  
      
                for (i = 0; i < iphp->rrp.peers->number; i++) {  
                    w -= iphp->rrp.peers->peer[i].weight;  
                    if (w < 0) {  
                        break;  
                    }  
                }  
      
                p = i;  
            }  
      
            //服务器对应在位图中的位置计算  
            n = p / (8 * sizeof(uintptr_t));  
            m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));  
      
            if (!(iphp->rrp.tried[n] & m)) {  
      
                ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,  
                               "get ip hash peer, hash: %ui %04XA", p, m);  
      
                //获取服务器  
                peer = &iphp->rrp.peers->peer[p];  
      
                /* ngx_lock_mutex(iphp->rrp.peers->mutex); */  
      
                //服务器未挂掉  
                if (!peer->down) {  
                    //失败次数已达上限  
                    if (peer->max_fails == 0 || peer->fails < peer->max_fails) {  
                        break;  
                    }  
      
                    if (now - peer->checked > peer->fail_timeout) {  
                        peer->checked = now;  
                        break;  
                    }  
                }  
                //更改位图标记值  
                iphp->rrp.tried[n] |= m;  
      
                /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */  
                //在连接一个远端服务器时,当前连接异常失败后可以尝试的次数  
                pc->tries--;  
            }  
      
            //已经尝试的次数超过阈值,采用RR轮询  
            if (++iphp->tries >= 20) {  
                return iphp->get_rr_peer(pc, &iphp->rrp);  
            }  
        }  
      
        //当前服务索引  
        iphp->rrp.current = p;  
        //服务器地址及名字保存  
        pc->sockaddr = peer->sockaddr;  
        pc->socklen = peer->socklen;  
        pc->name = &peer->name;  
      
        /* ngx_unlock_mutex(iphp->rrp.peers->mutex); */  
        //位图更新  
        iphp->rrp.tried[n] |= m;  
        //保留种子,使下次get_ip_hash_peer的时候能够选到同一个peer上  
        iphp->hash = hash;  
      
        return NGX_OK;  
    }  
上述计算过程中,依据ip的hash值进行映射的时候,依据服务器列表头部结构中weighted字段分了两种不同的情况。下面看看weighted的计算过程(ngx_http_upstream_round_robin.c):

    //指向服务器列表指针          
    server = us->servers->elts;  
      
    n = 0;  
    w = 0;  
    //遍历服务器列表,计算地址总数以及总的权值  
    for (i = 0; i < us->servers->nelts; i++) {  
        if (server[i].backup) {  
            continue;  
        }  
      
        n += server[i].naddrs;  
        w += server[i].naddrs * server[i].weight;  
    }  
    //weighted计算  
    peers->weighted = (w != n);  
server的类型是ngx_http_upstream_server_t。

    typedef struct {  
        ngx_addr_t                      *addrs;//指向存储IP地址的数组的指针,host信息(对应的是 ngx_url_t->addrs )  
        ngx_uint_t                       naddrs;//与第一个参数配合使用,数组元素个数(对应的是 ngx_url_t->naddrs )  
        ngx_uint_t                       weight;//权值  
        ngx_uint_t                       max_fails;  
        time_t                           fail_timeout;  
      
        unsigned                         down:1;  
        unsigned                         backup:1;  
    } ngx_http_upstream_server_t;  
一个域名可能对应多个ip地址

server weight字段,作为server权重,对应虚拟节点数目。

具体算法,将每个server虚拟成n个节点,均匀分布到hash环上,每次请求,根据配置的参数计算出一个hash值,在hash环上查找离这个hash最佳的虚拟节点,对应的server作为该次请求的后端机器。

因此,当weighted字段等于0的时候,表示虚拟节点数和ip地址数是相等的,因此直接将hash值针对ip地址总数取模即可。如果weighted不等于0,表示虚拟节点和ip地址数不等,因此需要按照虚拟节点数另外计算。查找离这个hash最佳的虚拟节点,作为该请求的后端机器。

整个ip哈希选择流程

流程图

加权轮询策略和ip哈希策略对比

加权轮询策略

优点:适用性更强,不依赖于客户端的任何信息,完全依靠后端服务器的情况来进行选择。能把客户端请求更合理更均匀的分配到各个后端服务器处理。

缺点:同一个客户端的多次请求可能会被分配到不同的后端服务器进行处理,无法满足做会话保持的应用的需求。

ip哈希策略

优点:能较好地把同一个客户端的多次请求分配到同一台服务器处理,避免了加权轮询无法适用会话保持的需求。

缺点:当某个时刻来自某个ip地址的请求特别多,那么将导致某后端服务器压力可能非常大,而其他后端服务器却空闲的不均衡情况。

<think>我们正在构建一个高可用负载均衡集群,使用Nginx、Tomcat、DNS、Keepalived和NFS。以下是详细的步骤和配置方法: ### 1. 架构概述 - **DNS**:解析域名到虚拟IP(由Keepalived提供)。 - **Keepalived**:提供虚拟IP(VIP)并在两台Nginx负载均衡器之间实现高可用(主备切换)。 - **Nginx**:作为负载均衡器,将请求分发到后端的Tomcat服务器。 - **Tomcat**:运行Java Web应用的服务器。 - **NFS**:网络文件系统,用于共享存储(如Web应用的静态资源,确保多台Tomcat内容一致)。 ### 2. 环境准备 假设我们有两台Nginx+Keepalived服务器(主:192.168.14.101,备:192.168.14.102),两台Tomcat服务器(192.168.14.103, 192.168.14.104),一台NFS服务器(192.168.14.105)。 ### 3. 步骤 #### 3.1 配置NFS共享存储(在NFS服务器上操作) 1. 安装NFS服务器: ```bash sudo apt-get update sudo apt-get install nfs-kernel-server ``` 2. 创建共享目录并设置权限: ```bash sudo mkdir /data/web_share sudo chown nobody:nogroup /data/web_share sudo chmod 777 /data/web_share ``` 3. 编辑`/etc/exports`文件,添加: ``` /data/web_share 192.168.14.0/24(rw,sync,no_subtree_check,no_root_squash) ``` 4. 重启NFS服务: ```bash sudo systemctl restart nfs-kernel-server ``` #### 3.2 在Tomcat服务器上挂载NFS共享目录 1. 安装NFS客户端: ```bash sudo apt-get install nfs-common ``` 2. 创建本地挂载点: ```bash sudo mkdir -p /var/www/html ``` 3. 挂载NFS共享目录(两台Tomcat都需要): ```bash sudo mount 192.168.14.105:/data/web_share /var/www/html ``` 4. 设置开机自动挂载(编辑`/etc/fstab`): ``` 192.168.14.105:/data/web_share /var/www/html nfs defaults 0 0 ``` #### 3.3 配置Tomcat(两台Tomcat服务器) 1. 安装Tomcat(以Tomcat 9为例): ```bash sudo apt-get install tomcat9 ``` 2. 配置Tomcat的webapps目录指向挂载的NFS目录(可选,如果应用需要共享存储): 通常,我们将应用部署到`/var/lib/tomcat9/webapps`,但为了使用共享存储,我们可以创建符号链接或修改`server.xml`中的`appBase`。这里我们采用符号链接: ```bash sudo rm -rf /var/lib/tomcat9/webapps sudo ln -s /var/www/html/webapps /var/lib/tomcat9/webapps ``` 3. 重启Tomcat: ```bash sudo systemctl restart tomcat9 ``` #### 3.4 配置Nginx负载均衡器(两台Nginx服务器都需要安装和配置) 1. 安装Nginx: ```bash sudo apt-get install nginx ``` 2. 配置负载均衡(编辑`/etc/nginx/nginx.conf`或`/etc/nginx/sites-available/default`): ```nginx upstream tomcat_servers { server 192.168.14.103:8080 weight=100; server 192.168.14.104:8080 weight=90; } server { listen 80; server_name yourdomain.com; location / { proxy_pass http://tomcat_servers; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } ``` 3. 重启Nginx: ```bash sudo systemctl restart nginx ``` #### 3.5 配置Keepalived(实现Nginx负载均衡器的高可用) 1. 安装Keepalived(两台Nginx服务器): ```bash sudo apt-get install keepalived ``` 2. 配置Keepalived(192.168.14.101): 编辑`/etc/keepalived/keepalived.conf`: ```conf vrrp_instance VI_1 { state MASTER interface ens192 # 根据实际网卡修改 virtual_router_id 51 priority 100 # 主服务器优先级更高 advert_int 1 authentication { auth_type PASS auth_pass yourpassword } virtual_ipaddress { 192.168.14.11 # 虚拟IP } } ``` 3. 配置Keepalived(192.168.14.102): ```conf vrrp_instance VI_1 { state BACKUP interface ens192 virtual_router_id 51 priority 90 # 备用服务器优先级较低 advert_int 1 authentication { auth_type PASS auth_pass yourpassword } virtual_ipaddress { 192.168.14.11 } } ``` 4. 启动Keepalived(两台): ```bash sudo systemctl start keepalived sudo systemctl enable keepalived ``` #### 3.6 配置DNS 在DNS解析服务商处,将域名解析到虚拟IP`192.168.14.11`。 ### 4. 验证 - 访问虚拟IP`192.168.14.11`应该能够访问到Tomcat应用。 - 关闭主Nginx服务器,备用Nginx服务器应接管虚拟IP(需要几秒切换时间)。 - 检查负载均衡Nginx根据权重分配请求到两台Tomcat。 ### 注意事项 - 防火墙:确保相关端口(80、8080、NFS端口)开放。 - SELinux:如果启用,可能需要调整策略。 此方案实现了: - DNS解析到虚拟IP- Keepalived保证Nginx负载均衡器的高可用- Nginx分发请求到Tomcat集群。 - NFS共享存储保证Tomcat应用内容一致。 §§相关问题§§ 1. 如何监控高可用负载均衡集群的健康状态? 2. 除了NFS,还有哪些共享存储方案可以用于Tomcat集群? 3. Keepalived的脑裂问题如何解决?</think>### 高可用负载均衡集群构建方案 使用 **Nginx + Tomcat + DNS + Keepalived + NFS** 实现高可用负载均衡集群的步骤如下: --- #### **一、架构设计** ```mermaid graph TD A[用户访问] --> B(DNS解析) B --> C[虚拟IP:192.168.14.11] C --> D{Keepalived} D -->|主节点| E[Nginx-Master] D -->|备节点| F[Nginx-Backup] E & F --> G[Tomcat集群] G --> H[NFS共享存储] ``` --- #### **、详细步骤与配置** ##### **1. DNS配置** - 将域名解析到Keepalived的虚拟IP(如`192.168.14.11`) ```bash # DNS记录示例 www.yourdomain.com A记录 192.168.14.11 ``` ##### **2. Keepalived配置(高可用VIP)** > **主节点配置** (`/etc/keepalived/keepalived.conf`)[^2] ```nginx vrrp_instance VI_1 { state MASTER # 主节点 interface ens192 # 网卡名称(需根据实际修改) virtual_router_id 51 priority 100 # 优先级(主>备) advert_int 1 authentication { auth_type PASS auth_pass yourpassword } virtual_ipaddress { 192.168.14.11 # 虚拟IP } } ``` > **备节点配置** ```nginx vrrp_instance VI_1 { state BACKUP # 备节点 interface ens192 virtual_router_id 51 priority 90 # 优先级低于主节点 advert_int 1 authentication { ... } # 同上 virtual_ipaddress { 192.168.14.11 } } ``` **启动服务** ```bash systemctl start keepalived && systemctl enable keepalived ``` ##### **3. Nginx负载均衡配置** > **Nginx主备节点相同配置** (`/etc/nginx/nginx.conf`) ```nginx upstream tomcat_servers { server 192.168.14.103:8080 weight=100; # Tomcat节点1 server 192.168.14.104:8080 weight=90; # Tomcat节点2 } server { listen 80; location / { proxy_pass http://tomcat_servers; proxy_set_header Host $host; } } ``` **重启Nginx** ```bash systemctl restart nginx ``` ##### **4. Tomcat集群配置** - 所有Tomcat节点部署相同应用 - 通过NFS共享资源(如上传文件目录) ```bash # 在每台Tomcat服务器挂载NFS mount -t nfs 192.168.14.105:/shared_data /opt/tomcat/webapps/shared ``` ##### **5. NFS共享存储配置** > **NFS服务器** (`/etc/exports`) ```bash /shared_data 192.168.14.0/24(rw,sync,no_root_squash) ``` **启动服务** ```bash systemctl start nfs-server && exportfs -a ``` --- #### **三、故障切换验证** 1. **模拟主Nginx故障** ```bash systemctl stop nginx # 在主节点执行 ``` 2. **观察Keepalived切换** - 备节点自动接管VIP `192.168.14.11` - 用户请求无缝转移至备Nginx[^1] --- #### **四、关键优化建议** - **健康检查**:在Keepalived中添加Nginx进程监控脚本,实现自动故障转移。 - **会话保持**:Nginx配置`ip_hash`或Tomcat使用Redis共享Session。 - **NFS高可用**:使用DRBD+Heartbeat替代单点NFS。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值