lua-resty-http发起请求时的问题?

534 views
Skip to first unread message

keke fan

unread,
Dec 5, 2017, 7:20:08 AM12/5/17
to openresty
hello:

   在用lua-resty-http库发起对远端的请求时,有这样两种方式:
    1 每次创建3个客户端进行连接不同的3个server,将每个返回的resp缓存在table中,然后统一进行resp的读取。
    2 每次只创建1个客户端,进行读取 。 

   所需要读取的数据都一样,请问在OR里面会有效率的差异吗?伪代码如下:

 
第一种情况:
        body = {} k=1
        for   i=1, 10, 1 do 
             local resp = {} 
             for j=1, 3, 1 do                       
                  local client = http.new()
                  client:connect(某个server)
                  resp[j] = make_request() 
            end

            for j=0, 2, 1 do     --读取数据
                 reader = resp[j].body_reader
                 body[k] = reader() 
                k = k +1 
            end
       end
                              
第二种情况:
         for i = 1, 30, 1 do
             local client = http.new()
             client:connect(某个server)
             resp[i] = make_request()
            reader = resp[j].body_reader
            body[k] = reader()
            k=k+1
        end


注意:每次连接都是不同的server,两种情况下需要获取的数据量大小都是一样的。

请问,这两种情况有效率上的差别吗?第一种情况下,同时开了两个连接,在OR的场景下,效率能否有提高? 

Zhantao Liu

unread,
Dec 5, 2017, 7:57:15 AM12/5/17
to open...@googlegroups.com
这两种都是串行的,试试转成并行: 

location /real_request/ {
content_by_lua '
local rsp1,rsp2,rsp3 = ngx.location.capture_multi{
        {"/proxy1"},
        {"/proxy2"},
        {"/proxy3"}
    } 
';
}



location /proxy1 {
content_by_lua ‘
           local client = http.new()
           client:connect(某个server)
           ngx.say(make_request()) 
';
}
location /proxy2 {
content_by_lua '
           local client = http.new()
           client:connect(某个server)
           ngx.say(make_request()) 
';
}
location /proxy3 {
content_by_lua '
          local client = http.new()
           client:connect(某个server)
           ngx.say(make_request()) 
';
}




--
--
邮件来自列表“openresty”,专用于技术讨论!
订阅: 请发空白邮件到 openresty...@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+...@googlegroups.com
归档: http://groups.google.com/group/openresty
官网: http://openresty.org/
仓库: https://github.com/agentzh/ngx_openresty
教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html

keke fan

unread,
Dec 5, 2017, 8:08:58 AM12/5/17
to openresty
谢谢回复~

您的意思是用子请求的方式,这个确实是并行的。

不过我们的项目都是纯Lua的,没有location的配置,不能够利用这种子请求中capture到某个location的操作。  


另外,能否请您解释下,第二种方式为啥是串行的,用lua-resty-http库发起的请求 是放在新的lua thread上下文中处理的吧(看代码貌似https://github.com/pintsized/lua-resty-http) 。 



谢谢

在 2017年12月5日星期二 UTC+8下午8:57:15,Zhantao Liu写道:

Zexuan Luo

unread,
Dec 5, 2017, 10:41:46 AM12/5/17
to open...@googlegroups.com
由于一个 Nginx worker 同时只会运行一个 Lua 协程,所有方式都是串行的,即使子请求也不例外。

个人认为,假定对端 Server 的服务效率跟请求数无关,且网络IO不是瓶颈,那么连续发起多个请求的延迟会比逐个请求更低,效率会更高。

keke fan

unread,
Dec 5, 2017, 9:51:02 PM12/5/17
to openresty
谢谢回复~

由于一个 Nginx worker 同时只会运行一个 Lua 协程,所有方式都是串行的,即使子请求也不例外。 
------- 
你的意思是说,在一个worker里面,在某一个时刻只会运行一个Lua协程,这个是没有疑问的。那请问下多个子请求的执行是是如何达到 那个最长时间的子请求时间为多个子请求执行完成的时间的? 


个人认为,假定对端 Server 的服务效率跟请求数无关,且网络IO不是瓶颈,那么连续发起多个请求的延迟会比逐个请求更低,效率会更高。 
----------------
你的意思是认为 在第一种情况下的效率会更高吗?  如果都是串行的方式,那么效率提升在哪里呢 ?  

谢谢


在 2017年12月5日星期二 UTC+8下午11:41:46,Zexuan Luo写道:

soul11201

unread,
Dec 5, 2017, 11:30:08 PM12/5/17
to open...@googlegroups.com
说的是你的第二种方式效率高些。
订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

Zexuan Luo

unread,
Dec 6, 2017, 1:09:57 AM12/6/17
to open...@googlegroups.com
> 你的意思是认为 在第一种情况下的效率会更高吗? 如果都是串行的方式,那么效率提升在哪里呢 ?

对的,我认为第一种效率会更高。

我的推理步骤是这样的:
首先定义两个概念:on-cpu time 和 off-cpu
time。前者是占用CPU的时间,这里简单地当作运行Lua代码的时间;后者是不占用CPU的时间,这里简单地当作网络IO占用的时间。

因为:一个请求花费的时间,是花在这个请求上的 on-cpu time + off-cpu time。其中 off-cpu time
是可以重叠的,比如同时等待多个对端的响应;而 on-cpu time 是独占的,在某一时刻只会运行一个Lua协程。

所以:如果要提高效率,在 on-cpu time 不变的情况下,应该增加 off-cpu time 的重叠率。换句话说,应该同时发起更多的网络请求。

当然,以上的推论隐含很多理想假设,有点类似于“真空中的球形鸡”。如果要想知道现实中运行的情况,建议做下火焰图分析。

> 那请问下多个子请求的执行是是如何达到 那个最长时间的子请求时间为多个子请求执行完成的时间的?

那个最长时间的子请求时间为多个子请求执行完成的时间 这个说法值得商榷...

如果所有请求的过程中都没有发生yield(没有网络IO,没有sleep,etc.),那么执行过程是这样的:
sr1 start
sr1 end
sr2 start
sr2 end

执行时间为 sr1 + sr2

如果请求过程中发生了 yield,那么执行过程(简单来说)是这样的:
sr1 start
sr1 yield
sr2 start
sr2 yield
wait_to_resume
sr1 resume
sr1 end
wait_to_resume
sr2 resume
sr2 end

执行时间为 sr1_on_cpu + sr2_on_cpu + wait_to_resume。

举个例子,如果有两个子请求,一个sleep 0.2 秒,一个 sleep 0.3 秒,除了 sleep,它们什么都不干。
由于它俩什么都不干,所以 on_cpu 时间都为
0.第一个请求等待0.2秒后结束,第二个请求等待0.3秒后结束,所以总等待时间为0.2(等待第一个请求)+0.1(等待第二个请求),为
0.3 秒。
注意只有在上述例子中,多个子请求执行完成的时间才等于最长的子请求时间。

soul11201

unread,
Dec 6, 2017, 1:56:26 AM12/6/17
to open...@googlegroups.com
sorry 刚才看错代码了,从理论上分析 第一种 效率更高些。

再补充一点个人的看法。


相比第二个代码,调用发送函数后,后续工作交给内核了。

所以多个发送的请求时间序列上可能出现重叠,这个时候就节省了一部分时间。如果没有,效率并没有提升。

读取数据时候,所用的时间,也会和发送有交叉影响的,有可能你还在发送着中间某个连接的请求,前面请求的响应数据已经准备好在接受缓冲区中等待你去读取。

个人以为,内核的tcp 接受缓冲区发送缓冲区 模型是理解你这两段代码问题的关键。系统调用函数,其实一般都是对这两个缓冲区操作。





> 订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
> 发言: 请发邮件到 open...@googlegroups.com
> 退订: 请发邮件至 openresty+unsubscribe@googlegroups.com
订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

Sen Han

unread,
Dec 6, 2017, 7:17:47 PM12/6/17
to open...@googlegroups.com
有一个问题,为什么不直接使用ngx.thread.spawn呢?这样总体能做到真正的异步事件触发,整体延迟会更低,效率更高。

之前测试过ngx.thread.spawn并发处理成千上万个连接任务都没有问题,效率很棒。

        body = {} k=1
        for   i=1, 10, 1 do 
             local resp = {} 
             for j=1, 3, 1 do                       
                  local client = http.new()
                  client:connect(某个server)
                  resp[j] = make_request() 
            end
            for j=0, 2, 1 do     --读取数据
                 reader = resp[j].body_reader
                 body[k] = reader() 
                k = k +1 
            end
       end

即使是例子中的第一个方法,如果其中的一个reader函数很久才会返回一次很小的数据量,它就会成为系统吞吐的短板。

当然,要控制好总体的请求时间,就需要自己加上严谨的超时处理逻辑了。

All best

soul11201

unread,
Dec 6, 2017, 9:54:14 PM12/6/17
to open...@googlegroups.com

​和第一种段代码效率应该差不多。​reader 是非阻塞的。

Sen Han

unread,
Dec 6, 2017, 10:22:36 PM12/6/17
to open...@googlegroups.com
当然了,这里只是语言名词的理解和对象不同, 更准确地说:
  
    从系统整体的角度来看它是非阻塞的,但是从这段lua脚本程序的角度来讲是“阻塞”的,
    这里更好的词应该是“同步”,既然是同步,就会有些时间消耗,
    ngx.thread.spawn是并行的同步,问题例子中的第一种方法是串行
    同步(只不过在内核协议栈中有一定轻微程度的并行)。

Sen Han

unread,
Dec 6, 2017, 11:02:11 PM12/6/17
to open...@googlegroups.com
当然了,这里只是语言名词的理解和对象不同, 更准确地说:
  
    从系统整体的角度来看它是非阻塞的,但是从这段lua脚本程序的角度来讲是“阻塞”的,
    这里更好的词应该是“同步”,既然是同步,就会有些时间消耗
    ngx.thread.spawn是并行的同步,问题例子中的第一种方法是串行
    同步(只不过在内核协议栈中有一定轻微程度的并行)。

更确切地说 这个”时间消耗 应该是 事件等待

Zexuan Luo

unread,
Dec 7, 2017, 8:47:44 PM12/7/17
to open...@googlegroups.com
> ngx.thread.spawn是并行的同步,问题例子中的第一种方法是串行

为什么 ngx.thread.spawn 是并行的同步呢?

Zexuan Luo

unread,
Dec 7, 2017, 10:02:10 PM12/7/17
to open...@googlegroups.com
好吧,貌似又是概念上的歧义。

soul11201

unread,
Dec 8, 2017, 10:16:22 PM12/8/17
to open...@googlegroups.com
并行这个概念看来又是 从lua 协程 角度来说的。



好吧,貌似又是概念上的歧义。
>>>>>> > 订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
>>>>>> > 发言: 请发邮件到 open...@googlegroups.com
>>>>>> > 退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

>>>>>> > 归档: http://groups.google.com/group/openresty
>>>>>> > 官网: http://openresty.org/
>>>>>> > 仓库: https://github.com/agentzh/ngx_openresty
>>>>>> > 教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
>>>>>>
>>>>>> --
>>>>>> --
>>>>>> 邮件来自列表“openresty”,专用于技术讨论!
>>>>>> 订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
>>>>>> 发言: 请发邮件到 open...@googlegroups.com
>>>>>> 退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

>>>>>> 归档: http://groups.google.com/group/openresty
>>>>>> 官网: http://openresty.org/
>>>>>> 仓库: https://github.com/agentzh/ngx_openresty
>>>>>> 教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
>>>>>
>>>>>
>>>>> --
>>>>> --
>>>>> 邮件来自列表“openresty”,专用于技术讨论!
>>>>> 订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
>>>>> 发言: 请发邮件到 open...@googlegroups.com
>>>>> 退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

>>>>> 归档: http://groups.google.com/group/openresty
>>>>> 官网: http://openresty.org/
>>>>> 仓库: https://github.com/agentzh/ngx_openresty
>>>>> 教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
>>>>
>>>>
>>>> --
>>>> --
>>>> 邮件来自列表“openresty”,专用于技术讨论!
>>>> 订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
>>>> 发言: 请发邮件到 open...@googlegroups.com
>>>> 退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

>>>> 归档: http://groups.google.com/group/openresty
>>>> 官网: http://openresty.org/
>>>> 仓库: https://github.com/agentzh/ngx_openresty
>>>> 教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
>>>
>>>
>>> --
>>> --
>>> 邮件来自列表“openresty”,专用于技术讨论!
>>> 订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
>>> 发言: 请发邮件到 open...@googlegroups.com
>>> 退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

>>> 归档: http://groups.google.com/group/openresty
>>> 官网: http://openresty.org/
>>> 仓库: https://github.com/agentzh/ngx_openresty
>>> 教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html
>>
>>
>> --
>> --
>> 邮件来自列表“openresty”,专用于技术讨论!
>> 订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
>> 发言: 请发邮件到 open...@googlegroups.com
>> 退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

>> 归档: http://groups.google.com/group/openresty
>> 官网: http://openresty.org/
>> 仓库: https://github.com/agentzh/ngx_openresty
>> 教程: http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html

--
--
邮件来自列表“openresty”,专用于技术讨论!
订阅: 请发空白邮件到 openresty+subscribe@googlegroups.com
发言: 请发邮件到 open...@googlegroups.com
退订: 请发邮件至 openresty+unsubscribe@googlegroups.com

soul11201

unread,
Dec 8, 2017, 11:21:57 PM12/8/17
to open...@googlegroups.com
 >   从系统整体的角度来看它是非阻塞的,但是从这段lua脚本程序的角度来讲是“阻塞”的,
 >   这里更好的词应该是“同步”,既然是同步,就会有些时间消耗,

只同意你这段说的,哈哈。

并行的同步,这两个概念加在一起就太难理解了。

并行是值的多个程序(这里如果只协程)执行的时间序列上有交集。

同步一般是指(下面是个人根据unp 第六章自己编纂的),需要调用方自己调用系统函数,从内核缓冲区拷贝到应用缓冲区中,内核不主动的干这件事情。个人感觉是强调被调用方主不主动去干这件事情。


并行的同步,用来形容这个 lua协程 这个调用方不合适,我给你改个称呼吧,改成并行的阻塞吧,哈哈。。。看来概念是越搅和越混了。:)



在 2017年12月7日 上午11:22,Sen Han <yunth...@gmail.com>写道:

Sen Han

unread,
Dec 10, 2017, 1:02:43 AM12/10/17
to open...@googlegroups.com
是的,语言描述有时很容易具有歧义或者混淆 :)

借用一张春哥的手绘图:


thread.spawn是建立了三条全新的“任务轨道”,而此讨论中例子1的做法,其实只是在之前的那条老轨道上“同步地较低效率串行三个任务”。

证明:

假设有三个http get任务,对象内容大小均为1GB,内核协议栈的每条tcp连接的接收缓冲区约为1MB(随便取个便于计算,实际大多数情况下只会更小,可能只有几十KB)。

其中,第一个连接因为其server或者中间网络链路的某种问题,拉取时每隔10s才会突发1MB的数据,而另外两条链接的平均下载速率为1MB/s。

例子1的方法:

body = {} k=1
for i=1, 10, 1 do
local resp = {}
for j=1, 3, 1 do
local client = http.new()
client:connect(某个server)
resp[j] = make_request()
end
for j=0, 2, 1 do
reader = resp[j].body_reader
body[k] = reader() --读取数据 <<--
k = k +1
end
end
 
尽管在全力读取第一条连接时,此时其它两条链接已经由协议栈代为接收,但是由于其接收缓冲区的1MB限制相比于1GB实在微乎其微,可以称为“轻微的并发”。

这样的话,第一条链接需要耗时1000*10秒来完成读取,总时间就是(1000*10+1000+1000-1-1)s。

假如加入1200s的超时检测逻辑,即使这样,总时间也需要(1200+1000+1000-1-1)s。

使用thread.spawn进行三个链接的并发拉取:

假如没有超时检测逻辑,总时间消耗(1000*10)s,而且相比例子1中的方法,其它两条快速链接在t=1000s时其实就已经提前完成任务了;

假如加入1200s的超时检测逻辑,总时间消耗(1200)s。

这样看来thread.spawn方法的效率是很明显的。

Sen Han

unread,
Dec 10, 2017, 1:31:29 AM12/10/17
to open...@googlegroups.com
再啰嗦一句,还有一个问题,这里也需要注意reader的全部读取隐患,
还应该至少加入最大body的流读取内存缓存限制,要不然有可能会
自己的http拉取给OOM了(Out Of Memory Killer)...

res.body_reader

The body_reader iterator can be used to stream the response body in chunk sizes of your choosing, as follows:

local reader = res.body_reader

repeat
  local chunk, err = reader(8192)
  if err then
    ngx.log(ngx.ERR, err)
    break
  end

  if chunk then
    -- process
  end
until not chunk

If the reader is called with no arguments, the behaviour depends on the type of connection. If the response is encoded as chunked, then the iterator will return the chunks as they arrive. If not, it will simply return the entire body.

Note that the size provided is actually a maximum size. So in the chunked transfer case, you may get chunks smaller than the size you ask, as a remainder of the actual HTTP chunks.



soul11201

unread,
Dec 10, 2017, 2:40:24 AM12/10/17
to open...@googlegroups.com
嗯,我也举个例子,接受大小为1K   <<  1M (就是数据远小于缓冲区大小的时刻) ,效率一样高了,不,可能还要好点,连协程调度的时间也省了,哈哈。。。

Sen Han

unread,
Dec 10, 2017, 4:18:19 AM12/10/17
to open...@googlegroups.com
对,是的。

但是有个问题,就是我们讨论问题的场景,如果是在当前最普遍的C10k场景下,
openresty基于中断触发和lua协程的架构,性能完全不是问题,这点点损耗换来
清晰的同步语义是完全值得的,它的协程调度损耗是很小的,还有一点,openresty
不像golang那样需要协程抢占和复杂的同步;当然,如果是在更高的性能需求场景
下如C10M,底层轮询确实是好方法,但是我觉得此时上层依然可以高性能地使用
协程,只要能保持简洁而不需要去实现太复杂的抢占与同步机制。
Reply all
Reply to author
Forward
0 new messages