Nginx의 업스트림 메커니즘 로드 밸런싱

WBOY
풀어 주다: 2016-08-08 09:29:55
원래의
1348명이 탐색했습니다.

디렉토리

    • 로드 밸런싱
    • 가중 폴링
      • 관련 구조 본문
      • 가중 폴링 전략 시작
      • 가중 폴링 워크플로
        • 서버 목록 초기화
        • 적절한 백엔드 서버 선택
          • 백엔드 서버 초기화
          • 가중치를 기준으로 백엔드 서버 선택
        • 백엔드 서버 출시
    • IP 해시
      • 백엔드 서버 목록 초기화
      • 백엔드 서버 선택
    • 요약

로드 밸런싱

업스트림 메커니즘은 Nginx를 역방향 프록시 형태로 실행하므로 Nginx는 클라이언트의 요청을 수신하고 클라이언트의 요청에 따라 Nginx는 요청을 처리할 적절한 백엔드 서버를 선택합니다. 그러나 백엔드 서버가 여러 개인 경우 Nginx는 요청 처리를 담당하는 백엔드 서버를 결정하기 위해 어떤 전략을 사용합니까? 여기에는 백엔드 서버의 로드 밸런싱 문제가 포함됩니다.
Nginx의 로드 밸런싱 전략은 내장 전략과 확장 전략의 두 가지 범주로 나눌 수 있습니다. 기본 제공 전략에는 가중치 기반 폴링 및 IP 해시가 포함됩니다. 이 두 가지 전략은 기본적으로 Nginx 커널로 컴파일됩니다. 확장 전략에는 기본적으로 Nginx 커널로 컴파일되지 않는 타사 모듈 전략(공정, URL 해시, 일관된 해시 등)이 포함됩니다. 이 문서에서는 가중치 폴링 및 IP_hash 전략만 설명합니다.

가중 폴링

가중 폴링 전략은 먼저 각 백엔드 서버의 가중치를 계산한 후 가장 높은 가중치를 갖는 백엔드 서버를 선택하여 요청을 처리하는 것입니다.

관련 구조

ngx_http_upstream_peer_t 구조

<code><span>typedef</span><span>struct</span> {
    <span>/* 负载均衡的类型 */</span>
    ngx_http_upstream_init_pt        init_upstream;
    <span>/* 负载均衡类型的初始化函数 */</span>
    ngx_http_upstream_init_peer_pt   init;
    <span>/* 指向 ngx_http_upstream_rr_peers_t 结构体 */</span><span>void</span>                            *data;
} ngx_http_upstream_peer_t;</code>
로그인 후 복사

ngx_http_upstream_server_t 구조

<code><span>/* 服务器结构体 */</span><span>typedef</span><span>struct</span> {
    <span>/* 指向存储 IP 地址的数组,因为同一个域名可能会有多个 IP 地址 */</span>
    ngx_addr_t                      *addrs;
    <span>/* IP 地址数组中元素个数 */</span>
    ngx_uint_t                       naddrs;
    <span>/* 权重 */</span>
    ngx_uint_t                       weight;
    <span>/* 最大失败次数 */</span>
    ngx_uint_t                       max_fails;
    <span>/* 失败时间阈值 */</span>
    time_t                           fail_timeout;

    <span>/* 标志位,若为 1,表示不参与策略选择 */</span><span>unsigned</span>                         down:<span>1</span>;
    <span>/* 标志位,若为 1,表示为备用服务器 */</span><span>unsigned</span>                         backup:<span>1</span>;
} ngx_http_upstream_server_t;</code>
로그인 후 복사

ngx_http_upstream_rr_peer_t 구조

<code>typedef <span>struct</span> {
    <span>/* 后端服务器 IP 地址 */</span><span>struct</span> sockaddr                *sockaddr;
    <span>/* 后端服务器 IP 地址的长度 */</span>
    socklen_t                       socklen;
    <span>/* 后端服务器的名称 */</span>
    ngx_str_t                       name;

    <span>/* 后端服务器当前的权重 */</span>
    ngx_int_t                       current_weight;
    <span>/* 后端服务器有效权重 */</span>
    ngx_int_t                       effective_weight;
    <span>/* 配置项所指定的权重 */</span>
    ngx_int_t                       weight;

    <span>/* 已经失败的次数 */</span>
    ngx_uint_t                      fails;
    <span>/* 访问时间 */</span>
    time_t                          accessed;
    time_t                          <span>checked</span>;

    <span>/* 最大失败次数 */</span>
    ngx_uint_t                      max_fails;
    <span>/* 失败时间阈值 */</span>
    time_t                          fail_timeout;

    <span>/* 后端服务器是否参与策略,若为1,表示不参与 */</span>
    ngx_uint_t                      down;          <span>/* unsigned  down:1; */</span><span>#<span>if</span> (NGX_HTTP_SSL)</span>
    ngx_ssl_session_t              *ssl_session;   <span>/* local to a process */</span><span>#<span>endif</span></span>
} ngx_http_upstream_rr_peer_t;</code>
로그인 후 복사

ng x_http_upstream_rr_peers_t 구조

<code><span>typedef</span><span>struct</span> ngx_http_upstream_rr_peers_s  ngx_http_upstream_rr_peers_t;

<span>struct</span> ngx_http_upstream_rr_peers_s {
    <span>/* 竞选队列中后端服务器的数量 */</span>
    ngx_uint_t                      number;

 <span>/* ngx_mutex_t                    *mutex; */</span><span>/* 所有后端服务器总的权重 */</span>
    ngx_uint_t                      total_weight;

    <span>/* 标志位,若为 1,表示后端服务器仅有一台,此时不需要选择策略 */</span><span>unsigned</span>                        single:<span>1</span>;
    <span>/* 标志位,若为 1,表示所有后端服务器总的权重等于服务器的数量 */</span><span>unsigned</span>                        weighted:<span>1</span>;

    ngx_str_t                      *name;

    <span>/* 后端服务器的链表 */</span>
    ngx_http_upstream_rr_peers_t   *next;

    <span>/* 特定的后端服务器 */</span>
    ngx_http_upstream_rr_peer_t     peer[<span>1</span>];
};</code>
로그인 후 복사

ngx_http_upstream_rr_peer_data_t 구조

<code><span>typedef</span> struct {
    ngx_http_upstream_rr_peers_t   *peers;
    ngx_uint_t                      current;
    uintptr_t                      *tried;
    uintptr_t                       <span><span>data</span>;</span>
} ngx_http_upstream_rr_peer_data_t;</code>
로그인 후 복사

가중 폴링 전략 시작

Nginx 시작 프로세스 중 http 구성 블록을 구문 분석한 후 각 http에 해당하는 초기 기능 모듈이 호출됩니다. 업스트림 메커니즘의 ngx_http_upstream_module 모듈의 경우 해당 기본 구성 초기 기능은 다음과 같이 ngx_http_upstream_init_main_conf()입니다.

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

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

    <span>if</span> (init(cf, uscfp[i]) != NGX_OK) {  
            <span>return</span> NGX_CONF_ERROR;  
    }  
}  </code>
로그인 후 복사

ngx_http_upstream_module 모듈에서 사용자가 어떤 전략 선택도 하지 않으면, then 기본적으로 Weighted Polling 전략의 초기 기능은 ngx_http_upstream_init_round_robin입니다. 그렇지 않으면 uscfp[i]->peer.init_upstream 포인터 함수가 실행됩니다.

클라이언트로부터 요청을 받으면 Nginx는 초기화 요청 프로세스 중에 ngx_http_upstream_init_request을 호출합니다. 업스트림 메커니즘의 가중치 폴링 전략의 경우 이 메서드는 uscf->peer.init(r, uscf)입니다. 요청 초기화 작업을 완료합니다. ngx_http_upstream_init_round_robin_peer

<code>static void  
ngx_http_upstream_init_request(ngx_http_request_t *r)  
{  
<span>...</span><span>if</span> (uscf->peer.init(r, uscf) != NGX_OK) {  
        ngx_http_upstream_finalize_request(r, u,  
                                           NGX_HTTP_INTERNAL_SERVER_ERROR);  
        <span>return</span>;  
    }  

    ngx_http_upstream_connect(r, u);  
}  </code>
로그인 후 복사
클라이언트 요청 초기화가 완료되면 요청을 처리할 백엔드 서버가 선택됩니다. 백엔드 서버 선택은

함수로 구현됩니다. 이 함수는 ngx_http_upstream_get_round_robin_peer에서 호출됩니다. ngx_event_connect_peer

<code>ngx_int_t  
ngx_event_connect_peer(ngx_peer_connection_t *pc)  
{  
<span>...</span>/* 调用 ngx_http_upstream_get_round_robin_peer  */
   rc = pc->get(pc, pc->data);  
   <span>if</span> (rc != NGX_OK) {  
       <span>return</span> rc;  
   }  

  s = ngx_socket(pc->sockaddr->sa_family, SOCK_STREAM, <span>0</span>);  
<span>...</span>}  </code>
로그인 후 복사
요청을 처리하기 위해 백엔드 서버가 선택되면 백엔드 서버에 대한 연결이 테스트됩니다. 연결 테스트는

함수에서 구현됩니다. 라고 불리는. ngx_http_upstream_test_connectngx_http_upstream_send_request

연결 테스트가 실패하면
<code>static void  
ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u)  
{  
<span>...</span><span>if</span> (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {  
        /* 测试连接失败 */ 
        ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);  
        <span>return</span>;  
    }  
<span>...</span>}  </code>
로그인 후 복사
함수가 다른 테스트를 시작합니다. 테스트가 성공하면 요청이 처리된 후

이 호출되어 백엔드 서버를 해제합니다. ngx_http_upstream_nextngx_http_upstream_free_round_robin_peer가중치 폴링 워크플로

가중치 폴링 전략의 기본 작업 프로세스는 로드 밸런싱 서버 목록 초기화, 백엔드 서버 초기화, 요청을 처리할 적절한 백엔드 서버 선택 및 릴리스입니다. 백엔드 서버.

서버 목록 초기화

서버 목록 초기화는

함수로 구현됩니다. 이 함수의 실행 흐름은 다음과 같습니다.

ngx_http_upstream_init_round_robin

첫 번째 사례: 업스트림인 경우 서버가 메커니즘 구성 항목에 구성되어 있습니다.

  • 은 비대기 서버 목록을 초기화하고
      에 마운트합니다.
    • us->peer.data은 대기 서버 목록을 초기화하고
    • 에 마운트합니다.
    • peers->next
    두 번째 경우: 기본 방법인 proxy_pass를 사용하여 백엔드 서버 주소를 구성합니다.

  • 비대기 서버 목록을 초기화합니다. ,
    • us->peer.data
    메소드를 실행한 후 얻은 구조는 다음과 같습니다.

적합한 백엔드 서버 선택

<code><span>/* 初始化服务器负载均衡列表 */</span>
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    *<span>server</span>;
    ngx_http_upstream_rr_peers_t  *peers, *backup;

    <span>/* 设置 ngx_http_upstream_peer_t 结构体中 init 的回调方法 */</span>
    us->peer.init = ngx_http_upstream_init_round_robin_peer;

    <span>/* 第一种情况:若 upstream 机制中有配置后端服务器 */</span><span>if</span> (us->servers) {
        <span>/* ngx_http_upstream_srv_conf_t us 结构体成员 servers 是一个指向服务器数组 ngx_array_t 的指针,*/</span><span>server</span> = us->servers->elts;

        n = <span>0</span>;
        w = <span>0</span>;
        <span>/* 在这里说明下:一个域名可能会对应多个 IP 地址,upstream 机制中把一个 IP 地址看作一个后端服务器 */</span><span>/* 遍历服务器数组中所有后端服务器,统计非备用后端服务器的 IP 地址总个数(即非备用后端服务器总的个数) 和 总权重 */</span><span>for</span> (i = <span>0</span>; i < us->servers->nelts; i++) {
            <span>/* 若当前服务器是备用服务器,则 continue 跳过以下检查,继续检查下一个服务器 */</span><span>if</span> (<span>server</span>[i].backup) {ngx_http_upstream_peer_t
                <span>continue</span>;
            }

            <span>/* 统计所有非备用后端服务器 IP 地址总的个数(即非备用后端服务器总的个数) */</span>
            n += <span>server</span>[i].naddrs;
            <span>/* 统计所有非备用后端服务器总的权重 */</span>
            w += <span>server</span>[i].naddrs * <span>server</span>[i].weight;
        }

        <span>/* 若 upstream 机制中配置项指令没有设置后端服务器,则出错返回 */</span><span>if</span> (n == <span>0</span>) {
            ngx_log_error(NGX_LOG_EMERG, cf->log, <span>0</span>,
                          <span>"no servers in upstream \"%V\" in %s:%ui"</span>,
                          &us->host, us->file_name, us->line);
            <span>return</span> NGX_ERROR;
        }

        <span>/* 值得注意的是:备用后端服务器列表 和 非备用后端服务器列表 是分开挂载的,因此需要分开设置 */</span><span>/* 为非备用后端服务器分配内存空间 */</span>
        peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
                              + sizeof(ngx_http_upstream_rr_peer_t) * (n - <span>1</span>));
        <span>if</span> (peers == NULL) {
            <span>return</span> NGX_ERROR;
        }

        <span>/* 初始化非备用后端服务器列表 ngx_http_upstream_rr_peers_t 结构体 */</span>
        peers->single = (n == <span>1</span>);<span>/* 表示只有一个非备用后端服务器 */</span>
        peers->number = n;<span>/* 非备用后端服务器总的个数 */</span>
        peers->weighted = (w != n);<span>/* 设置默认权重为 1 或 0 */</span>
        peers->total_weight = w;<span>/* 设置非备用后端服务器总的权重 */</span>
        peers->name = &us->host;<span>/* 非备用后端服务器名称 */</span>        n = <span>0</span>;

        <span>/* 遍历服务器数组中所有后端服务器,初始化非备用后端服务器 */</span><span>for</span> (i = <span>0</span>; i < us->servers->nelts; i++) {
            <span>if</span> (<span>server</span>[i].backup) {<span>/* 若为备用服务器则 continue 跳过 */</span><span>continue</span>;
            }
            <span>/* 以下关于 ngx_http_upstream_rr_peer_t 结构体中三个权重值的说明 */</span><span>/*
             * effective_weight 相当于质量(来源于配置文件配置项的 weight),current_weight 相当于重量。
             * 前者反应本质,一般是不变的。current_weight 是运行时的动态权值,它的变化基于 effective_weight。
             * 但是 effective_weight 在其对应的 peer 服务异常时,会被调低,
             * 当服务恢复正常时,effective_weight 会逐渐恢复到实际值(配置项的weight);
             */</span><span>/* 遍历非备用后端服务器所对应 IP 地址数组中的所有 IP 地址(即一个后端服务器域名可能会对应多个 IP 地址) */</span><span>for</span> (j = <span>0</span>; j < <span>server</span>[i].naddrs; j++) {
                <span>/* 为每个非备用后端服务器初始化 */</span>
                peers->peer[n].sockaddr = <span>server</span>[i].addrs[j].sockaddr;<span>/* 设置非备用后端服务器 IP 地址 */</span>
                peers->peer[n].socklen = <span>server</span>[i].addrs[j].socklen;<span>/* 设置非备用后端服务器 IP 地址长度 */</span>
                peers->peer[n].name = <span>server</span>[i].addrs[j].name;<span>/* 设置非备用后端服务器域名 */</span>
                peers->peer[n].weight = <span>server</span>[i].weight;<span>/* 设置非备用后端服务器配置项权重 */</span>
                peers->peer[n].effective_weight = <span>server</span>[i].weight;<span>/* 设置非备用后端服务器有效权重 */</span>
                peers->peer[n].current_weight = <span>0</span>;<span>/* 设置非备用后端服务器当前权重 */</span>
                peers->peer[n].max_fails = <span>server</span>[i].max_fails;<span>/* 设置非备用后端服务器最大失败次数 */</span>
                peers->peer[n].fail_timeout = <span>server</span>[i].fail_timeout;<span>/* 设置非备用后端服务器失败时间阈值 */</span>
                peers->peer[n].down = <span>server</span>[i].down;<span>/* 设置非备用后端服务器 down 标志位,若该标志位为 1,则不参与策略 */</span>
                n++;
            }
        }

        <span>/*
         * 将非备用服务器列表挂载到 ngx_http_upstream_srv_conf_t 结构体成员结构体
         * ngx_http_upstream_peer_t peer 的成员 data 中;
         */</span>
        us->peer.data = peers;

        <span>/* backup servers */</span>        n = <span>0</span>;
        w = <span>0</span>;

         <span>/* 遍历服务器数组中所有后端服务器,统计备用后端服务器的 IP 地址总个数(即备用后端服务器总的个数) 和 总权重 */</span><span>for</span> (i = <span>0</span>; i < us->servers->nelts; i++) {
            <span>if</span> (!<span>server</span>[i].backup) {
                <span>continue</span>;
            }

            n += <span>server</span>[i].naddrs;<span>/* 统计所有备用后端服务器的 IP 地址总的个数 */</span>
            w += <span>server</span>[i].naddrs * <span>server</span>[i].weight;<span>/* 统计所有备用后端服务器总的权重 */</span>
        }

        <span>if</span> (n == <span>0</span>) {<span>/* 若没有备用后端服务器,则直接返回 */</span><span>return</span> NGX_OK;
        }

        <span>/* 分配备用服务器列表的内存空间 */</span>
        backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
                              + sizeof(ngx_http_upstream_rr_peer_t) * (n - <span>1</span>));
        <span>if</span> (backup == NULL) {
            <span>return</span> NGX_ERROR;
        }

        peers->single = <span>0</span>;
        <span>/* 初始化备用后端服务器列表 ngx_http_upstream_rr_peers_t 结构体 */</span>
        backup->single = <span>0</span>;
        backup->number = n;
        backup->weighted = (w != n);
        backup->total_weight = w;
        backup->name = &us->host;

        n = <span>0</span>;

        <span>/* 遍历服务器数组中所有后端服务器,初始化备用后端服务器 */</span><span>for</span> (i = <span>0</span>; i < us->servers->nelts; i++) {
            <span>if</span> (!<span>server</span>[i].backup) {<span>/* 若是非备用后端服务器,则 continue 跳过当前后端服务器,检查下一个后端服务器 */</span><span>continue</span>;
            }

            <span>/* 遍历备用后端服务器所对应 IP 地址数组中的所有 IP 地址(即一个后端服务器域名可能会对应多个 IP 地址) */</span><span>for</span> (j = <span>0</span>; j < <span>server</span>[i].naddrs; j++) {
                backup->peer[n].sockaddr = <span>server</span>[i].addrs[j].sockaddr;<span>/* 设置备用后端服务器 IP 地址 */</span>
                backup->peer[n].socklen = <span>server</span>[i].addrs[j].socklen;<span>/* 设置备用后端服务器 IP 地址长度 */</span>
                backup->peer[n].name = <span>server</span>[i].addrs[j].name;<span>/* 设置备用后端服务器域名 */</span>
                backup->peer[n].weight = <span>server</span>[i].weight;<span>/* 设置备用后端服务器配置项权重 */</span>
                backup->peer[n].effective_weight = <span>server</span>[i].weight;<span>/* 设置备用后端服务器有效权重 */</span>
                backup->peer[n].current_weight = <span>0</span>;<span>/* 设置备用后端服务器当前权重 */</span>
                backup->peer[n].max_fails = <span>server</span>[i].max_fails;<span>/* 设置备用后端服务器最大失败次数 */</span>
                backup->peer[n].fail_timeout = <span>server</span>[i].fail_timeout;<span>/* 设置备用后端服务器失败时间阈值 */</span>
                backup->peer[n].down = <span>server</span>[i].down;<span>/* 设置备用后端服务器 down 标志位,若该标志位为 1,则不参与策略 */</span>
                n++;
            }
        }

        <span>/*
         * 将备用服务器列表挂载到 ngx_http_upstream_rr_peers_t 结构体中
         * 的成员 next 中;
         */</span>
        peers->next = backup;

        <span>/* 第一种情况到此返回 */</span><span>return</span> NGX_OK;
    }


     <span>/* 第二种情况:若 upstream 机制中没有直接配置后端服务器,则采用默认的方式 proxy_pass 配置后端服务器地址 */</span><span>/* an upstream implicitly defined by proxy_pass, etc. */</span><span>/* 若端口号为 0,则出错返回 */</span><span>if</span> (us->port == <span>0</span>) {
        ngx_log_error(NGX_LOG_EMERG, cf->log, <span>0</span>,
                      <span>"no port in upstream \"%V\" in %s:%ui"</span>,
                      &us->host, us->file_name, us->line);
        <span>return</span> NGX_ERROR;
    }

    <span>/* 初始化 ngx_url_t 结构体所有成员为 0 */</span>
    ngx_memzero(&u, sizeof(ngx_url_t));

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

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

        <span>return</span> NGX_ERROR;
    }

    n = u.naddrs;

    <span>/* 分配非备用后端服务器列表的内存空间 */</span>
    peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)
                              + sizeof(ngx_http_upstream_rr_peer_t) * (n - <span>1</span>));
    <span>if</span> (peers == NULL) {
        <span>return</span> NGX_ERROR;
    }

    <span>/* 初始化非备用后端服务器列表 */</span>
    peers->single = (n == <span>1</span>);
    peers->number = n;
    peers->weighted = <span>0</span>;
    peers->total_weight = n;
    peers->name = &us->host;

    <span>for</span> (i = <span>0</span>; 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 = <span>1</span>;
        peers->peer[i].effective_weight = <span>1</span>;
        peers->peer[i].current_weight = <span>0</span>;
        peers->peer[i].max_fails = <span>1</span>;
        peers->peer[i].fail_timeout = <span>10</span>;
    }

    <span>/* 挂载非备用后端服务器列表 */</span>
    us->peer.data = peers;

    <span>/* implicitly defined upstream has no backup servers */</span><span>return</span> NGX_OK;
}</code>
로그인 후 복사
고객 요청을 처리하기에 적합한 백엔드 서버를 선택할 때 먼저 백엔드 서버를 초기화한 다음, 가중치를 기준으로 가중치가 가장 높은 서버를 선택해야 합니다. 요청을 처리하는 백엔드 서버입니다.

初始化后端服务器

上面的初始化负载服务器列表的全局初始化工作完成之后,当客户端发起请求时,Nginx 会选择一个合适的后端服务器来处理该请求。在本轮选择后端服务器之前,Nginx 会对后端服务器进行初始化工作,该工作由函数 ngx_http_upstream_init_round_robin_peer 实现。

ngx_http_upstream_init_round_robin_peer 函数的执行流程如下所示:

  • 计算服务器列表中的数量 n,n 的取值为 非备用后端服务器数量 与 备用后端服务器数量 较大者;
  • 根据 n 的取值,创建一个位图 tried,该位图是记录后端服务器是否被选择过:
    • 若 n 不大于 32, 只需要在一个 int 中记录所有后端服务器的状态;
    • 若 n 大于 32,则需要从内存池申请内存来存储所有后端服务器的状态;
  • 设置 ngx_peer_connection_t 结构体中 get 的回调方法为 ngx_http_upstream_get_round_robin_peerfree 的回调方法为 ngx_http_upstream_free_round_robin_peer,设置 tries 重试连接的次数为非备用后端服务器的个数;
<code><span>/* 当客户端发起请求时,upstream 机制为本轮选择一个后端服务器做初始化工作 */</span>
ngx_int_t
ngx_http_upstream_init_round_robin_peer(ngx_http_request_t <span>*</span>r,
    ngx_http_upstream_srv_conf_t <span>*</span>us)
{
    ngx_uint_t                         n;
    ngx_http_upstream_rr_peer_data_t  <span>*</span>rrp;

    <span>/* 注意:r->upstream->peer 是 ngx_peer_connection_t 结构体类型 */</span><span>/* 获取当前客户端请求中的 ngx_http_upstream_rr_peer_data_t 结构体 */</span>
    rrp <span>=</span> r<span>-></span>upstream<span>-></span>peer<span>.</span><span>data</span>;

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

        r<span>-></span>upstream<span>-></span>peer<span>.</span><span>data</span><span>=</span> rrp;
    }

    <span>/* 获取非备用后端服务器列表 */</span>
    rrp<span>-></span>peers <span>=</span> us<span>-></span>peer<span>.</span><span>data</span>;
    rrp<span>-></span>current <span>=</span><span>0</span>;<span>/* 若采用遍历方式选择后端服务器时,作为起始节点编号 */</span><span>/* 下面是取值 n,若存在备用后端服务器列表,则 n 的值为非备用后端服务器个数 与 备用后端服务器个数 之间的较大者 */</span>    n <span>=</span> rrp<span>-></span>peers<span>-></span>number;

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

    <span>/* rrp->tried 是一个位图,在本轮选择中,该位图记录各个后端服务器是否被选择过 */</span><span>/*
     * 如果后端服务器数量 n 不大于 32,则只需在一个 int 中即可记录下所有后端服务器状态;
     * 如果后端服务器数量 n 大于 32,则需在内存池中申请内存来存储所有后端服务器的状态;
     */</span><span>if</span> (n <span><=</span><span>8</span><span>*</span> sizeof(uintptr_t)) {
        rrp<span>-></span>tried <span>=</span><span>&</span>rrp<span>-></span><span>data</span>;
        rrp<span>-></span><span>data</span><span>=</span><span>0</span>;

    } <span>else</span> {
        n <span>=</span> (n <span>+</span> (<span>8</span><span>*</span> sizeof(uintptr_t) <span>-</span><span>1</span>)) <span>/</span> (<span>8</span><span>*</span> sizeof(uintptr_t));

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

    <span>/*
     * 设置 ngx_peer_connection_t 结构体中 get 、free 的回调方法;
     * 设置 ngx_peer_connection_t 结构体中 tries 重试连接的次数为非备用后端服务器的个数;
     */</span>
    r<span>-></span>upstream<span>-></span>peer<span>.</span>get <span>=</span> ngx_http_upstream_get_round_robin_peer;
    r<span>-></span>upstream<span>-></span>peer<span>.</span>free <span>=</span> ngx_http_upstream_free_round_robin_peer;
    r<span>-></span>upstream<span>-></span>peer<span>.</span>tries <span>=</span> rrp<span>-></span>peers<span>-></span>number;
<span>#if</span> (NGX_HTTP_SSL)
    r<span>-></span>upstream<span>-></span>peer<span>.</span>set_session <span>=</span>
                               ngx_http_upstream_set_round_robin_peer_session;
    r<span>-></span>upstream<span>-></span>peer<span>.</span>save_session <span>=</span>
                               ngx_http_upstream_save_round_robin_peer_session;
<span>#endif</span><span>return</span> NGX_OK;
}</code>
로그인 후 복사
根据权重选择后端服务器

完成后端服务器的初始化工作之后,根据各个后端服务器的权重来选择权重最高的后端服务器处理客户端请求,由函数 ngx_http_upstream_get_round_robin_peer 实现。

ngx_http_upstream_get_round_robin_peer 函数的执行流程如下所示:

  • 步骤1:检查 ngx_http_upstream_rr_peers_t 结构体中的 single 标志位:
    • single 标志位为 1,表示只有一台非备用后端服务器:
      • 接着检查该非备用后端服务器的 down 标志位:
        • down 标志位为 0,则选择该非备用后端服务器来处理请求;
        • down 标志位为 1, 该非备用后端服务器表示不参与策略选择,则跳至 goto failed 步骤从备用后端服务器列表中选择后端服务器来处理请求;
    • single 标志位为 0,则表示不止一台非备用后端服务器,则调用 ngx_http_upstream_get_peer 方法根据非备用后端服务器的权重来选择一台后端服务器处理请求,根据该方法的返回值 peer 进行判断:
      • 若该方法返回值 peer = NULL, 表示在非备用后端服务器列表中没有选中到合适的后端服务器来处理请求,则跳至 goto failed 从备用后端服务器列表中选择一台后端服务器来处理请求;
      • 若该方法返回值 peer 不为 NULL,表示已经选中了合适的后端服务器来处理请求,设置该服务器重试连接次数 tries,并 return NGX_OK 从当前函数返回;
  • goto failed 步骤:计算备用后端服务器在位图 tried 中的位置 n,并把他们在位图的记录都设置为 0,此时,把备用后端服务器列表作为参数调用 ngx_http_upstream_get_round_robin_peer 选择一台后端服务器来处理请求;
<code>/* 选择一个后端服务器来处理请求 */
<span>ngx_int_t</span><span>ngx_http_upstream_get_round_robin_peer</span>(ngx_peer_connection_t *pc, void *<span><span>data</span>)</span>
{
    ngx_http_upstream_rr_peer_data_t  *rrp = <span><span>data</span>;</span>    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(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>,
                   <span>"get rr peer, try: %ui"</span>, pc->tries);

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

    pc->cached = <span>0</span>;
    pc->connection = <span>NULL</span>;

    /*
     * 检查 ngx_http_upstream_rr_peers_t 结构体中的 single 标志位;
     * 若 single 标志位为 <span>1</span>,表示只有一台非备用后端服务器,
     * 接着检查该非备用后端服务器的 down 标志位,若 down 标志位为 <span>0</span>,则选择该非备用后端服务器来处理请求;
     * 若 down 标志位为 <span>1</span>, 该非备用后端服务器表示不参与策略选择,
     * 则跳至 goto failed 步骤从备用后端服务器列表中选择后端服务器来处理请求;
     */
    <span>if</span> (rrp->peers->single) {
        peer = &rrp->peers->peer[<span>0</span>];

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

    } <span>else</span> {/* 若 single 标志位为 <span>0</span>,表示不止一台非备用后端服务器 */

        /* there are several peers */

        /* 根据非备用后端服务器的权重来选择一台后端服务器处理请求 */
        peer = ngx_http_upstream_get_peer(rrp);

        <span>if</span> (peer == <span>NULL</span>) {
            /*
             * 若从非备用后端服务器列表中没有选择一台合适的后端服务器处理请求,
             * 则 goto failed 从备用后端服务器列表中选择一台后端服务器来处理请求;
             */
            goto failed;
        }

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

    /*
     * 若从非备用后端服务器列表中已经选到了一台合适的后端服务器处理请求;
     * 则获取该后端服务器的地址信息;
     */
    pc->sockaddr = peer->sockaddr;/* 获取被选中的非备用后端服务器的地址 */
    pc->socklen = peer->socklen;/* 获取被选中的非备用后端服务器的地址长度 */
    pc->name = &peer->name;/* 获取被选中的非备用后端服务器的域名 */

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

    /*
     * 检查被选中的非备用后端服务器重试连接的次数为 <span>1</span>,且存在备用后端服务器列表,
     * 则将该非备用后端服务器重试连接的次数设置为 备用后端服务器个数加 <span>1</span>;
     * 否则不用重新设置;
     */
    <span>if</span> (pc->tries == <span>1</span> && rrp->peers->next) {
        pc->tries += rrp->peers->next->number;
    }

    /* 到此,表示已经选择到了一台合适的非备用后端服务器来处理请求,则成功返回 */
    return <span>NGX_OK</span>;

<span>failed</span>:
      /*
       * 若从非备用后端服务器列表中没有选择到后端服务器处理请求,
       * 若存在备用后端服务器,则从备用后端服务器列表中选择一台后端服务器来处理请求;
       */

    peers = rrp->peers;

    /* 若存在备用后端服务器,则从备用后端服务器列表中选择一台后端服务器来处理请求;*/
    <span>if</span> (peers->next) {

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

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

        /* 获取备用后端服务器列表 */
        rrp->peers = peers->next;
        /* 把后端服务器重试连接的次数 tries 设置为备用后端服务器个数 number */
        pc->tries = rrp->peers->number;

        /* 计算备用后端服务器在位图中的位置 n */
        n = (rrp->peers->number + (<span>8</span> * sizeof(uintptr_t) - <span>1</span>))
                / (<span>8</span> * sizeof(uintptr_t));

        /* 初始化备用后端服务器在位图 rrp->tried[i] 中的值为 <span>0</span> */
        for (i = <span>0</span>; i < n; i++) {
             rrp->tried[i] = <span>0</span>;
        }

        /* 把备用后端服务器列表当前非备用后端服务器列表递归调用 ngx_http_upstream_get_round_robin_peer 选择一台后端服务器 */
        rc = ngx_http_upstream_get_round_robin_peer(pc, rrp);

        /* 若选择成功则返回 */
        <span>if</span> (rc != <span>NGX_BUSY</span>) {
            return rc;
        }

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

    /*
     * 若从备用后端服务器列表中也没有选择到一台后端服务器处理请求,
     * 则重新设置非备用后端服务器连接失败的次数 fails 为 <span>0</span> ,以便重新被选择;
     */
    /* all peers failed, mark them <span>as</span> live for quick recovery */

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

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

    pc->name = peers->name;

    /* 选择失败,则返回 */
    return <span>NGX_BUSY</span>;
}</code>
로그인 후 복사

ngx_http_upstream_get_peer 函数是计算每一个后端服务器的权重值,并选择一个权重最高的后端服务器。

ngx_http_upstream_get_peer 函数的执行流程如下所示:

  • for 循环遍历后端服务器列表,计算当前后端服务器在位图 tried 中的位置 n,判断当前服务器是否在位图中记录过,若已经记录过,则 continue 继续检查下一个后端服务器;若没有记录过则继续当前后端服务器检查;
  • 检查当前后端服务器的标志位 down,若该标志位为 1,表示该后端服务器不参与选择策略,则 continue 继续检查下一个后端服务器;若该标志位为 0,继续当前后端服务器的检查;
  • 若当前后端服务器的连接失败次数已到达 max_failes,且睡眠时间还没到 fail_timedout ,则 continue 继续检查下一个后端服务器;否则继续当前后端服务器的检查;
  • 计算当前后端服务器的权重,设置当前后端服务器的权重 current_weight 的值为原始值加上 effective_weight;设置总的权重 total 为原始值加上 effective_weight;
  • 判断当前后端服务器是否异常,若 effective_weight 小于 weight,表示正常,则调整 effective_weight 的值 effective_weight++;
  • 根据权重在后端服务器列表中选择权重最高的后端服务器 best;
  • 计算被选中后端服务器咋服务器列表中的为 i,记录被选中后端服务器在 ngx_http_upstream_rr_peer_data_t 结构体 current 成员的值,在释放后端服务器时会用到该值;
  • 计算被选中后端服务器在位图中的位置 n,并在该位置记录 best 后端服务器已经被选中过;
  • 更新被选中后端服务器的权重,并返回被选中的后端服务器 best;
<code><span>/* 根据后端服务器的权重来选择一台后端服务器处理请求 */</span><span>static</span> 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 = <span>NULL</span>;
    total = <span>0</span>;

    <span>/* 遍历后端服务器列表 */</span><span>for</span> (i = <span>0</span>; i < rrp->peers->number; i++) {

        <span>/* 计算当前后端服务器在位图中的位置 n */</span>
        n = i / (<span>8</span> * <span>sizeof</span>(uintptr_t));
        m = (uintptr_t) <span>1</span> << i % (<span>8</span> * <span>sizeof</span>(uintptr_t));

        <span>/* 当前后端服务器在位图中已经有记录,则不再次被选择,即 continue 检查下一个后端服务器 */</span><span>if</span> (rrp->tried[n] & m) {
            <span>continue</span>;
        }

        <span>/* 若当前后端服务器在位图中没有记录,则可能被选中,接着计算其权重 */</span>
        peer = &rrp->peers->peer[i];

        <span>/* 检查当前后端服务器的 down 标志位,若为 1 表示不参与策略选择,则 continue 检查下一个后端服务器 */</span><span>if</span> (peer->down) {
            <span>continue</span>;
        }

        <span>/*
         * 当前后端服务器的 down 标志位为 0,接着检查当前后端服务器连接失败的次数是否已经达到 max_fails;
         * 且睡眠的时间还没到 fail_timeout,则当前后端服务器不被选择,continue 检查下一个后端服务器;
         */</span><span>if</span> (peer->max_fails
            && peer->fails >= peer->max_fails
            && now - peer->checked <= peer->fail_timeout)
        {
            <span>continue</span>;
        }

        <span>/* 若当前后端服务器可能被选中,则计算其权重 */</span><span>/*
         * 在上面初始化过程中 current_weight = 0,effective_weight = weight;
         * 此时,设置当前后端服务器的权重 current_weight 的值为原始值加上 effective_weight;
         * 设置总的权重为原始值加上 effective_weight;
         */</span>
        peer->current_weight += peer->effective_weight;
        total += peer->effective_weight;

        <span>/* 服务器正常,调整 effective_weight 的值 */</span><span>if</span> (peer->effective_weight < peer->weight) {
            peer->effective_weight++;
        }

        <span>/* 若当前后端服务器的权重 current_weight 大于目前 best 服务器的权重,则当前后端服务器被选中 */</span><span>if</span> (best == <span>NULL</span> || peer->current_weight > best->current_weight) {
            best = peer;
        }
    }

    <span>if</span> (best == <span>NULL</span>) {
        <span>return</span><span>NULL</span>;
    }

    <span>/* 计算被选中后端服务器在服务器列表中的位置 i */</span>
    i = best - &rrp->peers->peer[<span>0</span>];

    <span>/* 记录被选中后端服务器在 ngx_http_upstream_rr_peer_data_t 结构体 current 成员的值,在释放后端服务器时会用到该值 */</span>
    rrp->current = i;

    <span>/* 计算被选中后端服务器在位图中的位置 */</span>
    n = i / (<span>8</span> * <span>sizeof</span>(uintptr_t));
    m = (uintptr_t) <span>1</span> << i % (<span>8</span> * <span>sizeof</span>(uintptr_t));

    <span>/* 在位图相应的位置记录被选中后端服务器 */</span>
    rrp->tried[n] |= m;

    <span>/* 更新被选中后端服务器的权重 */</span>
    best->current_weight -= total;

    <span>if</span> (now - best->checked > best->fail_timeout) {
        best->checked = now;
    }

    <span>/* 返回被选中的后端服务器 */</span><span>return</span> best;
}</code>
로그인 후 복사

释放后端服务器

成功连接后端服务器并且正常处理完成客户端请求后需释放后端服务器,由函数 ngx_http_upstream_free_round_robin_peer 实现。

<code>/* 释放后端服务器 */
<span>void</span><span>ngx_http_upstream_free_round_robin_peer</span>(ngx_peer_connection_t *pc, void *<span><span>data</span>,</span>
    ngx_uint_t state)
{
    ngx_http_upstream_rr_peer_data_t  *rrp = <span><span>data</span>;</span>    time_t                       now;
    ngx_http_upstream_rr_peer_t  *peer;

    ngx_log_debug2(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>,
                   <span>"free rr peer %ui %ui"</span>, pc->tries, state);

    /* <span>TODO</span>: <span>NGX_PEER_KEEPALIVE</span> */

    /* 若只有一个后端服务器,则设置 ngx_peer_connection_t 结构体成员 tries 为 <span>0</span>,并 return 返回 */
    <span>if</span> (rrp->peers->single) {
        pc->tries = <span>0</span>;
        return;
    }

    /* 若不止一个后端服务器,则执行以下程序 */

    /* 获取已经被选中的后端服务器 */
    peer = &rrp->peers->peer[rrp->current];

    /*
     * 若在本轮被选中的后端服务器在进行连接测试时失败,或者在处理请求过程中失败,
     * 则需要进行重新选择后端服务器;
     */
    <span>if</span> (state & <span>NGX_PEER_FAILED</span>) {
        now = ngx_time();

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

        peer->fails++;/* 增加当前后端服务器失败的次数 */
        /* 设置当前后端服务器访问的时间 */
        peer->accessed = now;
        peer->checked = now;

        <span>if</span> (peer->max_fails) {
            /* 由于当前后端服务器失败,表示发生异常,此时降低 effective_weight 的值 */
            peer->effective_weight -= peer->weight / peer->max_fails;
        }

        ngx_log_debug2(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>,
                       <span>"free rr peer failed: %ui %i"</span>,
                       rrp->current, peer->effective_weight);

        /* 保证 effective_weight 的值不能小于 <span>0</span> */
        <span>if</span> (peer->effective_weight < <span>0</span>) {
            peer->effective_weight = <span>0</span>;
        }

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

    } <span>else</span> {/* 若被选中的后端服务器成功处理请求,并返回,则将其 fails 设置为 <span>0</span> */

        /* mark peer live <span>if</span> check passed */

        /* 若 fail_timeout 时间已过,则将其 fails 设置为 <span>0</span> */
        <span>if</span> (peer->accessed < peer->checked) {
            peer->fails = <span>0</span>;
        }
    }

    /* 减少 tries 的值 */
    <span>if</span> (pc->tries) {
        pc->tries<span>--;</span>
    }

    /* ngx_unlock_mutex(rrp->peers->mutex); */
}</code>
로그인 후 복사

IP 哈希

IP 哈希策略选择后端服务器时,将来自同一个 IP 地址的客户端请求分发到同一台后端服务器处理。在 Nginx 中,IP 哈希策略的一些初始化工作是基于加权轮询策略的,这样减少了一些工作。

Nginx 使用 IP 哈希负载均衡策略时,在进行策略选择之前由 ngx_http_upstream_init_ip_hash 函数进行全局初始化工作,其实该函数也是调用加权轮询策略的全局初始化函数。当一个客户端请求过来时,Nginx 将调用 ngx_http_upstream_init_ip_hash_peer() 为选择后端服务器处理该请求做初始化工作。在多次哈希选择失败后,Nginx 会将选择策略退化到加权轮询。

ngx_http_upstream_get_ip_hash_peer 函数会在选择后端服务器时计算客户端请求 IP 地址的哈希值,并根据哈希值得到被选中的后端服务器,判断其是否可用,如果可用则保存服务器地址,若不可用则在上次哈希选择结果基础上再次进行哈希选择。如果哈希选择失败次数达到 20 次以上,此时回退到采用轮询策略进行选择。

初始化后端服务器列表

初始化服务器列表工作是调用加权轮询策略的初始化函数,只是最后设置 IP 哈希的回调方法为 ngx_http_upstream_init_ip_hash_peer

<code><span>static</span> ngx_int_t
ngx_http_upstream_init_ip_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
    <span>/* 调用加权轮询策略的初始化函数 */</span><span>if</span> (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
        <span>return</span> NGX_ERROR;
    }

    <span>/* 由于 ngx_http_upstream_init_round_robin 方法的选择后端服务器处理客户请求的初始化函数
     * 为 us->peer.init = ngx_http_upstream_init_round_robin_peer;
     */</span><span>/* 重新设置 ngx_http_upstream_peer_t 结构体中 init 的回调方法为  ngx_http_upstream_init_ip_hash_peer */</span>    us->peer.init = ngx_http_upstream_init_ip_hash_peer;

    <span>return</span> NGX_OK;
}</code>
로그인 후 복사

选择后端服务器

选择后端服务器之前会调用函数 ngx_http_upstream_init_ip_hash_peer 进行一些服务器初始化工作。最终由函数 ngx_http_upstream_get_ip_hash_peer 进行 IP 哈希选择。

ngx_http_upstream_init_ip_hash_peer 函数执行流程:

  • 调用加权轮询策略的初始化函数 ngx_http_upstream_init_round_robin_peer
  • 设置 IP hash 的决策函数为 ngx_http_upstream_get_ip_hash_peer
  • 保存客户端 IP 地址;
  • 初始化 ngx_http_upstream_ip_hash_peer_data_t结构体成员 hash 值为 89;tries 重试连接次数为 0;get_rr_peer 为加权轮询的决策函数 ngx_http_upstream_get_round_robin_peer

ngx_http_upstream_get_ip_hash_peer 函数执行流程:

  • 若重试连接的次数 tries 大于 20,或 只有一台后端服务器,则直接调用加权轮询策略 get_rr_peer 选择当前后端服务器处理请求;
  • 计算 IP 地址的 hash 值,下面根据哈希值进行选择后端服务器;
  • ngx_http_upstream_rr_peers_t 结构体中 weighted 标志位为 1,则被选中的后端服务器在后端服务器列表中的位置为 hash 值与后端服务器数量的余数 p;
  • ngx_http_upstream_rr_peers_t 结构体中 weighted 标志位为 0,首先计算 hash 值与后端服务器总权重的余数 w; 将 w 值减去后端服务器的权重,直到有一个后端服务器使 w 值小于 0,则选中该后端服务器来处理请求,并记录在后端服务器列表中的位置 p;
  • 计算被选中后端服务器在位图中的位置 n;
  • 若当前被选中的后端服务器已经在位图记录过,则跳至 goto next 执行;
  • 检查当前被选中后端服务器的 down 标志位:
    • 若该标志位为1,则跳至 goto next_try 执行;
    • 若 down 标志位为 0,接着检查当前被选中后端服务器失败连接次数是否到达 max_fails,若已经达到 max_fails 次,并且睡眠时间还没到 fail_timeout,则跳至 goto next_try 执行;
  • 若不满足以上条件,表示选择成功,记录当前后端服务器的地址信息,把当前后端服务器记录在位图相应的位置,更新哈希值,最后返回该后端服务器;
  • goto next:tries 重试连接的次数加 1,并判断 tries 是否大于阈值 20,若大于,则采用加权轮询策略;
  • goto next_try :把当前后端服务器记录在位图中,减少当前后端服务器重试连接的次数 tries;
<code>static ngx_int_t
ngx_http_upstream_init_ip_hash_peer(ngx_http_request_t <span>*</span>r,
    ngx_http_upstream_srv_conf_t <span>*</span>us)
{
    struct sockaddr_in                     <span>*</span>sin;
<span>#if</span> (NGX_HAVE_INET6)
    struct sockaddr_in6                    <span>*</span>sin6;
<span>#endif</span>
    ngx_http_upstream_ip_hash_peer_data_t  <span>*</span>iphp;

    <span>/* 分配 ngx_http_upstream_ip_hash_peer_data_t 结构体内存空间 */</span>
    iphp <span>=</span> ngx_palloc(r<span>-></span>pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));
    <span>if</span> (iphp <span>==</span><span>NULL</span>) {
        <span>return</span> NGX_ERROR;
    }

    r<span>-></span>upstream<span>-></span>peer<span>.</span><span>data</span><span>=</span><span>&</span>iphp<span>-></span>rrp;

    <span>/* 调用加权轮询策略的初始化函数 ngx_http_upstream_init_round_robin_peer */</span><span>if</span> (ngx_http_upstream_init_round_robin_peer(r, us) <span>!=</span> NGX_OK) {
        <span>return</span> NGX_ERROR;
    }

    <span>/* 设置 IP hash 的决策函数  */</span>
    r<span>-></span>upstream<span>-></span>peer<span>.</span>get <span>=</span> ngx_http_upstream_get_ip_hash_peer;

    switch (r<span>-></span>connection<span>-></span>sockaddr<span>-></span>sa_family) {

    <span>/* 保存客户端 IP 地址 */</span><span>/* IPv4 地址 */</span><span>case</span> AF_INET:
        sin <span>=</span> (struct sockaddr_in <span>*</span>) r<span>-></span>connection<span>-></span>sockaddr;
        iphp<span>-></span>addr <span>=</span> (u_char <span>*</span>) <span>&</span>sin<span>-></span>sin_addr<span>.</span>s_addr;
        iphp<span>-></span>addrlen <span>=</span><span>3</span>;
        break;

    <span>/* IPv6 地址 */</span><span>#if</span> (NGX_HAVE_INET6)
    <span>case</span> AF_INET6:
        sin6 <span>=</span> (struct sockaddr_in6 <span>*</span>) r<span>-></span>connection<span>-></span>sockaddr;
        iphp<span>-></span>addr <span>=</span> (u_char <span>*</span>) <span>&</span>sin6<span>-></span>sin6_addr<span>.</span>s6_addr;
        iphp<span>-></span>addrlen <span>=</span><span>16</span>;
        break;
<span>#endif</span><span>/* 非法地址 */</span>
    default:
        iphp<span>-></span>addr <span>=</span> ngx_http_upstream_ip_hash_pseudo_addr;
        iphp<span>-></span>addrlen <span>=</span><span>3</span>;
    }

    <span>/* 初始化 ngx_http_upstream_ip_hash_peer_data_t结构体成员 */</span>
    iphp<span>-></span>hash <span>=</span><span>89</span>;
    iphp<span>-></span>tries <span>=</span><span>0</span>;
    <span>/* 这个是设置为加权轮询策略的决策函数 */</span>
    iphp<span>-></span>get_rr_peer <span>=</span> ngx_http_upstream_get_round_robin_peer;

    <span>return</span> NGX_OK;
}</code>
로그인 후 복사
<code>/* 选择后端服务器处理请求 */
<span>static</span> ngx_int_t
<span>ngx_http_upstream_get_ip_hash_peer</span>(ngx_peer_connection_t *pc, void *<span><span>data</span>)</span>
{
    ngx_http_upstream_ip_hash_peer_data_t  *iphp = <span><span>data</span>;</span>    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(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>,
                   <span>"get ip hash peer, try: %ui"</span>, pc->tries);

    /* <span>TODO</span>: cached */

    /* 若重试连接的次数 tries 大于 <span>20</span>,或 只有一台后端服务器,则直接调用加权轮询策略选择当前后端服务器处理请求 */
    <span>if</span> (iphp->tries > <span>20</span> || iphp->rrp.peers->single) {
        return iphp->get_rr_peer(pc, &iphp->rrp);
    }

    now = ngx_time();

    pc->cached = <span>0</span>;
    pc->connection = <span>NULL</span>;

    hash = iphp->hash;

    for ( ;; ) {

        /* 计算 <span>IP</span> 地址的 hash 值 */
        for (i = <span>0</span>; i < (ngx_uint_t) iphp->addrlen; i++) {
            hash = (hash * <span>113</span> + iphp->addr[i]) % <span>6271</span>;/* hash 函数 */
        }

        /* 以下是根据 hash 值选择合适的后端服务器来处理请求 */

        /* 若 ngx_http_upstream_rr_peers_t 结构体中 weighted 标志位为 <span>1</span>,
         * 表示所有后端服务器的总权重 与 后端服务器的数量 相等,
         * 则被选中的后端服务器在后端服务器列表中的位置为 hash 值与后端服务器数量的余数 p;
         */
        <span>if</span> (!iphp->rrp.peers->weighted) {
            p = hash % iphp->rrp.peers->number;

        } <span>else</span> {
            /* 若 ngx_http_upstream_rr_peers_t 结构体中 weighted 标志位为 <span>0</span>,
             * 首先计算 hash 值与后端服务器总权重的余数 w;
             * 将 w 值减去后端服务器的权重,直到有一个后端服务器使 w 值小于 <span>0</span>,
             * 则选中该后端服务器来处理请求,并记录在后端服务器列表中的位置 p;
             */
            w = hash % iphp->rrp.peers->total_weight;

            for (i = <span>0</span>; i < iphp->rrp.peers->number; i++) {
                w -= iphp->rrp.peers->peer[i].weight;
                <span>if</span> (w < <span>0</span>) {
                    break;
                }
            }

            p = i;
        }

        /* 计算被选中后端服务器在位图中的位置 n */
        n = p / (<span>8</span> * sizeof(uintptr_t));
        m = (uintptr_t) <span>1</span> << p % (<span>8</span> * sizeof(uintptr_t));

        /* 若当前被选中的后端服务器已经在位图记录过,则跳至 goto next 执行 */
        <span>if</span> (iphp->rrp.tried[n] & m) {
            goto next;
        }

        ngx_log_debug2(<span>NGX_LOG_DEBUG_HTTP</span>, pc->log, <span>0</span>,
                       <span>"get ip hash peer, hash: %ui %04XA"</span>, p, m);

        /* 获取当前被选中的后端服务器 */
        peer = &iphp->rrp.peers->peer[p];

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

        /* 检查当前被选中后端服务器的 down 标志位,若该标志位为<span>1</span>,则跳至 goto next_try 执行 */
        <span>if</span> (peer->down) {
            goto next_try;
        }

        /* 若 down 标志位为 <span>0</span>,接着检查当前被选中后端服务器失败连接次数是否到达 max_fails,
         * 若已经达到 max_fails 次,并且睡眠时间还没到 fail_timeout,则跳至 goto next_try 执行;
         */
        <span>if</span> (peer->max_fails
            && peer->fails >= peer->max_fails
            && now - peer->checked <= peer->fail_timeout)
        {
            goto next_try;
        }

        /* 若不满足以上条件,则表示选择后方服务器成功 */
        break;

    next_try:

        /* 把当前后端服务器记录在位图中 */
        iphp->rrp.tried[n] |= m;

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

        /* 减少当前后端服务器重试连接的次数 */
        pc->tries<span>--;</span>    next:

        /* tries 重试连接的次数加 <span>1</span>,并判断 tries 是否大于阈值 <span>20</span>,若大于,则采用加权轮询策略 */
        <span>if</span> (++iphp->tries >= <span>20</span>) {
            return iphp->get_rr_peer(pc, &iphp->rrp);
        }
    }

    /* 到此已经成功选择了后端服务器来处理请求 */

    /* 记录当前后端服务器在后端服务器列表中的位置,该位置方便释放后端服务器调用 */
    iphp->rrp.current = p;

    /* 记录当前后端服务器的地址信息 */
    pc->sockaddr = peer->sockaddr;
    pc->socklen = peer->socklen;
    pc->name = &peer->name;

    <span>if</span> (now - peer->checked > peer->fail_timeout) {
        peer->checked = now;
    }

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

    /* 把当前后端服务器记录在位图相应的位置 */
    iphp->rrp.tried[n] |= m;
    /* 记录 hash 值 */
    iphp->hash = hash;

    return <span>NGX_OK</span>;
}</code>
로그인 후 복사

总结

加权轮询策略:不依赖于客户端的任何信息,完全依靠后端服务器的情况来进行选择。但是同一个客户端的多次请求可能会被分配到不同的后端服务器进行处理,无法满足做会话保持的应用的需求。

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

以上就介绍了Nginx 中 upstream 机制的负载均衡,包括了方面的内容,希望对PHP教程有兴趣的朋友有所帮助。

관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿