个人博客: https://www.luozhiyun.com/archives/217nginx
想要学好 OpenResty,你必须理解下面 8 个重点:git
你不该该使用任何 Lua 世界的库来解决上述问题,而是应该使用 cosocket 的 lua-resty-* 库。Lua 世界的库极可能会带来阻塞,让本来高性能的服务,直接降低几个数量级。github
和nginx同样,都有阶段的概念,而且每一个阶段都有本身不一样的做用:编程
OpenResty 的 API 是有阶段使用限制的。每个 API 都有一个与之对应的使用阶段列表,若是你超范围使用就会报错。后端
具体的API能够查阅文档:https://github.com/openresty/lua-nginx-moduleapi
有些状况下,咱们须要的是跨越阶段的、能够读写的变量。缓存
OpenResty 提供了 ngx.ctx,来解决这类问题。它是一个 Lua table,能够用来存储基于请求的 Lua 数据,且生存周期与当前请求相同。咱们来看下官方文档中的这个示例:性能优化
location /test { rewrite_by_lua_block { ngx.ctx.foo = 76 } access_by_lua_block { ngx.ctx.foo = ngx.ctx.foo + 3 } content_by_lua_block { ngx.say(ngx.ctx.foo) } }
最终输出79网络
OPM(OpenResty Package Manager)是 OpenResty 自带的包管理器
opm search lua-resty-http
session
不一样于 OPM 里只包含 OpenResty 相关的包,LuaRocks 里面还包含 Lua 世界的库。
luarocks search lua-resty-http
咱们还能够去网站上看包的详细信息:https://luarocks.org/modules/pintsized/lua-resty-http,这里面包含了做者、License、GitHub 地址、下载次数、功能简介、历史版本、依赖等。
awesome-resty 这个项目,就维护了几乎全部 OpenResty 可用的包,而且都分门别类地整理好了。
由于nginx是多进程的程序:
因此信号分为Master进程信号和worker进程信号。
Master进程:
worker进程:与master进程命令一一对应
Nginx命令行,至关于直接向master进程发送命令
mkdir geektime cd luoluo mkdir logs/ conf/
events { worker_connections 1024; } http { server { listen 8080; location / { content_by_lua ' ngx.say("hello, world") '; } } }
openresty -p `pwd` -c conf/nginx.conf 指定运行目录:-p 使用指定的配置文件: -c
openresty后面跟随的命令和nginx是同样的
$ mkdir lua $ cat lua/hello.lua ngx.say("hello, world")
pid logs/nginx.pid; events { worker_connections 1024; } http { server { listen 8080; location / { content_by_lua_file lua/hello.lua; } } } }
这里把 content_by_lua_block 改成 content_by_lua_file
$ sudo kill -HUP `cat logs/nginx.pid`
我这里使用了发送信号的方式 -HUP表示重载配置文件
NYI,全称为 Not Yet Implemented。LuaJIT 中 JIT 编译器的实现还不完善,有一些原语它还没法编译,由于这些原语实现起来比较困难,再加上 LuaJIT 的做者目前处于半退休状态。这些原语包括常见的 pairs() 函数、unpack() 函数、基于 Lua CFunction 实现的 Lua C 模块等。这样一来,当 JIT 编译器在当前代码路径上遇到它不支持的操做时,便会退回到解释器模式。这些不能编译的函数称为NYI。
NYI函数都在:http://wiki.luajit.org/NYI
在开发中,能够先去找OpenResty的API:https://github.com/openresty/lua-nginx-module
例如,NYI 列表中 string 库的几个函数:
其中,string.byte 对应的可否被编译的状态是 yes,代表能够被 JIT。
string.char 对应的编译状态是 2.1,代表从 LuaJIT 2.1 开始支持。咱们知道,OpenResty 中的 LuaJIT 是基于 LuaJIT 2.1 的,因此你也能够放心使用。
string.dump 对应的编译状态是 never,即不会被 JIT,会退回到解释器模式。
string.find 对应的编译状态是 2.1 partial,意思是从 LuaJIT 2.1 开始部分支持,后面的备注中写的是 只支持搜索固定的字符串,不支持模式匹配。
LuaJIT 自带的 jit.dump 和 jit.v 模块。它们均可以打印出 JIT 编译器工做的过程。前者会输出很是详细的信息,能够用来调试 LuaJIT 自己;后者的输出比较简单,每行对应一个 trace,一般用来检测是否能够被 JIT。
使用resty:
$resty -j v -e
其中,resty 的 -j 就是和 LuaJIT 相关的选项;后面的值为 dump 和 v,就对应着开启 jit.dump 和 jit.v 模式。
以下例子:
$resty -j v -e 'local t = {} for i=1,100 do t[i] = i end for i=1, 1000 do for j=1,1000 do for k,v in pairs(t) do -- end end end'
上面的pairs是NYI的语句,不能被JIT,因此结果里面就会显示:
[TRACE 1 (command line -e):2 loop] [TRACE --- (command line -e):7 -- NYI: bytecode 72 at (command line -e):8]
shared dict(共享字典)是基于 NGINX 共享内存区的 Lua 字典对象,它能够跨多个 worker 来存取数据,通常用来存放限流、限速、缓存等数据。
例子:
http { lua_shared_dict dogs 10m; server { location /demo { content_by_lua_block { local dogs = ngx.shared.dogs dogs:set("Jim", 8) local v = dogs:get("Jim") ngx.say(v) } } } }
简单说明一下,在 Lua 代码中使用 shared dict 以前,咱们须要在 nginx.conf 中用 lua_shared_dict 指令增长一块内存空间,它的名字是 dogs,大小为 10M。
也可使用resty CLI:
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs dogs:set("Jim", 8) local v = dogs:get("Jim") ngx.say(v)'
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
能够看出, init 和 init_worker 两个阶段不在其中,也就是说,共享内存的 get API 不能在这两个阶段使用。
value, flags = ngx.shared.DICT:get(key)
正常状况下:
第一个参数value 返回的是字典中 key 对应的值;但当 key 不存在或者过时时,value 的值为 nil。
第二个参数 flags 就稍微复杂一些了,若是 set 接口设置了 flags,就返回,不然不返回。
一旦 API 调用出错,value 返回 nil,flags 返回具体的错误信息。
cosocket 是把协程和网络套接字的英文拼在一块儿造成的,即 cosocket = coroutine + socket。
遇到网络 I/O 时,它会交出控制权(yield),把网络事件注册到 Nginx 监听列表中,并把权限交给 Nginx;当有 Nginx 事件达到触发条件时,便唤醒对应的协程继续处理(resume),最终实现了非阻塞网络 I/O。
上下文:
rewrite_by_lua*, access_by_lua*, content_by_lua*, ngx.timer.*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*_
cosocket API 在 set_by_lua, log_by_lua, header_filter_by_lua* 和 body_filter_by_lua* 中是没法使用的。init_by_lua* 和 init_worker_by_lua* 中暂时也不能用。
与这些API相应的Nginx指令:
$ resty -e 'local sock = ngx.socket.tcp() sock:settimeout(1000) -- one second timeout local ok, err = sock:connect("www.baidu.com", 80) if not ok then ngx.say("failed to connect: ", err) return end local req_data = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\n\r\n" local bytes, err = sock:send(req_data) if err then ngx.say("failed to send: ", err) return end local data, err, partial = sock:receive() if err then ngx.say("failed to receive: ", err) return end sock:close() ngx.say("response is: ", data)'
在上面settimeout() ,做用是把链接、发送和读取超时时间统一设置为一个值。若是要想分开设置,就须要使用 settimeouts() 函数:
sock:settimeouts(1000, 2000, 3000)
receive 接收指定大小:
local data, err, partial = sock:receiveany(10240)
这段代码就表示,最多只接收 10K 的数据。
关于 receive,还有另外一个很常见的用户需求,那就是一直获取数据,直到遇到指定字符串才中止。
ocal reader = sock:receiveuntil("\r\n") while true do local data, err, partial = reader(4) if not data then if err then ngx.say("failed to read the data stream: ", err) break end ngx.say("read done") break end ngx.say("read chunk: [", data, "]") end
这段代码中的 receiveuntil 会返回 \r\n 以前的数据,并经过迭代器每次读取其中的 4 个字节。
没有链接池的话,每次请求进来都要新建一个链接,就会致使 cosocket 对象被频繁地建立和销毁,形成没必要要的性能损耗。
在你使用完一个 cosocket 后,能够调用 setkeepalive() 放到链接池中:
local ok, err = sock:setkeepalive(2 * 1000, 100) if not ok then ngx.say("failed to set reusable: ", err) end
这段代码设置了链接的空闲时间为 2 秒,链接池的大小为 100。在调用 connect() 函数时,就会优先从链接池中获取 cosocket 对象。
需注意:
OpenResty 的定时任务能够分为下面两种:
以下:
init_worker_by_lua_block { local function handler() local sock = ngx.socket.tcp() local ok, err = sock:connect(“www.baidu.com", 80) end local ok, err = ngx.timer.at(0, handler) }
启动了一个延时为 0 的定时任务。它启动了回调函数 handler,并在这个函数中,用 cosocket 去访问一个网站