nginx 1.15.1 版本说明

Feature List

主要包含如下两点:

  • Feature: the “random” directive inside the “upstream” block.

  • Feature: improved performance when using the “hash” and “ip_hash” directives with the “zone” directive.

一个是引入了random的upstream机制,还有一个是优化了zone区的hash性能; 下面我们详细拆解其功能

Upstream Zone机制

Nginx在1.9.0版本引入了zone的机制, 官方手册说明如下:

1
2
3
4
5
6
* Syntax: zone name [size];
* Default: —
* Context: upstream
> This directive appeared in version 1.9.0.
Defines the name and size of the shared memory zone that keeps the group’s configuration and run-time state that are shared between worker processes.

也就是其核心能力是使得upstream的相关配置以及状态,支持在进程间共享。这方面的优势显而易见,依照NGINX Load Balancing - HTTP Load Balancer 说明,我们可以总结为:

  • 其可以方便的支持健康检查,动态upstream配置等,而无需担心多进程数据同步的问题

  • 状态数据,比如max_fail等信息,可以被多个进程共享,这样是的业务避免无效的请求和重试, 否则每个进程都得去触发max_fail之类的阈值

关于upstream zone另外一个可以关注的点是其实现机制。直观来看的话,从单进程到多进程共享的数据结构,可能对源码会有较大的改动,尤其是有不少现存的upstream module, 不过nginx引入了一个相对巧妙的机制, 可以通过ngx_http_upstream_zone_module.c了解,其核心思路是:

  1. 将进程内的数据拷贝到共享内存里面
  2. 将所有的指针指向共享内存空间

这样几乎(主要有时候还需要解决多进程并发读写的问题)可以透明的将数据迁移到共享内存里面; 大致代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static ngx_int_t
ngx_http_upstream_init_zone(ngx_shm_zone_t *shm_zone, void *data)
{
size_t len;
ngx_uint_t i;
ngx_slab_pool_t *shpool;
ngx_http_upstream_srv_conf_t *uscf, **uscfp;
ngx_http_upstream_main_conf_t *umcf;
/* 省去部分代码,指向共享内存区域 */
shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
/* copy peers to shared memory */
umcf = shm_zone->data;
uscfp = umcf->upstreams.elts;
for (i = 0; i < umcf->upstreams.nelts; i++) {
uscf = uscfp[i];
if (uscf->shm_zone != shm_zone) {
continue;
}
/* 讲每个upstream conf 拷贝到共享内存区域 */
if (ngx_http_upstream_zone_copy_peers(shpool, uscf) != NGX_OK) {
return NGX_ERROR;
}
}
return NGX_OK;
}

所以回到这次的feature, 对所谓的『ip_hash/hash』的性能优化,其实就是对zone里面读写锁的精细化控制。

Random Upstream

另外一个比较大的特性是引入random upstream的支持, 其官方手册说明如下:

1
2
3
4
5
* Syntax: random [two [method]];
* Default: —
* Context: upstream
The optional two parameter instructs nginx to randomly select two servers and then choose a server using the specified method. The default method is least_conn which passes a request to a server with the least number of active connections.

这里我们主要关注random two least_conn的情况; 什么意思呢?

用随机算法的时候,我们最担心的就是随机不均衡的问题, 所以对于我们upstream的random算法,其也要考虑这个方面的情况, 而two least_conn就是处理这个问题的有效手段,下面具体分析。

首先回到一个纯粹的数学问题,N个请求让N个服务器处理,则处理请求最多的服务器其会处理多少请求呢?

我们知道正常情况下,平均一个服务器会处理一个请求,但是事实上这个是低概率事件, 从Balanced Allocations的分析,其原话是:

the fullest box has, with high probability, ln $n$ / ln ln $n(1 + o(1))$ ball in it

也就是其实最大负载的机器其处理的请求数会远超于均值, 而如果用Two Random Choices(随机选两个,其次选择负载低的策略), 其概率大概为 $ln \ ln\ n$

如下是去概率函数分布图:

two-random-choice

总而言之, 双次选择会使得我们的负载均衡的表现更为优异。

如上公式的数学推导其实是比较复杂的(至少我没有看懂), 所以这里在简单分享自己对”N个请求让N个服务器处理,则处理请求最多的服务器其会处理多少请求呢?”这个问题的程序员解法;(Maybe有错误哈)

首先定义概率密度函数:

  • $f(k)$ : 表示N个球放N个盒子,处理请求最多的盒子其处理请求数量为k的概率
  • 则上面的问题,其实求的就是该分布的期望, 可以有:

$$E = \sum_{k=1}^{n}{f(k)*k}$$

其次,我们分析如何求解$f(k)$, 这里面我们通过传统概率算法计数法来计算对应的概率, 具体如下:

  • N个球放入N个盒子,不考虑球的差异性,其有 $\binom{2*n-1}{n}$ 可能性
  • 再考虑如果最多盒子对应的球的个数为k的放法, 我们定义为$c(n_1,n_2,k)$, 表明$n_1$个球放入$n_2$盒子,最多的盒子里面有$k$个球的方法数,则显然有:

    • $c(n,n,1)$ = 1
    • $c(n,n,n)$ = n

而对于$c(n,n,k)$我们则使用递归法统计, 我们可以估算其最多有$i$个盒子里面有$k$个球,基于次分析:

$$c(n_1,n_2,k) = \sum_{i=1}^{n} (cc(i) (\sum_{j=1}^{k-1}c(n_1-ik,n_2-i, j)))$$

其中$cc(i)$ 表明$i$个盒子有$k$个球,选取的办法有$\binom{n_2}{i}$
而公示后半部分的意思是剩余的盒子里面,最多为1,2,…,k-1个球的放法

按照如上的公示,可以得到代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def s(ball,box,k) :
if ball > box and k == 1 :
return 0
if box > ball and k == 1 :
total = math.factorial(box) / math.factorial(ball) / math.factorial(box - ball)
return total
if ball == box and k == 1 :
return 1
if k <= 0 :
return 0
total = 0
for i in range(1,ball+1) :
if i * k > ball :
break
if i > box :
break
tmp1 = math.factorial(box) / math.factorial(i) / math.factorial(box - i)
tmp2 = 0
for j in range(1,k) :
tmp2 += s(ball- i*k, box-i, k-j)
total += tmp1 * tmp2
return total

依照如上公示,我们可以得到一些期望值如下:

1
2
3
ball_exp(10) = 3.7
ball_exp(20) = 4.7
ball_exp(40) = 5.6

和上面的数据推导数据不完全一致,但是还是符合相应的函数曲线的

参考