nginx在1.3.1添加了一个新模块 least_conn,也就是我们常说的最少连接负载均衡算法,简单来说就是每次选择的都是当前最少连接的一个server(这个最少连接不是全局的,是每个进程都有自己的一个统计列表)。
在看最少连接模块之前需要对round robin模块有一定的了解,这里我就不对round robin模块进行分析了,想要看这块代码,可以去我们组 卫岳的blog的这篇文章
http://blog.sina.com.cn/s/blog_7303a1dc01014i0j.html
ok,接下来就来看这个模块,首先来看如何打开least_conn模块:
static ngx_command_t ngx_http_upstream_least_conn_commands[] = {
{ ngx_string("least_conn"),
NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS,
ngx_http_upstream_least_conn,
,
,
NULL },
ngx_null_command
};
可以看到命令很简单,就是在upstream块里面加上 least_conn就行了。
来看命令回调,least_conn的入口就是在这个里面设置的.
static char *
ngx_http_upstream_least_conn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_upstream_srv_conf_t *uscf;
uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
uscf->peer.init_upstream = ngx_http_upstream_init_least_conn;
uscf->flags = NGX_HTTP_UPSTREAM_CREATE
|NGX_HTTP_UPSTREAM_WEIGHT
|NGX_HTTP_UPSTREAM_MAX_FAILS
|NGX_HTTP_UPSTREAM_FAIL_TIMEOUT
|NGX_HTTP_UPSTREAM_DOWN
|NGX_HTTP_UPSTREAM_BACKUP;
return NGX_CONF_OK;
}
可以看到很简单,就是设置了peer.init_upstream,然后设置了支持的flags。那么这里就有问题了,peer.init_upstream什么时候会被调用呢。
static char *
ngx_http_upstream_init_main_conf(ngx_conf_t *cf, void *conf)
{
ngx_http_upstream_main_conf_t *umcf = conf;
ngx_uint_t i;
ngx_array_t headers_in;
ngx_hash_key_t *hk;
ngx_hash_init_t hash;
ngx_http_upstream_init_pt init;
ngx_http_upstream_header_t *header;
ngx_http_upstream_srv_conf_t **uscfp;
uscfp = umcf->upstreams.elts;
for (i = 0; i < umcf->upstreams.nelts; i++) {
//判断是否有设置 initupstream,默认是round robin算法.
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;
}
}
.......................................................
}
上面的代码可以看到是在解析upstream命令的时候,调用init_upstream的,因此也就可以这么说,init_upstream中初始化的的东西,每个进程都有自己的一份拷贝.
所以我们来看ngx_http_upstream_init_least_conn,这个函数主要就是初始化round robin(主要是为了初始化权重/timeout等参数,而且least conn中如果多个server有相同的连接数,则会使用round robin算法)以及设置peer的init回调.
还有一个要注意的就是conns数组,这个数组每个slot保存了对应server的连接数
ngx_int_t
ngx_http_upstream_init_least_conn(ngx_conf_t *cf,
ngx_http_upstream_srv_conf_t *us)
{
ngx_uint_t n;
ngx_http_upstream_rr_peers_t *peers;
ngx_http_upstream_least_conn_conf_t *lcf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, cf->log, 0,
"init least conn");
//初始化round robin
if (ngx_http_upstream_init_round_robin(cf, us) != NGX_OK) {
return NGX_ERROR;
}
peers = us->peer.data;
n = peers->number;
if (peers->next) {
n += peers->next->number;
}
lcf = ngx_http_conf_upstream_srv_conf(us,
ngx_http_upstream_least_conn_module);
//创建conns数组
lcf->conns = ngx_pcalloc(cf->pool, sizeof(ngx_uint_t) * n);
if (lcf->conns == NULL) {
return NGX_ERROR;
}
//设置init
us->peer.init = ngx_http_upstream_init_least_conn_peer;
return NGX_OK;
}
而us->peer.init是在upstream的request初始化的时候调用的,也就是说每个request都会调用这个函数来初始化:
static void
ngx_http_upstream_init_request(ngx_http_request_t *r)
{
.............................
found:
if (uscf->peer.init(r, uscf) != NGX_OK) {
ngx_http_upstream_finalize_request(r, u,
NGX_HTTP_INTERNAL_SERVER_ERROR);
return;
}
ngx_http_upstream_connect(r, u);
}
然后我们就来看peer.init回调ngx_http_upstream_init_least_conn_peer,在这个函数中,主要是用来初始化对应的数据结构,然后挂载对应的回调(getpeer/freepeer).
typedef struct {
/* the round robin data must be first */
ngx_http_upstream_rr_peer_data_t rrp;
//连接信息保存
ngx_uint_t *conns;
//对应的get和free连接的回调
ngx_event_get_peer_pt get_rr_peer;
ngx_event_free_peer_pt free_rr_peer;
} ngx_http_upstream_lc_peer_data_t;
static ngx_int_t
ngx_http_upstream_init_least_conn_peer(ngx_http_request_t *r,
ngx_http_upstream_srv_conf_t *us)
{
ngx_http_upstream_lc_peer_data_t *lcp;
ngx_http_upstream_least_conn_conf_t *lcf;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
"init least conn peer");
lcf = ngx_http_conf_upstream_srv_conf(us,
ngx_http_upstream_least_conn_module);
lcp = ngx_palloc(r->pool, sizeof(ngx_http_upstream_lc_peer_data_t));
if (lcp == NULL) {
return NGX_ERROR;
}
lcp->conns = lcf->conns;
r->upstream->peer.data = &lcp->rrp;
//初始化round robin
if (ngx_http_upstream_init_round_robin_peer(r, us) != NGX_OK) {
return NGX_ERROR;
}
//设置回调
r->upstream->peer.get = ngx_http_upstream_get_least_conn_peer;
r->upstream->peer.free = ngx_http_upstream_free_least_conn_peer;
lcp->get_rr_peer = ngx_http_upstream_get_round_robin_peer;
lcp->free_rr_peer = ngx_http_upstream_free_round_robin_peer;
return NGX_OK;
}
上面可以看到每个lcp都有自己的get peer和free回调,这是什么原因呢,和upstream->peer的get和free的区别在哪里,这个是这样的原因,主要是least conn算法中,如果多个server都有相同的连接数,那么就需要使用round robin算法了,所以就保存了round robin的peer回调。
然后来看对应的peer get回调在那里调用的,首先通过前面的blog 我们知道每次当要和upstream建立连接的时候,我们都需要调用ngx_event_connect_peer,最终这个函数会创建连接,然后再去connect upstream,而我们的get回调也就是在这个函数中调用的。
ngx_int_t
ngx_event_connect_peer(ngx_peer_connection_t *pc)
{
int rc;
ngx_int_t event;
ngx_err_t err;
ngx_uint_t level;
ngx_socket_t s;
ngx_event_t *rev, *wev;
ngx_connection_t *c;
//调用get
rc = pc->get(pc, pc->data);
if (rc != NGX_OK) {
return rc;
}
.....................................
}
其实nginx的load balance模块中,最核心的就是peer.get回调了,基本上核心的算法都在get回调里面实现,所以我们来看ngx_http_upstream_get_least_conn_peer,这个函数比较长,我们分段来看,首先是选择最少连接的server,这里要注意,其实不仅仅是最少连接,还要加上权重,这里nginx使用的是连接数和权重的乘积。
还有一个要注意的,就是对于每一个请求,Nginx保存了一个位图,这个位图保存了所有server是否已经被当前request使用过的状态,如果使用过则对应的位是1,否则为0,这个主要是为了处理失败的情况。
for (i = 0; i < peers->number; i++) {
//一个字节8位,所以计算当前peer所处的位置
n = i / (8 * sizeof(uintptr_t));
//得到当前peer的状态
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
//如果已经服务过,则跳过
if (lcp->rrp.tried[n] & m) {
continue;
}
peer = &peers->peer[i];
if (peer->down) {
continue;
}
//如果超过最大失败次数,并且还没超时,则跳过.
if (peer->max_fails
&& peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
{
continue;
}
/*
* select peer with least number of connections; if there are
* multiple peers with the same number of connections, select
* based on round-robin
*/
//选择server
if (best == NULL
|| lcp->conns[i] * best->weight < lcp->conns[p] * peer->weight)
{
best = peer;
//many表示是否有多个server满足条件.
many = 0;
//p为best的位置
p = i;
} else if (lcp->conns[i] * best->weight
== lcp->conns[p] * peer->weight)
{
//相等则说明有多个server满足条件.
many = 1;
}
}
接下来这段就是当有多个server满足条件的时候的处理,这里是如果多个server满足条件,则进入round robin的处理逻辑。下面的代码和round robin的get peer回调中算法是一模一样的。就是根据权重选择一个合适的server,这里Nginx还调整过round robin算法,想了解,可以看这里 http://trac.nginx.org/nginx/changeset/4668/nginx.
if (many) {
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"get least conn peer, many");
for (i = p; i < peers->number; i++) {
n = i / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << i % (8 * sizeof(uintptr_t));
if (lcp->rrp.tried[n] & m) {
continue;
}
peer = &peers->peer[i];
if (peer->down) {
continue;
}
if (lcp->conns[i] * best->weight != lcp->conns[p] * peer->weight) {
continue;
}
if (peer->max_fails
&& peer->fails >= peer->max_fails
&& now - peer->checked <= peer->fail_timeout)
{
continue;
}
peer->current_weight += peer->effective_weight;
total += peer->effective_weight;
if (peer->effective_weight < peer->weight) {
peer->effective_weight++;
}
if (peer->current_weight > best->current_weight) {
best = peer;
p = i;
}
}
}
best->current_weight -= total;
best->checked = now;
最后就是更新一些状态位,比如更新server的连接数 等
pc->sockaddr = best->sockaddr;
pc->socklen = best->socklen;
pc->name = &best->name;
lcp->rrp.current = p;
n = p / (8 * sizeof(uintptr_t));
m = (uintptr_t) 1 << p % (8 * sizeof(uintptr_t));
//设置对应的server的状态
lcp->rrp.tried[n] |= m;
//更新连接数
lcp->conns[p]++;
if (pc->tries == 1 && peers->next) {
pc->tries += peers->next->number;
}
最后就来看peer的free回调,它主要是用来清理一些状态比如连接数减1等。
static void
ngx_http_upstream_free_least_conn_peer(ngx_peer_connection_t *pc,
void *data, ngx_uint_t state)
{
ngx_http_upstream_lc_peer_data_t *lcp = data;
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
"free least conn peer %ui %ui", pc->tries, state);
if (lcp->rrp.peers->single) {
lcp->free_rr_peer(pc, &lcp->rrp, state);
return;
}
if (state == 0 && pc->tries == 0) {
return;
}
//更新连接状态
lcp->conns[lcp->rrp.current]--;
//调用round robin的free.
lcp->free_rr_peer(pc, &lcp->rrp, state);
}
Related posts:
-
nginx 0.8.x稳定版对linux aio的支持
-
nginx的启动流程分析(一)
-
nginx的启动流程分析(二)
(责任编辑:IT) |