nginx回源时bind ip的一些优化
字数 1120 2025-08-15 21:33:32
Nginx回源时bind IP的优化与实现
1. 背景与问题描述
在Nginx作为反向代理服务器时,默认情况下proxy_bind指令只支持绑定单个IP地址进行回源连接。这会导致以下问题:
- 单IP回源连接数受限,可能触发源站连接数限制
- 缺乏IP轮换机制,无法充分利用多IP资源
- 健康检查与业务回源使用相同IP,缺乏隔离
本文介绍如何通过修改Nginx源码实现多IP回源绑定和健康检查IP隔离的优化方案。
2. 核心优化点
2.1 proxy_bind多IP支持优化
原proxy_bind配置:
{
ngx_string("proxy_bind"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE12,
ngx_http_upstream_bind_set_slot,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_proxy_loc_conf_t, upstream.local),
NULL
}
优化后配置:
{
ngx_string("proxy_bind"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
ngx_http_upstream_bind_set_slot_array,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_proxy_loc_conf_t, upstream.local_array),
NULL
}
关键修改:
- 将
NGX_CONF_TAKE12改为NGX_CONF_1MORE,支持多个参数 - 使用
ngx_http_upstream_bind_set_slot_array替代原函数 - 存储结构改为
local_array数组
2.2 多IP绑定实现代码
char *ngx_http_upstream_bind_set_slot_array(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
ngx_http_upstream_local_array_t **plocal, *local;
ngx_str_t *value;
ngx_addr_t *addr;
ngx_int_t rc;
ngx_uint_t i;
plocal = (ngx_http_upstream_local_array_t **) (p + cmd->offset);
if (*plocal != NGX_CONF_UNSET_PTR) {
return "bind is duplicate";
}
value = cf->args->elts;
// 建立local array
local = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_local_array_t));
*plocal = local;
// 建立local peer addr
local->addr = ngx_pcalloc(cf->pool, sizeof(ngx_peer_addrs_t));
// 建立addr array
local->addr->addrs = ngx_array_create(cf->pool, 1, sizeof(ngx_addr_t));
// 遍历所有的local ip,放进array中
for (i = 1; i < cf->args->nelts; i++) {
addr = ngx_array_push(local->addr->addrs);
if (addr == NULL) {
return NGX_CONF_ERROR;
}
rc = ngx_parse_addr(cf->pool, addr, value[i].data, value[i].len);
switch (rc) {
case NGX_OK:
addr->name = value[i];
break;
case NGX_DECLINED:
ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid address \"%V\"", &value[i]);
/* fall through */
default:
return NGX_CONF_ERROR;
}
}
// ...
}
2.3 初始化请求时绑定IP数组
static void ngx_http_upstream_init_request(ngx_http_request_t *r) {
// ...
u = r->upstream;
u->peer.local_array = ngx_http_upstream_get_local_array(r, u->conf->local_array);
// ...
}
2.4 获取配置的流程
- 通过
proxy_pass获取配置:
static ngx_int_t ngx_http_proxy_handler(ngx_http_request_t *r) {
// ...
u = r->upstream;
plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module);
u->conf = &plcf->upstream;
// ...
}
proxy_pass指令设置upstream:
static char *ngx_http_proxy_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
// ...
plcf->upstream.upstream = ngx_http_upstream_add(cf, &u, 0);
// ...
}
3. 健康检查IP隔离(check_bind)
3.1 check_bind配置
ngx_string("check_bind"),
NGX_HTTP_MAIN_CONF|NGX_CONF_1MORE,
ngx_http_upstream_bind_set_slot_array,
NGX_HTTP_MAIN_CONF_OFFSET,
offsetof(ngx_http_upstream_check_main_conf_t, global_local),
NULL
3.2 配置结构体
typedef struct {
ngx_uint_t check_shm_size;
ngx_http_upstream_check_peers_t *peers;
ngx_http_upstream_local_array_t *global_local;
} ngx_http_upstream_check_main_conf_t;
3.3 初始化主配置
static char * ngx_http_upstream_check_init_main_conf(ngx_conf_t *cf, void *conf) {
// ...
// 拿到upstream module的main conf
umcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_upstream_module);
// 拿到后端数组的指针
uscfp = umcf->upstreams.elts;
for (i = 0; i < umcf->upstreams.nelts; i++) {
// 循环赋值
if (ngx_http_upstream_check_init_srv_conf(cf, uscfp[i], ucmcf->global_local) != NGX_OK) {
return NGX_CONF_ERROR;
}
}
// ...
}
3.4 初始化服务器配置
static char * ngx_http_upstream_check_init_srv_conf(ngx_conf_t *cf, void *conf, ngx_http_upstream_local_array_t *global_local) {
// ...
ngx_http_upstream_srv_conf_t *us = conf;
// 拿到这个upstream srv conf下的check module conf
ucscf = ngx_http_conf_upstream_srv_conf(us, ngx_http_upstream_check_module);
// 进行赋值
if (ucscf->global_local == NGX_CONF_UNSET_PTR) {
if (global_local != NGX_CONF_UNSET_PTR && global_local != NULL) {
ucscf->global_local = global_local;
}
}
// ...
}
3.5 添加peer时的处理
ngx_uint_t ngx_http_upstream_check_add_peer(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us, ngx_addr_t *peer_addr) {
// ...
// add check_bind support for dyups modules.
if (ucscf->global_local == NGX_CONF_UNSET_PTR) {
if (ucmcf->global_local != NGX_CONF_UNSET_PTR && ucmcf->global_local != NULL) {
ucscf->global_local = ucmcf->global_local;
}
}
// 添加peer
peers = ucmcf->peers;
peer = ngx_array_push(&peers->peers);
peer->index = peers->peers.nelts - 1;
// 这部分很关键,上面刚刚赋值好global_local的ucscf被赋值为peer->conf
peer->conf = ucscf;
peer->upstream_name = &us->host;
peer->peer_addr = peer_addr;
// ...
}
3.6 健康检查连接处理
static void ngx_http_upstream_check_connect_handler(ngx_event_t *event) {
// ...
peer = event->data;
// peer的conf就是ucscf
ucscf = peer->conf;
// 赋值
if (peer->conf->global_local != NGX_CONF_UNSET_PTR && peer->conf->global_local != NULL) {
peer->pc.local_array = peer->conf->global_local->addr;
} else {
peer->pc.local_array = NULL;
}
rc = ngx_event_connect_peer(&peer->pc);
// ...
}
4. 实现效果
- 多IP回源:通过
proxy_bind可以指定多个IP地址,Nginx会轮询使用这些IP与后端建立连接 - 连接数扩展:突破单IP连接数限制,提高并发能力
- 健康检查隔离:通过
check_bind指定专门用于健康检查的IP组,与业务流量隔离 - 配置灵活性:支持在main、server和location级别配置不同的绑定IP策略
5. 使用示例
5.1 多IP回源配置
http {
upstream backend {
server 192.168.1.100:8080;
}
server {
location / {
proxy_pass http://backend;
proxy_bind 10.0.0.1 10.0.0.2 10.0.0.3;
}
}
}
5.2 健康检查IP隔离配置
http {
upstream backend {
server 192.168.1.100:8080;
check interval=3000 rise=2 fall=5 timeout=1000 type=http;
check_http_send "GET /health HTTP/1.0\r\n\r\n";
check_http_expect_alive http_2xx http_3xx;
}
check_bind 172.16.0.1 172.16.0.2;
server {
location / {
proxy_pass http://backend;
proxy_bind 10.0.0.1 10.0.0.2 10.0.0.3;
}
}
}
6. 注意事项
- 确保所有绑定的IP地址在服务器上已配置且可用
- 修改源码后需要重新编译Nginx
- 对于动态upstream(dyups)需要特殊处理,确保添加peer时正确继承配置
- 健康检查IP组和业务IP组最好使用不同的IP段,便于监控和管理
- 多IP轮询是简单的顺序轮询,没有复杂的负载均衡算法
7. 总结
通过本文介绍的Nginx源码修改方案,可以有效解决单IP回源连接数限制问题,并实现健康检查与业务流量的IP隔离。这种优化特别适用于以下场景:
- 需要大量回源连接的高并发应用
- 源站对单个IP有连接数限制的环境
- 需要严格隔离健康检查流量的场景
- 多线路接入需要指定不同出口IP的情况
该方案已经在生产环境中得到验证,能够显著提高Nginx作为反向代理时的连接能力和稳定性。