NGX负载均衡策略

负载均衡策略


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的配置上下文中通过配置指令ip_hash明确指定(该配置项最好放在其他server指令等的前面,以便检查server的配置选项是否合理)。比如采用Ip_hash的upstream配置如下所示:

[cpp]  view plain copy print ?
  1. upstream load_balance{  
  2.     ip_hash;  
  3.     server localhost:8001;  
  4.     server localhost:8002;  
  5. }  

Load-blance模块中4个关键回调函数:
表1

回调指针

函数功能

round_robin模块

IP_hash模块

uscf->peer.init_upstream

解析配置文件过程中调用,根据upstream里各个server配置项做初始准备工作,另外的核心工作是设置回调指针us->peer.init。配置文件解析完后不再被调用

ngx_http_upstream_init_round_robin

设置:us->peer.init = ngx_http_upstream_init_round_robin_peer;

ngx_http_upstream_init_ip_hash

设置:us->peer.init = ngx_http_upstream_init_ip_hash_peer;

us->peer.init

在每一次Nginx准备转发客户端请求到后端服务器前都会调用该函数。该函数为本次转发选择合适的后端服务器做初始准备工作,另外的核心工作是设置回调指针r->upstream->peer.getr->upstream->peer.free

ngx_http_upstream_init_round_robin_peer

设置:r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;

r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;

ngx_http_upstream_init_ip_hash_peer

设置:r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;

r->upstream->peer.free为空

r->upstream->peer.get

在每一次Nginx准备转发客户端请求到后端服务器前都会调用该函数。该函数实现具体的位本次转发选择合适的后端服务器的算法逻辑,即完成选择获取合适后端服务器的功能

ngx_http_upstream_get_round_robin_peer

加权选择当前权值最高的后端服务器

ngx_http_upstream_get_ip_hash_peer

根据IP哈希值选择后端服务器

r->upstream->peer.free

在每一次Nginx完成与后端服务器之间的交互后都会调用该函数。

ngx_http_upstream_free_round_robin_peer

更新相关数值,比如rrp->current



当整个http配置块被Nginx解析完毕之后,会调用各个http模块对应的初始函数。对于模块ngx_http_upstream_module而言,对应的main配置初始函数是ngx_http_upstream_init_main_conf(),在这个函数中有这样一段代码:
[cpp]  view plain copy print ?
  1. for (i = 0; i < umcf->upstreams.nelts; i++) {  
  2.   
  3.         init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:  
  4.                                             ngx_http_upstream_init_round_robin;  
  5.   
  6.         if (init(cf, uscfp[i]) != NGX_OK) {  
  7.             return NGX_CONF_ERROR;  
  8.         }  
  9. }  

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

加权轮询策略

全局准备工作


需要注意的是,配置文件中出现的参数只能和某些策略配合使用,所以如果发现某参数没有生效,则应该检查这一点。在配置解析的过程中,这些选项设置都被转换为Nginx内对于的变量值,对应的结构体ngx_http_upstream_server_t如下(ngx_http_upstream.h):
[cpp]  view plain copy print ?
  1. typedef struct {  
  2.     ngx_addr_t                      *addrs;//指向存储IP地址的数组的指针,host信息(对应的是 ngx_url_t->addrs )  
  3.     ngx_uint_t                       naddrs;//与第一个参数配合使用,数组元素个数(对应的是 ngx_url_t->naddrs )  
  4.     ngx_uint_t                       weight;  
  5.     ngx_uint_t                       max_fails;  
  6.     time_t                           fail_timeout;  
  7.   
  8.     unsigned                         down:1;  
  9.     unsigned                         backup:1;  
  10. } ngx_http_upstream_server_t;  
这个阶段的函数是ngx_http_upstream_init_round_robin(),其主要完成的工作详见表1.

首先是设置了一个回调指针,这个函数用来针对每个请求选择后端服务器之前做一些初始化工作:
[cpp]  view plain copy print ?
  1. us->peer.init = ngx_http_upstream_init_round_robin_peer;  

us类型是ngx_http_upstream_srv_conf_t:
[cpp]  view plain copy print ?
  1. typedef struct ngx_http_upstream_srv_conf_s  ngx_http_upstream_srv_conf_t;  
  2.   
  3. struct ngx_http_upstream_srv_conf_s {  
  4.     ngx_http_upstream_peer_t         peer;  
  5.     void                           **srv_conf;//在 ngx_http_upstream()函数中被设置,指向的是本层的srv_conf  
  6.   
  7.     ngx_array_t                     *servers;  /*array of ngx_http_upstream_server_t */  
  8.   
  9.     ngx_uint_t                       flags;//调用函数时ngx_http_upstream_add() 指定的标记  
  10.     ngx_str_t                        host;//在函数 ngx_http_upstream_add() 中设置(e.g. upstream backend中的backend)  
  11.     u_char                          *file_name;//"/usr/local/nginx/conf/nginx.conf"  
  12.     ngx_uint_t                       line;//proxy在配置文件中的行号  
  13.     in_port_t                        port;//使用的端口号(ngx_http_upstream_add()函数中添加, 指向ngx_url_t-->port,通常在函数ngx_parse_inet_url()中解析)  
  14.     in_port_t                        default_port;//默认使用的端口号(ngx_http_upstream_add()函数中添加, 指向ngx_url_t-->default_port)  
  15.     ngx_uint_t                       no_port;  /* unsigned no_port:1 */  
  16. };  

而ngx_http_upstream_peer_t :
[cpp]  view plain copy print ?
  1. typedef struct {  
  2.     //使用负载均衡的类型,默认采用 ngx_http_upstream_init_round_robin()  
  3.     ngx_http_upstream_init_pt        init_upstream;  
  4.     //使用的负载均衡类型的初始化函数  
  5.     ngx_http_upstream_init_peer_pt   init;  
  6.     //us->peer.data = peers; 指向的是 ngx_http_upstream_rr_peers_t(函数 ngx_http_upstream_init_round_robin()中设置)  
  7.     void                            *data;  
  8. } ngx_http_upstream_peer_t;  

ngx_http_upstream_init_peer_pt  是函数指针类型:
[cpp]  view plain copy print ?
  1. typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r,  
  2.     ngx_http_upstream_srv_conf_t *us);  
服务器类型ngx_http_upstream_server_t见前面的解释。

如果upstream中服务器为空,那么默认使用proxy_pass。将利用函数ngx_inet_resolve_host依据us参数中的host和port进行解析。将结果保存在一个ngx_url_t类型的变量中:
[cpp]  view plain copy print ?
  1. typedef struct {  
  2.     ngx_str_t                 url;                  //保存IP地址+端口信息(e.g. 192.168.124.129:8011 或 money.163.com)  
  3.     ngx_str_t                 host;                 //保存IP地址信息  
  4.     ngx_str_t                 port_text;                //保存port字符串  
  5.     ngx_str_t                 uri;                  //uri部分,在函数ngx_parse_inet_url()中设置  
  6.   
  7.     in_port_t                 port;                 //端口,e.g. listen指令中指定的端口(listen 192.168.124.129:8011)  
  8.     in_port_t                 default_port;             //默认端口(当no_port字段为真时,将默认端口赋值给port字段, 默认端口通常是80)  
  9.     int                       family;                   //address family, AF_xxx  
  10.   
  11.     unsigned                  listen:1;             //是否为指监听类的设置  
  12.     unsigned                  uri_part:1;  
  13.     unsigned                  no_resolve:1;             //根据情况决定是否解析域名(将域名解析到IP地址)  
  14.     unsigned                  one_addr:1;               //等于1时,仅有一个IP地址  
  15.   
  16.     unsigned                  no_port:1;                //标识url中没有显示指定端口(为1时没有指定)  
  17.     unsigned                  wildcard:1;               //标识是否使用通配符(e.g. listen *:8000;)  
  18.   
  19.     socklen_t                 socklen;              //sizeof(struct sockaddr_in)  
  20.     u_char                    sockaddr[NGX_SOCKADDRLEN];        //sockaddr_in结构指向它  
  21.   
  22.     ngx_addr_t               *addrs;                //数组大小是naddrs字段;每个元素对应域名的IP地址信息(struct sockaddr_in),在函数中赋值(ngx_inet_resolve_host())  
  23.     ngx_uint_t                naddrs;               //url对应的IP地址个数,IP格式的地址将默认为1  
  24.   
  25.     char                     *err;                  //错误信息字符串  
  26. } ngx_url_t;  

此函数会创建后端服务器列表,并且将非后备服务器与后备服务器分开进行各自单独的链表。每一个后端服务器用一个结构体ngx_http_upstream_rr_peer_t与之对应(ngx_http_upstream_round_robin.h):
[cpp]  view plain copy print ?
  1. typedef struct {  
  2.     struct sockaddr                *sockaddr;//后端服务器地址  
  3.     socklen_t                       socklen;//后端服务器地址长度  
  4.     ngx_str_t                       name;//后端名称  
  5.   
  6.     ngx_int_t                       current_weight;//当前权重,nginx会在运行过程中调整此权重  
  7.     ngx_int_t                       effective_weight;  
  8.     ngx_int_t                       weight;//配置的权重  
  9.   
  10.     ngx_uint_t                      fails;//已尝试失败次数  
  11.     time_t                          accessed;//检测失败时间,用于计算超时  
  12.     time_t                          checked;  
  13.   
  14.     ngx_uint_t                      max_fails;//最大失败次数  
  15.     time_t                          fail_timeout;//多长时间内出现max_fails次失败便认为后端down掉了  
  16.   
  17.     ngx_uint_t                      down;          /* unsigned  down:1; *///指定某后端是否挂了  
  18.   
  19. #if (NGX_HTTP_SSL)  
  20.     ngx_ssl_session_t              *ssl_session;   /* local to a process */  
  21. #endif  
  22. } ngx_http_upstream_rr_peer_t;  

列表最前面需要带有一些head信息,用结构体ngx_http_upstream_rr_peers_t与之对应:
[cpp]  view plain copy print ?
  1. typedef struct ngx_http_upstream_rr_peers_s  ngx_http_upstream_rr_peers_t;  
  2.   
  3. struct ngx_http_upstream_rr_peers_s {  
  4.     ngx_uint_t                      number;//队列中服务器数量  
  5.   
  6.  /* ngx_mutex_t                    *mutex; */  
  7.   
  8.     ngx_uint_t                      total_weight;//所有服务器总权重  
  9.   
  10.     unsigned                        single:1;//为1表示后端服务器总共只有一台,用于优化,此时不需要再做选择  
  11.     unsigned                        weighted:1;//为1表示总的权重值等于服务器数量  
  12.   
  13.     ngx_str_t                      *name;  
  14.   
  15.     ngx_http_upstream_rr_peers_t   *next;//后备服务器列表挂载在这个字段下  
  16.   
  17.     ngx_http_upstream_rr_peer_t     peer[1];  
  18. };  

函数的完整代码如下(ngx_http_upstream_round_robin.c):
[cpp]  view plain copy print ?
  1. //函数:初始化服务器负载均衡表      
  2. //参数:  
  3. //us:ngx_http_upstream_main_conf_t结构体中upstreams数组元素  
  4. ngx_int_t  
  5. ngx_http_upstream_init_round_robin(ngx_conf_t *cf,  
  6.     ngx_http_upstream_srv_conf_t *us)  
  7. {  
  8.     ngx_url_t                      u;  
  9.     ngx_uint_t                     i, j, n, w;  
  10.     ngx_http_upstream_server_t    *server;  
  11.     ngx_http_upstream_rr_peers_t  *peers, *backup;  
  12.   
  13.     //回调指针设置  
  14.     us->peer.init = ngx_http_upstream_init_round_robin_peer;  
  15.   
  16.     //服务器数组指针不为空  
  17.     if (us->servers) {  
  18.         server = us->servers->elts;  
  19.   
  20.         n = 0;  
  21.         w = 0;  
  22.   
  23.     //遍历所有服务器  
  24.         for (i = 0; i < us->servers->nelts; i++) {  
  25.         //是后备服务器,跳过  
  26.             if (server[i].backup) {  
  27.                 continue;  
  28.             }  
  29.   
  30.         //服务器地址数量统计  
  31.             n += server[i].naddrs;  
  32.         //总的权重计算  
  33.             w += server[i].naddrs * server[i].weight;  
  34.         }  
  35.   
  36.         if (n == 0) {  
  37.             ngx_log_error(NGX_LOG_EMERG, cf->log, 0,  
  38.                           "no servers in upstream \"%V\" in %s:%ui",  
  39.                           &us->host, us->file_name, us->line);  
  40.             return NGX_ERROR;  
  41.         }  
  42.   
  43.     //为非后备服务器分配空间  
  44.         peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)  
  45.                               + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));  
  46.         if (peers == NULL) {  
  47.             return NGX_ERROR;  
  48.         }  
  49.   
  50.     //非后备服务器列表头中各属性设置  
  51.         peers->single = (n == 1);  
  52.         peers->number = n;  
  53.         peers->weighted = (w != n);  
  54.         peers->total_weight = w;  
  55.         peers->name = &us->host;  
  56.   
  57.         n = 0;  
  58.   
  59.     //后备服务器列表中各服务器项设置  
  60.         for (i = 0; i < us->servers->nelts; i++) {  
  61.             for (j = 0; j < server[i].naddrs; j++) {  
  62.                 if (server[i].backup) {  
  63.                     continue;  
  64.                 }  
  65.   
  66.                 peers->peer[n].sockaddr = server[i].addrs[j].sockaddr;  
  67.                 peers->peer[n].socklen = server[i].addrs[j].socklen;  
  68.                 peers->peer[n].name = server[i].addrs[j].name;  
  69.                 peers->peer[n].max_fails = server[i].max_fails;  
  70.                 peers->peer[n].fail_timeout = server[i].fail_timeout;  
  71.                 peers->peer[n].down = server[i].down;  
  72.                 peers->peer[n].weight = server[i].weight;  
  73.                 peers->peer[n].effective_weight = server[i].weight;  
  74.                 peers->peer[n].current_weight = 0;  
  75.                 n++;  
  76.             }  
  77.         }  
  78.   
  79.     //非后备服务器列表挂载的位置  
  80.         us->peer.data = peers;  
  81.   
  82.         /* backup servers */  
  83.     //后备服务器  
  84.         n = 0;  
  85.         w = 0;  
  86.   
  87.         for (i = 0; i < us->servers->nelts; i++) {  
  88.             if (!server[i].backup) {  
  89.                 continue;  
  90.             }  
  91.         //后备服务器地址数量统计  
  92.             n += server[i].naddrs;  
  93.         //后备服务器总权重计算  
  94.             w += server[i].naddrs * server[i].weight;  
  95.         }  
  96.   
  97.         if (n == 0) {  
  98.             return NGX_OK;  
  99.         }  
  100.   
  101.     //后备服务器列表地址空间分配  
  102.         backup = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)  
  103.                               + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));  
  104.         if (backup == NULL) {  
  105.             return NGX_ERROR;  
  106.         }  
  107.   
  108.         peers->single = 0;  
  109.     //后备服务器列表头中各属性设置  
  110.         backup->single = 0;  
  111.         backup->number = n;  
  112.         backup->weighted = (w != n);  
  113.         backup->total_weight = w;  
  114.         backup->name = &us->host;  
  115.   
  116.         n = 0;  
  117.   
  118.     //后备服务器列表中各服务器项设置  
  119.         for (i = 0; i < us->servers->nelts; i++) {  
  120.             for (j = 0; j < server[i].naddrs; j++) {  
  121.                 if (!server[i].backup) {  
  122.                     continue;  
  123.                 }  
  124.   
  125.                 backup->peer[n].sockaddr = server[i].addrs[j].sockaddr;  
  126.                 backup->peer[n].socklen = server[i].addrs[j].socklen;  
  127.                 backup->peer[n].name = server[i].addrs[j].name;  
  128.                 backup->peer[n].weight = server[i].weight;  
  129.                 backup->peer[n].effective_weight = server[i].weight;  
  130.                 backup->peer[n].current_weight = 0;  
  131.                 backup->peer[n].max_fails = server[i].max_fails;  
  132.                 backup->peer[n].fail_timeout = server[i].fail_timeout;  
  133.                 backup->peer[n].down = server[i].down;  
  134.                 n++;  
  135.             }  
  136.         }  
  137.   
  138.     //后备服务器挂载  
  139.         peers->next = backup;  
  140.   
  141.         return NGX_OK;  
  142.     }  
  143.   
  144.     //us参数中服务器指针为空,例如用户直接在proxy_pass等指令后配置后端服务器地址  
  145.     /* an upstream implicitly defined by proxy_pass, etc. */  
  146.   
  147.     if (us->port == 0) {  
  148.         ngx_log_error(NGX_LOG_EMERG, cf->log, 0,  
  149.                       "no port in upstream \"%V\" in %s:%ui",  
  150.                       &us->host, us->file_name, us->line);  
  151.         return NGX_ERROR;  
  152.     }  
  153.   
  154.     ngx_memzero(&u, sizeof(ngx_url_t));  
  155.   
  156.     u.host = us->host;  
  157.     u.port = us->port;  
  158.   
  159.     //IP地址解析  
  160.     if (ngx_inet_resolve_host(cf->pool, &u) != NGX_OK) {  
  161.         if (u.err) {  
  162.             ngx_log_error(NGX_LOG_EMERG, cf->log, 0,  
  163.                           "%s in upstream \"%V\" in %s:%ui",  
  164.                           u.err, &us->host, us->file_name, us->line);  
  165.         }  
  166.   
  167.         return NGX_ERROR;  
  168.     }  
  169.   
  170.     n = u.naddrs;  
  171.   
  172.     peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_rr_peers_t)  
  173.                               + sizeof(ngx_http_upstream_rr_peer_t) * (n - 1));  
  174.     if (peers == NULL) {  
  175.         return NGX_ERROR;  
  176.     }  
  177.   
  178.     peers->single = (n == 1);  
  179.     peers->number = n;  
  180.     peers->weighted = 0;  
  181.     peers->total_weight = n;  
  182.     peers->name = &us->host;  
  183.   
  184.     for (i = 0; i < u.naddrs; i++) {  
  185.         peers->peer[i].sockaddr = u.addrs[i].sockaddr;  
  186.         peers->peer[i].socklen = u.addrs[i].socklen;  
  187.         peers->peer[i].name = u.addrs[i].name;  
  188.         peers->peer[i].weight = 1;  
  189.         peers->peer[i].effective_weight = 1;  
  190.         peers->peer[i].current_weight = 0;  
  191.         peers->peer[i].max_fails = 1;  
  192.         peers->peer[i].fail_timeout = 10;  
  193.     }  
  194.   
  195.     us->peer.data = peers;  
  196.   
  197.     /* implicitly defined upstream has no backup servers */  
  198.   
  199.     return NGX_OK;  
  200. }  

选择后端服务器

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

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

在前面的函数ngx_http_upstream_init_round_robin()中设置的回调函数us->peer.init,它的调用位置是函数ngx_http_upstream_init_request中(ngx_http_upstream.c):
[cpp]  view plain copy print ?
  1. static void  
  2. ngx_http_upstream_init_request(ngx_http_request_t *r)  
  3. {  
  4. ...  
  5. if (uscf->peer.init(r, uscf) != NGX_OK) {  
  6.         ngx_http_upstream_finalize_request(r, u,  
  7.                                            NGX_HTTP_INTERNAL_SERVER_ERROR);  
  8.         return;  
  9.     }  
  10.   
  11.     ngx_http_upstream_connect(r, u);  
  12. }  

即在针对每个请求选择后端服务器之前被调用。

下面看看函数ngx_http_upstream_init_round_robin_peer()完成了哪些工作。
它除了完成初始化工作之外,另外的核心工作是设置回调指针。

函数ngx_http_upstream_init_round_robin_peer的完整代码(ngx_http_upstream_round_robin.c):
[cpp]  view plain copy print ?
  1. //函数:  
  2. //功能:针对每个请求选择后端服务器前做一些初始化工作  
  3. ngx_int_t  
  4. ngx_http_upstream_init_round_robin_peer(ngx_http_request_t *r,  
  5.     ngx_http_upstream_srv_conf_t *us)  
  6. {  
  7.     ngx_uint_t                         n;  
  8.     ngx_http_upstream_rr_peer_data_t  *rrp;  
  9.   
  10.     rrp = r->upstream->peer.data;  
  11.   
  12.     if (rrp == NULL) {  
  13.         rrp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_rr_peer_data_t));  
  14.         if (rrp == NULL) {  
  15.             return NGX_ERROR;  
  16.         }  
  17.   
  18.         r->upstream->peer.data = rrp;  
  19.     }  
  20.   
  21.     rrp->peers = us->peer.data;  
  22.     rrp->current = 0;  
  23.   
  24.     //n取值为:非后备服务器和后备服务器列表中个数较大的那个值  
  25.     n = rrp->peers->number;  
  26.   
  27.     if (rrp->peers->next && rrp->peers->next->number > n) {  
  28.         n = rrp->peers->next->number;  
  29.     }  
  30.   
  31.     //如果n小于一个指针变量所能表示的范围  
  32.     if (n <= 8 * sizeof(uintptr_t)) {  
  33.     //直接使用已有的指针类型的data变量做位图(tried是位图,用来标识在一轮选择中,各个后端服务器是否已经被选择过)  
  34.         rrp->tried = &rrp->data;  
  35.         rrp->data = 0;  
  36.   
  37.     } else {  
  38.     //否则从内存池中申请空间  
  39.         n = (n + (8 * sizeof(uintptr_t) - 1)) / (8 * sizeof(uintptr_t));  
  40.   
  41.         rrp->tried = ngx_pcalloc(r->pool, n * sizeof(uintptr_t));  
  42.         if (rrp->tried == NULL) {  
  43.             return NGX_ERROR;  
  44.         }  
  45.     }  
  46.   
  47.     //回调函数设置  
  48.     r->upstream->peer.get = ngx_http_upstream_get_round_robin_peer;  
  49.     r->upstream->peer.free = ngx_http_upstream_free_round_robin_peer;  
  50.     r->upstream->peer.tries = rrp->peers->number;  
  51. #if (NGX_HTTP_SSL)  
  52.     r->upstream->peer.set_session =  
  53.                                ngx_http_upstream_set_round_robin_peer_session;  
  54.     r->upstream->peer.save_session =  
  55.                                ngx_http_upstream_save_round_robin_peer_session;  
  56. #endif  
  57.   
  58.     return NGX_OK;  
  59. }  

对后端服务器进行一次选择
对后端服务器做一次选择的逻辑在函数ngx_http_upstream_get_round_robin_peer内,流程图如下:
NGX负载均衡策略_第1张图片
代码如下:
[cpp]  view plain copy print ?
  1. //函数:  
  2. //功能:对后端服务器做一次选择  
  3. ngx_int_t  
  4. ngx_http_upstream_get_round_robin_peer(ngx_peer_connection_t *pc, void *data)  
  5. {  
  6.     ngx_http_upstream_rr_peer_data_t  *rrp = data;  
  7.   
  8.     ngx_int_t                      rc;  
  9.     ngx_uint_t                     i, n;  
  10.     ngx_http_upstream_rr_peer_t   *peer;  
  11.     ngx_http_upstream_rr_peers_t  *peers;  
  12.   
  13.     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,  
  14.                    "get rr peer, try: %ui", pc->tries);  
  15.   
  16.     /* ngx_lock_mutex(rrp->peers->mutex); */  
  17.   
  18.     pc->cached = 0;  
  19.     pc->connection = NULL;  
  20.   
  21.     //如果只有一台后端服务器,Nginx直接选择并返回  
  22.     if (rrp->peers->single) {  
  23.         peer = &rrp->peers->peer[0];  
  24.   
  25.         if (peer->down) {  
  26.             goto failed;  
  27.         }  
  28.   
  29.     } else {  
  30.     //有多台后端服务器  
  31.         /* there are several peers */  
  32.     //按照各台服务器的当前权值进行选择  
  33.         peer = ngx_http_upstream_get_peer(rrp);  
  34.   
  35.         if (peer == NULL) {  
  36.             goto failed;  
  37.         }  
  38.   
  39.         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,  
  40.                        "get rr peer, current: %ui %i",  
  41.                        rrp->current, peer->current_weight);  
  42.     }  
  43.   
  44.     //设置连接的相关属性  
  45.     pc->sockaddr = peer->sockaddr;  
  46.     pc->socklen = peer->socklen;  
  47.     pc->name = &peer->name;  
  48.   
  49.     /* ngx_unlock_mutex(rrp->peers->mutex); */  
  50.   
  51.     if (pc->tries == 1 && rrp->peers->next) {  
  52.         pc->tries += rrp->peers->next->number;  
  53.     }  
  54.   
  55.     return NGX_OK;  
  56.   
  57.     //选择失败,转向后备服务器  
  58. failed:  
  59.   
  60.     peers = rrp->peers;  
  61.   
  62.     if (peers->next) {  
  63.   
  64.         /* ngx_unlock_mutex(peers->mutex); */  
  65.   
  66.         ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0, "backup servers");  
  67.   
  68.         rrp->peers = peers->next;  
  69.         pc->tries = rrp->peers->number;  
  70.   
  71.         n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))  
  72.                 / (8 * sizeof(uintptr_t));  
  73.   
  74.         for (i = 0; i < n; i++) {  
  75.              rrp->tried[i] = 0;  
  76.         }  
  77.   
  78.         rc = ngx_http_upstream_get_round_robin_peer(pc, rrp);  
  79.   
  80.         if (rc != NGX_BUSY) {  
  81.             return rc;  
  82.         }  
  83.   
  84.         /* ngx_lock_mutex(peers->mutex); */  
  85.     }  
  86.   
  87.     /* all peers failed, mark them as live for quick recovery */  
  88.   
  89.     for (i = 0; i < peers->number; i++) {  
  90.         peers->peer[i].fails = 0;  
  91.     }  
  92.   
  93.     /* ngx_unlock_mutex(peers->mutex); */  
  94.   
  95.     pc->name = peers->name;  
  96.   
  97.     //如果后备服务器也选择失败,则返回NGX_BUSY  
  98.     return NGX_BUSY;  
  99. }  

后端服务器权值计算在函数ngx_http_upstream_get_peer中。
[cpp]  view plain copy print ?
  1. //按照当前各服务器权值进行选择  
  2. static ngx_http_upstream_rr_peer_t *  
  3. ngx_http_upstream_get_peer(ngx_http_upstream_rr_peer_data_t *rrp)  
  4. {  
  5.     time_t                        now;  
  6.     uintptr_t                     m;  
  7.     ngx_int_t                     total;  
  8.     ngx_uint_t                    i, n;  
  9.     ngx_http_upstream_rr_peer_t  *peer, *best;  
  10.   
  11.     now = ngx_time();  
  12.   
  13.     best = NULL;  
  14.     total = 0;  
  15.   
  16.     for (i = 0; i < rrp->peers->number; i++) {  
  17.     //计算当前服务器的标记位在位图中的位置  
  18.         n = i / (8 * sizeof(uintptr_t));  
  19.         m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));  
  20.   
  21.     //已经选择过,跳过  
  22.         if (rrp->tried[n] & m) {  
  23.             continue;  
  24.         }  
  25.   
  26.     //当前服务器对象  
  27.         peer = &rrp->peers->peer[i];  
  28.   
  29.     //当前服务器已宕机,排除  
  30.         if (peer->down) {  
  31.             continue;  
  32.         }  
  33.   
  34.     //根据指定一段时间内最大失败次数做判断  
  35.         if (peer->max_fails  
  36.             && peer->fails >= peer->max_fails  
  37.             && now - peer->checked <= peer->fail_timeout)  
  38.         {  
  39.             continue;  
  40.         }  
  41.   
  42.         peer->current_weight += peer->effective_weight;  
  43.         total += peer->effective_weight;  
  44.   
  45.         if (peer->effective_weight < peer->weight) {  
  46.             peer->effective_weight++;  
  47.         }  
  48.   
  49.         if (best == NULL || peer->current_weight > best->current_weight) {  
  50.             best = peer;  
  51.         }  
  52.     }  
  53.   
  54.     if (best == NULL) {  
  55.         return NULL;  
  56.     }  
  57.   
  58.     //所选择的服务器在服务器列表中的位置  
  59.     i = best - &rrp->peers->peer[0];  
  60.   
  61.     rrp->current = i;  
  62.   
  63.     n = i / (8 * sizeof(uintptr_t));  
  64.     m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));  
  65.   
  66.     //位图相应位置置位  
  67.     rrp->tried[n] |= m;  
  68.   
  69.     best->current_weight -= total;  
  70.     best->checked = now;  
  71.   
  72.     return best;  
  73. }  

要理解这个函数的工作原理,先要区分下表示服务的ngx_http_upstream_rr_peer_t结构体中的一下三个成员变量:
[cpp]  view plain copy print ?
  1. ngx_int_t                       current_weight;  
  2. ngx_int_t                       effective_weight;  
  3. ngx_int_t                       weight;  

网上搜索到的解释:
effective_weight相当于质量(来源于配置的weight),current_weight相当于重量。前者反应本质,一般是不变的。current_weight是运行时的动态权值,它的变化基于effective_weight。但是effective_weight在其对应的peer服务异常时,会被调低,当服务恢复正常时,effective_weight会逐渐恢复到实际值(配置的weight)。

下面我们结合具体的代码来看。
它们在函数ngx_http_upstream_init_round_robin中被初始化:
[cpp]  view plain copy print ?
  1. for (i = 0; i < us->servers->nelts; i++) {  
  2.            for (j = 0; j < server[i].naddrs; j++) {  
  3.                if (server[i].backup) {  
  4.                    continue;  
  5.                }  
  6.   
  7.                peers->peer[n].weight = server[i].weight;  
  8.                peers->peer[n].effective_weight = server[i].weight;  
  9.                peers->peer[n].current_weight = 0;  
  10.                n++;  
  11.            }  
  12.        }  
  13.   
  14.        /* backup servers */  
  15.        for (i = 0; i < us->servers->nelts; i++) {  
  16.            for (j = 0; j < server[i].naddrs; j++) {  
  17.                if (!server[i].backup) {  
  18.                    continue;  
  19.                }  
  20.   
  21.                backup->peer[n].weight = server[i].weight;  
  22.                backup->peer[n].effective_weight = server[i].weight;  
  23.                backup->peer[n].current_weight = 0;  
  24.   
  25.                n++;  
  26.            }  
  27.        }  
  28.   
  29.     /* an upstream implicitly defined by proxy_pass, etc. */  
  30.    for (i = 0; i < u.naddrs; i++) {  
  31.        peers->peer[i].weight = 1;  
  32.        peers->peer[i].effective_weight = 1;  
  33.        peers->peer[i].current_weight = 0;  
  34.    }  

可以看到weight、effective_weight都是初始化为配置项中的weight值。current_weight初始化为0.

下面分析这三个变量在负载均衡过程中的变化。
weight的值在整个运行过程中不发生变化。
total变量记录了针对一个服务列表的一次轮询过程中轮询到的所有服务的effective_weight总和。在每一次针对服务列表的轮询之前会置为为0.
遍历服务列表的过程中,每遍历到一个服务,会在该服务的current_weight上加上其对应的effective_weight。这个是累加。如果对统一的服务列表进行另一次轮询,那么会在前面计算的current_weight的基础之上再加上effective_weight。
轮询策略是取current_weight最大的服务器。每次取到后端服务(用best表示)后,都会把该对象peer的current_weight减去total的值。因为该服务刚被选中过,因此要降低权值。
关于effective_weight的变化,有两处,一个是在函数ngx_http_upstream_get_peer中:
[cpp]  view plain copy print ?
  1. //服务正常,effective_weight 逐渐恢复正常      
  2. if (peer->effective_weight < peer->weight) {  
  3.     peer->effective_weight++;  
  4. }  

另一处是在释放后端服务的函数ngx_http_upstream_free_round_robin_peer中:
[cpp]  view plain copy print ?
  1. if (peer->max_fails) {  
  2.      //服务发生异常时,调低effective_weight  
  3.     peer->effective_weight -= peer->weight / peer->max_fails;  
  4. }  

权重高的会优先被选中,而且被选中的频率也更高。权重低的也会由于权重逐渐增长获得被选中的机会。

下面给出一个加权轮询的选择实例(来自http://blog.sina.com.cn/s/blog_7303a1dc01014i0j.html):

selected server

current_weight beforeselected

current_weight afterselected

a

{ 5, 1, 2 }

{ -3, 1, 2 }

c

{ 2, 2, 4 }

{ 2, 2, -4 }

a

{ 7, 3, -2 }

{ -1, 3, -2 }

a

{ 4, 4, 0 }

{ -4, 4, 0 }

b

{ 1, 5, 2 }

{ 1, -3, 2 }

a

{ 6, -2, 4 }

{ -2, -2, 4 }

c

{ 3, -1, 6 }

{ 3, -1, -2 }

a

{ 8, 0, 0 }

{ 0, 0, 0 }



释放后端服务器

连接后端服务器并且正常处理当前客户端请求后需释放后端服务器。如果在某一轮选择里,某次选择的服务器因连接失败或请求处理失败而需要重新进行选择,那么这时候就需要做一些额外的处理。
[cpp]  view plain copy print ?
  1. //函数:  
  2. //功能:释放后端服务器  
  3. void  
  4. ngx_http_upstream_free_round_robin_peer(ngx_peer_connection_t *pc, void *data,  
  5.     ngx_uint_t state)  
  6. {  
  7.     ngx_http_upstream_rr_peer_data_t  *rrp = data;  
  8.   
  9.     time_t                       now;  
  10.     ngx_http_upstream_rr_peer_t  *peer;  
  11.   
  12.     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,  
  13.                    "free rr peer %ui %ui", pc->tries, state);  
  14.   
  15.     /* TODO: NGX_PEER_KEEPALIVE */  
  16.   
  17.     //后端服务只有一个  
  18.     if (rrp->peers->single) {  
  19.         pc->tries = 0;  
  20.         return;  
  21.     }  
  22.   
  23.     peer = &rrp->peers->peer[rrp->current];  
  24.   
  25.     //在某一轮选择里,某次选择的服务器因连接失败或请求处理失败而需要重新进行选择  
  26.     if (state & NGX_PEER_FAILED) {  
  27.         now = ngx_time();  
  28.   
  29.         /* ngx_lock_mutex(rrp->peers->mutex); */  
  30.   
  31.     //已尝试失败次数加一  
  32.         peer->fails++;  
  33.         peer->accessed = now;  
  34.         peer->checked = now;  
  35.   
  36.     //如果有最大失败次数限制  
  37.         if (peer->max_fails) {  
  38.         //服务发生异常时,调低effective_weight  
  39.             peer->effective_weight -= peer->weight / peer->max_fails;  
  40.         }  
  41.   
  42.         ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,  
  43.                        "free rr peer failed: %ui %i",  
  44.                        rrp->current, peer->effective_weight);  
  45.   
  46.     //effective_weight总大于0  
  47.         if (peer->effective_weight < 0) {  
  48.             peer->effective_weight = 0;  
  49.         }  
  50.   
  51.         /* ngx_unlock_mutex(rrp->peers->mutex); */  
  52.   
  53.     } else {  
  54.   
  55.         /* mark peer live if check passed */  
  56.   
  57.         if (peer->accessed < peer->checked) {  
  58.             peer->fails = 0;  
  59.         }  
  60.     }  
  61.   
  62.     //ngx_peer_connection_t结构体中tries字段:  
  63.     //表示在连接一个远端服务器时,当前连接出现异常失败后可以重试的次数,也就是允许失败的次数  
  64.     if (pc->tries) {  
  65.         pc->tries--;  
  66.     }  
  67.   
  68.     /* ngx_unlock_mutex(rrp->peers->mutex); */  
  69. }  

整个加权轮询的流程

整个加权轮询的流程图如下:
NGX负载均衡策略_第2张图片
说明:
首先是全局初始化,由函数ngx_http_upstream_init_round_robin完成,它在函数ngx_http_upstream_init_main_conf中被调用,代码:
[cpp]  view plain copy print ?
  1. static char *  
  2. ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)  
  3. {      
  4. ...  
  5.     for (i = 0; i < umcf->upstreams.nelts; i++) {  
  6.         //全局初始化  
  7.         init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:  
  8.                                             ngx_http_upstream_init_round_robin;  
  9.   
  10.         if (init(cf, uscfp[i]) != NGX_OK) {  
  11.             return NGX_CONF_ERROR;  
  12.         }  
  13.     }  
  14. ...  
  15. }  
收到客户请求之后,针对当前请求进行初始化,完成此功能的函数是ngx_http_upstream_init_round_robin_peer,它在函数ngx_http_upstream_init_request中被调用:
[cpp]  view plain copy print ?
  1. static void  
  2. ngx_http_upstream_init_request(ngx_http_request_t *r)  
  3. {  
  4. ...  
  5. if (uscf->peer.init(r, uscf) != NGX_OK) {  
  6.         ngx_http_upstream_finalize_request(r, u,  
  7.                                            NGX_HTTP_INTERNAL_SERVER_ERROR);  
  8.         return;  
  9.     }  
  10.   
  11.     ngx_http_upstream_connect(r, u);  
  12. }  
然后是针对每个请求选择后端服务器,实现此功能的函数是ngx_http_upstream_get_round_robin_peer。它在函数ngx_event_connect_peer中被调用:
[cpp]  view plain copy print ?
  1. //函数:连接后端upstream  
  2. ngx_int_t  
  3. ngx_event_connect_peer(ngx_peer_connection_t *pc)  
  4. {  
  5. ...  
  6.     //此处调用选择后端服务器功能函数ngx_http_upstream_get_round_robin_peer  
  7.     rc = pc->get(pc, pc->data);  
  8.     if (rc != NGX_OK) {  
  9.         return rc;  
  10.     }  
  11.   
  12.     s = ngx_socket(pc->sockaddr->sa_family, SOCK_STREAM, 0);  
  13. ...  
  14. }  
之后是测试连接ngx_http_upstream_test_connect。它在函数ngx_http_upstream_send_request被调用:
[cpp]  view plain copy print ?
  1. //函数:发送数据到后端upstream  
  2. static void  
  3. ngx_http_upstream_send_request(ngx_http_request_t *r, ngx_http_upstream_t *u)  
  4. {  
  5. ...  
  6.     if (!u->request_sent && ngx_http_upstream_test_connect(c) != NGX_OK) {  
  7.         //测试连接失败  
  8.         ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);  
  9.         return;  
  10.     }  
  11. ...  
  12. }  

如果测试成功,继续后续处理,并释放后端服务器。
如果测试失败,调用ngx_http_upstream_next函数,这个函数可能再次调用peer.get调用别的连接。
[cpp]  view plain copy print ?
  1. static void  
  2. ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u,  
  3.     ngx_uint_t ft_type)  
  4. {  
  5. ...  
  6.     if (u->peer.sockaddr) {  
  7.   
  8.         if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) {  
  9.             state = NGX_PEER_NEXT;  
  10.         } else {  
  11.             state = NGX_PEER_FAILED;  
  12.         }  
  13.         //释放后端服务器  
  14.         u->peer.free(&u->peer, u->peer.data, state);  
  15.         u->peer.sockaddr = NULL;  
  16.     }  
  17. ...  
  18. if (status) {  
  19.         u->state->status = status;  
  20.   
  21.         if (u->peer.tries == 0 || !(u->conf->next_upstream & ft_type)) {  
  22.   
  23. #if (NGX_HTTP_CACHE)  
  24.   
  25.             if (u->cache_status == NGX_HTTP_CACHE_EXPIRED  
  26.                 && (u->conf->cache_use_stale & ft_type))  
  27.             {  
  28.                 ngx_int_t  rc;  
  29.   
  30.                 rc = u->reinit_request(r);  
  31.   
  32.                 if (rc == NGX_OK) {  
  33.                     u->cache_status = NGX_HTTP_CACHE_STALE;  
  34.                     rc = ngx_http_upstream_cache_send(r, u);  
  35.                 }  
  36.   
  37.                 ngx_http_upstream_finalize_request(r, u, rc);  
  38.                 return;  
  39.             }  
  40. #endif  
  41.             //结束请求  
  42.             ngx_http_upstream_finalize_request(r, u, status);  
  43.             return;  
  44.         }  
  45.     }  
  46. ...  
  47. //再次发起连接  
  48. ngx_http_upstream_connect(r, u);  
  49. }  

函数ngx_http_upstream_connect中会调用ngx_event_connect_peer,进而调用ngx_http_upstream_get_round_robin_peer再次选择后端服务器。

你可能感兴趣的:(【14】Nginx)