OpenResty Module 模块介绍
介绍
OpenResty 支持的模块包括:
- lua-nginx-module
- lua-resty-memcached
- lua-resty-mysql
- lua-resty-redis
- lua-resty-dns
- lua-resty-upload
- lua-resty-websocket
- lua-resty-lock
- lua-resty-logger-socket
- lua-resty-lrucache
- lua-resty-string
- ngx_memc
- ngx_postgres
- ngx_redis2
- ngx_redis
- ngx_proxy
- ngx_fastcgi
- lrucache
- beanstalkd
可以通过 resty <keyword>
搜索 OpenResty 相关模块
高性能编程原则
- 多用长连接
- 合理设置连接池大小
- 链接首次认证
- 善用模块特性
- Lua Module is Global, 使用 local 缓存可以实现加速
- 通过 github.com/openresty/nginx-devel-utils lua-releng 检查全局变量
- local 变量函数更快
- luajit 比 lua 快
第三方模块
- FFI calling external C functions and using C data structures from pure Lua code
ffi.load
加载 *.so 动态库
ffi.cdef
声明 C 函数或数据结构
ffi.typeof
创建 C 类型对象
ffi.new
ffi.C
content_by_lua_file
通过引入 Lua 文件,将代码逻辑从 nginx.conf
中解耦
lua_package_path
设置默认 lua 搜索路径
$prefix
为 /opt/openresty/nginx/lua/ | /usr/local/openresty/nginx/
;;
is the default path
lua_package_cpath
设置查找 *.so
的搜索路径
lua_code_cache: on;
生产为 on
,off
在测试环境中为了避免每次修改 lua 后重新 reload
access_by_lua_file
content_by_lua_file
指定 lua 文件
worker_processes 1;
error_log logs/error.log;
events {
worker_connections 1024;
}
http {
lua_package_path '$prefix/lua/?.lua;/path/?.lua;;';
lua_code_cache off;
...
server {
listen 80;
location ~ ^/api/([-_a-zA-Z0-9/]+) {
access_by_lua_file lua/access_check.lua;
content_by_lua_file lua/$1.lua;
}
}
}
$ cd /opt/openresty/nginx && mkdir -p lua/common
$ cat << EOF > lua/access_check.lua
local param= require("common.param")
local args = ngx.req.get_uri_args()
if not args.a or not args.b or not param.is_number(args.a, args.b) then
ngx.exit(ngx.HTTP_BAD_REQUEST)
return
end
EOF
$ cat << EOF > lua/common/param.lua
local _M = {}
-- 对输入参数逐个进行校验,只要有一个不是数字类型,则返回 false
function _M.is_number(...)
local arg = {...}
local num
for _,v in ipairs(arg) do
num = tonumber(v)
if nil == num then
return false
end
end
return true
end
return _M
EOF
$ cat << EOF > lua/add.lua
local args = ngx.req.get_uri_args()
ngx.say(args.a + args.b)
EOF
$ curl -i "http://127.0.0.1/api/add?a=1&b=2"
HTTP/1.1 200 OK
Server: openresty/1.19.9.1
Date: Sun, 22 Jan 2023 08:00:05 GMT
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
3
$ curl -i "http://127.0.0.1/api/add"
HTTP/1.1 400 Bad Request
Server: openresty/1.19.9.1
Date: Sun, 22 Jan 2023 08:02:17 GMT
Content-Type: text/html
Content-Length: 163
Connection: close
...
dns
dns 使用示例
local resolver = require "resty.dns.resolver"
-- 创建对象
local r, err = resolver:new {
nameservers = {"8.8.8.8", {"8.8.4.4", 53},
timeout = 1000,
},
if not r then
ngx.say("failed to init resolver:", err)
end
-- 查询域名
local answers, err = r:query ("www.openresty.org")
if not answers then
ngx.say("failed to query:", err) return
end
if answers.errcode then
ngx.say("error code: ", answers.errcode, " answers:", answers.errstr)
end
-- 获取 ip 地址
for _, rec in ipairs(answers) do
ngx.say(rec.name, "", rec.addressorrec.cname, " ttl: ", rec.ttl)
end
地址可以结合 lurcache 缓存
http 客户端
使用 http 客户端代理 http 请求
opm install pintsized/lua-resty-http
location /test {
content_by_lua_block {
ngx.req.read_body()
local args, err = ngx.req.get_uri_args()
local http = require "resty.http"
local httpc = http.new()
httpc:set_timeout(1000) -- 1000ms
-- https://docs.github.com/zh/rest/guides/getting-started-with-the-rest-api?apiVersion=2022-11-28&tool=curl#using-body-parameters
local res, err = httpc:request_uri("https://api.github.com/repos/octocat/Spoon-Knife/issues", {
method = "POST",
body = args.data,
headers = {
["Accept"] = "Accept",
["Authorization"] = "Bearer YOUR-TOKEN",
}
})
if 200 ~= res.status then
ngx.exit(res.status)
end
if args.key == res.body then
ngx.say("valid request")
else
ngx.say("invalid request")
end
}
}
websocket 客户端
local client = require "resty.websocket.client"
local wb, err = client.new{
timeout = 5000,
" = 1024*64, -- 64KB
}
wb:set_timeout(1000) -- 1000ms
ok, err= wb:connect("ws://127.0.0.1:8080/srv", opts)
if not ok then
ngx.say("failed to connect:", err)
return
end
ok, err= wb:close()
-- 发送数据
wb:send_ping()
wb:send_text("something")
-- 接收数据
wb:recv_frame()
结合链接池复用
redis 客户端
local redis = require "resty.redis"
local rds = redis:new()
rds:settimeout(lOOO)
ok, err = rds:connect("127.0.0.1",6379)
if not ok then
ngx.say("failed to connect : ", err)
rds:close()
return
end
-- 执行 redis 命令
rds:command(key, ...)
rds:get("xxx")
rds:hset("key", "value", 200)
-- 使用 pipeline 批量操作
local results, err = rds:commit_pipeline()
if not results then
ngx.say("failed to commit pipeline")
return
end
for i, res in ipairs(results) do
...
end
-- 将连接放入连接池
rds:set_keepalive(10000, 100)
结合链接池复用,说明:
- lua 中对 redis 返回的
null
的判断使用 ngx.null
- 连接池,OpenResty 中,所有具备
set_keepalive
的类、库函数都支持连接池
- 使用连接池,一定要注意,不能污染已有的链接,如 redis 选择了不同的数据库的坑
- 长连接,若连接出现错误,则不要将其加入连接池
mysql
local mysql = require "resty.mysql"
local db, err = mysql:new ()
if not db then
ngx.say ("new msyql failed :", err)
return
end
db:settimeout(lOOO) -- 1000ms
local opts = {
host="127 .0.0.1",
port=3306,
database="test",
user="root",
password="root",
}
local ok, err = db:connect(opts}
if not ok then
ngx.say("connect msyql failed:",err}
db:close()
return
end
- 查看 MySQL 版本
db:server_ver()
-- 使用
db:query(stmt, nrows)
-- 关闭连接
db:close()
Websocket
- Websocket 介绍
- 实现集中在
content_by_lua
阶段,ngx.req.socket
获取 cosocket,flush 后直接与客户端通信
lua-resty-websocket
库提供完整的 WebSocket 功能
local server = require "resty.websocket.server"
local wb, err = server.new{
timeout = 10000, -- 超时时长 10s
max_payload_len = 1024 * 32, -- 数据帧大 32k
}
if not we then
ngx.exit(444)
end
local data, typ, bytes, err
-- 无限循环提供服务
while true do
data, typ, er = wb:recv_frame()
if not data then
-- 排除超时
if not string.find(err, "timeout", 1, true) then
ngx.exit(444)
end
-- close 数据帧
if typ == "close" then
bytes, err = wb:send_close()
ngx.exit(0)
end
-- ping 数据帧
if typ == "ping" then
bytes, err = wb:send_pong()
end
-- text 数据帧,发送相应数据
if typ == "text" then
bytes, err = wb:send_text(...)
...
end
end
end
TCP/UDP
- TCP/UDP 通过
stream{}
块配置
- stream 子系统和 http 子系统实现类似
stream {
// lua_package_path "$prefix/lua/?.lua;;"
// lua_package_cpath ".../?.so;;"
lua_code_cache on;
lua_socket_connect_timeout 2s;
lua_socket_send_timeout 2s;
lua_socket_read_timeout 2s;
lua_socket_pool_size 50;
lua_socket_keepalive_timeout 10s;
lua_socket_buffer_size 1k;
lua_check_client_abort on;
lua_max_pending_timers 100;
lua_shared_dict shmem 5m;
server {
listen 9000;
allow 127.0.0.1;
deny all;
// content_by_lua_block {
// ngx.say("hello stream")
// }
content_by_lua_block_file cosocket.lua;
}
}
local sock = ngx.req.socket()
local c, err = sock:receive()
local bytes = sock:send(...)
sock:shutdown("send)
执行阶段
- init_by_lua
- init_worker_by_lua
- preread_by_lua
- content_by_lua
- balancer_by_lua
- log_by_lua