Nginx+lua实践:简单的接口聚合实例
Posted 皖南笑笑生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Nginx+lua实践:简单的接口聚合实例相关的知识,希望对你有一定的参考价值。
nginx是高性能的HTTP和反向代理服务器。目前已经普遍应用到各大互联网公司,国内基于Nginx又分别改造推出了Tengix和Openresty,笔者所在的公司也在逐步推广Nginx作为业务系统的接入层,同时使用lua开发脚本,将业务逻辑前置来减轻后端服务器的压力(Nginx的并发能力非常强,很适合处理轻量级业务)。
接口聚合是很多业务系统需要实现的功能,可以前置到Nginx上做,聚合的目的一方面是减少用户请求次数,提高响应性能;另一方面,Nginx并发处理能力更强。下面给出一个简单的实现案例。
接口聚合的原理
假设页面展示要调用三个接口a.json,b.json,c.json
,(a,b,c之间没有逻辑依赖关系,且对应A、B、C三个服务端系统),则客户端需要发送三次请求才能拿到完整的接口数据。
接口内容分别为:
a.json
"name":"A","testA":[a1,a2,a3]
b.json
"name":"B","testB":[b1,b2,b3]
c.json
"name":"C","testC":[c1,c2,c3]
接口聚合的原理如下图,将a.json,b.json,c.json
聚合为/api/abc.json
,当请求到达Nginx,再分离成三个请求到不同服务端,拿到数据后再由Nginx组装好后返回给客户端。这样,减少了请求数,也减少了请求并发数(统一接入层的并发数减少,单个后端系统的并发数仍不变),同时提高了客户端和服务端的性能。
接口内容是:
ckey: ""name":"C","testC":[c1,c2,c3]",
akey: ""name":"A","testA":[a1,a2,a3]",
bkey: ""name":"B","testB":[b1,b2,b3]"
接口聚合的实现
下面我们实现一个简单的demo,我们使用openresty 1.9.15.1,这样就省去了重新编译lua-nginx-module
模块的时间,部署在服务器10.27.180.75上,作为服务统一接入层。假设a,b,c三个接口之间没有依赖关系,A系统IP为10.27.200.164,B系统IP为10.27.200.167,C系统IP为10.27.200.170。则75的nginx.conf配置如下:
server
listen 80;
server_name localhost;
location /
root html;
index index.html index.htm;
location /abc
charset UTF-8;
content_by_lua_file /usr/local/openresty/nginx/conf/lua/merge_abc.lua;
location ^~ /api/a.json
proxy_connect_timeout 1s;
proxy_read_timeout 2s;
proxy_pass http://10.27.200.164;
location ^~ /api/b.json
proxy_connect_timeout 1s;
proxy_read_timeout 2s;
proxy_pass http://10.27.200.167;
location ^~ /api/c.json
proxy_connect_timeout 1s;
proxy_read_timeout 2s;
proxy_pass http://10.27.200.170;
其中,content_by_lua_file
表示调用lua脚本来处理请求,proxy_pass
表示转发路径。
merge_abc.lua的具体实现如下,使用openresty的好处之一也是因为cjson模块是集成好的,不需要再去重新编译,十分方便。
每一个json请求可能是带多个参数的,将每个请求的param-value存储在key中,比如,a.json?a1=1&a2=2&a3=3,则将a1=1&a2=2&a3=3放入akey中。再通过ngx.req.get_uri_args()
将akey取出还原。
location.capture_multi
发送请求到后端服务器。如果请求存在301/2跳转,则直接返回0,否则,拼装json报文并返回客户端。
--启用cjson处理json
gcjson = require("cjson");
--获取请求参数值
local requestmap= ngx.req.get_uri_args();
--为了解决不同请求见参数重名的问题,通过唯一key记录每个请求param-value
local req_a="/api/a.json";
if requestmap["akey"] and requestmap["akey"] ~="" then
req_a=req_a.."?"..requestmap["akey"];
ngx.say("akey:"..requestmap["akey"])
end
local req_b="/api/b.json";
if requestmap["bkey"] and requestmap["bkey"] ~="" then
req_a=req_a.."?"..requestmap["bkey"];
ngx.say("bkey:"..requestmap["bkey"])
end
local req_c="/api/c.json";
if requestmap["ckey"] and requestmap["ckey"] ~="" then
req_a=req_a.."?"..requestmap["ckey"];
ngx.say("ckey:"..requestmap["ckey"])
end
--location.capture_multi发送请求到后端服务器
--如果存在跳转,则返回0
--否则,拼装json报文并返回客户端
local requestArray=req_a,req_b,req_c;
local res_a,res_b,res_c= ngx.location.capture_multi(requestArray);
if res_a and res_a.status == ngx.HTTP_MOVED_TEMPORARILY then
ngx.say("0");
do return end;
end
if res_b and res_b.status == ngx.HTTP_MOVED_TEMPORARILY then
ngx.say("0");
do return end;
end
if res_c and res_c.status == ngx.HTTP_MOVED_TEMPORARILY then
ngx.say("0");
do return end;
end
local data =;
if res_a and res_a.status == ngx.HTTP_OK then
data["akey"]=res_a.body;
end;
if res_b and res_b.status == ngx.HTTP_OK then
data["bkey"]=res_b.body;
end;
if res_c and res_c.status == ngx.HTTP_OK then
data["ckey"]=res_c.body;
end;
ngx.say(gcjson.encode(data));
这样,一个简单的Nginx实现接口聚合的实例已经完成了。
考虑一个问题,假如a,b,c中有一个接口超时,Nginx是返回超时,还是返回部分结果?我们设置了每个接口的超时时间是1s,同时,在后端服务器C上增加了sleep(5000)
,结果发现,返回的是a,b接口的结果:
所以如果在线上真实场景中使用Nginx实现接口聚合,一定要考虑好异常捕获与处理,比如单个接口的超时与重试,保证服务高可用。
以上是关于Nginx+lua实践:简单的接口聚合实例的主要内容,如果未能解决你的问题,请参考以下文章
高并发 Nginx+Lua OpenResty系列(10)——商品详情页
高并发 Nginx+Lua OpenResty系列(10)——商品详情页
分布式接口幂等性分布式限流:Guava nginx和lua限流