它是什么

OpenResty = Nginx + LuaJIT + 几十个高性能 Lua 库。在 Nginx 请求处理的各阶段插入 Lua 代码——把 Nginx 当成"可编程网关"。

  • 国内 / 国际很多大流量公司用它做 API 网关、CDN、WAF、限流
  • Cloudflare、12306、阿里 Tengine、Kong 网关都基于这套思路
  • 单核每秒能跑数十万 QPS

# macOS
brew install openresty/brew/openresty

# Linux: 见 openresty.org/en/installation.html
openresty -V    # 查版本

第一个 nginx.conf

worker_processes  1;
events { worker_connections 1024; }

http {
    server {
        listen 8080;

        location /hello {
            default_type text/plain;
            content_by_lua_block {
                ngx.say("Hello, OpenResty")
                ngx.say("client IP: ", ngx.var.remote_addr)
            }
        }
    }
}

跑:

openresty -p $PWD -c nginx.conf
curl http://localhost:8080/hello
# Hello, OpenResty
# client IP: 127.0.0.1

主要执行阶段

Nginx 处理请求分多个阶段,OpenResty 都能插 Lua:

阶段 指令 用途
初始化 init_by_lua_block 进程启动时(加载共享数据)
启动 worker init_worker_by_lua_block 每个 worker 启动时
重写 URL rewrite_by_lua_block 改 URI、重定向
访问控制 access_by_lua_block 鉴权、限流、IP 黑名单
生成内容 content_by_lua_block 完全用 Lua 出响应
修改响应头 header_filter_by_lua_block 加 / 改 header
修改响应体 body_filter_by_lua_block 改输出内容
日志 log_by_lua_block 记日志、上报

限流例

http {
    lua_shared_dict my_limit 10m;   -- 10MB 共享内存

    server {
        location /api/ {
            access_by_lua_block {
                local limit = require "resty.limit.count"
                local lim, err = limit.new("my_limit", 10, 60)   -- 60 秒 10 次
                if not lim then ngx.exit(500) end

                local key = ngx.var.binary_remote_addr
                local delay, err = lim:incoming(key, true)
                if not delay then
                    if err == "rejected" then ngx.exit(429) end
                    ngx.exit(500)
                end
            }

            proxy_pass http://upstream;
        }
    }
}

lua-resty-limit-traffic 是官方限流库——还有 token bucket、leaky bucket 等算法。

鉴权例

location /api/ {
    access_by_lua_block {
        local jwt = require "resty.jwt"
        local token = ngx.var.http_authorization
        if not token then ngx.exit(401) end

        local obj = jwt:verify("your-secret", token:sub(8))    -- 去掉 "Bearer "
        if not obj.verified then ngx.exit(401) end

        ngx.req.set_header("X-User-ID", obj.payload.user_id)
    }

    proxy_pass http://upstream;
}

lua-resty-jwt 验签后把 user_id 注入下游请求头。

后端调用

local resp, err = ngx.location.capture("/internal_api", {
    method = ngx.HTTP_POST,
    body = '{"x": 1}',
})

if resp.status == 200 then
    ngx.say(resp.body)
end

ngx.location.capture 是 OpenResty 高效的子请求——比 Lua 自己 fetch 快。

外部 HTTP 用 lua-resty-http

local http = require "resty.http"
local httpc = http.new()
local res, err = httpc:request_uri("http://api.example.com/data", {
    method = "GET",
    headers = { ["X-Api-Key"] = "secret" },
})
ngx.say(res.body)

连接 Redis

local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)

local ok, err = red:connect("127.0.0.1", 6379)
if not ok then ngx.exit(500) end

red:set("hello", "world")
local val = red:get("hello")
ngx.say(val)

red:set_keepalive(10000, 100)   -- 复用连接 10s,池大小 100

set_keepalive 不是关闭——是放回连接池给下个请求用。

共享字典(worker 间共享)

http {
    lua_shared_dict counters 10m;

    server {
        location /count {
            content_by_lua_block {
                local counters = ngx.shared.counters
                local newval, err = counters:incr("requests", 1, 0)
                ngx.say("总请求数: ", newval)
            }
        }
    }
}

ngx.shared.<name> 是所有 worker 进程共享的内存——避免开多 worker 各算各的。

cosocket 异步

OpenResty 的高性能秘密:Lua 协程 + Nginx 事件循环socket = ngx.socket.tcp() 操作不阻塞 worker——其他请求并发进行。

底层就是 第 13 篇 讲的协程被 Nginx 调度。

不要做的事

  • 不要写超长循环——会阻塞整个 worker
  • 不要 os.execute / 大 IO——同步阻塞
  • 不要在 init_by_lua 用 cosocket——那时事件循环没起来

OpenResty 官方文档 openresty.org/en/ 有详尽指南;中文社区 openresty.com.cn 教程多。

→ 下一篇 把 Lua 嵌入 C/C++