Nginx server级别动态加载方案

背景

Nginx server级别动态加载方案

nginx配置加载,既全量配置加载,主要过程如下

Nginx master收到reload信号

Nginx master全量加载配置

Nginx master fork 新work 处理新请求

Nginx master 给老worker发送shutting down消息通知其推出

shutting down worker不会处理新连接,不同场景下shutting down worker退出时机不同

http 1.1连接 :

对于已处理完请求,直接关闭连接

对于正在处理请求,处理完当前请求退出

http2连接:

reload时向client发送goaway消息,告知客户端不再接受新请求,处理完所有当前请求后退出

websocket连接 -- 对shutting down影响最大

nginx不感知websocket协议,tcp连接不断,shutting down worker不能退出

全量配置加载存在的问题

影响系统稳定性。

reload产生大量shutting down work,不能及时退出,占用大量内存,影响系统稳定性

生效慢

5000个server,需要5s。

不同规则的变更互相影响

server1的变更影响server2的长连接

负载均衡调度不准

业界解决方案调研

*

方案 实现原理 存在问题
nginx方案 通过设置shutting down状态进程存活时间解决 没有解决加载慢的问题,依然会fork进程影响系统稳定性

超过存活时间后,用户连接会被强制断开,影响用户体验

nginx+lua方案 nginx配置不变,域名+url相关配置交由lua管理,避免reload

 

功能限制: 无法支持server级别配置(端口等)的增量加载

后续开发维护成本高

nginx模块众多,配置指令更多,需要通过lua实现现有指令;使用新指令都需要进行开发

动态upstream方案

 

增量配置加载,不fork进程:

1.master收到增量reload信号

2.master首先增量加载配置

3.然后通知worker增量加载

只支持upstream级别配置的增量加载

不支持server、location级别配置增量加载,无法满足云上使用场景

 

nginx全量配置加载实现

worker_processes 1;

 

http {

upstream test {

server 127.0.0.1:80;

}

 

server {

listen 80;

server_name localhost;

 

location /proxy {

proxy_pass http://test;

}

}

}

配置加载: nginx全量配置加载由ngx_init_cycle()函数实现,主要流程如下:

更新时间、申请内存池

初始化pathes数组、open_files链表、shared_memory链表、listening数组等

调用core模块的create_conf()生成core模块配置

通过递归调用ngx_conf_parse完成配置解析

http配置块处理主要流程

创建http配置块的ngx_http_conf_ctx_t并初始化

递归调用ngx_conf_parse 完成所有server、upstream配置块的配置加载

调用ngx_http_optimize_servers完成server相关配置优化,用于加速查找

优化后,根据listen配置生成指定ip+port下的server配置的hash表

调用core模块的init_conf(),进行core模块的配置初始化

遍历open_files链表中的每一个文件并打开

创建共享内存并初始化

遍历cycle->listening数组并监听(old_cycle中没有监听的)

调用所有模块的init_module

释放残留在old_cycle中的资源: 释放多余的共享内存, 关闭多余的侦听sockets,关闭多余的打开文件等

加载后nginx配置管理结构简化版如下

Nginx server级别动态加载方案

方案设计

方案难点

基于改造nginx实现server级别配置的增量加载,面临的难点

nginx配置管理实现复杂

4级指针,错综复杂的数据结构关联

nginx配置贯穿请求的整个生命周期,改动影响大、风险高

nginx配置加载大量使用动态数组,不利于实现修改和删除

nginx基于内存池实现的内存管理,不利于实现删除

需要支持的配置加载场景多

server级别、location级别配置的增删改

用户对nginx指令需求多,后续会有持续需求

需要支持server、location级别下任意配置的增量加载

实现场景分析

Nginx server级别动态加载方案

需要实现场景:除了upstream级别配置变更,还有server级别配置的增删改、location级别的增删改,6种场景

首先简化实现场景,降低方案复杂度

将server、location级别配置变更归类为server级别变更

只需要实现新增server、删除server两种场景

location级别的增删改归类为server级别配置修改,通过新增和删除server实现

Nginx server级别动态加载方案

再单独梳理http配置块下的server配置加载+配置生效流程, 主要分为下面几步

配置加载: 加载server配置块到nginx动态数组

配置优化根据listen配置生成指定ip+port下的server配置的hash表

配置生效: 监听端口+ fork worker

配置查找: worker根据ip+port+domain查找server

Nginx server级别动态加载方案

根据上诉分析,提出我们的解决方案: 复用+改造nginx配置加载逻辑,实现server配置的增量新增、删除、修改。

 

新增server

以新增serverx为例,serverx中listen的是ip2的81端口,主要加载流程如下:

配置加载阶段:将serverx配置增量加载到内存,添加到动态数组中

配置优化阶段:更新ip2:81端口下的hash表,关联serverx

这样在配置查找阶段,新的连接就可以查找到serverx,达到新增server的目的

Nginx server级别动态加载方案

删除server

首先看下实现删除server面临的挑战

Nginx基于内存池实现内存管理

大块申请,避免频繁申请产生内存碎片

内存池释放时统一释放内存,简化实现,避免内存泄露

内存中删除规则需要改造nginx内存管理实现,影响大

Nginx配置管理大量使用动态数组,配置删除改动大

需要移动数组

还涉及大量相关数据结构的改动

难以实现内存中删除,是否可以不删除?

基于不从内存中删除的思路,提出了删除server的解决方案,以删除servern为例,servern listen的是ip2的81端口:

首先在配置加载阶段, 根据server_id在动态数组中查找到servern, 将其置为无效

然后配置优化阶段,会忽略掉无效的server,保证iP2 81端口下的hash表中不包含servern

这样在配置查找阶段,通过查找不到servern,达到删除server的目的。

Nginx server级别动态加载方案

内存不删除影响

内存占用情况,平均单次加载消耗内存20k,影响非常小

通过增加内存监控,超过阈值通过reload彻底释放内存

修改server:

通过新增+删除实现,已修改serverx为例

配置加载阶段:将serverx_old置位无效,serverx_new配置增量加载到内存,添加到动态数组中

配置优化阶段:更新ip2:81端口下的hash表,忽略掉无效的serverx_old,关联serverx_new

这样在配置查找阶段,新的连接就可以查找到serverx_new,达到新增serverx的目的

Nginx server级别动态加载方案

agent、master和worker配置一致性

主要流程如下

nginx master收到agent发来dysvr_reload信号

master先执行dysvr_reload流程,如果失败,回写配置文件通知agent使用正常reload方式

master reload成功,则向所有worker发送dysvr_reload信号,通知worker执行相同的dysvr_reload流程,如果失败,通知agent使用正常reload方式

所有worker执行成功后,master再通知agent reload成功

方案的优势和收益

优势

简化了实现场景,降低实现复杂度,以少量的内存占用换取系统的稳定。

通过新增和删除实现server级别配置的增量加载

复用nginx现有配置加载逻辑,可支持server、upstream块下任意nginx配置的增量加载,极大降低后续维护成本

收益

避免fork进程,提高系统稳定性

提升用户体验

7层规则配置加载从5000ms->200ms(5000个server)

用户间规则变更隔离

长连接不断

负载均衡调度准确

 

 


人生有無數種可能,人生有無限的精彩,人生沒有盡頭。一個人只要足夠的愛自己,尊重自己內心的聲音,就算是真正的活著。