前面介绍过nginx负载均衡的加权轮询策略(http://blog.csdn.net/xiajun07061225/article/details/9318871),它是Nginx负载均衡的基础策略,所以一些初始化工作,比如配置值转储,其他策略可以直接复用他。在后面的初始化的代码中将可以看到。

注:本文中源代码版本为Nginx-1.4.0。

IP哈希初始化

IP哈希的初始化函数ngx_http_upstream_init_ip_hash(ngx_http_upstream_ip_hash_module.c):
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;
}

选择后端服务器

当客户端请求过来之后,将会执行初始化函数ngx_http_upstream_init_ip_hash_peer。其中调用了轮询算法中的初始化函数。
源代码:
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;//针对IPv6的支持
#if (NGX_HAVE_INET6)struct sockaddr_in6                    *sin6;
#endifngx_http_upstream_ip_hash_peer_data_t  *iphp;iphp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_ip_hash_peer_data_t));if (iphp == NULL) {return NGX_ERROR;}r->upstream->peer.data = &iphp->rrp;//调用了RR算法中的初始化函数if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {return NGX_ERROR;}//回调函数设置,具体做选择的回调函数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;//转储IPv4只用到了前3个字节,因为在后面的hash计算过程中只用到了3个字节iphp->addrlen = 3;break;#if (NGX_HAVE_INET6)case AF_INET6:sin6 = (struct sockaddr_in6 *) r->connection->sockaddr;iphp->addr = (u_char *) &sin6->sin6_addr.s6_addr;iphp->addrlen = 16;break;
#endifdefault:iphp->addr = ngx_http_upstream_ip_hash_pseudo_addr;iphp->addrlen = 3;}//初始化hash种子iphp->hash = 89;//初始化尝试失败次数iphp->tries = 0;//做RR选择的函数iphp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;return NGX_OK;
}

其中结构体ngx_http_upstream_ip_hash_peer_data_t:

typedef struct {/* the round robin data must be first */ngx_http_upstream_rr_peer_data_t   rrp;//hash种子值ngx_uint_t                         hash;//IP地址u_char                             addrlen;u_char                            *addr;//尝试连接的次数u_char                             tries;ngx_event_get_peer_pt              get_rr_peer;
} ngx_http_upstream_ip_hash_peer_data_t;typedef struct {//指向所有服务器的指针ngx_http_upstream_rr_peers_t   *peers;//当前服务器ngx_uint_t                      current;//指向位图的指针uintptr_t                      *tried;//位图的实际存储位置uintptr_t                       data;
} ngx_http_upstream_rr_peer_data_t;typedef struct ngx_http_upstream_rr_peers_s  ngx_http_upstream_rr_peers_t;struct ngx_http_upstream_rr_peers_s {ngx_uint_t                      number;//所有服务器地址总数/* ngx_mutex_t                    *mutex; */ngx_uint_t                      total_weight;//所有服务总权重unsigned                        single:1;//是否只有一个后端服务unsigned                        weighted:1;//number != total_weight ?ngx_str_t                      *name;ngx_http_upstream_rr_peers_t   *next;ngx_http_upstream_rr_peer_t     peer[1];
};

具体做选择的函数是ngx_http_upstream_get_ip_hash_peer:

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 *///如果失败次数太多,或者只有一个后端服务,那么直接做RR选择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;for ( ;; ) {//计算IP的hash值for (i = 0; i < iphp->addrlen; i++) {//113质数,可以让哈希结果更散列hash = (hash * 113 + iphp->addr[i]) % 6271;}//根据哈希结果得到被选中的后端服务器if (!iphp->rrp.peers->weighted) {p = hash % iphp->rrp.peers->number;} else {w = hash % iphp->rrp.peers->total_weight;for (i = 0; i < iphp->rrp.peers->number; i++) {w -= iphp->rrp.peers->peer[i].weight;if (w < 0) {break;}}p = i;}//服务器对应在位图中的位置计算n = p / (8 * sizeof(uintptr_t));m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));if (!(iphp->rrp.tried[n] & m)) {ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,"get ip hash peer, hash: %ui %04XA", p, m);//获取服务器peer = &iphp->rrp.peers->peer[p];/* ngx_lock_mutex(iphp->rrp.peers->mutex); *///服务器未挂掉if (!peer->down) {//失败次数已达上限if (peer->max_fails == 0 || peer->fails < peer->max_fails) {break;}if (now - peer->checked > peer->fail_timeout) {peer->checked = now;break;}}//更改位图标记值iphp->rrp.tried[n] |= m;/* ngx_unlock_mutex(iphp->rrp.peers->mutex); *///在连接一个远端服务器时,当前连接异常失败后可以尝试的次数pc->tries--;}//已经尝试的次数超过阈值,采用RR轮询if (++iphp->tries >= 20) {return iphp->get_rr_peer(pc, &iphp->rrp);}}//当前服务索引iphp->rrp.current = p;//服务器地址及名字保存pc->sockaddr = peer->sockaddr;pc->socklen = peer->socklen;pc->name = &peer->name;/* ngx_unlock_mutex(iphp->rrp.peers->mutex); *///位图更新iphp->rrp.tried[n] |= m;//保留种子,使下次get_ip_hash_peer的时候能够选到同一个peer上iphp->hash = hash;return NGX_OK;
}
上述计算过程中,依据ip的hash值进行映射的时候,依据服务器列表头部结构中weighted字段分了两种不同的情况。
下面看看weighted的计算过程(ngx_http_upstream_round_robin.c):
        //指向服务器列表指针        server = us->servers->elts;n = 0;w = 0;//遍历服务器列表,计算地址总数以及总的权值for (i = 0; i < us->servers->nelts; i++) {if (server[i].backup) {continue;}n += server[i].naddrs;w += server[i].naddrs * server[i].weight;}//weighted计算peers->weighted = (w != n);

server的类型是ngx_http_upstream_server_t。

typedef struct {ngx_addr_t                      *addrs;//指向存储IP地址的数组的指针,host信息(对应的是 ngx_url_t->addrs )ngx_uint_t                       naddrs;//与第一个参数配合使用,数组元素个数(对应的是 ngx_url_t->naddrs )ngx_uint_t                       weight;//权值ngx_uint_t                       max_fails;time_t                           fail_timeout;unsigned                         down:1;unsigned                         backup:1;
} ngx_http_upstream_server_t;
一个域名可能对应多个IP地址。
server wegiht 字段,作为server权重,对应虚拟节点数目。
具体算法,将每个server虚拟成n个节点,均匀分布到hash环上,每次请求,根据配置的参数计算出一个hash值,在hash环上查找离这个hash最近的虚拟节点,对应的server作为该次请求的后端机器。

因此,当weighted字段等于0的时候,表示虚拟节点数和IP地址数是相等的,因此直接将hash值针对ip地址总数取模即可。如果weighted不等于0,表示虚拟节点数和IP地址数不等,因此需要按照虚拟节点数另外计算。查找离这个hash最近的虚拟节点,作为该请求的后端机器。

整个IP哈希选择流程

流程图如下:

轮询策略和IP哈希策略对比

加权轮询策略
优点:适用性更强,不依赖于客户端的任何信息,完全依靠后端服务器的情况来进行选择。能把客户端请求更合理更均匀地分配到各个后端服务器处理。
缺点:同一个客户端的多次请求可能会被分配到不同的后端服务器进行处理,无法满足做会话保持的应用的需求。
IP哈希策略
优点:能较好地把同一个客户端的多次请求分配到同一台服务器处理,避免了加权轮询无法适用会话保持的需求。
缺点:当某个时刻来自某个IP地址的请求特别多,那么将导致某台后端服务器的压力可能非常大,而其他后端服务器却空闲的不均衡情况、

参考资料:

http://blog.dccmx.com/2011/07/nginx-upstream-src-1/
http://itoedr.blog.163.com/blog/static/120284297201341044034750/

《深入剖析Nginx》

《深入理解Nginx-模块开发与架构解析》

Nginx学习之十三-负载均衡-IP哈希策略剖析相关推荐

  1. Nginx学习4:负载均衡实例

    Nginx配置实例-负载均衡 目标 在浏览器地址栏输入地址 http://192.168.126.131:8080/edu/a.html,负载均衡效果,平均分配到 8080 和 8081 端口中 准备 ...

  2. nginx学习九 upstream 负载均衡

    2019独角兽企业重金招聘Python工程师标准>>> 语法 Syntax: upstream name {...} Default:-- Context:http 后端服务器在负载 ...

  3. Nginx学习之十二-负载均衡-加权轮询策略剖析

    本文介绍的是客户端请求在多个后端服务器之间的均衡,注意与客户端请求在多个nginx进程之间的均衡相区别(Nginx根据每个工作进程的当前压力调整它们获取监听套接口的几率,那些当前比较空闲的工作进程有更 ...

  4. Nginx学习之九-负载均衡(客户端请求与Nginx进程间负载均衡)

    本文介绍的负载均衡是针对的客户端请求在多个Nginx进程之间的均衡.注意与客户端请求在多个后端服务器之间的均衡相区别. 负载均衡问题的产生 在nginx中,建立连接的时候,会设计负载均衡问题.在多个子 ...

  5. Nginx面试三连问:Nginx如何工作?负载均衡策略有哪些?如何限流?

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 1.什么是Nginx,谈谈个人都理解,项目中是否用到,为什 ...

  6. Nginx反向代理,负载均衡,redis session共享,keepalived高可用

    本站点停止更新,请访问:blog.coocap.com 相关知识自行搜索,直接上干货... 使用的资源: nginx主服务器一台,nginx备服务器一台,使用keepalived进行宕机切换. tom ...

  7. NGINX基于Tomcat配置负载均衡

    NGINX基于Tomcat配置负载均衡 本部署指南说明了如何使用NGINX开源和NGINX Plus在Apache Tomcat TM应用程序服务器池之间平衡HTTP和HTTPS流量.本指南中的详细说 ...

  8. Nginx反向代理与负载均衡等配置文件示例

    Nginx反向代理于负载均衡等配置文件示例 Nginx.conf配置文件 worker_processes 8;events {worker_connections 1024; }http {incl ...

  9. 懂点 Nginx 反向代理与负载均衡,是面试加分项没有之一

    点击上方"方志朋",选择"置顶公众号" 技术文章第一时间送达! 学到老活到老 前端圈一直很新,一直要不停的学习,而且在进入大厂的路上,还要求熟悉一门后台语言等等 ...

最新文章

  1. css选择器(css Selectors)的语法分析
  2. Fragment与Activity的接口回调
  3. LaText中插入带上下限的求和符号
  4. nginx 域名配置_nginx 一个域名配置多个vue项目
  5. Oracle 数据泵使用——导入、导出
  6. easyui java管理系统_EasyUI 后台管理系统
  7. 今日头条10.82亿元拿下 “中国最牛街道”土地:将与腾讯、阿里做邻居
  8. 码匠编程:7 个令人兴奋的 JavaScript 新特性
  9. 恢复Cisco路由器密码
  10. 【记录】ASP.NET MVC MapRoute .htm 不起作用
  11. 开源 免费使用 打包下载 自行部署 :升讯威 周报系统 2.0 最新版
  12. html怎么用pdf保存,html保存为PDF
  13. golang Windows下编译linux可执行文件
  14. linux 版本号 笔记本_Linux版ThinkPad笔记本正式亮相(图)
  15. 国产时钟芯片应用探讨,CLB2305对标CY2305, CLB30110兼容IDT:8L30110,TI:CDCLVC1310
  16. 如何发布类Excel的管理软件
  17. 【数据结构与算法】期末复习刷题日寄Part02
  18. 用计算机弹音乐我们一起猫叫,抖音上面我们一起学猫叫一起喵喵喵是什么歌 抖音学猫叫歌曲歌词...
  19. 初识C语言,一起迈入编程世界的大门
  20. MultiDex精补篇,进一步知道MultiDex的配置

热门文章

  1. [Unity] 播放 Generic Animation 导致无法移动的解决办法:在 Animator 中勾选 Apply Root Motion
  2. Mint-UI 报错提示缺少“raf.js / vue-lazyload / vue-popup” - 解决办法
  3. 稳定的货源社区新版云乐购免费开源源码
  4. matlab 防雷硒堆,单相全控桥式晶闸管整流电路的设计(阻感负载)电力电子课程设计...
  5. java窗口向mysql加信息_Java中如何实现向DBC方式向表中添加数据
  6. 将图片处理成圆形_设计基本功!图片处理技巧
  7. Unknown column 'password_lifetime' in 'field list';创建数据库时创建用户,修改用户时报错
  8. mysql数据库总览_MySQL架构总览-查询执行流程-SQL解析顺序
  9. Thinkphp宅音乐html5在线音乐播放器
  10. Thinkphp5内核大型程序员交流博客系统源码