Nginx的负载均衡 - 保持会话 (ip_hash)

算法介绍

 

ip_hash算法的原理很简单,根据请求所属的客户端IP计算得到一个数值,然后把请求发往该数值对应的后端。

所以同一个客户端的请求,都会发往同一台后端,除非该后端不可用了。ip_hash能够达到保持会话的效果。

ip_hash是基于round robin的,判断后端是否可用的方法是一样的。

 

第一步,根据客户端IP计算得到一个数值。

hash1 = (hash0 * 113 + addr[0]) % 6271;

hash2 = (hash1 * 113 + addr[1]) % 6271;

hash3 = (hash2 * 113 + addr[2]) % 6271;

hash3就是计算所得的数值,它只和初始数值hash0以及客户端的IP有关。

 

第二步,根据计算所得数值,找到对应的后端。

w = hash3 % total_weight;

while (w >= peer->weight) {

    w -= peer->weight;

    peer = peer->next;

    p++;

}

total_weight为所有后端权重之和。遍历后端链表时,依次减去每个后端的权重,直到w小于某个后端的权重。

选定的后端在链表中的序号为p。因为total_weight和每个后端的weight都是固定的,所以如果hash3值相同,

则找到的后端相同。

 

指令的解析函数

 

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

ip_hash指令的解析函数为ngx_http_upstream_ip_hash,主要做了:

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

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

[java]   view plain  copy
  1. static char *ngx_http_upstream_ip_hash (ngx_conf_t *cf, ngx_command_t *cmd, void *conf)  
  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_ip_hash;  
  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.   
  21.     return NGX_CONF_OK;  
  22. }  

以下是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块的初始化函数。

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

ngx_http_upstream_init_ip_hash。它主要做了:

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

指定初始化请求的负载均衡数据的函数peer.init

 

因为脏活累活都让默认的函数给干了,所以ngx_http_upstream_init_ip_hash的代码就几行:)

[java]   view plain  copy
  1. static ngx_int_t ngx_http_upstream_init_ip_hash (ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)  
  2. {  
  3.     if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK)  
  4.         return NGX_ERROR;  
  5.   
  6.     us->peer.init = ngx_http_upstream_init_ip_hash_peer; /* 初始化请求负载均衡数据的函数 */  
  7.     return NGX_OK;  
  8. }  

 

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

 

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

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

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

对于ip_hash,peer.init实例为ngx_http_upstream_init_ip_hash_peer,主要做了:

调用round robin的per request负载均衡初始化函数,创建和初始化其per request负载均衡数据,即iphp->rrp。

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

保存客户端的地址,初始化ip_hash的per request负载均衡数据。

 

ip_hash的per request负载均衡数据的结构体为ngx_http_upstream_ip_hash_peer_data_t。

[java]   view plain  copy
  1. typedef struct {  
  2.     ngx_http_upstream_rr_peer_data_t rrp; /* round robin的per request负载均衡数据 */  
  3.     ngx_uint_t hash; /* 根据客户端IP计算所得的hash值 */  
  4.     u_char addrlen; /* 使用客户端IP的后三个字节来计算hash值 */  
  5.     u_char *addr; /* 客户端的IP */  
  6.     u_char tries; /* 已经尝试了多少次 */  
  7.     ngx_event_get_peer_pt get_rr_peer; /* round robin算法的peer.get函数 */  
  8. } ngx_http_upstream_ip_hash_peer_data_t;  
[java]   view plain  copy
  1. static ngx_int_t ngx_http_upstream_init_ip_hash_peer (ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)  
  2. {  
  3.     struct sockaddr_in *sin;  
  4.     ...  
  5.     ngx_http_upstream_ip_hash_peer_data_t *iphp;  
  6.   
  7.     /* 创建ip_hash的per request负载均衡数据的实例 */  
  8.     iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));  
  9.     if (iphp == NULL)  
  10.         return NGX_ERROR;  
  11.    
  12.    /* 首先调用round robin的per request负载均衡数据的初始化函数, 
  13.     * 创建和初始化round robin的per request负载均衡数据实例,即iphp->rrp。 
  14.     */  
  15.    r->upstream->peer.data = &iphp->rrp;   
  16.    if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK)  
  17.         return NGX_ERROR:  
  18.    
  19.     /* 重新指定peer.get,用于从集群中选取一台后端服务器 */  
  20.     r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;  
  21.   
  22.     /* 客户端的地址类型 */  
  23.     switch(r->connection->sockaddr->sa_family) {  
  24.     case AF_INET:  
  25.         sin = (struct sockaddr_in *) r->connection->sockaddr;  
  26.         iphp->addr = (u_char *) &sin->sin_addr.s_addr; /* 客户端的IP */  
  27.         iphp->addrlen = 3/* 使用客户端IP的后三个字节来计算hash值 */  
  28.         break;  
  29.   
  30. #if (NGX_HAVE_INET6)  
  31.     ...  
  32. #endif  
  33.   
  34.     default:  
  35.         iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr;  
  36.         iphp->addrlen = 3;  
  37.     }  
  38.   
  39.     iphp->hash = 89;  
  40.     iphp->tries = 0;  
  41.     iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer; /* 保存round robin的peer.get函数 */  
  42. }  

 

选取一台后端服务器

 

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

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

采用ip_hash算法,从集群中选出一台后端来处理本次请求。 选定后端的地址保存在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_ip_hash_peer (ngx_peer_connection_t *pc, void *data)  
  2. {  
  3.     ngx_http_upstream_ip_hash_peer_data_t *iphp = data; /* 请求的负载均衡数据 */  
  4.     time_t now;  
  5.     ngx_int_t w;  
  6.     uintptr_t m;  
  7.     ngx_uint_t i, n, p, hash;  
  8.     ngx_http_upstream_rr_peer_t *peer;  
  9.     ...  
  10.     /* 如果只有一台后端,或者尝试次数超过20次,则使用轮询的方式来选取后端 */  
  11.     if (iphp->tries > 20 || iphp->rrp.peers->single) {  
  12.         return iphp->get_rr_peer(pc, &iphp->rrp);  
  13.     }  
  14.   
  15.     now = ngx_time();  
  16.     pc->cached = 0;  
  17.     pc->connection = NULL;  
  18.     hash = iphp->hash; /* 本次选取的初始hash值 */  
  19.   
  20.     for ( ; ; ) {  
  21.         /* 根据客户端IP、本次选取的初始hash值,计算得到本次最终的hash值 */  
  22.         for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++)  
  23.             hash = (hash * 113 + iphp->addr[i]) % 6271;  
  24.   
  25.         /* total_weight和weight都是固定值 */  
  26.         w = hash % iphp->rrp.peers->total_weight;  
  27.         peer = iphp->rrp.peers->peer; /* 第一台后端 */  
  28.         p = 0;  
  29.   
  30.         while (w >= peer->weight) {  
  31.             w -= peer->weight;  
  32.             peer = peer->next;  
  33.             p++;  
  34.         }  
  35.   
  36.         /* 检查第此后端在状态位图中对应的位,为1时表示不可用 */  
  37.         n = p / (8 * sizeof(uintptr_t));  
  38.         m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));  
  39.   
  40.         if (iphp->rrp.tried[n] & m)  
  41.             goto next;  
  42.   
  43.         /* 检查后端是否永久不可用 */  
  44.         if (peer->down)  
  45.             goto next;  
  46.   
  47.         /* 在一段时间内,如果此后端服务器的失败次数,超过了允许的最大值,那么不允许使用此后端了 */  
  48.         if (peer->max_fails && peer->fails >= peer->max_fails &&  
  49.             now - peer->checked <= peer->fail_timeout)  
  50.             goto next;  
  51.   
  52.         break;  
  53.   
  54.     next:  
  55.         /* 增加已尝试的次数,如果超过20次,则使用轮询的方式来选取后端 */  
  56.         if (++iphp->tries > 20)  
  57.             return iphp->get_rr_peer(pc, &iphp->rrp);  
  58.     }  
  59.   
  60.     iphp->rrp.current = peer; /* 选定的可用后端 */  
  61.   
  62.     /* 保存选定的后端服务器的地址,之后会向这个地址发起连接 */  
  63.     pc->sockaddr = peer->sockaddr;  
  64.     pc->socklen = peer->socklen;  
  65.     pc->name = &peer->name;  
  66.   
  67.     peer->conns++;  
  68.   
  69.     /* 更新checked时间 */  
  70.     if (now - peer->checked > peer->fail_timeout)  
  71.         peer->checked = now;  
  72.   
  73.     iphp->rrp.tried[n] |= m; /* 对于此请求,如果之后需要再次选取后端,不能再选取这个后端了 */  
  74.     iphp->hash = hash; /* 保存hash值,下次可能还会用到 */  
  75.   
  76.     return NGX_OK:  
  77. }  

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值