Nginx ("engine x") 是一个高性能的 HTTP 和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。 Nginx 是由 Igor Sysoev 为俄罗斯访问量第二的 Rambler.ru 站点开发的,第一个公开版本0.1.0发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示例配置文件和低系统资源的消耗而闻名。
nginx也可以做web站点,不过更多情况下,nginx都是作为负载均衡服务器来使用的。负载均衡简单的分流类似如下图:
nginx的upstream模块,主要完成网络数据的接收、处理和转发,是做负载均衡的关键。下面介绍几种常见的负载分配算法。
Nginx的几种常见负载分配算法
轮询(round-robin 默认)
默认情况下,Nginx 会为你提供轮询作为负载均衡策略。即每台服务器的权重是一样的。每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
轮询调度算法(Round-Robin Scheduling)
轮询调度算法的原理是每一次把来自用户的请求轮流分配给内部中的服务器,从1开始,直到N(内部服务器个数),然后重新开始循环。
算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。
轮询调度算法流程
假设有一组服务器N台,S = {S1, S2, …, Sn},一个指示变量i表示上一次选择的服务器ID。变量i被初始化为N-1。其算法如下:
j = i;
do
{
j = (j + 1) mod n;
i = j;
return Si;
} while (j != i);
return NULL;
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
参考:http://www.iteye.com/topic/1113595
权重 weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况,默认每个服务器的weight权重是1,如果后端服务器down掉,能自动剔除。
当一个客户请求到达后,RR策略是从upstream的所有server中选择一个当前权重(current_weight)最大的server作为最初的server.
权重配置例子:
http {
upstream myproject {
server 127.0.0.18000 weight=3;
server 127.0.0.18001;
server 127.0.0.18002;
server 127.0.0.18003;
} server {
listen 80;
server_name www.domain.com;
location / {
proxy_pass http//myproject;
}
}
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
这样的权重算法逻辑:http://blog.sina.com.cn/s/blog_7303a1dc01014i0j.html
最早的算法代码逻辑如下:
算法如下:
//按照weight来返回一个peer的下标
//找到当前比率最大的那项,返回这项的下标
static ngx_uint_t
ngx_http_upstream_get_peer( ngx_http_upstream_rr_peers_t *peers )
{
ngx_uint_t i, n;
ngx_http_upstream_rr_peer_t *peer;
peer = &peers->peer[0];
for ( ; ; ){
for ( i = 0; i < peers->number; i++ ) {
if ( peer[i].current_weight <= 0 ){
continue;
}
n = i;
while ( i < peers->number - 1 ) {
i++; // 和下一个比较是否大,如果比下一个小,则返回下一个
if ( peer[i].current_weight <= 0 ){
continue;
}
if ( peer[n].current_weight * 1000 / peer[i].current_weight
> peer[n].weight * 1000 / peer[i].weight ){
return n;
}
n = i;
}
//找到最后了,还没有比第i项比率大的,则说明这项就是,返回这项
if ( peer[i].current_weight > 0 ) {
n = i;
}
return n;
}
//走到这里的时候,说明都是0了,重新初始化为满额,接着循环找,总是能找到一个
for ( i = 0; i < peers->number; i++ ){
peer[i].current_weight = peer[i].weight;
}
}
}
http://blog.xcai.net/fav/nginx-load-balance-analyze
http://stblog.baidu-tech.com/?p=2027
算法伪代码(2012.5.14后修正的算法):
foreach peer in peers {
peer->current_weight += peer->effective_weight;
total += peer->effective_weight; if (best == NULL || peer->current_weight > best->current_weight) {
best = peer;
}
}
best->current_weight -= total;
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
一个具体计算的例子:
selected server | current_weight before selected | current_weight after selected |
a | { 5, 1, 2 } | { -3, 1, 2 } |
c | { 2, 2, 4 } | { 2, 2, -4 } |
a | { 7, 3, -2 } | { -1, 3, -2 } |
a | { 4, 4, 0 } | { -4, 4, 0 } |
b | { 1, 5, 2 } | { 1, -3, 2 } |
a | { 6, -2, 4 } | { -2, -2, 4 } |
b | { 3, -1, 6 } | { 3, -1, -2 } |
a | { 8, 0, 0 } | { 0, 0, 0 } |
http://book.51cto.com/art/201202/314691.htm
ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
在ip_hash策略中,它选择最初的server的方法是根据请求客户端的IP计算出一个哈希值,再根据哈希值选择后台的服务器。
由IP计算哈希值的算法如下, 其中公式中hash初始值为89,iphp->addr[i]表示客户端的IP, 通过三次哈希计算得出一个IP的哈希值:
for (i = 0; i < 3; i++) {
hash = (hash * 113 + iphp->addr[i]) % 6271;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
在选择下一个server时,ip_hash的选择策略是这样的:它在上一次哈希值的基础上,再次哈希,就会得到一个全新的哈希值,再根据哈希值选择另外一个后台的服务器。
哈希算法仍然是:
for (i = 0; i < 3; i++) {
hash = (hash * 113 + iphp->addr[i]) % 6271;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
在这种ip_hash策略,如果一个后台服务器不能提供提服务(连接超时或读超时),该服务器的失败次数就会加一,当一个服务器的失败次数达到max_fails所设置的值,就会在fail_timeout所设置的时间段内不能对外提供服务,这点和RR是一致的。
如果当前server不能提供服务,就会根据当前的哈希值再哈希出一个新哈希值,选择另一个服务器继续尝试,尝试的最大次是upstream中server的个数,如果server的个数超过20,也就是要最大尝试次数在20次以上,当尝试次数达到20次,仍然找不到一个合适的服务器,ip_hah策略不再尝试ip哈希值来选择server, 而在剩余的尝试中,它会转而使用RR的策略,使用轮循的方法,选择新的server。
参考: http://blog.sina.com.cn/s/blog_9c3ba23d01010rof.html
fair
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
配置例子如下:
upstream web_pool {
server 172.23.136.148;
server 172.23.136.149;
fair;
}
.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, "Courier New", courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }
url_hash
按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法。
upstream web_pool {
server squid1:3128;
server squid2:3128;
hash $request_uri;
hash_method crc32;
}
一致性hash模块(淘宝 tengine特有的算法)
这个模块提供一致性hash作为负载均衡算法。
具体算法,将每个server虚拟成n个节点(根据server权重对应到n个节点),均匀分布到hash环上,每次请求,根据配置的参数计算出一个hash值,在hash环
上查找离这个hash最近的虚拟节点,对应的server作为该次请求的后端机器。
具体哈希值的计算,依赖于客户端信息(如:$ip, $uri, $args等变量)。
例子:
worker_processes 1; http {
upstream test {
consistent_hash $request_uri; server 127.0.0.1:9001 id=1001 weight=3;
server 127.0.0.1:9002 id=1002 weight=10;
server 127.0.0.1:9003 id=1003 weight=20;
}
}
说明:该模块可以根据配置参数采取不同的方式将请求均匀映射到后端机器,比如:
- consistent_hash $remote_addr:可以根据客户端ip映射;
- consistent_hash $request_uri: 根据客户端请求的uri映射;
- consistent_hash $args:根据客户端携带的参数进行映射;
上面配置中我们可以看到server id 字段,如果配置id字段,则使用id字段作为server标识,否则使用server ip和端口作为server标识,使用id字段可以手动设置server的标识,比如一台机器的ip或者端口变化,id仍然可以表示这台机器。使用id字段.可以减低增减服务器时hash的波动。
参考:
nginx(tengine)反向代理以及upstream的六种调度算法
http://itoedr.blog.163.com/blog/static/12028429720137113244975
这几种负载均衡分配方法并不是互相冲突的,我们可以组合使用。
另外还有一些其他负载算法,比如tengine支持的: Session保持模块、后端连接数限制模块、随机负载均衡模块 等就不详细描述了,参看:http://tengine.taobao.org/download/programmer-201209-Tengine.pdf
参考资料:
nginx 的模块及处理流程
http://www.cnblogs.com/ghj1976/p/3363039.html
Module ngx_http_upstream_module
http://nginx.org/en/docs/http/ngx_http_upstream_module.html
upstream模块
http://tengine.taobao.org/book/chapter_05.html
nginx中upstream的设计和实现(三)
http://www.pagefault.info/?p=273
Nginx学习之六-nginx核心进程模型
http://blog.csdn.net/xiajun07061225/article/details/9241179
解析nginx负载均衡
http://stblog.baidu-tech.com/?p=2027
nginx 配置轮询分流 实现负载均衡的方法
http://www.jbxue.com/article/11590.html
高性能Web服务器Nginx的配置与部署研究(15)Upstream负载均衡模块
http://blog.csdn.net/poechant/article/details/7256184
Nginx中的upstream轮询机制介绍
http://www.cnblogs.com/liqiu/p/3140329.html
高性能Linux服务器构建实战:运维监控、性能调优与集群应用
http://book.51cto.com/art/201202/314644.htm
Nginx 做负载均衡的几种轮询策略
http://zhuruxin86.iteye.com/blog/1694557