首页 » 网站推广 » php聚合api应用技巧_运用 Docker 和 Nginx NJS 实现 API 聚合做事前篇

php聚合api应用技巧_运用 Docker 和 Nginx NJS 实现 API 聚合做事前篇

访客 2024-10-23 0

扫一扫用手机浏览

文章目录 [+]

如果你熟习 Node 或者其他后端措辞,下面代码要做的事情,就一览无余了:首先定义了一个名为 simple 的函数 ,接着定义了我们要展示的接口数据,然后设置 Nginx 相应内容类型为 UTF8 编码的 JSON,以及接口 HTTP Code 为 200,末了声明模块中的 simple 是可被公开调用的。

function simple(req) { var result = { code: 200, desc: "这是描述内容" }; req.headersOut["Content-Type"] = "application/json;charset=UTF-8"; req.return(200, JSON.stringify(result));}export default { simple };

将上面的内容保存为 app.js,并放置于一个名为 script 目录中,我们稍后利用。
接着我们声明一份可以让 Nginx 调用 NJS 的配置文件:

php聚合api应用技巧_运用 Docker 和 Nginx NJS 实现 API 聚合做事前篇

load_module modules/ngx_http_js_module.so;user nginx;worker_processes auto;error_log /var/log/nginx/error.log warn;pid /var/run/nginx.pid;events { worker_connections 1024;}http { include /etc/nginx/mime.types; default_type application/octet-stream; js_import app from script/app.js; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; keepalive_timeout 65; server { listen 80; server_name localhost; charset utf-8; gzip on; location / { js_content app.simple; } }}

将上述内容保存为 nginx.conf,我们同样稍后利用。

php聚合api应用技巧_运用 Docker 和 Nginx NJS 实现 API 聚合做事前篇
(图片来自网络侵删)

可以看到这份配置文件和以往的配置文件看起来差别不大,但是确实又有一些“不同”,将所有和 NJS 无关的内容去掉,就可以清晰的看到 NJS 是如何和 Nginx 联动的。

load_module modules/ngx_http_js_module.so;...http {... js_import app from script/app.js; server {... location / { js_content app.simple; } }}

首先是全局显式声明加载 ngx_http_js_module.so 模块,然后是将我们编写的脚本引入 Nginx HTTP 块浸染域内,末了则是调用脚本详细的方法供应做事。

为了方便的验证做事,我们还须要编写一个大略的 compose 编排文件:

version: '3'services: nginx-api-demo: image: nginx:1.19.8-alpine restart: always ports: - 8080:80 volumes: - ./nginx.conf:/etc/nginx/nginx.conf - ./script:/etc/nginx/script

上一篇文章提过,目前 NJS 已经是 Nginx 官方模块,并默认附带在官方 Docker 镜像中,以是我们这里直策应用最新的官方镜像 nginx:1.19.8-alpine 就可以了。

将上面的文件保存为 docker-compose.yml ,适当调度下上面文件的目录构造,并利用 docker-compose up 启动做事,访问 localhost:8080,可以看到我们得到了我们想要的结果,浏览器中涌现了接口内容。

浏览器中展示接口结果

和我们利用 Nginx 调用 CGI 程序不同,可以看到接口处理韶光只花费了 1ms ,虽然这和我们实现的代码繁芜度非常低有关系,但是常日网络开销导致我们得到的结果会远大于这个数值。
从某个角度解释不须要“外部程序”打算参与时, Nginx 直接参与结果打算在性能方面是有潜力的。

考试测验编写获取远端数据的接口

接着我们来编写一个能够获取远端数据的接口,和之前编写的办法类似,只须要将我们定义的接口返回数据更换为利用 subrequest 方法要求的数据接口结果即可。

function fetchRemote(req) { req.subrequest("https://www.mysql.com/common/chat/chat-translation-data.json").then((response) => { req.headersOut["Content-Type"] = "application/json;charset=UTF-8"; req.return(200, JSON.stringify(response)); })}export default { fetchRemote };

为了便于区分,我们这里将函数名改为更贴切的“fetchRemote”,接着将 nginx.conf 文件中的调用方法也进行更新:

...location / { js_content app.fetchRemote;}...

随后利用 docker-compose up 重新启动做事,再次访问 localhost:8080 来验证程序的结果是否符合预期。

然而页面返回了类似下面的结果:

{"status":404,"args":{},"httpVersion":"1.1","remoteAddress":"172.21.0.1","headersOut":{"Content-Type":"text/html","Content-Length":"555"},"method":"GET","uri":"https://www.mysql.com/common/chat/chat-translation-data.json","responseText":"<html>\r\n<head><title>404 Not Found</title></head>\r\n<body>\r\n<center><h1>404 Not Found</h1></center>\r\n<hr><center>nginx/1.19.8</center>\r\n</body>\r\n</html>\r\n<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n<!-- a padding to disable MSIE and Chrome friendly error page -->\r\n","headersIn":{"Host":"localhost:8080","Connection":"keep-alive","Cache-Control":"max-age=0","sec-ch-ua":"\"Google Chrome\";v=\"89\", \"Chromium\";v=\"89\", \";Not A Brand\";v=\"99\"","sec-ch-ua-mobile":"?0","DNT":"1","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9","Sec-Fetch-Site":"none","Sec-Fetch-Mode":"navigate","Sec-Fetch-User":"?1","Sec-Fetch-Dest":"document","Accept-Encoding":"gzip, deflate, br","Accept-Language":"zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7"}}

页面虽然返回了数据,但是显然不是我们想要的结果。

检讨 Nginx 日志,可以进一步理解这个缺点发生的缘故原由。

[error] 33#33: 1 open() "/etc/nginx/htmlhttps://www.mysql.com/common/chat/chat-translation-data.json" failed (2: No such file or directory), client: 172.21.0.1, server: localhost, request: "GET / HTTP/1.1", subrequest: "https://www.mysql.com/common/chat/chat-translation-data.json", host: "localhost:8080"...

不卖关子了,来聊聊“精确答案”。

精确的获取远程数据

这里会发生缺点由于 NJS 的 subrequest 方法仅支持将要求利用异步办法发送给反向代理。

将要要求地址改为由 Nginx 反向代理,这里由于这个接口我们仅用作 NJS 调用,不须要供应开放访问,以是可以添加 internal 指令,来进行外部访问限定处理,避免 NJS 之外调用过程访问我们的远端接口:

location /proxy/api-mysql { internal; proxy_pass https://www.mysql.com/; proxy_set_header Host www.mysql.com;}

接着修正之前代码中的要求地址:

function fetchRemote(req) { req.subrequest("/proxy/api-mysql/common/chat/chat-translation-data.json").then((response) => { req.headersOut["Content-Type"] = "application/json;charset=UTF-8"; req.return(200, JSON.stringify(response)); })}export default { fetchRemote };

再次启动做事,可以看到我们已经能够获取远端数据,但是结果看起来有一些问题:

{"status":200,"args":{},"httpVersion":"1.1","remoteAddress":"172.27.0.1","headersOut":{"Content-Type":"application/json","Content-Length":"1863","X-Frame-Options":"SAMEORIGIN","Strict-Transport-Security":"max-age=15768000","Last-Modified":"Tue, 27 Nov 2018 20:34:52 GMT","Accept-Ranges":"bytes","Vary":"Accept-Encoding","Content-Encoding":"gzip","X-XSS-Protection":"1; mode=block","X-Content-Type-Options":"nosniff"},"method":"GET","uri":"/proxy/api-mysql/common/chat/chat-translation-data.json","responseText":"\u001f�\b\u0000\u0000\u0000\u0000\u0000\u0000\u0003�Z[o\u0013G\u0014~G�?��W(\u0002�J�R�\u0014���Bk�JT}\u0018{��$�]3��4��|!j�i�4��&$��P(��;qA��}�\u001b\u0016\u0007'1�_�\u0019�\u001d��c�(�M\"9^9����sf��\u0006\u0019+!\u0003���p\u0016}�\b����\u0017B\rD���?ᄆ�e�98�B�D�\u0010�o�q\u0003�؂��c[lh@U\u00022�xk��\u0004

涌现这个问题的缘故原由是由于远端做事器给我们返回了 GZip 后的数据,以是这里我们有两个选择,见告做事器我们不支持 GZip,或者让 Nginx 对取回的数据进行解压缩。

由于存在即是我们见告远程做事器,我们不支持 GZip,远程做事器还是会发送压缩后的数据(常见于CDN),以是这里建议利用方案二,再次修正 Nginx 配置,让 Nginx 能够自动解压缩远端数据。

location /proxy/api-mysql { internal; gunzip on; proxy_pass https://www.mysql.com/; proxy_set_header Host www.mysql.com;}

但是当我们重新启动做事进行测试的时候会发生其余一个问题:

间隔成功很近的时的缺点

[error] 33#33: 4 pending events while closing request, client: 172.28.0.1, server: 0.0.0.0:80[error] 33#33: 8 too big subrequest response while sending to client, client: 172.28.0.1, server: localhost, request: "GET / HTTP/1.1", subrequest: "/proxy/api-mysql/common/chat/chat-translation-data.json", upstream: "https://137.254.60.6:443//common/chat/chat-translation-data.json", host: "localhost:8080"

检讨日志可以看到上面的缺点提示,这是由于 GZip 解压缩之后,数据量远大于 Nginx 默认处理临时数据的 Buffer 容量,以是我们要进一步对此进行调度:

subrequest_output_buffer_size 200k;location /proxy/api-mysql { internal; gunzip on; proxy_pass https://www.mysql.com/; proxy_set_header Host www.mysql.com;}

这里的subrequest_output_buffer_size 配置数值根据自己的场景需求进行调度即可。
再次重启做事,会看到我们已经能够获取精确的远程接口数据内容了。

从远端获取的数据内容

编写具备聚合功能的程序

由于我们要聚合多个接口,以是我们将 NJS 代码和 Nginx 配置同时进行一些调度。

我在这里就不演示很挫的顺序实行模式了,由于对付这些无高下文依赖的接口,利用异步并发获取的办法可以花费尽可能少的韶光来供应结果。
当然,串行要求也是有场景的,我会在后面的文章中提到如何灵巧利用 NJS 掌握要求流程。

// https://github.com/nginx/njs/issues/352#issuecomment-721126632function resolveAll(promises) { return new Promise((resolve, reject) => { var n = promises.length; var rs = Array(n); var done = () => { if (--n === 0) { resolve(rs); } }; promises.forEach((p, i) => { p.then((x) => { rs[i] = x; }, reject).then(done); }); });}function aggregation(req) { var apis = ["/proxy/api-mysql/common/chat/chat-translation-data.json", "/proxy/api-redis/wp-content/themes/wpx/proxy/signup_proxy.php"]; resolveAll(apis.map((api) => req.subrequest(api))) .then((responses) => { var result = responses.reduce((prev, response) => { var uri = response.uri; var prop = uri.split("/proxy/api-")[1].split("/")[0]; try { var parsed = JSON.parse(response.responseText); if (response.status === 200) { prev[prop] = parsed; } } catch (err) { req.error(`Parse ${uri} failed.`); } return prev; }, {}); req.headersOut["Content-Type"] = "application/json;charset=UTF-8"; req.return(200, JSON.stringify(result)); }) .catch((e) => req.return(501, e.message));}export default { aggregation };

接着对 Nginx 配置文件中的部分进行调度:

...location / { js_content app.aggregation;}subrequest_output_buffer_size 200k;location /proxy/api-mysql { internal; gunzip on; proxy_pass https://www.mysql.com/; proxy_set_header Host www.mysql.com;}location /proxy/api-redis { internal; gunzip on; proxy_pass https://redislabs.com/; proxy_set_header Host redislabs.com;}...

末了再次启动做事,来验证我们能否拿到精确的远程数据,并将数据们进行聚合。

It works

看样子,我们已经拿到了我们想要的结果,接着来大略聊聊容器封装。

利用容器对 NJS 运用进行封装

前文提到,NJS 模块由 Nginx 官方镜像默认支持,我们可以直策应用 nginx:1.19.8-alpine 为根本来进行镜像构建。

镜像文件非常大略,只须要三行:

FROM nginx:1.19.8-alpineCOPY nginx.conf /etc/nginx/nginx.confCOPY app.js /etc/nginx/script/app.js

将上面的内容保存为 Dockerfile,然后利用 docker build -t njs-api . 构建出我们的镜像。

如果你选择利用 docker images 查看镜像,你会创造我们构建的镜像非常小巧,险些能够和 Nginx 官方镜像尺寸保持同等,以是在公网分发的时候,会有非常大的上风,根据 docker 增量分发的特性,我们实在只会分发上面那三行配置中的后两行构建结果(layers),差不多几 KB。

njs-api latest f4b6de5dacb8 3 minutes ago 22.6MBnginx 1.19.8-alpine 5fd75c905b52 7 days ago 22.6MB

在构建镜像之后,利用 docker run --rm -it -p 8090:80 njs-api 可以进一步验证做事是否能够正常运行,不出意外,会得到上一小节图片中的结果。

末了

好了,来总结一下。

本篇文章中,由于我们没有利用任何非 Nginx 镜像外的 Runtime ,以是得到的镜像结果非常小巧,十分利于进行网络分发。

同时由于 NJS 和 Nginx 大略清晰的设计理念,NJS 程序伴随要求生命周期结束而开释,NJS 引擎实行效率比较高,以及NJS 引擎本身只是实现了 ECMA 的一个子集(整体繁芜度低),加之子要求的生命周期非常短暂,以是我们的做事可以利用非常低的资源(靠近于 Nginx 原生资源占用)供应一个靠近 Nginx 原生做事的性能。

如果你常常写业务代码,你会创造本文留下了一些明显可以改进性能的话题没有诉诸笔墨:如何提聚合接口的性能,如何在定制过的 Nginx 镜像、环境中和三方模块一起事情,以及 NJS 到底能够干哪些更繁芜的活?

标签:

相关文章

模具专业创新驱动产业发展的关键力量

模具,作为工业制造中的“母机”,广泛应用于汽车、家电、电子信息、航空航天等领域。模具产业已成为推动制造业升级的关键力量。本文将从模...

网站推广 2025-02-18 阅读0 评论0

永恒之塔单机地图代码虚拟世界的构建奥秘

永恒之塔是一款备受玩家喜爱的单机角色扮演游戏,其丰富的游戏内容和精美的画面吸引了无数玩家。而在这背后,是开发者们精心编写的地图代码...

网站推广 2025-02-18 阅读0 评论0