Nginx版本:1.9.1

我的博客:http://blog.csdn.net/zhangskd

算法介绍

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

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

负载较高。在这种场景下,把请求转发给连接数较少的后端,能够达到更好的负载均衡效果,这就是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指令支持的属性

  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很简单。

  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实例。

  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)。

  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. }

Nginx的负载均衡 - 最少连接 (least_conn)的更多相关文章

  1. Nginx的负载均衡 - 整体架构

    Nginx的负载均衡 - 整体架构 Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd Nginx目前提供的负载均衡模块: ngx_http_upstre ...

  2. Nginx实现负载均衡&Nginx缓存功能

    一.Nginx是什么 Nginx (engine x) 是一个高性能的HTTP和反向代理服务器,也是一个IMAP/POP3/SMTP服务器.Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambl ...

  3. 死磕nginx系列--使用nginx做负载均衡

    使用nginx做负载均衡的两大模块: upstream 定义负载节点池. location 模块 进行URL匹配. proxy模块 发送请求给upstream定义的节点池. upstream模块解读 ...

  4. 关于Nginx的负载均衡

    一.关于Nginx的负载均衡 在服务器集群中,Nginx起到一个代理服务器的角色(即反向代理),为了避免单独一个服务器压力过大,将来自用户的请求转发给不同的服务器.详情请查看我的另一篇博客. 二.Ng ...

  5. Nginx搭建负载均衡集群

    (1).实验环境 youxi1 192.168.5.101 负载均衡器 youxi2 192.168.5.102 主机1 youxi3 192.168.5.103 主机2 (2).Nginx负载均衡策 ...

  6. NginX——配置负载均衡

    A.            在http模块加上upstream配置 upstream www.myweb.com { server  127.0.0.1:9100 weight=3; server  ...

  7. nginx实现负载均衡、缓存功能实战

    nginx实现负载均衡.缓存功能实战 什么是正向代理?应用场景:翻墙 什么是反向代理?例如:haproxy和nginx   Nginx实现反向代理 nginx代理基于是ngx_http_proxy_m ...

  8. 如何利用nginx实现负载均衡(总结)

    如何利用nginx实现负载均衡(总结) 一.总结 一句话总结: 推荐使用nginx七层(应用层)负载均衡的实现:配置那是相当的简单 1.nginx配置实例? |||-begin #这里的域名要和下面p ...

  9. Nginx之负载均衡配置(一)

    前文我们聊了下nginx作为反向代理服务器,代理后端动态应用服务器的配置,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/12430543.html:今天我们来聊 ...

随机推荐

  1. [USACO 09FEB]Fair Shuttle

    Description 逛逛集市,兑兑奖品,看看节目对农夫约翰来说不算什么,可是他的奶牛们非常缺乏锻炼——如果要逛完一整天的集市,他们一定会筋疲力尽的.所以为了让 奶牛们也能愉快地逛集市,约翰准备让奶 ...

  2. bzoj 4945: [Noi2017]游戏

    Description Solution 首先我们发现一个位置如果不是 \('x'\),那么就只有两种选择 而 \('x'\) 的个数小于等于 \(8\),直接枚举是哪个就好了 然后就是 \(2-sa ...

  3. POJ 3276 Face The Right Way

    Description Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing ...

  4. hdu 5533(几何水)

    Input The first line contains a integer T indicating the total number of test cases. Each test case ...

  5. [usaco6.1.1Postal Vans]

    来自FallDream的博客,未经允许,请勿转载,谢谢. 给你一个4*n的棋盘,问从(1,1)出发恰好经过所有格子一次的走法数量.(n<=1000) 插头dp,用f[i][j][k]表示转移到第 ...

  6. bzoj2007 NOI2010 网络流转对偶图

    2007: [Noi2010]海拔 Time Limit: 20 Sec  Memory Limit: 552 MBSubmit: 2775  Solved: 1331[Submit][Status] ...

  7. js 删除字符串中所有空格

    //去除头尾和中间空格,制表符 function trimSpaces(Str){               var ResultStr = "";               ...

  8. 一口一口吃掉Hibernate(七)——继承映射

    前几篇博文中讲到了常用的几种关联映射.其实hibernate中还有一种"省劲儿"的映射,那就是--"继承映射". 学了这么多的关系映射了,继承映射,从字面上也能 ...

  9. pm2进阶使用

    启用集群模式 只需要在启动应用时带上i参数 pm2 start app.js -i max max:意味着PM2将自动检测可用的CPU数量和运行多个进程可以在负载均衡模式(但是不推荐使用) 或者使用j ...

  10. 手把手教你全家桶之React(一)

    前言 最近项目用到react,其实前年我就开始接触react,时光匆匆,一直没有时间整理下来(太懒啦)!如今再次用到,称工作间隙,对全家桶做一次总结,项目源码地址.废话不多说,上码. 创建一个文件目录 ...