赞
踩
根据用户请求的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;
}
通过查看源码得知,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;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。