Nginx的负载均衡 - 保持会话 (ip_hash)
Nginx版本:1.9.1
我的博客:http://blog.csdn.net/zhangskd
算法介绍
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指令支持的属性
static char *ngx_http_upstream_ip_hash (ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_upstream_srv_conf_t *uscf; /* 获取对应的upstream配置块 */
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module); if (uscf->peer.init_upstream)
ngx_conf_log_error(NGX_LOG_WARN, cf, 0, "load balancing method redefined"); /* 指定初始化此upstream块的函数 */
uscf->peer.init_upstream = ngx_http_upstream_init_ip_hash; /* 指定此upstream块中server指令支持的属性 */
uscf->flags = NGX_HTTP_UPSTREAM_CREATE
| NGX_HTTP_UPSTREAM_WEIGHT
| NGX_HTTP_UPSTREAM_MAX_FAILS
| NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
| NGX_HTTP_UPSTREAM_DOWN; return NGX_CONF_OK;
}
以下是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的代码就几行:)
static ngx_int_t ngx_http_upstream_init_ip_hash (ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
{
if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK)
return NGX_ERROR; us->peer.init = ngx_http_upstream_init_ip_hash_peer; /* 初始化请求负载均衡数据的函数 */
return NGX_OK;
}
初始化请求的负载均衡数据
收到一个请求后,一般使用的反向代理模块(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。
typedef struct {
ngx_http_upstream_rr_peer_data_t rrp; /* round robin的per request负载均衡数据 */
ngx_uint_t hash; /* 根据客户端IP计算所得的hash值 */
u_char addrlen; /* 使用客户端IP的后三个字节来计算hash值 */
u_char *addr; /* 客户端的IP */
u_char tries; /* 已经尝试了多少次 */
ngx_event_get_peer_pt get_rr_peer; /* round robin算法的peer.get函数 */
} ngx_http_upstream_ip_hash_peer_data_t;
static ngx_int_t ngx_http_upstream_init_ip_hash_peer (ngx_http_request_t *r, ngx_http_upstream_srv_conf_t *us)
{
struct sockaddr_in *sin;
...
ngx_http_upstream_ip_hash_peer_data_t *iphp; /* 创建ip_hash的per request负载均衡数据的实例 */
iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));
if (iphp == NULL)
return NGX_ERROR; /* 首先调用round robin的per request负载均衡数据的初始化函数,
* 创建和初始化round robin的per request负载均衡数据实例,即iphp->rrp。
*/
r->upstream->peer.data = &iphp->rrp;
if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK)
return NGX_ERROR: /* 重新指定peer.get,用于从集群中选取一台后端服务器 */
r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer; /* 客户端的地址类型 */
switch(r->connection->sockaddr->sa_family) {
case AF_INET:
sin = (struct sockaddr_in *) r->connection->sockaddr;
iphp->addr = (u_char *) &sin->sin_addr.s_addr; /* 客户端的IP */
iphp->addrlen = 3; /* 使用客户端IP的后三个字节来计算hash值 */
break; #if (NGX_HAVE_INET6)
...
#endif default:
iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr;
iphp->addrlen = 3;
} iphp->hash = 89;
iphp->tries = 0;
iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer; /* 保存round robin的peer.get函数 */
}
选取一台后端服务器
一般upstream块中会有多台后端,那么对于本次请求,要选定哪一台后端呢?
这时候第三步中r->upstream->peer.get指向的函数就派上用场了:
采用ip_hash算法,从集群中选出一台后端来处理本次请求。 选定后端的地址保存在pc->sockaddr,pc为主动连接。
函数的返回值:
NGX_DONE:选定一个后端,和该后端的连接已经建立。之后会直接发送请求。
NGX_OK:选定一个后端,和该后端的连接尚未建立。之后会和后端建立连接。
NGX_BUSY:所有的后端(包括备份集群)都不可用。之后会给客户端发送502(Bad Gateway)。
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;
...
/* 如果只有一台后端,或者尝试次数超过20次,则使用轮询的方式来选取后端 */
if (iphp->tries > 20 || iphp->rrp.peers->single) {
return iphp->get_rr_peer(pc, &iphp->rrp);
} now = ngx_time();
pc->cached = 0;
pc->connection = NULL;
hash = iphp->hash; /* 本次选取的初始hash值 */ for ( ; ; ) {
/* 根据客户端IP、本次选取的初始hash值,计算得到本次最终的hash值 */
for (i = 0; i < (ngx_uint_t) iphp->addrlen; i++)
hash = (hash * 113 + iphp->addr[i]) % 6271; /* total_weight和weight都是固定值 */
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++;
} /* 检查第此后端在状态位图中对应的位,为1时表示不可用 */
n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t)); if (iphp->rrp.tried[n] & m)
goto next; /* 检查后端是否永久不可用 */
if (peer->down)
goto next; /* 在一段时间内,如果此后端服务器的失败次数,超过了允许的最大值,那么不允许使用此后端了 */
if (peer->max_fails && peer->fails >= peer->max_fails &&
now - peer->checked <= peer->fail_timeout)
goto next; break; next:
/* 增加已尝试的次数,如果超过20次,则使用轮询的方式来选取后端 */
if (++iphp->tries > 20)
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++; /* 更新checked时间 */
if (now - peer->checked > peer->fail_timeout)
peer->checked = now; iphp->rrp.tried[n] |= m; /* 对于此请求,如果之后需要再次选取后端,不能再选取这个后端了 */
iphp->hash = hash; /* 保存hash值,下次可能还会用到 */ return NGX_OK:
}
Nginx的负载均衡 - 保持会话 (ip_hash)的更多相关文章
- 若依项目利用nginx实现负载均衡及保持会话
记录一下若依项目利用nginx实现负载均衡及保持会话的步骤. 此次作为试验性的测试,为了方便在本地window的环境上实现. 具体步骤: 1.安装两个tomcat8,可以下载一个后,另一个复制即可,下 ...
- Nginx的负载均衡 - 整体架构
Nginx的负载均衡 - 整体架构 Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd Nginx目前提供的负载均衡模块: ngx_http_upstre ...
- Nginx+DNS负载均衡实现
负载均衡有多种实现方法,nginx.apache.LVS.F5硬件.DNS等. DNS的负载均衡就是一个域名指向多个ip地址,客户访问的时候进行轮询解析 操作方法,在域名服务商解析的DNS也可以是第三 ...
- 关于Nginx的负载均衡
一.关于Nginx的负载均衡 在服务器集群中,Nginx起到一个代理服务器的角色(即反向代理),为了避免单独一个服务器压力过大,将来自用户的请求转发给不同的服务器.详情请查看我的另一篇博客. 二.Ng ...
- Nginx的负载均衡的几种方式
Nginx的负载均衡的那点事 本节就聊聊采用Nginx负载均衡之后碰到的问题: Session问题 文件上传下载 通常解决服务器负载问题,都会通过多服务器分载来解决.常见的解决方案有: 网站入口通过分 ...
- nginx的负载均衡的问题
本节就聊聊采用Nginx负载均衡之后碰到的问题: Session问题 文件上传下载 通常解决服务器负载问题,都会通过多服务器分载来解决.常见的解决方案有: 网站入口通过分站链接负载(天空软件站,华军软 ...
- Nginx搭建负载均衡集群
(1).实验环境 youxi1 192.168.5.101 负载均衡器 youxi2 192.168.5.102 主机1 youxi3 192.168.5.103 主机2 (2).Nginx负载均衡策 ...
- NginX——配置负载均衡
A. 在http模块加上upstream配置 upstream www.myweb.com { server 127.0.0.1:9100 weight=3; server ...
- [转帖]利用nginx实现负载均衡 | 哈希算法,sticky模块实现session粘滞
利用nginx实现负载均衡 | 哈希算法,sticky模块实现session粘滞 2018年08月02日 10:06:03 Minza 阅读数 483 https://blog.csdn.net/ha ...
随机推荐
- Android Studio安装、配置、第一个程序的那些坑
最近在上Android课,老师布置了量大题难的作业,然而出师未捷身先死,还没看题目,就被Android Studio的安装和环境配置搞得要死要死的,网上的教程也多也杂,良莠不齐,在经历了5小时通过的搜 ...
- python爬虫——爬取小说 | 探索白子画和花千骨的爱恨情仇(转载)
转载出处:药少敏 ,感谢原作者清晰的讲解思路! 下述代码是我通过自己互联网搜索和拜读完此篇文章之后写出的具有同样效果的爬虫代码: from bs4 import BeautifulSoup imp ...
- Centos6.9连接工具设置
由于vm下面的centos6.9这种操作环境非常的不友好,用起来非常的不方便, 所以我们需要用一个远程连接工具来连接,我们的虚拟机.我们使用的是teraterm. 下载地址:https://osdn. ...
- [ZJOI 2008]泡泡堂BNB
Description 题库链接 双方 \(n\) 人,给出每人的战斗力,赢一场加 \(2\) 分,平局 \(1\) 分,失败不得分.求最大和最小的得分. \(1\leq n\leq 100000\) ...
- hdu 5869 区间不同GCD个数(树状数组)
Different GCD Subarray Query Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K ( ...
- 【BZOJ2241】【Sdoi2011R1D1】打地鼠
原题传送门 Description 打地鼠是这样的一个游戏:地面上有一些地鼠洞,地鼠们会不时从洞里探出头来很短时间后又缩回洞中.玩家的目标是在地鼠伸出头时,用锤子砸其头部,砸到的地鼠越多分数也就越高. ...
- [bzoj4908][BeiJing2017]开车
来自FallDream的博客,未经允许,请勿转载,谢谢. 你有n辆车,分别a1, a2, ..., an位置和n个加油站,分别在b1, b2, ... ,bn .每个加油站只能支持一辆车的加油,所以你 ...
- (MariaDB/MySQL)之DML(2):数据更新、删除
本文目录:1.update语句2.delete语句 2.1 单表删除 2.2 多表删除3.truncate table 1.update语句 update用于修改表中记录. # 单表更新语法: UPD ...
- Python中文件的操作
文件的操作介绍 文件打开的方法 主要有两种: no with 格式:open(file, mode='r', buffering=-1, encoding=None, errors=None, new ...
- Java锁机制了解一下
前言 回顾前面: 多线程三分钟就可以入个门了! Thread源码剖析 多线程基础必要知识点!看了学习多线程事半功倍 只有光头才能变强! 本文章主要讲的是Java多线程加锁机制,有两种: Synchro ...