在openResty中,ngx.location.capture_multi是一个非常强大的功能。可以应用于并发多个相互之间没有依赖的请求。在现代的应用架构中经常使用微服务,提供低粒度的接口;但在客户端(例如:app、网页服务)经常需要请求多个微服务接口,才能完整显示页面内容。
例如:打开一个商品详情页,需要请求:
- banner广告接口;
- 商品详情;
- 商品评论等。
那么ngx.location.capture_multi就派上大用场了,当然使用ngx.location.capture_multi不是唯一的办法,呵呵~。下面就来看看这个东东的用法;
先介绍一下下面这几个应用之间的差别;
- ngx.exec:nginx跳转;跳转到其他的location中执行。但仅限nginx内部的location。
- ngx.redirect:和nginx.exec相似,但支持外部跳转。
- ngx.location.capture_multi:并发请求;但仅限nginx内部的location。
- http包中multi方法:概念上与ngx.location.capture_multi相似,但支持外部接口。
一、ngx.location.capture
语法: res = ngx.location.capture(uri, options?)
作用域: rewrite_by_lua*, access_by_lua*, content_by_lua*
1.1 uri
直接看栗子:
location ~ /comment/([0-9]+) {
internal;
set $goodsId $1;
content_by_lua_block{
local args = ngx.req.get_uri_args()
ngx.say("comments for goodsId :", ngx.var.goodsId)
ngx.say("comments for goods:", args.offset)
}
}
location ~ /goods/detail/([0-9]+) {
set $goodsId $1;
default_type plain/text;
content_by_lua_block{
local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?offset=0")
ngx.say(res.status)
ngx.say(res.body)
}
}
返回结果:
200 comments for goodsId :123123 comments for goods:0
1.2 options
method: 请求方法,默认为ngx.HTTP_GET body: 请求内容,仅限于string 或 nil args: 请求参数,支持string 或 table vars: 变量,仅限于table ctx: 可参考中ngx.ctx的用法: openResty中ngx_lua模块提供的API copy_all_vars: 复制变量 share_all_vars: 共享变量 always_forward_body: 当设置为true时,父请求中的body转发到子请求。 默认是false,仅转发put和post请求方式中的body。如果设置body选项,则该设置失效。
1.2.1 always_forward_body
请看栗子:
栗子 01: location ~ /comment/([0-9]+) { internal; set $goodsId $1; content_by_lua_block{ ngx.req.read_body(); local args = ngx.req.get_uri_args() local data = ngx.req.get_body_data() ngx.say("comments for goodsId :", ngx.var.goodsId) ngx.say("comments for rank:", args.rank) ngx.say("comments for data :", data) } } location ~ /goods/detail/([0-9]+) { set $goodsId $1; default_type plain/text; content_by_lua_block{ ngx.req.read_body(); local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{ method = ngx.HTTP_GET, always_forward_body = false, }) ngx.say(res.status) ngx.say(res.body) } }
请求raw: uid=37A059714A2B4B4280794DCA5C150DF0,请看如下输出
200 comments for goodsId :123123 comments for rank:5 comments for data :nil
将 栗子 01 中的:method = ngx.HTTP_GET ,更改成 method = ngx.HTTP_PUT或 method = ngx.HTTP_POST,请看如下输出:
200 comments for goodsId :123123 comments for rank:5 comments for data :uid=37A059714A2B4B4280794DCA5C150DF0
重新将 栗子 01 中的 always_forward_body = false 更改成 always_forward_body = true,其他不变,请看如下输出:
200 comments for goodsId :123123 comments for rank:5 comments for data :uid=37A059714A2B4B4280794DCA5C150DF0
结论 01:
always_forward_body:当设置为true时,父请求中的body转发到子请求。设置为false,仅转发put和post
请求方式中的body.
继续更改 栗子 01 :
local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{ method = ngx.HTTP_GET, body = 'hello, world', always_forward_body = false, --也可以设置为true })
查看输出结果:
200 comments for goodsId :123123 comments for rank:5 comments for data :hello, world
结论 02:
当选项中设置 body (只能为string)时,always_forward_body 选项失效。
1.2.2 args 和 vars
这一组的用法比较相似,放在一块讲了。看栗子吧。
栗子 02: location ~ /comment/([0-9]+) { internal; set $goodsId $1; content_by_lua_block{ local args = ngx.req.get_uri_args() ngx.say("comments for goodsId :", ngx.var.goodsId) ngx.say("comments for rank:", args.rank) ngx.say("comments for args.a:", args.a) ngx.say("comments for args.b:", args.b) ngx.say("comments for vars.a:", ngx.var.a) ngx.say("comments for vars.b:", ngx.var.b) } } location ~ /goods/detail/([0-9]+) { set $goodsId $1; set $a ''; set $b ''; default_type plain/text; content_by_lua_block{ local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{ method = ngx.HTTP_GET, args = {a = "aa", b = "bb"}, vars = {a = "aa", b = "bb"}, }) ngx.say(res.status) ngx.say(res.body) } }
输出结果:
200 comments for goodsId :123123 comments for rank:5 comments for args.a:aa comments for args.b:bb comments for vars.a:aa comments for vars.b:bb
从栗子 02 中可以看出,args 和 vars的区别。
结论 03 :
在发送参数到子请求中,一般参数使用 args;如特殊参数可以使用 vars,但也可以使用 args 代替。
1.23 ctx
请看栗子 03
栗子 03: location ~ /comment/([0-9]+) { internal; set $goodsId $1; content_by_lua_block{ ngx.ctx.foo = "bar" } } location ~ /goods/detail/([0-9]+) { set $goodsId $1; default_type plain/text; content_by_lua_block{ local c = {} local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{ method = ngx.HTTP_GET, ctx = c, }) ngx.say(c.foo) ngx.say(ngx.ctx.foo) } }
输出结果:
bar nil
1.24 copy_all_vars、share_all_vars
请看栗子04
栗子 04 : location ~ /comment/([0-9]+) { internal; set $goodsId $1; set $dog "$dog world"; echo "$uri dog: $dog"; } location ~ /goods/detail/([0-9]+) { set $goodsId $1; default_type plain/text; set $dog 'hello'; content_by_lua_block{ local res = ngx.location.capture("/comment/"..ngx.var.goodsId.."?rank=5",{ method = ngx.HTTP_GET, share_all_vars = true, }) ngx.print(res.body) ngx.say(ngx.var.uri, ": ", ngx.var.dog) } }
输出结果:
/comment/123123 dog: hello world /goods/detail/123123/view: hello world
更改栗子 04 :更改 share_all_vars = true 成 copy_all_vars = true
查看输出结果:
/comment/123123 dog: hello world /goods/detail/123123/view: hello
结论 04 :
share_all_vars 可能会污染全局变量,不推荐使用。
二、ngx.location.capture_multi
和 ngx.location.capture 的用法相似,但可以同时并发多个请求。
查看栗子 0 5
res1, res2, res3 = ngx.location.capture_multi{ { "/foo", { args = "a=3&b=4" } }, { "/bar" }, { "/baz", { method = ngx.HTTP_POST, body = "hello" } }, } --注意:这里省略了(),相当于({{}}) if res1.status == ngx.HTTP_OK then ... end if res2.body == "BLAH" then ... end
local reqs = {} table.insert(reqs, { "/mysql" }) table.insert(reqs, { "/postgres" }) table.insert(reqs, { "/redis" }) table.insert(reqs, { "/memcached" }) -- issue all the requests at once and wait until they all return local resps = { ngx.location.capture_multi(reqs) } -- loop over the responses table for i, resp in ipairs(resps) do -- process the response table "resp" end
ngx.location.capture = function (uri, args) return ngx.location.capture_multi({ {uri, args} }) end
Pingback引用通告: openResty中ngx_lua模块提供的API | 精彩每一天