Nginx的负载均衡 - 最少连接 (least_conn)

算法介绍

 

我们知道轮询算法是把请求平均的转发给各个后端,使它们的负载大致相同。

这有个前提,就是每个请求所占用的后端时间要差不多,如果有些请求占用的时间很长,会导致其所在的后端

负载较高。在这种场景下,把请求转发给连接数较少的后端,能够达到更好的负载均衡效果,这就是least_conn算法。

 

least_conn算法很简单,首选遍历后端集群,比较每个后端的conns/weight,选取该值最小的后端。

如果有多个后端的conns/weight值同为最小的,那么对它们采用加权轮询算法。

 

指令的解析函数

 

在一个upstream配置块中,如果有least_conn指令,表示使用least connected负载均衡算法。

least_conn指令的解析函数为ngx_http_upstream_least_conn,主要做了:

指定初始化此upstream块的函数uscf->peer.init_upstream

指定此upstream块中server指令支持的属性

[java]   view plain  copy
  1. static char *ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *ctx)  
  2. {  
  3.     ngx_http_upstream_srv_conf_t *uscf;  
  4.       
  5.     /* 获取所在的upstream{}块 */  
  6.     uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);  
  7.   
  8.    if (uscf->peer.init_upstream)  
  9.        ngx_conf_log_error(NGX_LOG_WARN, cf, 0"load balancing method redefined");  
  10.   
  11.     /* 此upstream块的初始化函数 */  
  12.     uscf->peer.init_upstream = ngx_http_upstream_init_least_conn;  
  13.   
  14.     /* 指定此upstream块中server指令支持的属性 */  
  15.     uscf->flags = NGX_HTTP_UPSTREAM_CREATE  
  16.         | NGX_HTTP_UPSTREAM_WEIGHT  
  17.         | NGX_HTTP_UPSTREAM_MAX_FAILS  
  18.         | NGX_HTTP_UPSTREAM_FAIL_TIMEOUT  
  19.         | NGX_HTTP_UPSTREAM_DOWN  
  20.         | NGX_HTTP_UPSTREAM_BACKUP;  
  21.   
  22.     return NGX_CONF_OK;  
  23. }  

以下是upstream块中server指令可支持的属性

NGX_HTTP_UPSTREAM_CREATE:检查是否重复创建,以及必要的参数是否填写

NGX_HTTP_UPSTREAM_WEIGHT:server指令支持weight属性

NGX_HTTP_UPSTREAM_MAX_FAILS:server指令支持max_fails属性

NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:server指令支持fail_timeout属性

NGX_HTTP_UPSTREAM_DOWN:server指令支持down属性

NGX_HTTP_UPSTREAM_BACKUP:server指令支持backup属性

 

初始化upstream块

 

执行完指令的解析函数后,紧接着会调用所有HTTP模块的init main conf函数。

在执行ngx_http_upstream_module的init main conf函数时,会调用所有upstream块的初始化函数。

对于使用least_conn的upstream块,其初始化函数(peer.init_upstream)就是上一步中指定

ngx_http_upstream_init_least_conn,它主要做了:

调用round robin的upstream块初始化函数来创建和初始化后端集群,保存该upstream块的数据

指定per request的负载均衡初始化函数peer.init

 

因为脏活累活都让round robin的upstream块初始化函数给干了,所以ngx_http_upstream_init_least_conn很简单。

[java]   view plain  copy
  1. static ngx_int_t ngx_http_upstream_init_least_conn(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)  
  2. {  
  3.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0"init least conn");  
  4.       
  5.     /* 使用round robin的upstream块初始化函数,创建和初始化后端集群 */  
  6.     if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK)  
  7.         return NGX_ERROR;  
  8.   
  9.     /* 重新设置per request的负载均衡初始化函数 */  
  10.     us->peer.init = ngx_http_upstream_init_least_conn_peer;  
  11.   
  12.     return NGX_OK;  
  13. }  

 

初始化请求的负载均衡数据 

 

收到一个请求后,一般使用的反向代理模块(upstream模块)为ngx_http_proxy_module,

其NGX_HTTP_CONTENT_PHASE阶段的处理函数为ngx_http_proxy_handler,在初始化upstream机制的

ngx_http_upstream_init_request函数中,调用在第二步中指定的peer.init,主要用于初始化请求的负载均衡数据。

对于least_conn,peer.init实例为ngx_http_upstream_init_least_conn_peer,主要做了:

调用round robin的peer.init来初始化请求的负载均衡数据

重新指定peer.get,用于从集群中选取一台后端服务器

 

least_conn的per request负载均衡数据和round robin的完全一样,都是一个ngx_http_upstream_rr_peer_data_t实例。

[java]   view plain  copy
  1. static ngx_int_t ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)  
  2. {  
  3.     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0"init least conn peer");  
  4.       
  5.     /* 调用round robin的per request负载均衡初始化函数 */  
  6.     if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK)  
  7.         return NGX_ERROR;  
  8.   
  9.     /* 指定peer.get,用于从集群中选取一台后端 */  
  10.     r->upstream->peer.get = ngx_http_upstream_get_least_conn_peer;  
  11.   
  12.     return NGX_OK;  
  13. }  

 

选取一台后端服务器

 

一般upstream块中会有多台后端,那么对于本次请求,要选定哪一台后端呢?

这时候第三步中r->upstream->peer.get指向的函数就派上用场了:

采用least connected算法,从集群中选出一台后端来处理本次请求。 选定后端的地址保存在pc->sockaddr,pc为主动连接。

函数的返回值:

NGX_DONE:选定一个后端,和该后端的连接已经建立。之后会直接发送请求。

NGX_OK:选定一个后端,和该后端的连接尚未建立。之后会和后端建立连接。

NGX_BUSY:所有的后端(包括备份集群)都不可用。之后会给客户端发送502(Bad Gateway)。

[java]   view plain  copy
  1. static ngx_int_t ngx_http_upstream_get_least_conn_peer(ngx_peer_connection_t *pc, void *data)  
  2. {  
  3.     ngx_http_upstream_rr_peer_data_t *rrp = data; /* 请求的负载均衡数据 */  
  4.     time_t now;  
  5.     uintptr_t m;  
  6.     ngx_int_t rc, total;  
  7.     ngx_uint_t i, n, p, many;  
  8.     ngx_http_upstream_rr_peer_t *peer, *best;  
  9.     ngx_http_upstream_rr_peers_t *peers;  
  10.     ...  
  11.     /* 如果集群只包含一台后端,那么就不用选了 */  
  12.     if (rrp->peers->single)  
  13.         return ngx_http_upstream_get_round_robin_peer(pc, rrp);  
  14.   
  15.     pc->cached = 0;  
  16.     pc->connection = NULL;  
  17.     now = ngx_time();  
  18.     peers = rrp->peers; /* 后端集群 */  
  19.     best = NULL;  
  20.     total = 0;  
  21.     ...  
  22.     /* 遍历后端集群 */  
  23.     for (peer = peers->peer, i = 0; peer; peer = peer->next, i++)  
  24.     {  
  25.         /* 检查此后端在状态位图中对应的位,为1时表示不可用 */   
  26.         n = i / (8 * sizeof(uintptr_t));  
  27.         m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));  
  28.   
  29.         if (rrp->tried[n] & m)  
  30.            continue;  
  31.   
  32.         /* server指令中携带了down属性,表示后端永久不可用 */  
  33.         if (peer->down)  
  34.             continue;  
  35.   
  36.         /* 在一段时间内,如果此后端服务器的失败次数,超过了允许的最大值,那么不允许使用此后端了 */  
  37.          if (peer->max_fails && peer->fails >= peer->max_fails  
  38.              && now - peer->checked <= peer->fail_timeout)  
  39.             continue;  
  40.   
  41.         /* select peer with least number of connections; if there are multiple peers 
  42.          * with the same number of connections, select based on round-robin. 
  43.          */  
  44.         /* 比较各个后端的conns/weight,选取最小者; 
  45.          * 如果有多个最小者,记录第一个的序号p,且设置many标志。 
  46.          */  
  47.         if (best == NULL || peer->conns * best->weight < best->conns * peer->weight)  
  48.         {  
  49.             best = peer;  
  50.             many = 0;  
  51.             p = i;  
  52.         } else if (peer->conns * best->weight == best->conns * peer->weight)  
  53.             many = 1;  
  54.     }  
  55.   
  56.     /* 找不到可用的后端 */  
  57.     if (best == NULL)  
  58.         goto failed;  
  59.   
  60.     /* 如果有多个后端的conns/weight同为最小者,则对它们使用轮询算法 */  
  61.     if (many) {  
  62.         for (peer = best, i = p; peer; peer->peer->next, i++)  
  63.         {  
  64.             /* 检查此后端在状态位图中对应的位,为1时表示不可用 */   
  65.             n = i / (8 * sizeof(uintptr_t));  
  66.             m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));  
  67.   
  68.            /* server指令中携带了down属性,表示后端永久不可用 */  
  69.             if (peer->down)  
  70.                 continue;  
  71.   
  72.            /* conns/weight必须为最小的 */  
  73.             if (peer->conns * best->weight != best->conns * peer->weight)  
  74.                 continue;  
  75.   
  76.            /* 在一段时间内,如果此后端服务器的失败次数,超过了允许的最大值,那么不允许使用此后端了 */  
  77.             if (peer->max_fails && peer->fails >= peer->max_fails  
  78.                 && now - peer->checked <= peer->fail_timeout)  
  79.                continue;  
  80.   
  81.             peer->current_weight += peer->effective_weight; /* 对每个后端,增加其当前权重 */  
  82.             total += peer->effective_weight; /* 累加所有后端的有效权重 */  
  83.   
  84.            /* 如果之前此后端发生了失败,会减小其effective_weight来降低它的权重。           
  85.               * 此后在选取后端的过程中,又通过增加其effective_weight来恢复它的权重。           
  86.               */          
  87.             if (peer->effective_weight < peer->weight)   
  88.                 peer->effective_weight++;  
  89.           
  90.             /* 选取当前权重最大者,作为本次选定的后端 */  
  91.             if (best == NULL || peer->current_weight > best->current_weight) {  
  92.                 best = peer;  
  93.                 p = i;  
  94.             }  
  95.         }  
  96.     }  
  97.   
  98.     best->current_weight -= total; /* 如果使用轮询,要降低选定后端的当前权重 */  
  99.   
  100.     /* 更新checked时间 */  
  101.     if (now - best->checked > best->fail_timeout)  
  102.          best->checked = now;  
  103.   
  104.     /* 保存选定的后端服务器的地址,之后会向这个地址发起连接 */  
  105.     pc->sockaddr = best->sockaddr;  
  106.     pc->socklen = best->socklen;  
  107.     pc->name = &best->name;  
  108.   
  109.     best->conns++; /* 增加选定后端的当前连接数 */      
  110.   
  111.     n = p / (8 * sizeof(uintptr_t));  
  112.     m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));  
  113.     rrp->tried[n] |= m; /* 对于此请求,如果之后需要再次选取后端,不能再选取这个后端了 */  
  114.   
  115.     return NGX_OK;  
  116.   
  117. failed:  
  118.     /* 如果不能从集群中选取一台后端,那么尝试备用集群 */  
  119.     if (peers->next) {  
  120.         ...          
  121.         rrp->peers = peers->next;  
  122.         n = (rrp->peers->number + (8 * sizeof(uintptr_t) - 1))  
  123.                 / (8 * sizeof(uintptr_t));  
  124.         for (i = 0; i < n; i++)  
  125.              rrp->tried[i] = 0;  
  126.          
  127.         /* 重新调用本函数 */          
  128.         rc = ngx_http_upstream_get_least_conn_peer(pc, rrp);  
  129.   
  130.         if (rc != NGX_BUSY)  
  131.             return rc;  
  132.     }  
  133.   
  134.     /* all peers failed, mark them as live for quick recovery */  
  135.     for (peer = peers->peer; peer; peer = peer->next) {  
  136.         peer->fails = 0;  
  137.     }  
  138.     pc->name = peers->name;  
  139.     return NGX_BUSY;  
  140. }  

转载于:https://my.oschina.net/zhangjie830621/blog/653089

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值