背景最近我们的在线网关换成了APISIX,遇到了一些问题。其中一个比较困难的问题是APISIX的进程隔离。
不同种类的APISIX请求之间的交互。首先,我们遇到APISIX Prometheus插件在监控数据过多时影响正常业务接口响应的问题。启用Prometheus插件后,可以通过HTTP接口获取APISIX收集的监控信息,然后显示在特定的看板中。
Curl我们http://172.30.xxx.xxx:9091/apisix/prometheus/metrics网关接入的业务系统非常复杂,有400小编条路由。每次拉普罗米修斯插件,度量数超过50万,大小超过80M+。这部分信息需要在lua层组装发送。请求时,处理该请求的工作进程的CPU会非常高,处理时间会超过2s,导致该工作进程处理正常的业务请求会有2s+的延迟。
当时的临时措施是修改Prometheus插件,减少采集和传输的范围和数量,暂时绕过这个问题。分析Prometheus插件收集的信息后,收集的数据数量如下。
407171 API six _ http _ latency _ bucket 29150 API six _ http _ latency _ sum 29150 API six _ http _ latency _ count 20024 API six _ bandwidth 17707 API six _ http _ status 11 API six _ Etcd _ modify _ indexes 6 API six _ nginx _ http _ current _ connections 1 API six _ node _ info结合我们业务的实际需要,去掉了一些信息,减少了一些延迟。
然后与github商量问题(github.com/apache/apis…),并且发现APISIX在商业版中提供了这个功能。因为还是想直接用开源版,这个问题暂时可以绕过,所以没有继续深究。
但是,还有一个问题,就是在业务高峰期,Admin API处理不及时。我们使用管理API进行版本切换。在业务高峰期,APISIX负载较高,影响admin相关接口,导致版本切换时偶尔出现超时故障。
这里的原因很明显,影响是双向的:之前的Prometheus插件是APISIX内部请求影响了正常的业务请求。在这里,情况正好相反。正常的业务请求会影响APISIX中的请求。所以把APISIX里的请求和正常的业务请求分开是非常重要的,所以实现这个功能花了一点时间。
上述对应将生成如下nginx.conf配置示例文件,如下所示。
// 9091端口处理普罗米修斯插件服务器的接口请求{ listen 0 . 0 . 0:9091;access _ log offlocation/{ content _ by _ Lua _ block { local Prometheus = require(" API six . plugins . Prometheus . exporter ")Prometheus . export _ metrics()} }//9180端口句柄管理接口服务器{ listen 0 . 0 . 0 . 0:9180;location/API six/admin { content _ by _ Lua _ block { API six . http _ admin()} }//正常处理业务请求80和443。服务器{ Listen 0 . 0 . 0:80;监听0 . 0 . 0 . 0:443 SSL;server _ name _location/{ proxy _ pass $ upstream _ scheme://API six _ back end $ upstream _ uri;access _ by _ Lua _ block { API six . http _ access _ phase()} }
修改Nginx的源代码,实现进程隔离。对OpenResty比较了解的同学应该知道,OpenResty是在Nginx的基础上扩展的,增加了权限。
特权代理特权进程不监听任何端口,不向外界提供任何服务,主要用于预定任务等。
我们需要做的是添加一个或多个woker进程来处理APISIX内部的请求。
Nginx采用多进程模式,主进程调用bind和listen监听socket。fork函数创建的工作进程复制这些监听状态的套接字句柄。
Nginx源代码中创建worker子进程的伪代码如下:
void ngx _ master _ process _ cycle(ngx _ cycle _ t * cycle){ ngx _ setproctitle("主进程");ngx _ start _ worker _ processes()for(I = 0;我& ltn;++){//根据cpu核心数创建子进程NGX _ spawn _ process (I,“worker process”);PID = fork();(;的ngx _ worker _ process _ cycle()ngx _ setproctitle(" worker process ");;){//工作子进程的无限循环//...} } for(;;) {//...主进程无限循环}}我们需要做的是在for循环中启动一个或n个以上的子进程,专用于处理特定端口的请求。
这里的演示以启动一个工作进程为例。修改ngx_start_worker_processes的逻辑如下。使用命令名“隔离进程”再启动一个工作进程表示内部隔离过程。
静态void ngx _ start _ worker _ processes(ngx _ cycle _ t * cycle,ngx_int_t n,ngx _ int _ t type){ ngx _ int _ t I;// ...for(I = 0;我& ltn+1;++){//这里N改为n+1,多启动一个进程if (i == 0) {//第一个子进程组作为隔离进程NGX _ spawn _ process (cycle,NGX _ worker _ process _ cycle,(void *) (intptr_t) i,“隔离进程”,type);} else { ngx_spawn_process(cycle,ngx_worker_process_cycle,(void *) (intptr_t) i," worker process ",type);}}//...}然后,ngx_worker_process_cycle的逻辑对0号工人做了特殊处理,这里的演示使用18080、18081、18082作为隔离端口。
静态void ngx _ worker _ process _ cycle(ngx _ cycle _ t * cycle,void * data){ ngx _ int _ t worker =(int ptr _ t)data;港口间
可以看到,已经启动了一个内部隔离工作进程(pid=3355)和四个普通工作进程(pid=3356~3359)。
首先,我们可以查看端口监视器,以确定我们的更改是否有效。
可以看出,隔离进程3355监听端口18080、18081和18082,普通进程3356监听端口20880和20881。
使用ab请求端口18080,看看它是否只运行3355进程CPU满。
a b+ n 1000-c 10 localhost:18080 top -p 3355,3356,3357,3358,3359可以看出此时只有3355,隔离进程,是满的。
接下来,让我们看看非隔离端口请求是否只会填充其他四个woker进程。
ab -n 10000 -c 10 localhost:28080 top -p 3355,3356,3357,3358,3359
不出所料,只会运行4个普通工作进程(pid=3356~3359),此时3355的cpu利用率为0。
到目前为止,我们已经通过修改Nginx的源代码,实现了一个特定的基于端口号的进程隔离方案。这个演示中的端口号被写死了。当我们实际使用它时,它是通过lua代码传入的。
init _ by _ Lua _ block { local process = require " ngx . process " local ports = { 18080,18081,18083} local ok,err = process . enable _ isolation _ process(ports)如果不ok,则ngx.log(ngx。ERR," enable _ isolation _ process failed ")返回else ngx.log (ngx.err," enable _ isolation _ process success ")end } Lua在这里需要通过ffi引入OpenResty,这不是本文的重点,就不说了。
后记这个方案有一点hack,可以更好的解决我们目前遇到的问题,但是也是有成本的。你需要维护你自己的OpenResty代码分支。喜欢折腾或者确实需要这个功能的同学可以试试。
上面的方案只是我对Nginx源代码肤浅的认识所做的改动。如有使用不当之处,请反馈给我。
以上是修改Nginx源代码实现工作者进程隔离的详细内容。有关Nginx工作进程隔离的更多信息,请关注主机频道zhujipindao的其他相关文章。com!
评论前必须登录!
注册