[转帖]nginx的ip_hash算法

nginx,ip,hash,算法 · 浏览次数 : 0

小编点评

**算法核心处理代码:** ```nginx for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) { hash = (hash * 113 + iphp->addr[i]) % 6271; } ``` **算法步骤:** 1. 遍历每个 IP 地址的第一个字节。 2. 计算每个字节的权重(假设为 113)。 3. 累加所有权重的值。 4. 根据权重找到对应服务器的索引。 5. 从服务器中获取连接数。 6. 如果连接数超过最大连接数限制,释放连接。 7. 如果超过最大连接数,尝试使用其他服务器。 8. 如果所有服务器都不可用,释放所有连接。 9. 更新服务器状态,记录连接数。 10. 返回连接状态。

正文

概念

根据用户请求的ip,利用算法映射成hash值,分配到特定的tomcat服务器中。主要是为了实现负载均衡,只要用户ip固定,则hash值固定,特定用户只能访问特定服务器,解决了session的问题。

源码分析

ip_hash算法的处理代码位于src\http\modules\ngx_http_upstream_ip_hash_module.c。主要的处理代码如下:

// 最大失败次数、超时时间、最大连接数等相关配置
#define NGX_HTTP_UPSTREAM_CREATE        0x0001
#define NGX_HTTP_UPSTREAM_WEIGHT        0x0002
#define NGX_HTTP_UPSTREAM_MAX_FAILS     0x0004
#define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT  0x0008
#define NGX_HTTP_UPSTREAM_DOWN          0x0010
#define NGX_HTTP_UPSTREAM_BACKUP        0x0020
#define NGX_HTTP_UPSTREAM_MAX_CONNS     0x0100

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-&gt;log, 0,
               "get ip hash peer, try: %ui", pc-&gt;tries);

/* TODO: cached */

ngx_http_upstream_rr_peers_rlock(iphp-&gt;rrp.peers);

if (iphp-&gt;tries &gt; 20 || iphp-&gt;rrp.peers-&gt;single) {
    ngx_http_upstream_rr_peers_unlock(iphp-&gt;rrp.peers);
    return iphp-&gt;get_rr_peer(pc, &amp;iphp-&gt;rrp);
}

now = ngx_time();

pc-&gt;cached = 0;
pc-&gt;connection = NULL;

// 原始哈希值
hash = iphp-&gt;hash;

for ( ;; ) {

    // 计算ip hash
    for (i = 0; i &lt; (ngx_uint_t) iphp-&gt;addrlen; i++) {
        hash = (hash * 113 + iphp-&gt;addr[i]) % 6271;
    }

    // 根据hash和权重找到对应服务器
    w = hash % iphp-&gt;rrp.peers-&gt;total_weight;
    peer = iphp-&gt;rrp.peers-&gt;peer;
    p = 0;

    while (w &gt;= peer-&gt;weight) {
        w -= peer-&gt;weight;
        peer = peer-&gt;next;
        p++;
    }

    n = p / (8 * sizeof(uintptr_t));
    m = (uintptr_t) 1 &lt;&lt; p % (8 * sizeof(uintptr_t));

    if (iphp-&gt;rrp.tried[n] &amp; m) {
        goto next;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc-&gt;log, 0,
                   "get ip hash peer, hash: %ui %04XL", p, (uint64_t) m);

    ngx_http_upstream_rr_peer_lock(iphp-&gt;rrp.peers, peer);

    // 如果服务器不可用,不做后续处理
    if (peer-&gt;down) {
        ngx_http_upstream_rr_peer_unlock(iphp-&gt;rrp.peers, peer);
        goto next;
    }

    // 如果超过最大失败次数或者超时,不做后续处理
    if (peer-&gt;max_fails
        &amp;&amp; peer-&gt;fails &gt;= peer-&gt;max_fails
        &amp;&amp; now - peer-&gt;checked &lt;= peer-&gt;fail_timeout)
    {
        ngx_http_upstream_rr_peer_unlock(iphp-&gt;rrp.peers, peer);
        goto next;
    }

    // 如果超过最大连接数,不做后续处理
    if (peer-&gt;max_conns &amp;&amp; peer-&gt;conns &gt;= peer-&gt;max_conns) {
        ngx_http_upstream_rr_peer_unlock(iphp-&gt;rrp.peers, peer);
        goto next;
    }

    break;

next:

    if (++iphp-&gt;tries &gt; 20) {
        ngx_http_upstream_rr_peers_unlock(iphp-&gt;rrp.peers);
        return iphp-&gt;get_rr_peer(pc, &amp;iphp-&gt;rrp);
    }
}

iphp-&gt;rrp.current = peer;

pc-&gt;sockaddr = peer-&gt;sockaddr;
pc-&gt;socklen = peer-&gt;socklen;
pc-&gt;name = &amp;peer-&gt;name;

// 连接数自增
peer-&gt;conns++;

if (now - peer-&gt;checked &gt; peer-&gt;fail_timeout) {
    peer-&gt;checked = now;
}

ngx_http_upstream_rr_peer_unlock(iphp-&gt;rrp.peers, peer);
ngx_http_upstream_rr_peers_unlock(iphp-&gt;rrp.peers);

iphp-&gt;rrp.tried[n] |= m;
iphp-&gt;hash = hash;

return NGX_OK;

}

    算法的核心处理代码如下:

    for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) {
        hash = (hash * 113 + iphp->addr[i]) % 6271;
    }
    
    • 1
    • 2
    • 3

    通过查看源码得知,ipv4的情况下iphp->addrlen的值固定为3,源代码如下:

    switch (r->connection->sockaddr->sa_family) {
    
    case AF_INET:   // ipv4
        sin = (struct sockaddr_in *) r-&gt;connection-&gt;sockaddr;
        iphp-&gt;addr = (u_char *) &amp;sin-&gt;sin_addr.s_addr;
        iphp-&gt;addrlen = 3;  
        break;
    

    if (NGX_HAVE_INET6)

    case AF_INET6:  // ipv6
        sin6 = (struct sockaddr_in6 *) r-&gt;connection-&gt;sockaddr;
        iphp-&gt;addr = (u_char *) &amp;sin6-&gt;sin6_addr.s6_addr;
        iphp-&gt;addrlen = 16; 
        break;
    

    endif

    default:
        iphp-&gt;addr = ngx_http_upstream_ip_hash_pseudo_addr;
        iphp-&gt;addrlen = 3;
    }
    

      假如我们传递的ip为192.168.41.33,hash值得计算流程可以拆分为:

      hash0 = iphp->hash;
      

      iphp->addr[0]--->192

      hash1 = (hash0 * 113 + iphp->addr[0]) % 6271;

      iphp->addr[1]--->168

      hash2 = (hash1 * 113 + iphp->addr[1]) % 6271;

      iphp->addr[2]--->41

      hash3 = (hash2 * 113 + iphp->addr[2]) % 6271;

        经分析得知,ip_hash实际是取ip地址得前三个字段计算得来,那么譬如192.168.41.xxx同一网段的ip访问的是同一个tomacat。

        根据hash和权重找到对应的后端,源代码如下:

        w = hash % iphp->rrp.peers->total_weight;
        peer = iphp->rrp.peers->peer;
        p = 0;
        

        while (w >= peer->weight) {
        w -= peer->weight;
        peer = peer->next;
        p++;
        }

        // ...

          需要注意的一点是,当一个正在以ip_hash方式管理的服务器在实际操作环境中被移除时,在nginx.conf中不能直接把对应的配置删除,在后面加down即可,因为删除后需要重新计算hash,处理这一过程会更加消耗资源。nginx.conf配置文件修改如下:

          # 以ip_hash的方式实现负载均衡, 添加ip_hash即可
          upstream dongserver {
              ip_hash;      
              server 192.168.41.33:8080;
              server 192.168.41.34:8081;
              server 192.168.41.35:8082 down;  # 服务器不可用
          }
          

            nginx源码中对此的处理如下:

            # upstream块结构体
            struct ngx_http_upstream_rr_peer_s {
                //...
            
            ngx_uint_t                      down;    // 服务器是否可用
            
            //...
            

            };

            当判断到down标志被竖起来之后,不做后续处理

            if (peer->down) {
            ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
            goto next;
            }

              文章知识点与官方知识档案匹配,可进一步学习相关知识
              算法技能树首页概览33264 人正在系统学习中

              与[转帖]nginx的ip_hash算法相似的内容:

              [转帖]nginx的ip_hash算法

              概念 根据用户请求的ip,利用算法映射成hash值,分配到特定的tomcat服务器中。主要是为了实现负载均衡,只要用户ip固定,则hash值固定,特定用户只能访问特定服务器,解决了session的问题。 源码分析 ip_hash算法的处理代码位于src\http\modules\ngx_http_u

              [转帖]Nginx负载均衡之ip_hash

              原理: 通过哈希值和ip进行运算,得出一个哈希字符串,一个值。分发的时候进行判断请求之前是否和哈希绑定过。有的话则优先分配 匹配到对应哈希值的服务器上。 什么是ip_hash? ip_hash是根据用户请求过来的ip,然后映射成hash值,然后分配到一个特定的服务器里面;使用ip_hash这种负载均

              [转帖]Nginx之ngx_http_realip_module

              https://www.jianshu.com/p/80a779b3bf20 问题描述 今日在线上查询nginx日志文件的用户真实IP时,发现remote_addr和XFF地址一模一样,这点让我很是不理解,正常来讲remote_addr应该获取到的是上一个节点转发的IP地址,我们却是获得了用户的真实

              [转帖]@nginx多server及使用优化(php)

              文章目录​ ​一、nginx多server优先级​​​ ​二、禁止IP访问页面​​​ ​三、nginx的包含include​​​ ​四、nginx 路径的alias和root​​​ ​1.配置​​​ ​2.总结​​​ ​五、nginx的try_files​​​ ​1.配置try_files​​​ ​

              [转帖]浅析Nginx配置获取客户端真实IP的proxy_set_header、X-Real-IP、$remote_addr、X-Forwarded-For、$proxy_add_x_forwarded_for分别是什么意思

              https://www.cnblogs.com/goloving/p/15588668.html 一、问题背景 在实际应用中,我们可能需要获取用户的ip地址,比如做异地登陆的判断,或者统计ip访问次数等,通常情况下我们使用 request.getRemoteAddr() 就可以获取到客户端ip,但是

              [转帖]Nginx代理获取后端用户真实IP

              https://www.cnblogs.com/paul8339/p/15740137.html nginx代理后想获取用户的真实IP, 1.在http 模块内增加map模块参数: map $http_x_forwarded_for $clientRealIp { "" $remote_addr;

              [转帖]浅析nginx的server及server_name的意义详解

              https://www.cnblogs.com/goloving/p/7010713.html 一、server_name 详解 当Nginx接到请求后,会匹配其配置中的server模块。匹配方法就是靠请求携带的host和port正好对应其配置中的server_name 和listen。如果做过ip

              [转帖]Nginx四层负载均衡详解

              https://developer.aliyun.com/article/885599?spm=a2c6h.24874632.expert-profile.315.7c46cfe9h5DxWK 2022-04-14 322举报 简介: Nginx四层负载均衡就是实现通过访问某个ip的端口转发至对应的

              [转帖]Nginx 反向代理解决跨域问题

              https://juejin.cn/post/6995374680114741279 编写代码两分钟,解决跨域两小时,我吐了。 如果对跨域还不了解的朋友,可以看这篇:【基础】HTTP、TCP/IP 协议的原理及应用 最近一段时间,在搞一个 SDK 的项目,使用的 TS + rollup。rollup

              [转帖]Nginx禁止ip访问或非法域名访问

              https://www.jb51.net/article/243661.htm 这篇文章主要介绍了Nginx禁止ip访问或非法域名访问,需要的朋友可以参考下 在生产环境中,为了网站的安全访问,需要Nginx禁止一些非法访问,如恶意域名解析,直接使用IP访问网站。下面记录一些常用的配置示例: 1)禁止