在openResty中,ngx.location.capture_multi是一个非常强大的功能。可以应用于并发多个相互之间没有依赖的请求。在现代的应用架构中经常使用微服务,提供低粒度的接口;但在客户端(例如:app、网页服务)经常需要请求多个微服务接口,才能完整显示页面内容。

例如:打开一个商品详情页,需要请求:

  1. banner广告接口;
  2. 商品详情;
  3. 商品评论等。

那么ngx.location.capture_multi就派上大用场了,当然使用ngx.location.capture_multi不是唯一的办法,呵呵~。下面就来看看这个东东的用法;

 

先介绍一下下面这几个应用之间的差别;

  1. ngx.exec:nginx跳转;跳转到其他的location中执行。但仅限nginx内部的location。
  2. ngx.redirect:和nginx.exec相似,但支持外部跳转。
  3. ngx.location.capture_multi:并发请求;但仅限nginx内部的location。
  4. 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