• nginx的ip_hash算法


    概念

    根据用户请求的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->log, 0,
                       "get ip hash peer, try: %ui", pc->tries);
    
        /* TODO: cached */
    
        ngx_http_upstream_rr_peers_rlock(iphp->rrp.peers);
    
        if (iphp->tries > 20 || iphp->rrp.peers->single) {
            ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
            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 < (ngx_uint_t) iphp->addrlen; i++) {
                hash = (hash * 113 + iphp->addr[i]) % 6271;
            }
    
            // 根据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++;
            }
    
            n = p / (8 * sizeof(uintptr_t));
            m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
    
            if (iphp->rrp.tried[n] & m) {
                goto next;
            }
    
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                           "get ip hash peer, hash: %ui %04XL", p, (uint64_t) m);
    
            ngx_http_upstream_rr_peer_lock(iphp->rrp.peers, peer);
    
            // 如果服务器不可用,不做后续处理
            if (peer->down) {
                ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
                goto next;
            }
    
            // 如果超过最大失败次数或者超时,不做后续处理
            if (peer->max_fails
                && peer->fails >= peer->max_fails
                && now - peer->checked <= peer->fail_timeout)
            {
                ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
                goto next;
            }
    
            // 如果超过最大连接数,不做后续处理
            if (peer->max_conns && peer->conns >= peer->max_conns) {
                ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
                goto next;
            }
    
            break;
    
        next:
    
            if (++iphp->tries > 20) {
                ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
                return iphp->get_rr_peer(pc, &iphp->rrp);
            }
        }
    
        iphp->rrp.current = peer;
    
        pc->sockaddr = peer->sockaddr;
        pc->socklen = peer->socklen;
        pc->name = &peer->name;
    
        // 连接数自增
        peer->conns++;
    
        if (now - peer->checked > peer->fail_timeout) {
            peer->checked = now;
        }
    
        ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
        ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
    
        iphp->rrp.tried[n] |= m;
        iphp->hash = hash;
    
        return NGX_OK;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122

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

    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->connection->sockaddr;
            iphp->addr = (u_char *) &sin->sin_addr.s_addr;
            iphp->addrlen = 3;  
            break;
    
    #if (NGX_HAVE_INET6)
        case AF_INET6:  // ipv6
            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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    假如我们传递的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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    经分析得知,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++;
    }
    
    // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    需要注意的一点是,当一个正在以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;  # 服务器不可用
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
  • 相关阅读:
    好高的佣金,《新程序员》合伙人计划来袭,人人皆可参与
    解析idea中的debug调试模式
    05 创建不可修改的Lists, Sets 和 Maps
    JWT安全问题
    关于Mysql中的索引与事务
    CRMEB多端多语言系统文件上传0Day代审历程
    ESP32 分区表修改导致重启的问题
    postman token 请求头添加
    自动镜像打包&&互联网企业规范化上线流程 —— k8s从入门到高并发系列教程 (六)
    第二章:基于分解的求水仙花数,基于组合的求水仙花数, 兰德尔数,求[x,y]内的守形数,探求n位守形数,递推探索n位逐位整除数
  • 原文地址:https://blog.csdn.net/www_dong/article/details/126897653