• 深入理解ngx_http_upstream_vnswrr_module负载均衡模块


    1. 引言

       之前有讨论了nginx的swrr算法的两个问题,并引出了阿里tengine的vnswrr算法如何来克服swrr的问题。本文通过源码层面对ngx_http_upstream_vnswrr_module模块进行分析,来深入理解vnswrr负载均衡算法。关于swrr算法的思考可以查看《nginx upstream server主动健康检测模块添加https检测功能》。关于vnswrr的算法原理可以参考《阿里七层流量入口负载均衡算法演变之路》

    2. 启用vnswrr负载均衡模块

      配置指令的格式为:

    指令:    vnswrr  [max_init=init_vode_num]
    默认值:  -
    上下文:   upstream
    
    
    • 1
    • 2
    • 3
    • 4

      其中init_vnode_num是初始化虚拟节点的数量,具体可以参考《阿里七层流量入口负载均衡算法演变之路》中**接入层 VNSWRR 算法(V2)**部分的描述。

      以5台rs服务器为例开启vnswrr,距离如下:

    upstream {
        vnswrr 5;
        server 192.168.0.1 weight=1;
        server 192.168.0.2 weight=1;
        server 192.168.0.3 weight=3;
        server 192.168.0.4 weight=3;
        server 192.168.0.5 weight=5;
        server 192.168.0.6 weight=5;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3. 源码剖析

    3.1 配置指令分析

       本模块定义了配置指令vnswrr,代码如下:

    static ngx_command_t  ngx_http_upstream_vnswrr_commands[] = {
    
        { ngx_string("vnswrr"),
          NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
          ngx_http_upstream_vnswrr,
          0,
          0,
          NULL },
    
          ngx_null_command
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

      以上定义了指令分析回调函数ngx_http_upstream_vnswrr, 其源码如下:

    static char *
    ngx_http_upstream_vnswrr(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        ngx_http_upstream_srv_conf_t            *uscf;
        ngx_http_upstream_vnswrr_srv_conf_t     *uvnscf;
        ngx_str_t                               *value;
        ngx_int_t                                max_init;
    
        uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
    
        if (uscf->peer.init_upstream) {
            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,
                               "load balancing method redefined");
        }
    
    	/* 将vnswrr的负载均衡算法配置初始化回调函数挂进去 */
        uscf->peer.init_upstream = ngx_http_upstream_init_vnswrr;
    
    	/* 不象哈希负载均衡算法,本算法可以支持主备服务器 */
        uscf->flags = NGX_HTTP_UPSTREAM_CREATE
                      |NGX_HTTP_UPSTREAM_WEIGHT
                      |NGX_HTTP_UPSTREAM_BACKUP
                      |NGX_HTTP_UPSTREAM_MAX_FAILS
                      |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
    #if defined(nginx_version) && nginx_version >= 1011005
                      |NGX_HTTP_UPSTREAM_MAX_CONNS
    #endif
                      |NGX_HTTP_UPSTREAM_DOWN;
    
    	/* 获取vnswrr的配置上下文 */
        uvnscf = ngx_http_conf_upstream_srv_conf(uscf,
                                    ngx_http_upstream_vnswrr_module);
    
        value = cf->args->elts;
    
        max_init = 0;
    	
    	/* 如果有max_init参数,就从配置指令中解析初始虚拟节点数量 */
        if (cf->args->nelts > 1) {
    
            if (ngx_strncmp(value[1].data, "max_init=", 9) == 0) {
    
                max_init = ngx_atoi(&value[1].data[9], value[1].len - 9);
    
                if (max_init == NGX_ERROR) {
    
                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                       "invalid parameter \"%V\"", &value[1]);
    
                    return NGX_CONF_ERROR;
                }
            }
        }
    
        uvnscf->max_init = max_init;
    
        return NGX_CONF_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

    3.2 负载均衡算法配置初始化

      nginx在解析完配置文件后,会为每个upstream调用前面设置好的init_upstream回调函数来初始化设置好的负载均衡算法,对于开启了vnswrr算法,则会回调ngx_http_upstream_init_vnswrr函数,该回调由3.1节中ngx_http_upstream_vnswrr函数设置。下面来分析一下ngx_http_upstream_init_vnswrr函数:

    static ngx_int_t
    ngx_http_upstream_init_vnswrr(ngx_conf_t *cf,
        ngx_http_upstream_srv_conf_t *us)
    {
        ngx_http_upstream_rr_peers_t           *peers, *backup;
        ngx_http_upstream_vnswrr_srv_conf_t    *uvnscf, *ubvnscf;
        ngx_http_upstream_server_t             *server;
        ngx_uint_t                              i, g, bg, max_init;
    
        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0, "init vnswrr");
    
    	/* 借用round-robin的ngx_http_upstream_init_round_robin初始化peer链表 */
        if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
            return NGX_ERROR;
        }
    
    	/* 对于配置的每个server(包括主和备),计算配置的所有server权重的最大公约数 */
        g = 0;
        bg = 0;
        if (us->servers) {
            server = us->servers->elts;
    
            for (i = 0; i < us->servers->nelts; i++) {
                if (server[i].backup) {
                    bg = ngx_http_upstream_gcd(bg, server[i].weight);
                } else {
                    g = ngx_http_upstream_gcd(g , server[i].weight);
                }
            }
        }
        if (g == 0) {
            g = 1;
        }
        if (bg == 0) {
            bg = 1;
        }
    
        uvnscf = ngx_http_conf_upstream_srv_conf(us,
                                    ngx_http_upstream_vnswrr_module);
        if (uvnscf == NULL) {
            return NGX_ERROR;
        }
    
        peers = (ngx_http_upstream_rr_peers_t *) us->peer.data;
    
        max_init = uvnscf->max_init;
    
    	/* init_number为初始虚拟节点的序号
    	   last_number为最后一次分配的虚拟节点的序号
    	   last_peer为最后一次分配的peer的指针
    	 */
        uvnscf->init_number = NGX_CONF_UNSET_UINT;
        uvnscf->last_number = NGX_CONF_UNSET_UINT;
        uvnscf->last_peer = NULL;
        uvnscf->next = NULL;
        uvnscf->gcd = g;
    
    	/* 如果没有配置max_init,则设置为peer的数量
    	   max_init最大为总的权重
    	 */
        if (!max_init) {
            uvnscf->max_init = peers->number;
    
        } else if (max_init > peers->total_weight) {
            uvnscf->max_init = peers->total_weight;
        }
    
    	/* 设置负载均衡请求上下文初始化回调函数 */
        us->peer.init = ngx_http_upstream_init_vnswrr_peer;
    
    	/* 如果upstream是配置成带权重模式的,即所有服务器的weight不都等于1,则走正常vnswrr
    	   算法,否则,退化为简单的round-robin算法。对于vnswrr,需要分配虚拟节点并进行初始化,
    	   虚拟节点的数量是总权重除以上面算出的最大公约数。稍微思考一下,就知道这个是合理的,
    	   譬如三台server,他们的权重都分别是2,4,6,那么其效果和1,2,3是一样的,
    	   所以找到最大公约数,并把这个最大公约数除掉以后得到有效权重。
    	*/
    	
        if (peers->weighted) {
            uvnscf->vpeers = ngx_pcalloc(cf->pool,
                                        sizeof(ngx_http_upstream_rr_vpeers_t)
                                        * peers->total_weight / uvnscf->gcd);
            if (uvnscf->vpeers == NULL) {
                return NGX_ERROR;
            }
    		/* 初始化一批虚拟节点,最多是max_init个虚拟节点,避免一次性初始化大量的虚拟节点
    		   当值nginx的cpu突发overload
    		*/
            ngx_http_upstream_init_virtual_peers(peers, uvnscf, 0, uvnscf->max_init);
    
        }
    
        /* 下面是backup服务器部分的初始化逻辑,和主服务器是一样的 */
        backup = peers->next;
        if (backup) {
            ubvnscf = ngx_pcalloc(cf->pool,
                                  sizeof(ngx_http_upstream_vnswrr_srv_conf_t));
            if (ubvnscf == NULL) {
                return NGX_ERROR;
            }
    
            ubvnscf->init_number = NGX_CONF_UNSET_UINT;
            ubvnscf->last_number = NGX_CONF_UNSET_UINT;
            ubvnscf->last_peer = NULL;
            ubvnscf->gcd = bg;
            
            ubvnscf->max_init = max_init;
    
            if (!max_init) {
                ubvnscf->max_init = backup->number;
    
            } else if (max_init > backup->total_weight) {
                ubvnscf->max_init = backup->total_weight;
            }
    
    		/* 把主服务器和backup服务器链起来 */
            uvnscf->next = ubvnscf;
    
            if (!backup->weighted) {
                return NGX_OK;
            }
    
            ubvnscf->vpeers = ngx_pcalloc(cf->pool,
                                          sizeof(ngx_http_upstream_rr_vpeers_t)
                                          * backup->total_weight / ubvnscf->gcd);
            if (ubvnscf->vpeers == NULL) {
                return NGX_ERROR;
            }
    
            ngx_http_upstream_init_virtual_peers(backup, ubvnscf, 0, 
                                                 ubvnscf->max_init);
        }
    
        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
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134

      ngx_http_upstream_init_vnswrr函数的逻辑就是分别对主服务器和备服务器组进行加载操作,初始化一部分虚拟节点,详细的逻辑在源码中已经进行了注释,不再赘述。

    3.3 负载均衡请求上下文的初始化

      当nginx接收到http请求需要连接上游服务器的时候,就会发起负载均衡请求上下文的初始化回调,对于vnswrr算法就是回调ngx_http_upstream_init_vnswrr_peer函数了。

    static ngx_int_t
    ngx_http_upstream_init_vnswrr_peer(ngx_http_request_t *r,
        ngx_http_upstream_srv_conf_t *us)
    {
        ngx_http_upstream_vnswrr_srv_conf_t    *uvnscf;
        ngx_http_upstream_vnswrr_peer_data_t   *vnsp;
    
        uvnscf = ngx_http_conf_upstream_srv_conf(us,
                                              ngx_http_upstream_vnswrr_module);
    
    	/* 创建请求上下文并进行初始化设置 */
        vnsp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_vnswrr_peer_data_t));
        if (vnsp == NULL) {
            return NGX_ERROR;
        }
    
        vnsp->uvnscf = uvnscf;
        r->upstream->peer.data = &vnsp->rrp;
    
    	/* 因为本模块是依赖于round-robin模块的,譬如上游服务器的已分配状态等,
    	   这里也需要调用ngx_http_upstream_init_round_robin_peer进行初始化 */
        if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
            return NGX_ERROR;
        }
    
    	/* 设置获取peer的回调 */
        r->upstream->peer.get = ngx_http_upstream_get_vnswrr_peer;
    
        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

      这里最关键的就是设置了获取peer的回调函数ngx_http_upstream_get_vnswrr_peer。

    3.4 获取peer

      一切准备就绪后,nginx会在请求上游连接的时候调用ngx_event_connect_peer,而在ngx_event_connect_peer函数中将回调ngx_http_upstream_get_vnswrr_peer函数来获取目的服务器的地址信息。接下来来详细分析这个函数,源码如下:

    static ngx_int_t
    ngx_http_upstream_get_vnswrr_peer(ngx_peer_connection_t *pc, void *data)
    {
        ngx_http_upstream_vnswrr_peer_data_t  *vnsp = data;
    
        ngx_int_t                              rc;
        ngx_uint_t                             i, n;
        ngx_http_upstream_rr_peer_t           *peer;
        ngx_http_upstream_rr_peers_t          *peers;
        ngx_http_upstream_rr_peer_data_t      *rrp;
    
        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "get vnswrr peer, try: %ui", pc->tries);
    
        pc->cached = 0;
        pc->connection = NULL;
    
        rrp = &vnsp->rrp;
    
        peers = rrp->peers;
        ngx_http_upstream_rr_peers_wlock(peers);   /* 共享内存加写锁 */
    
        if (peers->single) {
    	    /*对于只有一个peer的情况,如果这个peer没有down且连接数没有超过限制,
    	      则直接分配这个peer*/
            peer = peers->peer;
    
            if (peer->down) {
                goto failed;
            }
    
    #if defined(nginx_version) && nginx_version >= 1011005
            if (peer->max_conns && peer->conns >= peer->max_conns) {
                goto failed;
            }
    #endif
    
    #if (NGX_HTTP_UPSTREAM_CHECK)
            if (ngx_http_upstream_check_peer_down(peer->check_index)) {
                goto failed;
            }
    #endif
            rrp->current = peer;
    
        } else {
    
            /* 如果有多个peer,则调用ngx_http_upstream_get_vnswrr获取peer信息 */
    
            peer = ngx_http_upstream_get_vnswrr(vnsp);
    
            if (peer == NULL) {
                goto failed;
            }
    
            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                           "get vnswrr peer, current: %p %i",
                           peer, peer->current_weight);
        }
    
    	/* 将分配到的peer的地址写入到ngx_peer_connection_t(pc_中 */
        pc->sockaddr = peer->sockaddr;
        pc->socklen = peer->socklen;
        pc->name = &peer->name;
    #if (T_NGX_HTTP_DYNAMIC_RESOLVE)
        pc->host = &peer->host;
    #endif    
    
        peer->conns++;
    
    	/* 释放上面加的写锁 */
        ngx_http_upstream_rr_peers_unlock(peers);
    
        return NGX_OK;
    
    failed:
    
    	/* 主服务器分配失败了,如果有备服务器,那么从备服务器进行分配 */
        if (peers->next) {
    
            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers");
    
    		/* 切换到备服务器组 */
            rrp->peers = peers->next;
    
            vnsp->uvnscf = vnsp->uvnscf ? vnsp->uvnscf->next : vnsp->uvnscf;
    
            n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
                    / (8 * sizeof(uintptr_t));
    
            for (i = 0; i < n; i++) {
                rrp->tried[i] = 0;
            }
            
            /* 释放上面加的写锁 */
            ngx_http_upstream_rr_peers_unlock(peers);
    
    		/* 递归调用本函数自己,重新进行一次获取peer的操作 */
            rc = ngx_http_upstream_get_vnswrr_peer(pc, vnsp);
    
    	    /* 备服务器也分配失败,则返回NGX_BUSY */
            if (rc != NGX_BUSY) {
                return rc;
            }
            
    		/* 重新加上写锁,在返回前释放 */
            ngx_http_upstream_rr_peers_wlock(peers);
        }
        
        /* 释放上面加的写锁 */
        ngx_http_upstream_rr_peers_unlock(peers);
    
        pc->name = peers->name;
    
        return NGX_BUSY;
    }
    
    • 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

      本函数针对如果只有一个peer的情况来说,就不需要再进行vnswrr算法了,反过来则进行vnswrr的分配操作,vnswrr算法调用了ngx_http_upstream_get_vnswrr函数进行实际的分配工作。下面就是vnswrr的最核心的代码了,源码如下:

    static ngx_http_upstream_rr_peer_t *
    ngx_http_upstream_get_vnswrr(ngx_http_upstream_vnswrr_peer_data_t  *vnsp)
    {
        time_t                                  now;
        uintptr_t                               m;
        ngx_uint_t                              i, n, p, flag, begin_number;
        ngx_http_upstream_rr_peer_t            *peer, *best;
        ngx_http_upstream_rr_peers_t           *peers;
        ngx_http_upstream_rr_vpeers_t          *vpeers;
        ngx_http_upstream_rr_peer_data_t       *rrp;
        ngx_http_upstream_vnswrr_srv_conf_t    *uvnscf;
    
        now = ngx_time();
    
        best = NULL;
    
    #if (NGX_SUPPRESS_WARN)
        p = 0;
    #endif
    
        rrp = &vnsp->rrp;
        peers = rrp->peers;
        uvnscf = vnsp->uvnscf;
        vpeers = uvnscf->vpeers;
    
    	/* last_number == NGX_CONF_UNSET_UINT
    	   表示本worker进程第一次进入到ngx_http_upstream_get_vnswrr函数,
    	   这里通过将init_number设置为一个随机值来避免多进程产生的“共振”效应。
    	   初始化随机值这个机制在《阿里七层流量入口负载均衡算法演变之路》中有提到
    	 */
        if (uvnscf->last_number == NGX_CONF_UNSET_UINT) {
            uvnscf->init_number = ngx_random() % peers->number;
    		
    		/* 如果是带权重模式,则使用了虚拟节点来进行负载均衡,
    		   所以从虚拟节点中选取peer
    		 */
            if (peers->weighted) {
                peer = vpeers[uvnscf->init_number].vpeer;
    
            } else {
                /* 如果是不带权重的模式,则没有虚拟节点,
                   需要直接在peers列表中循环init_number次数,选择第nit_number个peer
                */
                for (peer = peers->peer, i = 0; i < uvnscf->init_number; i++) {
                    peer = peer->next;
                }
            }
    
            uvnscf->last_number = uvnscf->init_number;
            uvnscf->last_peer = peer;
        }
    
        if (peers->weighted) {
            /* 如果当前初始化好的虚拟节点已经都被分配过一次了,并且还有没初始化过的虚拟节点,
               则再次分配虚拟节点,最多max_init个。 
            */
            if (uvnscf->vnumber != peers->total_weight / uvnscf->gcd
                && (uvnscf->last_number + 1 == uvnscf->vnumber))
            {
                n = peers->total_weight / uvnscf->gcd - uvnscf->vnumber;
                if (n > uvnscf->max_init) {
                    n = uvnscf->max_init;
                }
    
                ngx_http_upstream_init_virtual_peers(peers, uvnscf, uvnscf->vnumber,
                                         n + uvnscf->vnumber);
    
            }
    
            /* 在虚拟节点循环队列中分配下一个vpeer
               begin_numer为当前分配的虚拟节点在虚拟节点循环队列中的序号
             */ 
            begin_number = (uvnscf->last_number + 1) % uvnscf->vnumber;
            peer = vpeers[begin_number].vpeer;
    
        } else {
    		/* 如果是不带权重模式,那么直接通过peer链进行peer的分配
    		   一个peer中有多个地址的,那么先分配这个peer的地址,
    		   否则,找下一个peer,begin_number为当前分配的peer在peer列表中的序号
    		 */
            if (uvnscf->last_peer && uvnscf->last_peer->next) {
                begin_number = (uvnscf->last_number + 1) % peers->number;
                peer = uvnscf->last_peer->next;
    
            } else {
                begin_number = 0;
                peer = peers->peer;
            }
        }
    
    	/* 以下对上面分配的peer进行状态过滤,如果分配的peer不能用,
    	   需要再往下循环获取下一个peer */
    	/* 这里 i != begin_number || flag的判断用来检测是否已经循环了一圈回来了
    	   循环了一圈回来的,那么所有的peer就已经遍历了,还是不能满足分配的需要。
    	 */
        for (i = begin_number, flag = 1; i != begin_number || flag;
             i = peers->weighted
             ? ((i + 1) % uvnscf->vnumber) : ((i + 1) % peers->number),
             peer = peers->weighted
             ? vpeers[i].vpeer : (peer->next ? peer->next : peers->peer))
        {
    
            flag = 0;
            if (peers->weighted) {
    			/* 这里也有可能分配的虚拟节点已经被遍历过一次了,并且还有没初始化过的虚拟节点,
               则再次分配虚拟节点,最多max_init个 */
                n = peers->total_weight / uvnscf->gcd - uvnscf->vnumber;
                if (n > uvnscf->max_init) {
                    n = uvnscf->max_init;
                }
    
                if (n > 0) {
                    ngx_http_upstream_init_virtual_peers(peers, uvnscf, uvnscf->vnumber,
                                            n + uvnscf->vnumber);
                }
    
                n = vpeers[i].rindex / (8 * sizeof(uintptr_t));
                m = (uintptr_t) 1 << vpeers[i].rindex % (8 * sizeof(uintptr_t));
    
            } else {
                n =  i / (8 * sizeof(uintptr_t));
                m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
            }
    
    		/* 节点是否已经分配过的状态判断 */
            if (rrp->tried[n] & m) {
                continue;
            }
    
    		/* 节点是否已经被设置为down状态判断 */
            if (peer->down) {
                continue;
            }
    
    		/* 节点是否故障保护状态判断 */
            if (peer->max_fails
                && peer->fails >= peer->max_fails
                && now - peer->checked <= peer->fail_timeout)
            {
                continue;
            }
    
    		/* 节点的当前在线连接是否超过限制判断 */
    #if defined(nginx_version) && nginx_version >= 1011005
            if (peer->max_conns && peer->conns >= peer->max_conns) {
                continue;
            }
    #endif
    
    #if (NGX_HTTP_UPSTREAM_CHECK)
            if (ngx_http_upstream_check_peer_down(peer->check_index)) {
                continue;
            }
    #endif
    
    		/*  得到了分配好的节点 */
    		best = peer;
            uvnscf->last_peer = peer;
            uvnscf->last_number = i;
            p = i;
            break;
        }
    
        if (best == NULL) {
            return NULL;
        }
    
        rrp->current = best;
    
    	/* 在tried位表中设置当前节点已经被分配过 */
        if (peers->weighted) {
            n = vpeers[p].rindex / (8 * sizeof(uintptr_t));
            m = (uintptr_t) 1 << vpeers[p].rindex % (8 * sizeof(uintptr_t));
    
        } else {
            n = p / (8 * sizeof(uintptr_t));
            m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
        }
    
        rrp->tried[n] |= m;
    
        if (now - best->checked > best->fail_timeout) {
            best->checked = now;
        }
    
        return best;
    }
    
    • 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
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187

      以上函数中,如果是不带权重的模式,那么就是最简单的round-robin分配机制,每次分配就循环往后前进一个peer,一个特别的地方就是第一次分配的时候设置了一个随机值位置,从这个随机位置开始进行正式分配,避免产生“共振”;如果是带权重的模式,那么才是真正的vnswrr算法,这个算法另外创建了虚拟节点,虚拟节点的总数量是总权重/各服务器权重的最大公约数,为了避免一次性集中分配虚拟节点导致CPU压力突发,所以每次最多分配max_init个数的虚拟节点。
      这是这些逻辑交织在一起,看上去ngx_http_upstream_get_vnswrr函数似乎有些复杂了。
      和不带权重的模式一样,它也会在第一次分配的时候设置一个随机值位置,从随机的虚拟节点开始分配,避免“共振”现象的发生。

      除了以上特别说明的部分,其他逻辑几乎就是round-robin代码的翻版,还是非常好理解的,本文列出的源码中也给出了注释,就不再赘述了。

  • 相关阅读:
    【uniApp新模式: 使用Vue3 + Vite4 + Pinia + Axios技术栈构建】
    apollo分布式配置优先级学习
    【Qt】QPalette
    教程七 在Go中使用Energy创建跨平台GUI - Cookies
    Unity图形节点插件xNode简单使用说明
    【C# 基础精讲】List 集合的使用
    Charles 乱码解决办法
    SpringMVC+Vue实现前后端的农业信息管理系统
    AttributeError: Can only use .dt accessor with datetimelike values
    ESP8266--SDK开发(延时、定时器)
  • 原文地址:https://blog.csdn.net/bluestn/article/details/136687518