【Nginx】负载均衡-加权轮询策略剖析

转自:江南烟雨

本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别。

如果Nginx是以反向代理的形式配置运行,那么对请求的实际处理需要转发到后端服务器运行,如果后端服务器有多台,如何选择一台合适的后端服务器来处理当前请求,就是本文要说的负载均衡。这两种均衡互不冲突并且能同时生效。

nginx不单可以作为强大的web服务器,也可以作为一个反向代理服务器,而且nginx还可以按照调度规则实现动态、静态页面的分离,可以按照轮询、ip哈希、URL哈希、权重等多种方式对后端服务器做负载均衡,同时还支持后端服务器的健康检查。

负载均衡模块简介

负载均衡模块Load-balance是辅助模块,主要为Upstream模块服务,目标明确且单一:如何从多台后端服务器中选择出一台合适的服务器来处理当前请求

nginx负载均衡模块ngx_http_upstream_module允许定义一组服务器,这组服务器可以被proxy_pass,fastcgi_pass和memcached_pass这些指令引用。

配置例子:

upstream backend {
    server backend1.example.com       weight=5;
    server backend2.example.com:8080;
    server unix:/tmp/backend3;

    server backup1.example.com:8080   backup;
    server backup2.example.com:8080   backup;
}

server {
    location / {
        proxy_pass http://backend;
    }
}

负载均衡策略

nginx的负载均衡策略可以划分为两大类:内置策略和扩展策略。内置策略包含加权轮询和ip hash,在默认情况下这两种策略会编译进nginx内核,只需在nginx配置中指明参数即可。扩展策略有很多,如fair、通用hash、consistent hash等,默认不编译进nginx内核,是第三方模块。

nginx 的 upstream目前支持 4 种方式的分配 :

1)轮询(默认)

每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。

2)weight

指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。

2)ip_hash

每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。

3)fair(第三方)

按后端服务器的响应时间来分配请求,响应时间短的优先分配。

4)url_hash(第三方)

Nginx 默认采用round_robin加权算法。如果要选择其他的负载均衡算法,必须在upstream的配置上下文中通过配置指令明确指定(该配置项最好放在其他server指令等的前面,以便检查server的配置选项是否合理)。比如采用Ip_hash的upstream配置如下所示:

upstream load_balance{
    ip_hash;
    server localhost:8001;
    server localhost:8002;
}

当 整个http配置块被Nginx解析完毕之后,会调用各个http模块对应的初始函数。对于模块ngx_http_upstream_module而言, 对应的main配置初始函数是ngx_http_upstream_init_main_conf(),在这个函数中有这样一段代码:

for (i = 0; i < umcf->upstreams.nelts; i++) {

        init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:
                                            ngx_http_upstream_init_round_robin;

        if (init(cf, uscfp[i]) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
}

默 认采用加权轮询策略的原因就是在于上述代码中的init赋值一行。如果用户没有做任何策略选择,那么执行的策略初始函数为 ngx_http_upstream_init_round_robin,也就是加权轮询策略。否则的话执行的是 uscfp[i]->peer.init_upstream指针函数,如果有配置执行ip_hash ,那么就是ngx_http_upstream_init_ip_hash()。

全局准备工作

//函数:初始化服务器负载均衡表
//参数:
//us:ngx_http_upstream_main_conf_t结构体中upstreams数组元素
ngx_int_t
ngx_http_upstream_init_round_robin(ngx_conf_t *cf,
    ngx_http_upstream_srv_conf_t *us)
{
    ngx_url_t                      u;
    ngx_uint_t                     i, j, n, w;
    ngx_http_upstream_server_t    *server;
    ngx_http_upstream_rr_peers_t  *peers, *backup;

    //回调指针设置
    us->peer.init = ngx_http_upstream_init_round_robin_peer;

    //服务器数组指针不为空
    if (us->servers) {
        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;
        }

        if (n == 0) {
            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                          "no servers in upstream \"%V\" in %s:%ui",
                          &us->host, us->file_name, us->line);
            return NGX_ERROR;
        }

    //为非后备服务器分配空间
        peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
                              + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));
        if (peers == NULL) {
            return NGX_ERROR;
        }

    //非后备服务器列表头中各属性设置
        peers->single = (n == 1);
        peers->number = n;
        peers->weighted = (w != n);
        peers->total_weight = w;
        peers->name = &us->host;

        n = 0;

    //后备服务器列表中各服务器项设置
        for (i = 0; i < us->servers->nelts; i++) {
            for (j = 0; j < server[i].naddrs; j++) {
                if (server[i].backup) {
                    continue;
                }

                peers->peer[n].sockaddr = server[i].addrs[j].sockaddr;
                peers->peer[n].socklen = server[i].addrs[j].socklen;
                peers->peer[n].name = server[i].addrs[j].name;
                peers->peer[n].max_fails = server[i].max_fails;
                peers->peer[n].fail_timeout = server[i].fail_timeout;
                peers->peer[n].down = server[i].down;
                peers->peer[n].weight = server[i].weight;
                peers->peer[n].effective_weight = server[i].weight;
                peers->peer[n].current_weight = 0;
                n++;
            }
        }

    //非后备服务器列表挂载的位置
        us->peer.data = peers;

        /* backup servers */
    //后备服务器
        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;
        }

        if (n == 0) {
            return NGX_OK;
        }

    //后备服务器列表地址空间分配
        backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
                              + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));
        if (backup == NULL) {
            return NGX_ERROR;
        }

        peers->single = 0;
    //后备服务器列表头中各属性设置
        backup->single = 0;
        backup->number = n;
        backup->weighted = (w != n);
        backup->total_weight = w;
        backup->name = &us->host;

        n = 0;

    //后备服务器列表中各服务器项设置
        for (i = 0; i < us->servers->nelts; i++) {
            for (j = 0; j < server[i].naddrs; j++) {
                if (!server[i].backup) {
                    continue;
                }

                backup->peer[n].sockaddr = server[i].addrs[j].sockaddr;
                backup->peer[n].socklen = server[i].addrs[j].socklen;
                backup->peer[n].name = server[i].addrs[j].name;
                backup->peer[n].weight = server[i].weight;
                backup->peer[n].effective_weight = server[i].weight;
                backup->peer[n].current_weight = 0;
                backup->peer[n].max_fails = server[i].max_fails;
                backup->peer[n].fail_timeout = server[i].fail_timeout;
                backup->peer[n].down = server[i].down;
                n++;
            }
        }

    //后备服务器挂载
        peers->next = backup;

        return NGX_OK;
    }

    //us参数中服务器指针为空,例如用户直接在proxy_pass等指令后配置后端服务器地址
    /* an upstream implicitly defined by proxy_pass, etc. */

    if (us->port == 0) {
        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                      "no port in upstream \"%V\" in %s:%ui",
                      &us->host, us->file_name, us->line);
        return NGX_ERROR;
    }

    ngx_memzero(&u, sizeof(ngx_url_t));

    u.host = us->host;
    u.port = us->port;

    //IP地址解析
    if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {
        if (u.err) {
            ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                          "%s in upstream \"%V\" in %s:%ui",
                          u.err, &us->host, us->file_name, us->line);
        }

        return NGX_ERROR;
    }

    n = u.naddrs;

    peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
                              + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));
    if (peers == NULL) {
        return NGX_ERROR;
    }

    peers->single = (n == 1);
    peers->number = n;
    peers->weighted = 0;
    peers->total_weight = n;
    peers->name = &us->host;

    for (i = 0; i < u.naddrs; i++) {
        peers->peer[i].sockaddr = u.addrs[i].sockaddr;
        peers->peer[i].socklen = u.addrs[i].socklen;
        peers->peer[i].name = u.addrs[i].name;
        peers->peer[i].weight = 1;
        peers->peer[i].effective_weight = 1;
        peers->peer[i].current_weight = 0;
        peers->peer[i].max_fails = 1;
        peers->peer[i].fail_timeout = 10;
    }

    us->peer.data = peers;

    /* implicitly defined upstream has no backup servers */

    return NGX_OK;
}

针对一个客户端请求的初始化工作

全 局初始化完成之后,当一个客户端请求过来时,Nginx就要选择合适的后端服务器来处理该请求。在正式开始选择前,Nginx还要单独为本轮选择做一些初 始化(针对一个客户端请求,nginx会进行多次尝试选择,尝试全部失败后才返回502错误,所以注意一轮选择与一次选择的区别)。

下面看看函数ngx_http_upstream_init_round_robin_peer()完成了哪些工作。

它除了完成初始化工作之外,另外的核心工作是设置回调指针。

//函数:
//功能:针对每个请求选择后端服务器前做一些初始化工作
ngx_int_t
ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r,
    ngx_http_upstream_srv_conf_t *us)
{
    ngx_uint_t                         n;
    ngx_http_upstream_rr_peer_data_t  *rrp;

    rrp = r->upstream->peer.data;

    if (rrp == NULL) {
        rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t));
        if (rrp == NULL) {
            return NGX_ERROR;
        }

        r->upstream->peer.data = rrp;
    }

    rrp->peers = us->peer.data;
    rrp->current = 0;

    //n取值为:非后备服务器和后备服务器列表中个数较大的那个值
    n = rrp->peers->number;

    if (rrp->peers->next && rrp->peers->next->number > n) {
        n = rrp->peers->next->number;
    }

    //如果n小于一个指针变量所能表示的范围
    if (n <= 8 * sizeof(uintptr_t)) {
    //直接使用已有的指针类型的data变量做位图(tried是位图,用来标识在一轮选择中,各个后端服务器是否已经被选择过)
        rrp->tried = &rrp->data;
        rrp->data = 0;

    } else {
    //否则从内存池中申请空间
        n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t));

        rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t));
        if (rrp->tried == NULL) {
            return NGX_ERROR;
        }
    }

    //回调函数设置
    r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;
    r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;
    r->upstream->peer.tries = rrp->peers->number;
#if (NGX_HTTP_SSL)
    r->upstream->peer.set_session =
                               ngx_http_upstream_set_round_robin_peer_session;
    r->upstream->peer.save_session =
                               ngx_http_upstream_save_round_robin_peer_session;
#endif

    return NGX_OK;
}

对后端服务器进行一次选择

对后端服务器做一次选择的逻辑在函数ngx_http_upstream_get_round_robin_peer内,流程图如下:

代码如下:

//函数:
//功能:对后端服务器做一次选择
ngx_int_t
ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)
{
    ngx_http_upstream_rr_peer_data_t  *rrp = 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_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                   "get rr peer, try: %ui", pc->tries);

    /* ngx_lock_mutex(rrp->peers->mutex); */

    pc->cached = 0;
    pc->connection = NULL;

    //如果只有一台后端服务器,Nginx直接选择并返回
    if (rrp->peers->single) {
        peer = &rrp->peers->peer[0];

        if (peer->down) {
            goto failed;
        }

    } else {
    //有多台后端服务器
        /* there are several peers */
    //按照各台服务器的当前权值进行选择
        peer = ngx_http_upstream_get_peer(rrp);

        if (peer == NULL) {
            goto failed;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                       "get rr peer, current: %ui %i",
                       rrp->current, peer->current_weight);
    }

    //设置连接的相关属性
    pc->sockaddr = peer->sockaddr;
    pc->socklen = peer->socklen;
    pc->name = &peer->name;

    /* ngx_unlock_mutex(rrp->peers->mutex); */

    if (pc->tries == 1 && rrp->peers->next) {
        pc->tries += rrp->peers->next->number;
    }

    return NGX_OK;

    //选择失败,转向后备服务器
failed:

    peers = rrp->peers;

    if (peers->next) {

        /* ngx_unlock_mutex(peers->mutex); */

        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers");

        rrp->peers = peers->next;
        pc->tries = rrp->peers->number;

        n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))
                / (8 * sizeof(uintptr_t));

        for (i = 0; i < n; i++) {
             rrp->tried[i] = 0;
        }

        rc = ngx_http_upstream_get_round_robin_peer(pc, rrp);

        if (rc != NGX_BUSY) {
            return rc;
        }

        /* ngx_lock_mutex(peers->mutex); */
    }

    /* all peers failed, mark them as live for quick recovery */

    for (i = 0; i < peers->number; i++) {
        peers->peer[i].fails = 0;
    }

    /* ngx_unlock_mutex(peers->mutex); */

    pc->name = peers->name;

    //如果后备服务器也选择失败,则返回NGX_BUSY
    return NGX_BUSY;
}

后端服务器权值计算在函数ngx_http_upstream_get_peer中:

//按照当前各服务器权值进行选择
static ngx_http_upstream_rr_peer_t *
ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp)
{
    time_t                        now;
    uintptr_t                     m;
    ngx_int_t                     total;
    ngx_uint_t                    i, n;
    ngx_http_upstream_rr_peer_t  *peer, *best;

    now = ngx_time();

    best = NULL;
    total = 0;

    for (i = 0; i < rrp->peers->number; i++) {
    //计算当前服务器的标记位在位图中的位置
        n = i / (8 * sizeof(uintptr_t));
        m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));

    //已经选择过,跳过
        if (rrp->tried[n] & m) {
            continue;
        }

    //当前服务器对象
        peer = &rrp->peers->peer[i];

    //当前服务器已宕机,排除
        if (peer->down) {
            continue;
        }

    //根据指定一段时间内最大失败次数做判断
        if (peer->max_fails
            && peer->fails >= peer->max_fails
            && now - peer->checked <= peer->fail_timeout)
        {
            continue;
        }

        peer->current_weight += peer->effective_weight;
        total += peer->effective_weight;

        if (peer->effective_weight < peer->weight) {
            peer->effective_weight++;
        }

        if (best == NULL || peer->current_weight > best->current_weight) {
            best = peer;
        }
    }

    if (best == NULL) {
        return NULL;
    }

    //所选择的服务器在服务器列表中的位置
    i = best - &rrp->peers->peer[0];

    rrp->current = i;

    n = i / (8 * sizeof(uintptr_t));
    m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));

    //位图相应位置置位
    rrp->tried[n] |= m;

    best->current_weight -= total;
    best->checked = now;

    return best;
}

整个加权轮询的流程图如下:

【Nginx】负载均衡-加权轮询策略剖析,布布扣,bubuko.com

时间: 2024-12-28 21:18:37

【Nginx】负载均衡-加权轮询策略剖析的相关文章

Nginx 负载均衡-加权轮询策略剖析

本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别(Nginx根据每个工作进程的当前压力调整它们获取监听套接口的几率,那些当前比较空闲的工作进程有更多机会获取到监听套接口,从而客户端的请求到达后也就相应地被它捕获并处理).如果Nginx是以反向代理的形式配置运行,那么对请求的实际处理需要转发到后端服务器运行,如果后端服务器有多台,如何选择一台合适的后端服务器来处理当前请求,就是本文要说的负载均衡.这两种均衡互不冲突并且能同时生效. nginx不

【Nginx】负载均衡-IP哈希策略剖析

转自:江南烟雨 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)

应用负载均衡(轮询&&一致性哈希)

今天看书看到了应用负载均衡的轮询与一致性哈希,其中说到,一致性哈希可以根据应用请求的URL或者URL参数将系统的请求转发到同一节点,而轮询是将请求均匀地转发到每个服务器. 1.轮询: 优点:到后端应用的请求更加均匀,使得每个服务器基本均衡 缺点:随着后端应用服务器的增加,缓存的命中率为下降,这种方式不会因为热点问题导致其中某一台 服务器负载过重 2.一致性哈希 优点:相同的请求会转发到同一台服务器,命中率不会因为增加服务器而下降 缺点:因为相同的请求会转发到同一服务器,可能会导致某台服务器负载过

Nginx负载均衡和防爬虫策略

Nginx负载均衡和防爬虫策略 vim langba888.confupstream langba888 {#每60s进行一次健康检测,检测2次失败,则进行剔除server 192.168.137.52:888 max_fails=2 fail_timeout=60s;server 192.168.137.48:888 max_fails=2 fail_timeout=60s;}server {listen 9999;#防止爬虫策略if ($http_user_agent ~* "WordPre

nginx反向代理负载均衡(轮询权重IPHash)

1 Nginx反向代理 1.1 实现图片回显 1.1.1 反向代理 说明:当用户发出image.jt.com/2018/1.jpg时,首先将请求发往Nginx.经过Nginx内部配置自动进行路径的切换.E:jt-upload/2018/1.jpg并且访问切换后的路径.将返回的结果再次返回给浏览器.这时浏览器可以进行正常的展现 1.1.2 Nginx介绍 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.其特点是

nginx负载均衡的5种策略

nginx可以根据客户端IP进行负载均衡,在upstream里设置ip_hash,就可以针对同一个C类地址段中的客户端选择同一个后端服务器,除非那个后端服务器宕了才会换一个. nginx的upstream目前支持的5种方式的分配 1.轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. upstream backserver { server 192.168.0.14; server 192.168.0.15; } 2.指定权重指定轮询几率,weigh

nginx负载均衡的5种策略(转载)

nginx可以根据客户端IP进行负载均衡,在upstream里设置ip_hash,就可以针对同一个C类地址段中的客户端选择同一个后端服务器,除非那个后端服务器宕了才会换一个. nginx的upstream目前支持的5种方式的分配 1.轮询(默认)每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除. upstream backserver { server 192.168.0.14; server 192.168.0.15; } 2.指定权重指定轮询几率,weigh

Nginx 负载均衡学习

Nginx-负载均衡 学习资料 Nginx的负载均衡 - 加权轮询 (Weighted Round Robin) 上篇,这个介绍了upstream资源池的调度算法之一,在其专栏还有其他介绍. 平滑的基于权重的轮询算法 :本文也是介绍资源池调度算法的 理解nginx的配置:介绍的比较全面的一篇文章 Nginx实战(五) 反向代理 Understanding Nginx HTTP Proxying, Load Balancing, Buffering, and Caching 13 Nginx Lo

Dubbo加权轮询负载均衡的源码和Bug,了解一下?

本文是对于Dubbo负载均衡策略之一的加权随机算法的详细分析.从2.6.4版本聊起,该版本在某些情况下存在着比较严重的性能问题.由问题入手,层层深入,了解该算法在Dubbo中的演变过程,读懂它的前世今生. 之前也写了Dubbo的负载均衡策略: <一文讲透Dubbo负载均衡之最小活跃数算法> <Dubbo一致性哈希负载均衡的源码和Bug,了解一下?> 本文目录 第一节:什么是轮询? 本小节主要是介绍轮询算法和其对应的优缺点.引出加权轮询算法. 第二节:什么是加权轮询? 本小节主要是介