摘要:通过lua-nginx-module中的ngx.thread同时执行多个任务。

ngx_lua中访问多个第三方服务

ngx_lua中提供了ngx.socket API,可以方便的访问第三方网络服务。如下面的代码,通过get_response函数从两个(或者更多)的源服务器获取数据,再生成响应发给客户端。

location / {
content_by_lua_block {
local get_response(host, port)
local sock = ngx.socket.tcp()
local ok, err = sock:connect(host, port)
if not ok then
return nil, err
end
local data, err = sock:receive()
if not data then
return nil, err
end return data
end local first = get_response("lua.org", )
local second = get_response("nginx.org", )
ngx.say(first .. second)
}
}

如果需要10个第三方网络服务,需要调用get_response 10次。总的响应时间与需要连接源的数量成正比。那么如何缩短源的响应时间呢?ngx.thread就是用来解决这种问题的。

二、lua-nginx-module提供了三个API

  1、ngx.thread.spawn

  2、ngx.thread.wait

  3、ngx.thread.kill

三、详解

1、ngx.thread.spawn

重点:ngx.thread.spawn生成新的"light thread",这个"light thread"运行优先级比它的父协程高,会优先运行,父协程被迫暂停。"light thread"运行结束或者yield后,再由ngx_http_lua_run_posted_threads去运行父协程。

参考:ngx_lua中的协程调度(六)之ngx_http_lua_run_posted_thread

通过ngx.thread.spawn可以生成一个"light thread",一个”light thread“和Lua的协程类似,区别在于"light thread"是由ngx_lua模块进行调度的,多个"light thread"同时运行。

"light thread",协程 和 进程。"light thread"比Lua中的协程更像操作系统中的进程。

  • fork生成新的进程,生成的多个进程可以同时运行,而ngx.thread.spawn生成新的协程,多个协程同时在跑。
  • kill可以杀死不需要的子进程,ngx.thread.kill可以杀死不需要的"light thread"
  • wait可以等待子进程结束并取得子进程退出状态,ngx.thread.wait可以等待"light thread"结束并获取其返回值。

ngx.thread的使用,用ngx.thread重写上面的代码

location / {
content_by_lua_block {
local get_response(host, port)
local sock = ngx.socket.tcp()
local ok, err = sock:connect(host, port)
if not ok then
return nil, err
end
local data, err = sock:receive()
if not data then
return nil, err
end return data
end local t1 = ngx.thread.spawn(get_response, "lua.org", )
local t2 = ngx.thread.spawn(get_response, "nginx.org", )
local ok, res1, res2 = ngx.thread.wait(t1, t2)
ngx.say(res1 .. res2)
}
}

生成的两个"light thread"可以同时运行,总的耗时只相当于访问一个源服务器的时间,即使需要访问的源服务器增加,耗时没有太大的变化。

"light thread"的调度

  Linux中的fork生成新的子进程,父进程与子进程谁先运行呢?都有可能,和系统的调度有关。

  把调用ngx.thread.spawn的这个Lua协程称为父协程,生成的"light thread"和父协程谁先运行呢? 在ngx_lua的调度逻辑中,是生成的"light thread"先运行,运行结束或者被挂起后,父协程才会继续运行。实际的代码在ngx_http_lua_run_thread函数中,这个函数比较复杂,涉及的东西太多,稍后再细说。

如下面的代码,没有调用ngx.thread.wait去等待"light thread"的结束。

# [] 没有调用ngx.thread.wait去等待"light thread"的结束
location /thread002 {
content_by_lua_block {
local function f(name)
ngx.log(ngx.ERR, "thread name: ", name, ", now start")
ngx.sleep()
ngx.log(ngx.ERR, "thread name: ", name, ", now end")
end
local t1 = ngx.thread.spawn(f, "first")
local t2 = ngx.thread.spawn(f, "second")
ngx.log(ngx.ERR, "main thread end")
}
}

由Nginx的日志中可以看到当前的请求一直延迟到t1,t2两个"light thread"最后退出才会结束。 Nginx中日志的顺序也可以看出父协程和两个"light thread"的执行那个顺序。

// :: [error] #: * [lua] content_by_lua(thread.conf:):: thread name: first, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
// :: [error] #: * [lua] content_by_lua(thread.conf:):: thread name: second, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
// :: [error] #: * [lua] content_by_lua(thread.conf:):: main thread end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
// :: [error] #: * [lua] content_by_lua(thread.conf:):: thread name: first, now end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"
// :: [error] #: * [lua] content_by_lua(thread.conf:):: thread name: second, now end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread002 HTTP/1.1", host: "127.0.0.1:8689"

而如果代码中主动调用了ngx.exit()结束请求,那么t1,t2两个没有打印出完全的信息就被kill掉了。

# [] 代码中主动调用了ngx.exit()结束请求,那么t1,t2两个没有打印出完全的信息就被kill掉了
location /thread003 {
content_by_lua_block {
local function f(name)
ngx.log(ngx.ERR, "thread name: ", name, ", now start")
ngx.sleep()
ngx.log(ngx.ERR, "thread name: ", name, ", now end")
end
local t1 = ngx.thread.spawn(f, "first")
local t2 = ngx.thread.spawn(f, "second")
ngx.exit()
}
}

相应的Nginx日志

// :: [error] #: * [lua] content_by_lua(thread.conf:):: thread name: first, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread003 HTTP/1.1", host: "127.0.0.1:8689"
// :: [error] #: * [lua] content_by_lua(thread.conf:):: thread name: second, now start, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread003 HTTP/1.1", host: "127.0.0.1:8689"
// :: [error] #: * [lua] content_by_lua(thread.conf:):: main thread end, client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread003 HTTP/1.1", host: "127.0.0.1:8689"

"light thread"的限制

  "light thread"毕竟是基于依附于请求的,如在content_by_lua中创建的"light thread",是完全与当前的请求关联的,如果"light thread"没有退出,当前请求也无法结束。同样如果当前请求因为错误退出,或调用ngx.exit强制退出时,处于运行状态的"light thread"也会被kill掉。不像操作系统的进程,父进程退出后,子进程可以被init进程"收养"。

错误代码:

2017/07/21 09:14:10 [error] 114598#0: *1 failed to load inlined Lua code: content_by_lua(thread.conf:128):2: ')' expected near ',', client: 127.0.0.1, server: 127.0.0.1, request: "GET /thread001 HTTP/1.1", host: "127.0.0.1:8689"

ngx_lua_API 指令详解(六)ngx.thread.spawn、ngx.thread.wait、ngx.thread.kill介绍的更多相关文章

  1. ngx_lua_API 指令详解(二)ngx.re.match/find/gmatch/sub/gsub指令集合

    1.先来个官方的ngx.re.match location /ngx_re_match { default_type text/html; content_by_lua_block { local m ...

  2. ngx_lua_API 指令详解(五)coroutine.create,coroutine.resume,coroutine.yield 等集合指令介绍

    ngx_lua 模块(原理实现) 1.每个worker(工作进程)创建一个Lua VM,worker内所有协程共享VM: 2.将Nginx I/O原语封装后注入 Lua VM,允许Lua代码直接访问: ...

  3. ngx_lua_API 指令详解(四)ngx.exec指令

    https://github.com/openresty/lua-nginx-module#ngxexec 参照:http://blog.csdn.net/weiyuefei/article/deta ...

  4. ngx_lua_API 指令详解(一)ngx.timer.at 指令

    语法: ok,err = ngx.timer.at(delay,callback,user_arg1,user_arg2 ...) 上下文: init_worker_by_lua *,set_by_l ...

  5. ngx_lua_API 指令详解(三)怎样理解 cosocket指令

    参考:https://moonbingbing.gitbooks.io/openresty-best-practices/content/ngx_lua/whats_cosocket.html 春哥演 ...

  6. [转]JVM指令详解(上)

    作者:禅楼望月(http://www.cnblogs.com/yaoyinglong) 本文主要记录一些JVM指令,便于记忆与查阅. 一.未归类系列A 此系列暂未归类. 指令码    助记符      ...

  7. C#中的预处理器指令详解

    这篇文章主要介绍了C#中的预处理器指令详解,本文讲解了#define 和 #undef.#if.#elif.#else和#endif.#warning和#error.#region和#endregio ...

  8. rsync指令详解

    rsync指令详解(更详细的看官方文档http://rsync.samba.org/ftp/rsync/rsync.html) [root@Centos epel]# rsync --help rsy ...

  9. #pragma 预处理指令详解

    源地址:http://blog.csdn.net/jx_kingwei/article/details/367312 #pragma  预处理指令详解              在所有的预处理指令中, ...

随机推荐

  1. sudo apt-get update 去除设置的代理

    今天想装个软件(wine),使用 sudo apt-get update 命令时,发现给出很多Ign 语句,总出现 Connecting to proxy.http://10.0.126.1:1312 ...

  2. ELK安装部署

    一.ELK简介 ELK是Elasticsearch.Logstash.Kibana的简称,这三者是核心套件,但并非全部.Elasticsearch是实时全文搜索和分析引擎,提供搜集.分析.存储数据三大 ...

  3. springboot 前后端分离开发 从零到整(一、环境的搭建)

    第一次写文章,有什么错误地方请大家指正,也请大家见谅. 这次为大家分享我做毕业设计的一个过程,之前没有接触过springboot,一直做的都是Javaweb和前端,做了几个前后端分离的项目.现在听说s ...

  4. 32bit 天堂2脚本修改资料大全【客户端+服务端】

    该资料夹中所有教程资料全部适合天堂2初章32位服务端的脚本修改,已经1.2章相关客户端的修改. https://pan.baidu.com/s/1RuGMFNgERd2JMYQpdceQwg 提取码: ...

  5. java实验报告二

    一.实验内容 1. 初步掌握单元测试和TDD 2. 理解并掌握面向对象三要素:封装.继承.多态 3. 初步掌握UML建模 4. 熟悉S.O.L.I.D原则 5. 了解设计模式 二.实验步骤 (一)单元 ...

  6. linux 常用命令-变量命令

    想要的结果,有时候我们想使用上一句命令的执行结果,当然可以通过鼠标去复制粘贴,但是这样既不库又效率低,所以想能不能通过快捷键获取上一句命令的值执行结果呢,答案是不能,后来想如果能把执行结果存入变量那不 ...

  7. linux 取消控制台报警音

    可以通过setterm -blength 0 设置报警音报警时间,0表示没有报警音 也可以通过setterm -bfreq 10 设置报警音的频率(Hz) 如果通过命令行直接设置,当下会生效,但是重启 ...

  8. 开机自启动nginx,php-fpm

    开机自启动nginx,php-fpm(其他服务类似) centos 7以上是用Systemd进行系统初始化的,Systemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目标是克服 ...

  9. delphi Timage 加上滚动条方法

    elphi Timage 加上滚动条方法 1:将  Timage 放入 TScrollBox内,即   [1]设image1.parent:= ScrollBox1;   [2]在Object Ins ...

  10. 如何把EntityList转换成DataSet

    public static DataSet ToDataSet<TSource>(this IList<TSource> list) { Type elementType = ...