nginx-学习笔记8
负载均衡模块
负载均衡模块用于从”upstream”指令定义的后端主机列表中选取一台主机。nginx先使用负载均衡模块找到一台主机,再使用upstream模块实现与这台主机的交互。
配置
要了解负载均衡模块的开发方法,首先需要了解负载均衡模块的使用方法。
在配置文件中,我们如果需要使用ip hash的负载均衡算法。我们需要写一个类似下面的配置:
upstream test { ip_hash; server 192.168.0.1; server 192.168.0.2; }
指令
配置决定指令系统,现在就来看ip_hash的指令定义:
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 };
钩子
负载均衡模块的钩子代码都是有规律的,这里通过ip_hash模块来分析这个规律。
static char * ngx_http_upstream_ip_hash(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_ip_hash; uscf->flags = NGX_HTTP_UPSTREAM_CREATE |NGX_HTTP_UPSTREAM_MAX_FAILS |NGX_HTTP_UPSTREAM_FAIL_TIMEOUT |NGX_HTTP_UPSTREAM_DOWN; return NGX_CONF_OK; }
设置uscf->flags
- NGX_HTTP_UPSTREAM_CREATE:创建标志,如果含有创建标志的话,nginx会检查重复创建,以及必要参数是否填写;
- NGX_HTTP_UPSTREAM_MAX_FAILS:可以在server中使用max_fails属性;
- NGX_HTTP_UPSTREAM_FAIL_TIMEOUT:可以在server中使用fail_timeout属性;
- NGX_HTTP_UPSTREAM_DOWN:可以在server中使用down属性;
- NGX_HTTP_UPSTREAM_WEIGHT:可以在server中使用weight属性;
- NGX_HTTP_UPSTREAM_BACKUP:可以在server中使用backup属性。
在负载均衡模块的指令处理函数中可以设置并修改upstream{}中”server”指令支持的属性。
设置init_upstream回调
nginx初始化upstream时,会在ngx_http_upstream_init_main_conf函数中调用设置的回调函数初始化负载均衡模块。这里不太好理解的是uscf的具体位置。通过下面的示意图,说明upstream负载均衡模块的配置的内存布局。
从图上可以看出,MAIN_CONF中ngx_upstream_module模块的配置项中有一个指针数组upstreams,数组中的每个元素对应就是配置文件中每一个upstream{}的信息。
初始化配置
init_upstream回调函数执行时需要初始化负载均衡模块的配置,还要设置一个新钩子,这个钩子函数会在nginx处理每个请求时作为初始化函数调用
ngx_http_upstream_init_round_robin(cf, us);
us->peer.init = ngx_http_upstream_init_ip_hash_peer;
这段代码非常简单:IP hash模块首先调用另一个负载均衡模块Round Robin的初始化函数,然后再设置自己的处理请求阶段初始化钩子。实际上几个负载均衡模块可以组成一条链表,每次都是从链首的模块开始进行处理。如果模块决定不处理,可以将处理权交给链表中的下一个模块。这里,IP hash模块指定Round Robin模块作为自己的后继负载均衡模块,所以在自己的初始化配置函数中也对Round Robin模块进行初始化。
初始化请求
nginx收到一个请求以后,如果发现需要访问upstream,就会执行对应的peer.init函数。这是在初始化配置时设置的回调函数。这个函数最重要的作用是构造一张表,当前请求可以使用的upstream服务器被依次添加到这张表中。之所以需要这张表,最重要的原因是如果upstream服务器出现异常,不能提供服务时,可以从这张表中取得其他服务器进行重试操作。此外,这张表也可以用于负载均衡的计算。之所以构造这张表的行为放在这里而不是在前面初始化配置的阶段,是因为upstream需要为每一个请求提供独立隔离的环境。
为了讨论peer.init的核心,我们还是看IP hash模块的实现:
r->upstream->peer.data = &iphp->rrp; ngx_http_upstream_init_round_robin_peer(r, us); r->upstream->peer.get = ngx_http_upstream_get_ip_hash_peer;
第一行是设置数据指针,这个指针就是指向前面提到的那张表;
第二行是调用Round Robin模块的回调函数对该模块进行请求初始化。面前已经提到,一个负载均衡模块可以调用其他负载均衡模块以提供功能的补充。
第三行是设置一个新的回调函数get。该函数负责从表中取出某个服务器。除了get回调函数,还有另一个r->upstream->peer.free的回调函数。该函数在upstream请求完成后调用,负责做一些善后工作。比如我们需要维护一个upstream服务器访问计数器,那么可以在get函数中对其加1,在free中对其减1。如果是SSL的话,nginx还提供两个回调函数peer.set_session和peer.save_session。一般来说,有两个切入点实现负载均衡算法,其一是在这里,其二是在get回调函数中。
peer.get和peer.free回调函数
这两个函数是负载均衡模块最底层的函数,负责实际获取一个连接和回收一个连接的预备操作。之所以说是预备操作,是因为在这两个函数中,并不实际进行建立连接或者释放连接的动作,而只是执行获取连接的地址或维护连接状态的操作。需要理解的清楚一点,在peer.get函数中获取连接的地址信息,并不代表这时连接一定没有被建立,相反的,通过get函数的返回值,nginx可以了解是否存在可用连接,连接是否已经建立。这些返回值总结如下:
返回值 | 说明 | nginx后续动作 |
NGX_DONE | 得到了连接地址信息,并且连接已经建立。 | 直接使用连接,发送数据。 |
NGX_OK | 得到了连接地址信息,但连接并未建立。 | 建立连接,如连接不能立即建立,设置事件, 暂停执行本请求,执行别的请求。 |
NGX_BUSY | 所有连接均不可用。 |
返回502错误至客户端。 |
在这里有几个问题:
Q: | 什么时候连接是已经建立的? |
---|---|
A: | 使用后端keepalive连接的时候,连接在使用完以后并不关闭,而是存放在一个队列中,新的请求只需要从队列中取出连接,这些连接都是已经准备好的。 |
Q: | 什么叫所有连接均不可用? |
A: | 初始化请求的过程中,建立了一张表,get函数负责每次从这张表中不重复的取出一个连接,当无法从表中取得一个新的连接时,即所有连接均不可用。 |
Q: | 对于一个请求,peer.get函数可能被调用多次么? |
A: | 正式如此。当某次peer.get函数得到的连接地址连接不上,或者请求对应的服务器得到异常响应,nginx会执行ngx_http_upstream_next,然后可能再次调用peer.get函数尝试别的连接。upstream整体流程如下: |