upstream负载均衡模块主要是用于从“upstream”定义的后端服务器中选择一台服务器进行连接。nginx先使用负载均衡模块选择一台主机,再使用upstream模块实现与这台主机的交互。
负载均衡策略
Nginx负载均衡策略主要分成两大类:内置策略和扩展策略。我们主要分析内置策略,内置策略主要是ip hash策略和加权轮询策略。默认情况下,这两种策略会被编译进内核,只需在配置时指明参数就行。扩展策略有很多,通用hash,consistent hash等,默认不编译进内核,是第三方模块。
nginx的upstream目前支持四种方式:
1)轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
2)weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
2)ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
3)fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。
4)url_hash(第三方)
Nginx默认采用round_robin加权算法。如果要选择其他的负载均衡算法,必须在upstream的配置上下文中通过配置指令ip_hash明确指定
upstream load_balance{
ip_hash;
server localhost:8001;
server localhost:8002;
}
指令如下
static ngx_command_t ngx_http_upstream_ip_hash_commands[] = {
{ ngx_string("ip_hash"),
NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
ngx_http_upstream_ip_hash,
0,
0,
NULL },
ngx_null_command
};
for (i = 0; i < umcf->upstreams.nelts; i++) {
init = uscfp[i]->peer.init_upstream ? uscfp[i]->peer.init_upstream:
ngx_http_upstream_init_round_robin;
if (init(cf, uscfp[i]) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
默认采用加权轮询方式就是因为init赋值的那一行代码。如果没有进行策略选择,那么就调用默认的策略初始函数ngx_http_upstream_init_round_robin,也就是加权轮询策略。否则的话就调用uscfp[i]->peer.init_upstream函数,如果选择ip hash负载策略,那么就会用ngx_http_upstream_init_ip_hash()。
在正式分析负载模块的具体代码前,我们先介绍熟悉几个相关的结构体
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;
typedef struct ngx_http_upstream_srv_conf_s ngx_http_upstream_srv_conf_t;
struct ngx_http_upstream_srv_conf_s {
ngx_http_upstream_peer_t peer;
void **srv_conf;//在 ngx_http_upstream()函数中被设置,指向的是本层的srv_conf
ngx_array_t *servers; /*array of ngx_http_upstream_server_t */
ngx_uint_t flags;//调用函数时ngx_http_upstream_add() 指定的标记
ngx_str_t host;//在函数 ngx_http_upstream_add() 中设置(e.g. upstream backend中的backend)
u_char *file_name;//"/usr/local/nginx/conf/nginx.conf"
ngx_uint_t line;//proxy在配置文件中的行号
in_port_t port;//使用的端口号(ngx_http_upstream_add()函数中添加, 指向ngx_url_t-->port,通常在函数ngx_parse_inet_url()中解析)
in_port_t default_port;//默认使用的端口号(ngx_http_upstream_add()函数中添加, 指向ngx_url_t-->default_port)
ngx_uint_t no_port; /* unsigned no_port:1 */
};
typedef struct {
//使用负载均衡的类型,默认采用 ngx_http_upstream_init_round_robin()
ngx_http_upstream_init_pt init_upstream;
//使用的负载均衡类型的初始化函数
ngx_http_upstream_init_peer_pt init;
//us->peer.data = peers; 指向的是 ngx_http_upstream_rr_peers_t(函数 ngx_http_upstream_init_round_robin()中设置)
void *data;
} ngx_http_upstream_peer_t;
typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r,
ngx_http_upstream_srv_conf_t *us);
如果upstream中服务器为空,那么默认使用proxy_pass。将利用函数ngx_inet_resolve_host依据us参数中的host和port进行解析。将结果保存在一个ngx_url_t类型的变量中:
typedef struct {
ngx_str_t url; //保存IP地址+端口信息(e.g. 192.168.124.129:8011 或 money.163.com)
ngx_str_t host; //保存IP地址信息
ngx_str_t port_text; //保存port字符串
ngx_str_t uri; //uri部分,在函数ngx_parse_inet_url()中设置
in_port_t port; //端口,e.g. listen指令中指定的端口(listen 192.168.124.129:8011)
in_port_t default_port; //默认端口(当no_port字段为真时,将默认端口赋值给port字段, 默认端口通常是80)
int family; //address family, AF_xxx
unsigned listen:1; //是否为指监听类的设置
unsigned uri_part:1;
unsigned no_resolve:1; //根据情况决定是否解析域名(将域名解析到IP地址)
unsigned one_addr:1; //等于1时,仅有一个IP地址
unsigned no_port:1; //标识url中没有显示指定端口(为1时没有指定)
unsigned wildcard:1; //标识是否使用通配符(e.g. listen *:8000;)
socklen_t socklen; //sizeof(struct sockaddr_in)
u_char sockaddr[NGX_SOCKADDRLEN]; //sockaddr_in结构指向它
ngx_addr_t *addrs; //数组大小是naddrs字段;每个元素对应域名的IP地址信息(struct sockaddr_in),在函数中赋值(ngx_inet_resolve_host())
ngx_uint_t naddrs; //url对应的IP地址个数,IP格式的地址将默认为1
char *err; //错误信息字符串
} ngx_url_t;
此函数会创建后端服务器列表,并且将非后备服务器与后备服务器分开进行各自单独的链表。每一个后端服务器用一个结构体ngx_http_upstream_rr_peer_t与之对应(ngx_http_upstream_round_robin.h):
typedef struct {
struct sockaddr *sockaddr;//后端服务器地址
socklen_t socklen;//后端服务器地址长度
ngx_str_t name;//后端名称
ngx_int_t current_weight;//当前权重,nginx会在运行过程中调整此权重
ngx_int_t effective_weight;
ngx_int_t weight;//配置的权重
ngx_uint_t fails;//已尝试失败次数
time_t accessed;//检测失败时间,用于计算超时
time_t checked;
ngx_uint_t max_fails;//最大失败次数
time_t fail_timeout;//多长时间内出现max_fails次失败便认为后端down掉了
ngx_uint_t down; /* unsigned down:1; *///指定某后端是否挂了
#if (NGX_HTTP_SSL)
ngx_ssl_session_t *ssl_session; /* local to a process */
#endif
} ngx_http_upstream_rr_peer_t;
列表最前面需要带有一些head信息,用结构体ngx_http_upstream_rr_peers_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;//为1表示后端服务器总共只有一台,用于优化,此时不需要再做选择
unsigned weighted:1;//为1表示总的权重值等于服务器数量
ngx_str_t *name;
ngx_http_upstream_rr_peers_t *next;//后备服务器列表挂载在这个字段下
ngx_http_upstream_rr_peer_t peer[1];
};
ngx_http_upstream_init_round_robin函数具体分析:
- //函数:初始化服务器负载均衡表
- //参数:
- //us:ngx_http_upstream_main_conf_t结构体中upstreams数组元素
- ngx_int_t
- ngx_http_upstream_init_round_robin(ngx_conf_t *cf,
- ngx_http_upstream_srv_conf_t *us)
- {
- ngx_url_t u;
- ngx_uint_t i, j, n, w;
- ngx_http_upstream_server_t *server;
- ngx_http_upstream_rr_peers_t *peers, *backup;
- //回调指针设置
- us->peer.init = ngx_http_upstream_init_round_robin_peer;
- //服务器数组指针不为空
- if (us->servers) {
- 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;
- }
- if (n == 0) {
- ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
- "no servers in upstream \"%V\" in %s:%ui",
- &us->host, us->file_name, us->line);
- return NGX_ERROR;
- }
- //为非后备服务器分配空间
- peers = ngx_pcalloc(cf->pool,