概念
根据用户请求的ip,利用算法映射成hash值,分配到特定的tomcat服务器中。主要是为了实现负载均衡,只要用户ip固定,则hash值固定,特定用户只能访问特定服务器,解决了session的问题。
源码分析
ip_hash算法的处理代码位于src\http\modules\ngx_http_upstream_ip_hash_module.c。主要的处理代码如下:
// 最大失败次数、超时时间、最大连接数等相关配置
#define NGX_HTTP_UPSTREAM_CREATE 0x0001
#define NGX_HTTP_UPSTREAM_WEIGHT 0x0002
#define NGX_HTTP_UPSTREAM_MAX_FAILS 0x0004
#define NGX_HTTP_UPSTREAM_FAIL_TIMEOUT 0x0008
#define NGX_HTTP_UPSTREAM_DOWN 0x0010
#define NGX_HTTP_UPSTREAM_BACKUP 0x0020
#define NGX_HTTP_UPSTREAM_MAX_CONNS 0x0100
static ngx_int_t
ngx_http_upstream_get_ip_hash_peer(ngx_peer_connection_t *pc, void *data)
{
ngx_http_upstream_ip_hash_peer_data_t *iphp = data;
time_t now;
ngx_int_t w;
uintptr_t m;
ngx_uint_t i, n, p, hash;
ngx_http_upstream_rr_peer_t *peer;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"get ip hash peer, try: %ui", pc->tries);
/* TODO: cached */
ngx_http_upstream_rr_peers_rlock(iphp->rrp.peers);
if (iphp->tries > 20 || iphp->rrp.peers->single) {
ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
return iphp->get_rr_peer(pc, &iphp->rrp);
}
now = ngx_time();
pc->cached = 0;
pc->connection = NULL;
// 原始哈希值
hash = iphp->hash;
for ( ;; ) {
// 计算ip hash
for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) {
hash = (hash * 113 + iphp->addr[i]) % 6271;
}
// 根据hash和权重找到对应服务器
w = hash % iphp->rrp.peers->total_weight;
peer = iphp->rrp.peers->peer;
p = 0;
while (w >= peer->weight) {
w -= peer->weight;
peer = peer->next;
p++;
}
n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
if (iphp->rrp.tried[n] & m) {
goto next;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"get ip hash peer, hash: %ui %04XL", p, (uint64_t) m);
ngx_http_upstream_rr_peer_lock(iphp->rrp.peers, peer);
// 如果服务器不可用,不做后续处理
if (peer->down) {
ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
goto next;
}
// 如果超过最大失败次数或者超时,不做后续处理
if (peer->max_fails
&& peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
{
ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
goto next;
}
// 如果超过最大连接数,不做后续处理
if (peer->max_conns && peer->conns >= peer->max_conns) {
ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
goto next;
}
break;
next:
if (++iphp->tries > 20) {
ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
return iphp->get_rr_peer(pc, &iphp->rrp);
}
}
iphp->rrp.current = peer;
pc->sockaddr = peer->sockaddr;
pc->socklen = peer->socklen;
pc->name = &peer->name;
// 连接数自增
peer->conns++;
if (now - peer->checked > peer->fail_timeout) {
peer->checked = now;
}
ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
ngx_http_upstream_rr_peers_unlock(iphp->rrp.peers);
iphp->rrp.tried[n] |= m;
iphp->hash = hash;
return NGX_OK;
}
算法的核心处理代码如下:
for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++) {
hash = (hash * 113 + iphp->addr[i]) % 6271;
}
- 1
- 2
- 3
通过查看源码得知,ipv4的情况下iphp->addrlen的值固定为3,源代码如下:
switch (r->connection->sockaddr->sa_family) {
case AF_INET: // ipv4
sin = (struct sockaddr_in *) r->connection->sockaddr;
iphp->addr = (u_char *) &sin->sin_addr.s_addr;
iphp->addrlen = 3;
break;
if (NGX_HAVE_INET6)
case AF_INET6: // ipv6
sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;
iphp->addr = (u_char *) &sin6->sin6_addr.s6_addr;
iphp->addrlen = 16;
break;
endif
default:
iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr;
iphp->addrlen = 3;
}
假如我们传递的ip为192.168.41.33,hash值得计算流程可以拆分为:
hash0 = iphp->hash;
iphp->addr[0]--->192
hash1 = (hash0 * 113 + iphp->addr[0]) % 6271;
iphp->addr[1]--->168
hash2 = (hash1 * 113 + iphp->addr[1]) % 6271;
iphp->addr[2]--->41
hash3 = (hash2 * 113 + iphp->addr[2]) % 6271;
经分析得知,ip_hash实际是取ip地址得前三个字段计算得来,那么譬如192.168.41.xxx同一网段的ip访问的是同一个tomacat。
根据hash和权重找到对应的后端,源代码如下:
w = hash % iphp->rrp.peers->total_weight;
peer = iphp->rrp.peers->peer;
p = 0;
while (w >= peer->weight) {
w -= peer->weight;
peer = peer->next;
p++;
}
// ...
需要注意的一点是,当一个正在以ip_hash方式管理的服务器在实际操作环境中被移除时,在nginx.conf中不能直接把对应的配置删除,在后面加down即可,因为删除后需要重新计算hash,处理这一过程会更加消耗资源。nginx.conf配置文件修改如下:
# 以ip_hash的方式实现负载均衡, 添加ip_hash即可
upstream dongserver {
ip_hash;
server 192.168.41.33:8080;
server 192.168.41.34:8081;
server 192.168.41.35:8082 down; # 服务器不可用
}
nginx源码中对此的处理如下:
# upstream块结构体
struct ngx_http_upstream_rr_peer_s {
//...
ngx_uint_t down; // 服务器是否可用
//...
};
当判断到down标志被竖起来之后,不做后续处理
if (peer->down) {
ngx_http_upstream_rr_peer_unlock(iphp->rrp.peers, peer);
goto next;
}