OpenResty Module 模块介绍

发布时间: 更新时间: 总字数:1458 阅读时间:3m 作者: IP上海 分享 网址

OpenResty Module 模块介绍

介绍

OpenResty 支持的模块包括:

  • lua-nginx-module
    • api 说明在 https://www.nginx.com/resources/wiki/modules/lua/
    • 尽量使用 OpenResty 的库函数(同步非阻塞的),尽量不用 Lua 的库函数(同步阻塞的)
      • 前者只会引起进程内协程的切换,但进程还是处于运行状态(其他协程还在运行)
      • 后者却会触发进程切换,当前进程会变成睡眠状态, 结果 CPU 就进入空闲状态
    • pcall 调用系统命令
  • 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; 生产为 onoff 在测试环境中为了避免每次修改 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;
        }
    }
}
  • 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;
  }

}
  • 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
Home Archives Categories Tags Statistics
本文总阅读量 次 本站总访问量 次 本站总访客数