`

Nginx学习之十三-负载均衡-IP哈希策略剖析

 
阅读更多
前面介绍过nginx负载均衡的加权轮询策略(http://blog.csdn.net/xiajun07061225/article/details/9318871),它是Nginx负载均衡的基础策略,所以一些初始化工作,比如配置值转储,其他策略可以直接复用他。在后面的初始化的代码中将可以看到。

注:本文中源代码版本为Nginx-1.4.0。

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 wegiht 字段,作为server权重,对应虚拟节点数目。
具体算法,将每个server虚拟成n个节点,均匀分布到hash环上,每次请求,根据配置的参数计算出一个hash值,在hash环上查找离这个hash最近的虚拟节点,对应的server作为该次请求的后端机器。

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

整个IP哈希选择流程
流程图如下:


轮询策略和IP哈希策略对比
加权轮询策略
优点:适用性更强,不依赖于客户端的任何信息,完全依靠后端服务器的情况来进行选择。能把客户端请求更合理更均匀地分配到各个后端服务器处理。
缺点:同一个客户端的多次请求可能会被分配到不同的后端服务器进行处理,无法满足做会话保持的应用的需求。

IP哈希策略
优点:能较好地把同一个客户端的多次请求分配到同一台服务器处理,避免了加权轮询无法适用会话保持的需求。
缺点:当某个时刻来自某个IP地址的请求特别多,那么将导致某台后端服务器的压力可能非常大,而其他后端服务器却空闲的不均衡情况、


参考资料:
http://blog.dccmx.com/2011/07/nginx-upstream-src-1/
http://itoedr.blog.163.com/blog/static/120284297201341044034750/

参考:http://blog.csdn.net/xiajun07061225/article/details/9334477
分享到:
评论

相关推荐

    Nginx-GUI-For-Windows-x64-v1.6.zip

    4. **负载均衡**:Nginx强大的负载均衡功能可以通过GUI进行配置,如轮询、最少连接、IP哈希等策略。用户可以通过界面轻松分配后端服务器的流量,保证服务的高可用性。 5. **安全与访问控制**:GUI工具通常会提供...

    nginx-sticky-module-ng-1.2.6.tar.gz

    **Nginx Sticky Module Ng 1.2.6 源码分析与应用** Nginx 是一款高性能、轻量级的 HTTP 和反向代理服务器,广泛用于网站部署和负载均衡。在某些应用场景中,例如在线购物车、游戏服务器或者需要保持用户会话一致性...

    nginx负载均衡实现

    ### Nginx负载均衡实现 #### 一、负载均衡概念及必要性 负载均衡是一种用于在网络环境中分散工作负载的技术,通常用于改善网络性能、提高可用性和最大化资源利用。当单台服务器难以应对高流量和并发请求时,负载...

    张宴 使用Nginx轻松实现开源负载均衡

    2. Nginx负载均衡原理: - 轮询(Round Robin):每个请求按时间顺序轮流分配到不同的后端服务器,如果后端服务器出现故障,会自动排除故障服务器,将请求转发到其他正常的服务器。 - 权重(Weighted Round Robin...

    nginx负载均衡中RR和ip_hash策略分析

    ### nginx负载均衡中RR和ip_hash策略分析 #### 一、引言 在现代互联网架构中,随着业务规模的增长和技术的发展,单台服务器往往难以满足大量用户访问的需求。因此,负载均衡技术应运而生,它能够有效地分散访问请求...

    nginx-1.21.6.zip和nginx-1.21.6.tar.gz

    - 负载均衡:通过轮询、权重、IP哈希等方式将请求分发到多个后端服务器,提高系统的可用性和响应速度。 - URL重写:通过配置rewrite规则,可以实现URL的美化和动态链接的静态化。 - SSL/TLS支持:提供HTTPS服务,...

    解析nginx负载均衡

    通过对nginx负载均衡策略的深入解析,我们可以看出nginx不仅具备强大的功能,而且能够根据实际需求灵活配置各种负载均衡策略。无论是内置的加权轮询、IP Hash还是扩展的Fair、通用Hash和一致性Hash策略,都能够有效...

    nginx-cookbook-recipes-high-performance

    3. **负载均衡**:Nginx支持多种负载均衡策略,如轮询、最少连接、IP哈希等,可以根据实际需求选择合适的策略。 4. **缓存机制**:Nginx能作为静态资源的缓存服务器,减少对后端服务器的压力,加快响应速度,提高...

    nginx-1.19.0(反向代理及负载均衡配置).rar

    ### 三、设置Nginx负载均衡 Nginx可以实现基于轮询、权重、IP哈希等多种策略的负载均衡。以下是一个基本的轮询策略配置示例: ```nginx http { upstream backend { server backend1.example.com weight=3; ...

    Nginx负载均衡模式测试附件

    【Nginx负载均衡模式测试附件】主要涵盖了Nginx作为高性能反向代理服务器在实现负载均衡中的关键技术和常见模式。Nginx以其轻量级、高性能的特点,常被用作互联网服务的前端,用于分发请求到后端的多个服务器,以...

    nginx配置优化+负载均衡+动静分离详解

    ### Nginx配置优化、负载均衡与动静分离详解 #### 一、Nginx配置优化 在现代Web应用中,Nginx作为一种高性能的HTTP服务器和反向代理服务器,在提高网站响应速度、处理高并发连接方面起着至关重要的作用。通过对...

    nginx--aarch64.rar

    - 负载均衡策略包括轮询、权重轮询、最少连接数、IP哈希等,可根据实际需求选择合适的方式。 7. **缓存功能**: - Nginx可以作为缓存服务器,预先加载常用资源,减少对后端服务器的压力,提高响应速度。 - 使用`...

    nginx负载均衡加FastDfs

    【标题】:“nginx负载均衡加FastDfs” 在IT领域,服务器负载均衡是提升系统可用性和性能的关键技术,而Nginx与FastDFS的结合应用则是一个常见的解决方案。Nginx是一款高性能的HTTP和反向代理服务器,常用于实现...

    nginx+tomcat实现负载均衡

    Nginx支持多种负载均衡策略,如轮询、最少连接、IP哈希等。例如,轮询策略会将请求均匀分配给所有后端服务器;最少连接策略会将新请求发送给当前连接最少的服务器,以平衡工作负载。 **4. Tomcat调优** Tomcat是...

    Nginx负载均衡集群配置文档

    Nginx支持多种策略,如轮询(默认)、最少连接、IP哈希等。例如,设置为轮询策略: ``` upstream backend { server tomcat1.example.com:8081; server tomcat2.example.com:8082; least_conn; } ``` 五、...

    nginx配置upstream负载均衡的资源文件文件通用版

    **Nginx配置Upstream负载均衡详解** 在现代Web服务架构中,负载均衡是一项至关重要的技术,它能够有效地分散网络流量,确保服务器集群的稳定性和高可用性。Nginx作为一款高性能的反向代理服务器和HTTP缓存服务器,...

    nginx-1.23.0nginx-1.23.0nginx-1.23.0

    3. **负载均衡**:Nginx内置了负载均衡器,可以根据不同的策略(如轮询、最少连接、IP哈希等)将请求分发到多个后端服务器。 4. **静态文件处理**:Nginx对静态文件的处理能力非常强,是其一大优势,能快速响应静态...

    使用Nginx实现负载均衡的策略

    Nginx负载均衡的配置和应用是一个复杂的过程,它不仅涉及到对Nginx本身的理解,还涉及到对后端服务器能力的评估,以及对应用场景的深入分析。在实际部署中,还需要注意保持Nginx配置的正确性和安全性,定期更新和...

    nginx学习资料

    - 负载均衡:通过设置不同的负载均衡策略(如轮询、权重、IP哈希等),Nginx 可以有效地分发流量到多个后端服务器,提高系统整体性能和可用性。 3. **Keepalive 连接** - Keepalive 连接原理:HTTP/1.1 协议支持...

Global site tag (gtag.js) - Google Analytics