openResty中的ngx.location.capture和ngx.location.capture_multi的使用

在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

openResty中的ngx.location.capture和ngx.location.capture_multi的使用》上有1条评论

  1. Pingback引用通告: openResty中ngx_lua模块提供的API | 精彩每一天

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>