nginx 负载均衡(普通hash 和⼀致性hash 负载均衡的实现)
哈希负载均衡原理
ngx_http_upstream_hash_module⽀持普通的hash及⼀致性hash两种负载均衡算法,默认的是普通的hash来进⾏负载均衡。 nginx 普通的hash算法⽀持配置http变量值作为hash值计算的key,通过hash计算得出的hash值和总权重的余数作为挑选server的依据;nginx的⼀致性hash(chash)算法则要复杂⼀些。这⾥会对⼀致性hash的机制原理作详细的说明。
⼀致性hash算法的原理
⼀致性hash⽤于对hash算法的改进,后端服务器在配置的server的数量发⽣变化后,同⼀个upstream server接收到的请求会的数量和server数量变化之间会有变化。尤其是在负载均衡配置的upstream server数量发⽣增长后,造成产⽣的请求可能会在后端的upstream server中并不均匀,有的upstream server负载很低,有的upstream server负载较⾼,这样的负载均衡的效果⽐较差,可能对upstream server造成不良的影响。由此,产⽣了⼀致性hash算法来均衡。
那么为什么⼀致性hash算法能改善这种情况呢?这⾥引⽤⽹上资料的⼀致性hash算法的图例,并以图例作为说明。
peer 上图表明了产⽣的hash值与server之间的关系,⾸先计算出虚拟节点的hash值 这⾥的hash值在配置初始化的时候就已经是计算完成了的。这⾥的key值就是上图中的对象,机器就是配置的upstream server节点。 这样在计算配置key值⽣成的hash之后,通过此hash值映射的位置开始顺时针查,将upstream server定位到到的第⼀个upstream server节点上。
⼀致性hash与普通的hash的⽅式在于 在配置的upstream server的数量发⽣变化之后,普通的hash计算的⽅法会对所有访问造成影响,从⽽使得upstream server对于请求的负载可能会不均匀;⽽⼀致性hash通过上述的处理,upstream server的数量变化只会影响计算出key值hash与其”最近”的预分配的虚拟节,这⾥起到的作⽤是将配置upstream server数量变化造成的整体hash计算的影响收敛到了局部。这样,就算配置的upstream server的数量发⽣了变化,对负载整体⽽⾔,请求发送到upstream server的分布还是均匀的。⼀致性hash point点结构(虚拟节点 这⾥是单个虚拟节点的描述)
⼀致性hash虚拟节点集合描述upstream hash的server配置结构
typedef struct {
uint32_t hash; //hash 值
ngx_str_t *server; //server 名} ngx_http_upstream_chash_point_t;
typedef struct {
ngx_uint_t number;
ngx_http_upstream_chash_point_t point[1];
} ngx_http_upstream_chash_points_t
ngx_http_complex_value_t key; //⽣成hash的key值变量
ngx_http_upstream_chash_points_t *points; //⼀致性hash的集合点结构
} ngx_http_upstream_hash_srv_conf_t;
实际upstream连接发⽣时的数据的结构
typedef struct {
/* the round robin data must be first */
ngx_http_upstream_rr_peer_data_t rrp; //round-robin轮询数据
ngx_http_upstream_hash_srv_conf_t *conf; //server的配置
ngx_str_t key; //hash的key值
ngx_uint_t tries; //尝试连接到upstream server的次数
ngx_uint_t rehash; //rehash 重新hash的值会在挑选upstream server的时候进⾏更新
uint32_t hash; //crc32计算得出的hash值
ngx_event_get_peer_pt get_rr_peer; //轮询获取upstream server的函数在round-robin中实现(ngx_http_upstream_get_round_robin_peer) } ngx_http_upstream_hash_peer_data_t
以下是普通hash⽅式的处理
1.配置upstream server hash
static ngx_int_t
ngx_http_upstream_init_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
//轮询的upstream server配置初始化设置
return NGX_ERROR;
}
//设置在产⽣获取upstream server(连接动作之间) 的初始化函数
us->peer.init = ngx_http_upstream_init_hash_peer;
return NGX_OK;
}
2.设置获取upstream server(连接动作发⽣时 但是此时还没有产⽣实际的tcp连接 只是作为挑选server的初始化设置)
ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
ngx_http_upstream_srv_conf_t *us)
{
...
//为hash_peer中发⽣获取upstream server动作的数据分配内存
hp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t));
if (hp == NULL) {
return NGX_ERROR;
}
r->upstream->peer.data = &hp->rrp;
if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
return NGX_ERROR;
}
//upstream server的获取设置为upstream hash模块配置的获取⽅式
r->upstream-& = ngx_http_upstream_get_hash_peer;
hcf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_hash_module);
//获得待使⽤的⽣成hash值的key值 (这⾥的key值是nginx配置⽂件中的变量)
if (ngx_http_complex_value(r, &hcf->key, &hp->key) != NGX_OK) {
return NGX_ERROR;
}
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"upstream hash key:\"%V\"", &hp->key);
//初始值的设置
hp->conf = hcf;
hp->tries = 0;
hp->rehash = 0;
hp->hash = 0;
//这⾥设置了默认的挑选upstream server的⽅式(在hash⽅式不到合适的upstream server时启⽤)
hp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
return NGX_OK;
}
ngx_http_upstream_get_hash_peer
static ngx_int_t
ngx_http_upstream_get_hash_peer(ngx_peer_connection_t *pc, void *data)
{
ngx_http_upstream_hash_peer_data_t *hp = data;
.
..
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"get hash peer, try: %ui", pc->tries);
ngx_http_upstream_rr_peers_wlock(hp->rrp.peers); //读写锁设置在多进程竞争时同步
if (hp->tries > 20 || hp->rrp.peers->single) {
//尝试次数超过了20此或者只有⼀个upstream server 判定⽆必要进⾏hash的⽅式获取upstream server ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
return hp->get_rr_peer(pc, &hp->rrp);
}
now = ngx_time();
pc->cached = 0;
pc->connection = NULL;
/*
* Hash expression is compatible with Cache::Memcached:
* ((crc32([REHASH] KEY) >> 16) & 0x7fff) + PREV_HASH
* with REHASH omitted at the first iteration.
*/
ngx_crc32_init(hash);
if (hp->rehash > 0) {
//rehash的值⼤于0意味着⾄少经过了⼀次hash计算
//这时要⽤rehash的值来更新hash的计算否则hash的值是不会改变的
size = ngx_sprintf(buf, "%ui", hp->rehash) - buf;
ngx_crc32_update(&hash, buf, size);
}
//得到了hash值
ngx_crc32_update(&hash, hp->key.data, hp->key.len);
ngx_crc32_final(hash);
hash = (hash >> 16) & 0x7fff; //hash值是32位的数字这⾥⽤⾼位的16位作为hash的值
hp->hash += hash; //这⾥的hash值会被累计
hp->rehash++; //表⽰经过了⼀次hash计算
w = hp->hash % hp->rrp.peers->total_weight;//防⽌⽣成的hash值超过了total_weight值的⼤⼩ peer = hp->rrp.peers->peer;
p = 0;
while (w >= peer->weight) { //这⾥是计算得到的权重值超过了配置的权重值
//这⾥会调整w的值直到挑选出合适的peer (这⾥的peer 实质就是upstream server)
w -= peer->weight;
peer = peer->next;
p++;
}
n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
if (hp-&ied[n] & m) { //已经⽤过了
goto next;
}
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"get hash peer, value:%uD, peer:%ui", hp->hash, p);
if (peer->down) { //upstream server down标记
goto next;
}
if (peer->max_fails
&& peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
//判定upstream server不可⽤
{
goto next;
}
if (peer->max_conns && peer->conns >= peer->max_conns) {
/
/超过了配置的upstream server的最⼤连接数
goto next;
}
break;
if (++hp->tries > 20) { //超过了20次会启⽤轮询的⽅式获取upstream server
ngx_http_upstream_rr_peers_unlock(hp->rrp.peers);
return hp->get_rr_peer(pc, &hp->rrp);
}
}
//这⾥是到了合适的upstream server 并且把这个upstream server设置为当前使⽤的
hp->rrp.current = peer;
/
/socket 及server name 设置
pc->sockaddr = peer->sockaddr;
pc->socklen = peer->socklen;
pc->name = &peer->name;
peer->conns++; //由于使⽤了因此增加⼀次挑选出的upstream server的连接数
if (now - peer->checked > peer->fail_timeout) { //由于当前的upstream server能使⽤更新下checked的时间 peer->checked = now;
}
ngx_http_upstream_rr_peers_unlock(hp->rrp.peers); //此时释放upstream servers集合的锁
hp-&ied[n] |= m; //尝试过的标记更新
return NGX_OK;
}
普通hash的⽅式获取upstream server到此。
以下是⼀致性hash(consistency hash)负载均衡的处理
1.初始化⼀致性hash upstream server的配置初始化
static ngx_int_t
ngx_http_upstream_init_chash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
...
union {
uint32_t value;
u_char byte[4];
} prev_hash;
if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
//与普通hash⽅式⼀样设置round-robin配置这⾥是⼀些nginx配置⽂件中设置的配置
return NGX_ERROR;
}
//upstream server连接动作发⽣前的⼀致性hash的upstream server的初始化
us->peer.init = ngx_http_upstream_init_chash_peer;
peers = us->peer.data;
//设置虚拟节点的数量,这⾥⽤的值是upstream server总权重的160倍
npoints = peers->total_weight * 160;
//计算所需分配空间的⼤⼩
size = sizeof(ngx_http_upstream_chash_points_t)
+ sizeof(ngx_http_upstream_chash_point_t) * (npoints - 1);
points = ngx_palloc(cf->pool, size); //这⾥为虚拟节点分配内存
if (points == NULL) {
return NGX_ERROR;
}
points->number = 0; //虚拟节点的数量初始化为0
for (peer = peers->peer; peer; peer = peer->next) {
server = &peer->server;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论