Nginx版本:1.9.1

算法介绍

当后端是缓存服务器时,经常使用一致性哈希算法来进行负载均衡。

使用一致性哈希的好处在于,增减集群的缓存服务器时,只有少量的缓存会失效,回源量较小。

在nginx+ats / haproxy+squid等CDN架构中,nginx/haproxy所使用的负载均衡算法便是一致性哈希。

我们举个例子来说明一致性哈希的好处。

假设后端集群包含三台缓存服务器,A、B、C。

请求r1、r2落在A上。

请求r3、r4落在B上。

请求r5、r6落在C上。

使用一致性哈希时,当缓存服务器B宕机时,r1/r2会仍然落在A上,r5/r6会仍然落在C上,

也就是说这两台服务器上的缓存都不会失效。r3/r4会被重新分配给A或者C,并产生回源。

使用其它算法,当缓存服务器B宕机时,r1/r2不再落在A上,r5/r6不再落在C上了。

也就是说A、B、C上的缓存都失效了,所有的请求都要回源。

这里不介绍一致性哈希算法的基本原理,如果不了解,先花个10分钟看下这篇文章:

http://www.codeproject.com/Articles/56138/Consistent-hashing

在分析模块代码之前,先来看下nginx所实现的一致性哈希算法。

1. 初始化upstream块

主要工作是创建和初始化真实节点、创建和初始化虚拟节点。

其中真实节点是使用round robin的方法创建的。

Q:总共有多少个虚拟节点,一个真实节点对应多少个虚拟节点?

累加真实节点的权重,算出总的权重值total_weight,虚拟节点的个数一般为total_weight * 160。

一个权重为weight的真实节点,对应的虚拟节点数为weight * 160。

Q:对于每一个真实节点,是如何创建其对应的虚拟节点的?

1. 真实节点的server成员是其server指令的第一个参数,首先把它解析为HOST和PORT。

base_hash = crc32(HOST 0 PORT)

一个真实节点对应weight * 160个虚拟节点,对于每个虚拟节点来说,base_hash都是一样的。

2. 为了使每个虚拟节点的hash值都不同,又引入了PREV_HASH,它是上一个虚拟节点的hash值。

hash = crc32(base_hash PREV_HASH)

3. 虚拟节点的server成员,指向真实节点的server成员。如此一来,通过比较虚拟节点和真实节点的

server成员是否相同,可以判断它们是否是相对应的。

创建和初始化好虚拟节点数组后,对其中的虚拟节点按照hash值进行排序,对于hash值相同的虚拟节点,只保留第一个。

经过上述步骤,我们得到一个所有虚拟节点组成的数组,其元素的hash值有序而不重复。也就是说,ring建立起来了。

2. 初始话请求的负载均衡数据

根据hash指令第一个参数的实时值KEY,KEY一般是$host$uri之类的,计算出本次请求的哈希值。

hash = crc32(KEY)

根据请求的哈希值,在虚拟节点数组中,找到“顺时针方向”最近的一个虚拟节点,其索引为i。

什么叫顺时针方向最近?就是point[i - 1].hash < hash <= point[i].hash。

本次请求就落在该虚拟节点上了,之后交由其对应的真实节点来处理。

3. 选取真实节点

在peer.init中,已经知道请求落在哪个虚拟节点上了。

在peer.get中,需要查找虚拟节点对应的真实节点。

根据虚拟节点的server成员,在真实节点数组中查找server成员相同的、可用的真实节点。

如果找不到,那么沿着顺时针方向,继续查找下一个虚拟节点对应的真实节点。

如果找到了一个,那么就是它了。

如果找到了多个,使用轮询的方法从中选取一个。

4. 缺陷和改进

一个虚拟节点和一个真实节点,是依据它们的server成员来关联的。

这会出现一种情况,一个虚拟节点对应了多个真实节点,因为:

如果server指令的第一个参数为域名,可能解析为多个真实节点,那么这些真实节点的server成员都是一样的。

对于一个请求,计算其KEY的hash值,顺时针找到最近的虚拟节点后,发现该虚拟节点对应了多个真实节点。

使用哪个真实节点呢?本模块就使用轮询的方法,来从多个真实节点中选一个。

但我们知道使用一致性哈希的场景中,真实节点一般是缓存服务器。

一个虚拟节点对应多个真实节点,会导致一个文件被缓存在多个缓存服务器上。

这会增加磁盘的使用量,以及回源量,显然不是我们希望看到的。

解决这个问题的方法其实很简单,就是虚拟节点和真实节点通过name成员来建立关联。

因为就算对应同一条server配置,server的第一个参数为域名,各个真实节点的name成员也是唯一的。

这样一来,找到了一个虚拟节点,就能找到一个唯一的真实节点,不会有上述问题了。

数据结构

1. 真实节点

就是采用round robin算法所创建的后端服务器,类型为ngx_http_upstream_rr_peer_t。

需要注意的是,如果server指令的第一个参数是IP和端口,那么一条server指令只对应一个真实节点。

如果server指令的第一个参数是域名,一条server指令可能对应多个真实节点。

它们的server成员是相同的,可以通过name成员区分。

[java] view plain copy
  1. struct ngx_http_upstream_rr_peer_s {
  2. struct sockaddr *sockaddr; /* 后端服务器的地址 */
  3. socklen_t socklen; /* 地址的长度*/
  4. ngx_str_t name; /* 后端服务器地址的字符串,server.addrs[i].name */
  5. ngx_str_t server; /* server的名称,server.name */
  6. ngx_int_t current_weight; /* 当前的权重,动态调整,初始值为0 */
  7. ngx_int_t effective_weight; /* 有效的权重,会因为失败而降低 */
  8. ngx_int_t weight; /* 配置项指定的权重,固定值 */
  9. ngx_uint_t conns; /* 当前连接数 */
  10. ngx_uint_t fails; /* "一段时间内",已经失败的次数 */
  11. time_t accessed; /* 最近一次失败的时间点 */
  12. time_t checked; /* 用于检查是否超过了"一段时间" */
  13. ngx_uint_t max_fails; /* "一段时间内",最大的失败次数,固定值 */
  14. time_t fail_timeout; /* "一段时间"的值,固定值 */
  15. ngx_uint_t down; /* 服务器永久不可用的标志 */
  16. ...
  17. ngx_http_upstream_rr_peer_t *next; /* 指向下一个后端,用于构成链表 */
  18. ...
  19. } ngx_http_upstream_rr_peer_t;

ngx_http_upstream_rr_peers_t表示一组后端服务器,比如一个后端集群。

[java] view plain copy
  1. struct ngx_http_upstream_rr_peers_s {
  2. ngx_uint_t number; /* 后端服务器的数量 */
  3. ...
  4. ngx_uint_t total_weight; /* 所有后端服务器权重的累加值 */
  5. unsigned single:1; /* 是否只有一台后端服务器 */
  6. unsigned weighted:1; /* 是否使用权重 */
  7. ngx_str_t *name; /* upstream配置块的名称 */
  8. ngx_http_upstream_rr_peers_t *next; /* backup服务器集群 */
  9. ngx_http_upstream_rr_peer_t *peer; /* 后端服务器组成的链表 */
  10. };

2. 虚拟节点

一个真实节点,一般会对应weight * 160个虚拟节点。

虚拟节点的server成员,指向它所归属的真实节点的server成员,如此一来找到了一个虚拟节点后,

就能找到其归属的真实节点。

但这里有一个问题,通过一个虚拟节点的server成员,可能会找到多个真实节点,而不是一个。

因为如果server指令的第一个参数为域名,那么多个真实节点的server成员都是一样的。

[java] view plain copy
  1. typedef struct {
  2. uint32_t hash; /* 虚拟节点的哈希值 */
  3. ngx_str_t *server; /* 虚拟节点归属的真实节点,对应真实节点的server成员 */
  4. } ngx_http_upstream_chash_point_t;
  5. typedef struct {
  6. ngx_uint_t number; /* 虚拟节点的个数 */
  7. ngx_http_upstream_chash_point_t point[1]; /* 虚拟节点的数组 */
  8. } ngx_http_upstream_chash_points_t;
  9. typedef struct {
  10. ngx_http_complex_value_t key; /* 关联hash指令的第一个参数,用于计算请求的hash值 */
  11. ngx_http_upstream_chash_points_t *points; /* 虚拟节点的数组 */
  12. } ngx_http_upstream_chash_points_t;

3. 请求的一致性哈希数据

[java] view plain copy
  1. typedef struct {
  2. /* the round robin data must be first */
  3. ngx_http_upstream_rr_peer_data_t rrp; /* round robin的per request负载均衡数据 */
  4. ngx_http_upstream_hash_srv_conf_t *conf; /* server配置块 */
  5. ngx_str_t key; /* 对于本次请求,hash指令的第一个参数的具体值,用于计算本次请求的哈希值 */
  6. ngx_uint_t tries; /* 已经尝试的虚拟节点数 */
  7. ngx_uint_t rehash; /* 本算法不使用此成员 */
  8. uint32_t hash; /* 根据请求的哈希值,找到顺时方向最近的一个虚拟节点,hash为该虚拟节点在数组中的索引 */
  9. ngx_event_get_peer_pt get_rr_peer; /* round robin算法的peer.get函数 */
  10. } ngx_http_upstream_hash_peer_data_t;

round robin的per request负载均衡数据。

[java] view plain copy
  1. typedef struct {
  2. ngx_http_upstream_rr_peers_t *peers; /* 后端集群 */
  3. ngx_http_upstream_rr_peer_t *current; /* 当前使用的后端服务器 */
  4. uintptr_t *tried; /* 指向后端服务器的位图 */
  5. uintptr_t data; /* 当后端服务器的数量较少时,用于存放其位图 */
  6. } ngx_http_upstream_rr_peer_data_t;

指令的解析函数

在一个upstream配置块中,如果有hash指令,且它只带一个参数,则使用的负载均衡算法为哈希算法,比如:

hash $host$uri;

在一个upstream配置块中,如果有hash指令,且它带了两个参数,且第二个参数为consistent,则使用的

负载均衡算法为一致性哈希算法,比如:

hash $host$uri consistent;

这说明hash指令所属的模块ngx_http_upstream_hash_module同时实现了两种负载均衡算法,而实际上

哈希算法、一致性哈希算法完全可以用两个独立的模块来实现,它们本身并没有多少关联。

哈希算法的实现比较简单,类似之前分析过的ip_hash,接下来分析的是一致性哈希算法。

hash指令的解析函数主要做了:

把hash指令的第一个参数,关联到一个ngx_http_complex_value_t变量,之后可以通过该变量获取参数的实时值。

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

根据hash指令携带的参数来判断是使用哈希算法,还是一致性哈希算法。如果hash指令的第二个参数为"consistent",

则表示使用一致性哈希算法,指定upstream块的初始化函数uscf->peer.init_upstream。

[java] view plain copy
  1. static char *ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  2. {
  3. ngx_http_upstream_hash_srv_conf_t *hcf = conf;
  4. ngx_str_t *value;
  5. ngx_http_upstream_srv_conf_t *uscf;
  6. ngx_http_compile_complex_value_t ccv;
  7. value = cf->args->elts;
  8. ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t));
  9. /* 把hash指令的第一个参数,关联到一个ngx_http_complex_value_t变量,
  10. * 之后可以通过该变量获取参数的实时值。
  11. */
  12. ccv.cf = conf;
  13. ccv.value = &value[1];
  14. ccv.complex_value = &hcf->key;
  15. if (ngx_http_compile_complex_value(&ccv) != NGX_OK)
  16. return NGX_CONF_ERROR;
  17. /* 获取所在的upstream{}块 */
  18. uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
  19. if (uscf->peer.init_upstream)
  20. ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "load balancing method redefined");
  21. /* 指定此upstream块中server指令支持的属性 */
  22. uscf->flags = NGX_HTTP_UPSTREAM_CREATE
  23. | NGX_HTTP_UPSTREAM_WEIGHT
  24. | NGX_HTTP_UPSTREAM_MAX_FAILS
  25. | NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
  26. | NGX_HTTP_UPSTREAM_DOWN;
  27. /* 根据hash指令携带的参数来判断是使用哈希算法,还是一致性哈希算法。
  28. * 每种算法都有自己的upstream块初始化函数。
  29. */
  30. if (cf->args->nelts == 2)
  31. uscf->peer.init_upstream = ngx_http_upstream_init_hash;
  32. else if (ngx_strcmp(value[2].data, "consistent") == 0)
  33. uscf->peer.init_upstream = ngx_http_upstream_init_chash;
  34. else
  35. ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameter \"%V\"", &value[2]);
  36. return NGX_CONF_OK;
  37. }

初始化upstream块

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

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

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

ngx_http_upstream_init_chash,它主要做了:

调用round robin的upstream块初始化函数来创建和初始化真实节点

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

创建和初始化虚拟节点数组,使该数组中的虚拟节点有序而不重复

[java] view plain copy
  1. static ngx_int_t ngx_http_upstream_init_chash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
  2. {
  3. u_char *host, *port, c;
  4. size_t host_len, port_len, size;
  5. uint32_t hash, base_hash;
  6. ngx_str_t *server;
  7. ngx_uint_t npoints, i, j;
  8. ngx_http_upstream_rr_peer_t *peer;
  9. ngx_http_upstream_rr_peers_t *peers;
  10. ngx_http_upstream_chash_points_t *points;
  11. ngx_http_upstream_hash_srv_conf_t *hcf;
  12. union {
  13. uint32_t value;
  14. u_char byte[4];
  15. } prev_hash;
  16. /* 使用round robin的upstream块初始化函数,创建和初始化真实节点 */
  17. if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK)
  18. return NGX_ERROR:
  19. /* 重新设置per request的负载均衡初始化函数 */
  20. us->peer.init = ngx_http_upstream_init_chash_peer;
  21. peers = us->peer.data; /* 真实节点的集群 */
  22. npoints = peers->total_weight * 160;
  23. /* 一共创建npoints个虚拟节点 */
  24. size = sizeof(ngx_http_upstream_chash_points_t) +
  25. sizeof(ngx_http_upstream_chash_point_t) * (npoints - 1);
  26. points = ngx_palloc(cf->pool, size);
  27. if (points == NULL)
  28. return NGX_ERROR;
  29. points->number = 0;
  30. /* 初始化所有的虚拟节点 */
  31. for (peer = peers->peer; peer; peer = peer->next) {
  32. server = &peer->server; /* server指令的第一个参数, server.name */
  33. /* Hash expression is compatible with Cache::Memcached::Fast:
  34. * crc32(HOST 0 PORT PREV_HASH).
  35. */
  36. if (server->len >= 5 && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0)
  37. {
  38. host = server->data + 5;
  39. host_len = server->len - 5;
  40. port = NULL;
  41. port_len = 0;
  42. goto done;
  43. }
  44. /* 把每个peer的server成员,解析为HOST和PORT */
  45. for (j = 0; j < server->len; j++) {
  46. c = server->data[server->len - j - 1];
  47. if (c == ":") {
  48. host = server->data;
  49. host_len = server->len - j - 1;
  50. port = server->data + server->len - j;
  51. port_len = j;
  52. goto done;
  53. }
  54. if (c < '0' || c > '9') /* 表示没有指定端口 */
  55. break;
  56. }
  57. host = server->data;
  58. host_len = server->len;
  59. port = NULL;
  60. port_len = 0;
  61. done:
  62. /* 根据解析peer的server成员所得的HOST和PORT,计算虚拟节点的base_hash值 */
  63. ngx_crc32_init(base_hash);
  64. ngx_crc32_update(&base_hash, host, host_len);
  65. ngx_crc32_update(&base_hash, (u_char *) "", 1); /* 空字符串包含字符\0 */
  66. ngx_crc32_update(&base_hash, port, port_len);
  67. /* 对于归属同一个真实节点的虚拟节点,它们的base_hash值相同,而prev_hash不同 */
  68. prev_hash.value = 0;
  69. npoints = peer->weight * 160;
  70. for (j = 0; j < npoints; j++) {
  71. hash = base_hash;
  72. ngx_crc32_update(&hash, prev_hash.byte, 4);
  73. ngx_crc32_final(hash);
  74. points->point[points->number].hash = hash; /* 虚拟节点的哈希值 */
  75. points->point[points->number].server = server; /* 虚拟节点所归属的真实节点,对应真实节点的server成员 */
  76. points->number++;
  77. #if (NGX_HAVE_LITTLE_ENDIAN)
  78. prev_hash.value = hash;
  79. #else
  80. prev_hash.byte[0] = (u_char) (hash & 0xff);
  81. prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff);
  82. prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff);
  83. prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff);
  84. #endif
  85. }
  86. }
  87. /* 使用快速排序,使虚拟节点数组的元素,按照其hash值从小到大有序 */
  88. ngx_qsort(points->point, points->number, sizeof(ngx_http_upstream_chash_point_t),
  89. ngx_http_upstream_chash_cmp_points);
  90. /* 如果虚拟节点数组中,有多个元素的hash值相同,只保留第一个 */
  91. for (i = 0, j = 1; j < points->number; j++)
  92. if (points->point[i].hash != points->point[j].hash)
  93. points->point[++i] = points->point[j];
  94. /* 经过上述步骤后,虚拟节点数组中的元素,有序而不重复 */
  95. points->number = i + 1;
  96. hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
  97. hcf->points = points; /* 保存虚拟节点数组 */
  98. return NGX_OK;
  99. }
[java] view plain copy
  1. static int ngx_libc_cdel ngx_http_upstream_chash_cmp_points(const void *one, const void *two)
  2. {
  3. ngx_http_upstream_chash_point_t *first = (ngx_http_upstream_chash_point_t *) one;
  4. ngx_http_upstream_chash_point_t *second = (ngx_http_upstream_chash_point_t *) two;
  5. if (first->hash < second->hash)
  6. return -1;
  7. else if (first->hash > second->hash)
  8. return 1;
  9. else
  10. return 0;
  11. }

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

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

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

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

对于一致性哈希,peer.init实例为ngx_http_upstream_init_chash_peer,主要做了:

首先调用hash算法的per request负载均衡初始化函数,创建和初始化请求的负载均衡数据。

重新指定peer.get,用于选取一个真实节点来处理本次请求。

获取的本请求对应的hash指令的第一个参数值,计算请求的hash值。

寻找第一个hash值大于等于请求的哈希值的虚拟节点,即寻找“顺时针方向最近”的一个虚拟节点。

[java] view plain copy
  1. static ngx_int_t ngx_http_upstream_init_chash_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)
  2. {
  3. uint32_t hash;
  4. ngx_http_upstream_hash_srv_conf_t *hcf;
  5. ngx_http_upstream_hash_peer_data_t *hp;
  6. /* 调用hash算法的per request负载均衡初始化函数,创建和初始化请求的负载均衡数据 */
  7. if (ngx_http_upstream_init_hash_peer(r, us) != NGX_OK)
  8. return NGX_ERROR;
  9. /* 重新指定peer.get,用于选取一个真实节点 */
  10. r->upstream->peer.get = ngx_http_upstream_get_chash_peer;
  11. hp = r->upstream->peer.data;
  12. hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
  13. /* 根据获取的本请求对应的hash指令的第一个参数值,计算请求的hash值 */
  14. hash = ngx_crc32_long(hp->key.data, hp->key.len);
  15. /* 根据请求的hash值,找到顺时针方向最近的一个虚拟节点,hp->hash记录此虚拟节点
  16. * 在数组中的索引。
  17. */
  18. hp->hash = ngx_http_upstream_find_chash_point(hcf->points, hash);
  19. return NGX_OK:
  20. }

hash算法的per request负载均衡初始化函数。

[java] view plain copy
  1. static ngx_int_t ngx_http_upstream_init_hash_peer(ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)
  2. {
  3. ngx_http_upstream_hash_srv_conf_t *hcf;
  4. ngx_http_upstream_hash_peer_data_t *hp;
  5. hp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t));
  6. if (hp == NULL)
  7. return NGX_ERROR:
  8. /* 调用round robin的per request负载均衡初始化函数 */
  9. r->upstream->peer.data = &hp->rrp;
  10. if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK)
  11. return NGX_ERROR;
  12. r->upstream->peer.get = ngx_http_upstream_get_hash_peer;
  13. hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
  14. /* 获取本请求对应的hash指令的第一个参数值,用于计算请求的hash值 */
  15. if (ngx_http_complex_value(r, &hcf->key, &hp->key) != NGX_OK)
  16. return NGX_ERROR;
  17. ...
  18. hp->conf = hcf;
  19. hp->tries = 0;
  20. hp->rehash = 0;
  21. hp->hash = 0;
  22. hp->get_rr_peer = ngx_http_upstream_get_round_robin_peer; /* round robin的peer.get函数 */
  23. return NGX_OK;
  24. }

我们知道虚拟节点数组是有序的,事先已按照虚拟节点的hash值从小到大排序好了。

现在使用二分查找,寻找第一个hash值大于等于请求的哈希值的虚拟节点,即“顺时针方向最近”的一个虚拟节点。

[java] view plain copy
  1. static ngx_uint_t ngx_http_upstream_find_chash_point(ngx_http_upstream_chash_points_t *points, uint32_t hash)
  2. {
  3. ngx_uint_t i, j, k;
  4. ngx_http_upstream_chash_point_t *point;
  5. /* find first point >= hash */
  6. point = &points->point[0];
  7. i = 0;
  8. j = points->number;'
  9. while(i < j) {
  10. k = (i + j) / 2;
  11. if (hash > point[k].hash)
  12. i = k + 1;
  13. else if (hash < point[k].hash)
  14. j = k;
  15. else
  16. return k;
  17. }
  18. return i;
  19. }

选取一个真实节点

一般upstream块中会有多个真实节点,那么对于本次请求,要选定哪一个真实节点呢?

对于一致性哈希算法,选取真实节点的peer.get函数为ngx_http_upstream_get_chash_peer。

其实在peer.init中,已经找到了该请求对应的虚拟节点了:

根据请求对应的hash指令的第一个参数值,计算请求的hash值。

寻找第一个哈希值大于等于请求的hash值的虚拟节点,即“顺时针方向最近”的一个虚拟节点。

在peer.get中,需查找此虚拟节点对应的真实节点。

根据虚拟节点的server成员,在真实节点数组中查找server成员一样的且可用的真实节点。

如果找不到,那么沿着顺时针方向,继续查找下一个虚拟节点对应的真实节点。

如果找到一个真实节点,那么就是它了。

如果找到多个真实节点,使用轮询的方法从中选取一个。

[java] view plain copy
  1. static ngx_http_upstream_get_chash_peer(ngx_peer_connection_t *pc, void *data)
  2. {
  3. ngx_http_upstream_hash_peer_data_t *hp = data; /* 请求的负载均衡数据 */
  4. time_t now;
  5. intptr_t m;
  6. ngx_str_t *server;
  7. ngx_int_t total;
  8. ngx_uint_t i, n, best_i;
  9. ngx_http_upstream_rr_peer_t *peer, *best;
  10. ngx_http_upstream_chash_point_t *point;
  11. ngx_http_upstream_chash_points_t *points;
  12. ngx_http_upstream_hash_srv_conf_t *hcf;
  13. ...
  14. pc->cached = 0;
  15. pc->connection = NULL:
  16. now = ngx_time();
  17. hcf = hp->conf;
  18. points = hcf->points; /* 虚拟节点数组 */
  19. point = &points->point[0]; /* 指向第一个虚拟节点 */
  20. for ( ; ; ) {
  21. /* 在peer.init中,已根据请求的哈希值,找到顺时针方向最近的一个虚拟节点,
  22. * hash为该虚拟节点在数组中的索引。
  23. * 一开始hash值肯定小于number,之后每尝试一个虚拟节点后,hash++。取模是为了防止越界访问。
  24. */
  25. server = point[hp->hash % points->number].server;
  26. best = NULL;
  27. best_i = 0;
  28. total = 0;
  29. /* 遍历真实节点数组,寻找可用的、该虚拟节点归属的真实节点(server成员相同),
  30. * 如果有多个真实节点同时符合条件,那么使用轮询来从中选取一个真实节点。
  31. */
  32. for (peer = hp->rrp.peers->peer, i = 0; peer; peer = peer->next, i++) {
  33. /* 检查此真实节点在状态位图中对应的位,为1时表示不可用 */
  34. n = i / (8 * sizeof(uintptr_t));
  35. m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
  36. if (hp->rrp.tried[n] & m)
  37. continue;
  38. /* server指令中携带了down属性,表示后端永久不可用 */
  39. if (peer->down)
  40. continue;
  41. /* 如果真实节点的server成员和虚拟节点的不同,表示虚拟节点不属于此真实节点 */
  42. if (peer->server.len != server->len ||
  43. ngx_strncmp(peer->server.data, server->data, server->len) != 0)
  44. continue;
  45. /* 在一段时间内,如果此真实节点的失败次数,超过了允许的最大值,那么不允许使用了 */
  46. if (peer->max_fails
  47. && peer->fails >= peer->max_fails
  48. && now - peer->checked <= peer->fail_timeout)
  49. continue;
  50. peer->current_weight += peer->effective_weight; /* 对每个真实节点,增加其当前权重 */
  51. total += peer->effective_weight; /* 累加所有真实节点的有效权重 */
  52. /* 如果之前此真实节点发生了失败,会减小其effective_weight来降低它的权重。
  53. * 此后又通过增加其effective_weight来恢复它的权重。
  54. */
  55. if (peer->effective_weight < peer->weight)
  56. peer->effective_weight++;
  57. /* 选取当前权重最大者,作为本次选定的真实节点 */
  58. if (best == NULL || peer->current_weight > best->current_weight) {
  59. best = peer;
  60. best_i = i;
  61. }
  62. }
  63. /* 如果选定了一个真实节点 */
  64. if (best) {
  65. best->current_weight -= total; /* 如果使用了轮询,需要降低选定节点的当前权重 */
  66. goto found;
  67. }
  68. hp->hash++; /* 增加虚拟节点的索引,即“沿着顺时针方向” */
  69. hp->tries++; /* 已经尝试的虚拟节点数 */
  70. /* 如果把所有的虚拟节点都尝试了一遍,还找不到可用的真实节点 */
  71. if (hp->tries >= points->number)
  72. return NGX_BUSY;
  73. }
  74. found: /* 找到了和虚拟节点相对应的、可用的真实节点了 */
  75. hp->rrp.current = best; /* 选定的真实节点 */
  76. /* 保存选定的后端服务器的地址,之后会向这个地址发起连接 */
  77. pc->sockaddr = peer->sockaddr;
  78. pc->socklen = peer->socklen;
  79. pc->name = &peer->name;
  80. best->conns++;
  81. /* 更新checked时间 */
  82. if (now - best->checked > best->fail_timeout)
  83. best->checked = now;
  84. n = best_i / (8 * sizeof(uintptr_t));
  85. m = (uintptr_t) 1 << best_i % (8 * sizeof(uintptr_t));
  86. /* 对于本次请求,如果之后需要再次选取真实节点,不能再选取同一个了 */
  87. hp->rrp->tried[n] |= m;
  88. return NGX_OK;
  89. }

2016 -Nginx的负载均衡 - 一致性哈希 (Consistent Hash)相关推荐

  1. 负载均衡一致性哈希算法实现 | nginx 负载均衡一致性哈希源码分析 | ngx_http_upstream_consistent_hash_module 源码分析

    这是本学期分布式计算/系统课程负载均衡节的课后作业,理解七层反向代理的负载均衡 Nginx 中使用的的一致性哈希算法.开头只是讲一些没用的东西,后面主要是分析 Nginx 的 O(1) 时间复杂度的一 ...

  2. 配置Nginx实现负载均衡

    在关于高并发负载均衡一文中已经提到,企业在解决高并发问题时,一般有两个方向的处理策略,软件.硬件,硬件上添加负载均衡器分发大量请求,软件上可在高并发瓶颈处:数据库+web服务器两处添加解决方案,其中w ...

  3. nginx 一个请求发给多台机器_配置Nginx实现负载均衡

    企业在解决高并发问题时,一般有两个方向的处理策略,软件.硬件,硬件上添加负载均衡器分发大量请求,软件上可在高并发瓶颈处:数据库+web服务器两处添加解决方案,其中web服务器前面一层最常用的的添加负载 ...

  4. 算法高级(14)-Nginx的负载均衡策略

    一.nginx初体验 Nginx是一个http服务器.是一个使用c语言开发的高性能的http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.nginx能够支撑5万并发链接,并且cp ...

  5. nginx配置 负载均衡_如何配置NGINX负载平衡

    nginx配置 负载均衡 The load balancing is the process of distributing traffic to multiple instances of an a ...

  6. Nginx之负载均衡upstream模块简介和使用

    一.upstream模块简介   Nginx的负载均衡功能依赖于ngx_http_upsteam_module模块,所支持的代理方式包括proxy_pass, fastcgi_pass, uwsgi_ ...

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

    目录 一.Nginx是什么 二.Nginx实现反向代理 2.1 正向代理和反向代理 2.2 nginx实现反向代理 2.2.1 proxy_pass配置 2.2.1.1ngx_http_rewrite ...

  8. Nginx搭建负载均衡集群

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

  9. docker nginx 配置负载均衡

    在上篇的文章中我们介绍了docker nginx配置简单的代理 在这篇文章我们介绍docker 利用nginx配置负载均衡 所谓的负载均衡是两台服务器,或者是多台服务器上面部署项目,但是访问的方式是通 ...

最新文章

  1. LeetCode简单题之公平的糖果交换
  2. 网页空间php可以赋值,js如何赋值给php
  3. VGA、DVI、HDMI、DP、Type-C不同视频接口有啥区别?
  4. 两张动图,彻底明白TCP的三次握手与四次挥手
  5. iphone怎样关闭副屏_iPhone手机关掉这3个设置,不仅省电,而且手机还不会卡
  6. MySql 错误 Err [Imp] 1153 - Got a packet bigger than 'max_allowed_packet' bytes
  7. python sqlite并发处理_python sqlite大数据 处理
  8. 使用Akka持久化——消息发送与接收
  9. MapReduce :通过数据具有爷孙关系的结果
  10. Vim 编辑器的兼容模式
  11. yagmail发送附件
  12. gmx_MMPBSA.py的安装及使用--只翻译部分内容,具体可参考官方文档(https://valdes-tresanco-ms.github.io/gmx_MMPBSA/dev/)
  13. 【PHP】面试经历总结之——新浪微博
  14. java并发编程第七课 集合类并发处理
  15. javafx label设置字体大小_JavaFX-实现文本
  16. Android微信支付彻底扫坑
  17. PixelUtils:像素转换工具
  18. IDC发布2021年中国人工智能市场10大预测
  19. “50份简历没获得面试”也正常
  20. 字符串去重的(6)种方法

热门文章

  1. 【渝粤题库】陕西师范大学151107 管理会计 作业(高起专)
  2. 1108 String复读机 (20 分)
  3. 【英语:语法基础】B1.核心语法-名词与代词
  4. OSChina 周一乱弹 ——月薪三万长的帅的为啥找不到女朋友
  5. 【Scratch-外观模块】漩涡特效指令
  6. Python 网络爬虫实战:去哪儿网旅游攻略图文爬取保存为 Markdown电子书
  7. 差分近似图像导数算子之Sobel算子
  8. VPS云主机网站上的WordPress安全
  9. 中鑫优配:黄金高位震荡等破位,原油顺势做空看跌!
  10. Cesium加载局部地区单张图片底图