- config: 反向代理、缓存、负载均衡、安全、SSL 等配置模板 - lua: API 网关、认证、动态路由、限流、WebSocket 等脚本示例 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
239 lines
7.7 KiB
Plaintext
239 lines
7.7 KiB
Plaintext
# ============================================================
|
||
# Nginx lua_shared_dict 共享字典配置示例
|
||
# ============================================================
|
||
#
|
||
# 功能说明:
|
||
# - 跨请求共享数据
|
||
# - 实现缓存、计数器、限流
|
||
# - 进程间通信
|
||
#
|
||
# Lolly 对应配置:
|
||
# lolly 内置 Lua 沙箱,支持共享字典功能
|
||
# 相关文档: docs/lua-nginx-module/05-shdict.md
|
||
# ============================================================
|
||
|
||
http {
|
||
# 定义共享字典
|
||
# lua_shared_dict name size;
|
||
# size: 内存大小,支持 k, m 单位
|
||
lua_shared_dict cache 10m; # 缓存存储
|
||
lua_shared_dict rate_limit 5m; # 限流计数
|
||
lua_shared_dict sessions 10m; # 会话存储
|
||
lua_shared_dict config 1m; # 配置存储
|
||
|
||
server {
|
||
listen 80;
|
||
server_name shdict.example.com;
|
||
|
||
# 缓存示例
|
||
location /api/cached {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson.safe"
|
||
local cache = ngx.shared.cache
|
||
|
||
local key = ngx.var.arg_key or "default"
|
||
|
||
-- 尝试从缓存获取
|
||
local value, flags = cache:get(key)
|
||
|
||
if value then
|
||
ngx.header["X-Cache"] = "HIT"
|
||
ngx.say(value)
|
||
return
|
||
end
|
||
|
||
-- 生成数据(模拟)
|
||
local data = {
|
||
key = key,
|
||
value = "generated_value_" .. key,
|
||
timestamp = ngx.now()
|
||
}
|
||
local json = cjson.encode(data)
|
||
|
||
-- 存入缓存,60 秒过期
|
||
local success, err, forcible = cache:set(key, json, 60)
|
||
if not success then
|
||
ngx.log(ngx.WARN, "failed to set cache: ", err)
|
||
end
|
||
|
||
-- 检查是否 LRU 强制淘汰
|
||
if forcible then
|
||
ngx.log(ngx.WARN, "cache LRU eviction occurred")
|
||
end
|
||
|
||
ngx.header["X-Cache"] = "MISS"
|
||
ngx.say(json)
|
||
}
|
||
}
|
||
|
||
# 限流示例
|
||
location /api/limited {
|
||
access_by_lua_block {
|
||
local limit = ngx.shared.rate_limit
|
||
local ip = ngx.var.remote_addr
|
||
local key = "limit:" .. ip
|
||
|
||
-- 获取当前计数
|
||
local count, err = limit:incr(key, 1, 0, 60) # 60 秒窗口
|
||
|
||
if not count then
|
||
ngx.log(ngx.ERR, "failed to incr: ", err)
|
||
count = 0
|
||
end
|
||
|
||
-- 设置过期时间(仅首次)
|
||
if count == 1 then
|
||
limit:expire(key, 60)
|
||
end
|
||
|
||
-- 检查限制
|
||
local max_requests = 100
|
||
if count > max_requests then
|
||
ngx.status = 429
|
||
ngx.header["X-RateLimit-Limit"] = max_requests
|
||
ngx.header["X-RateLimit-Remaining"] = 0
|
||
ngx.header["X-RateLimit-Reset"] = 60
|
||
ngx.say('{"error": "Rate limit exceeded"}')
|
||
ngx.exit(429)
|
||
end
|
||
|
||
-- 设置响应头
|
||
ngx.header["X-RateLimit-Limit"] = max_requests
|
||
ngx.header["X-RateLimit-Remaining"] = math.max(0, max_requests - count)
|
||
}
|
||
|
||
content_by_lua_block {
|
||
ngx.say('{"status": "ok"}')
|
||
}
|
||
}
|
||
|
||
# 会话存储示例
|
||
location /api/session {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson.safe"
|
||
local sessions = ngx.shared.sessions
|
||
|
||
local session_id = ngx.var.cookie_session_id
|
||
|
||
if ngx.req.get_method() == "GET" then
|
||
-- 获取会话
|
||
if not session_id then
|
||
ngx.status = 401
|
||
ngx.say('{"error": "No session"}')
|
||
ngx.exit(401)
|
||
end
|
||
|
||
local session_data = sessions:get("session:" .. session_id)
|
||
if not session_data then
|
||
ngx.status = 401
|
||
ngx.say('{"error": "Session expired"}')
|
||
ngx.exit(401)
|
||
end
|
||
|
||
ngx.say(session_data)
|
||
|
||
elseif ngx.req.get_method() == "POST" then
|
||
-- 创建会话
|
||
ngx.req.read_body()
|
||
local data = cjson.decode(ngx.req.get_body_data())
|
||
|
||
-- 生成 session ID
|
||
local session_id = ngx.md5(ngx.now() .. math.random())
|
||
|
||
-- 存储会话,30 分钟过期
|
||
sessions:set("session:" .. session_id, cjson.encode(data), 1800)
|
||
|
||
-- 设置 Cookie
|
||
ngx.header["Set-Cookie"] = "session_id=" .. session_id .. "; Path=/; HttpOnly"
|
||
|
||
ngx.say(cjson.encode({session_id = session_id}))
|
||
end
|
||
}
|
||
}
|
||
|
||
# 配置管理示例
|
||
location /admin/config {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson.safe"
|
||
local config = ngx.shared.config
|
||
|
||
local method = ngx.req.get_method()
|
||
|
||
if method == "GET" then
|
||
local keys = config:get_keys()
|
||
local result = {}
|
||
for _, key in ipairs(keys) do
|
||
result[key] = config:get(key)
|
||
end
|
||
ngx.say(cjson.encode(result))
|
||
|
||
elseif method == "POST" then
|
||
ngx.req.read_body()
|
||
local data = cjson.decode(ngx.req.get_body_data())
|
||
|
||
for k, v in pairs(data) do
|
||
config:set(k, v)
|
||
end
|
||
|
||
ngx.say(cjson.encode({success = true}))
|
||
end
|
||
}
|
||
}
|
||
|
||
# 统计信息
|
||
location /admin/stats {
|
||
content_by_lua_block {
|
||
local cjson = require "cjson.safe"
|
||
|
||
local function get_stats(dict_name)
|
||
local dict = ngx.shared[dict_name]
|
||
return {
|
||
capacity = dict:capacity(),
|
||
free_space = dict:free_space(),
|
||
keys = #dict:get_keys()
|
||
}
|
||
end
|
||
|
||
local stats = {
|
||
cache = get_stats("cache"),
|
||
rate_limit = get_stats("rate_limit"),
|
||
sessions = get_stats("sessions"),
|
||
config = get_stats("config")
|
||
}
|
||
|
||
ngx.header["Content-Type"] = "application/json"
|
||
ngx.say(cjson.encode(stats))
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
# lua_shared_dict API 说明:
|
||
#
|
||
# 1. 基础操作:
|
||
# - get(key): 获取值
|
||
# - set(key, value, exptime?, flags?): 设置值
|
||
# - add(key, value, exptime?, flags?): 仅当 key 不存在时设置
|
||
# - replace(key, value, exptime?, flags?): 仅当 key 存在时替换
|
||
# - delete(key): 删除
|
||
#
|
||
# 2. 计数操作:
|
||
# - incr(key, value?, init?, init_ttl?): 增加
|
||
# - decr(key, value?): 减少
|
||
# - flush_all(): 清空所有
|
||
# - flush_expired(max_count?): 清理过期
|
||
#
|
||
# 3. 过期管理:
|
||
# - expire(key, exptime): 设置过期时间
|
||
# - ttl(key): 获取剩余过期时间
|
||
#
|
||
# 4. 统计:
|
||
# - capacity(): 总容量(字节)
|
||
# - free_space(): 剩余空间(字节)
|
||
# - get_keys(max_count?): 获取所有 key
|
||
#
|
||
# 5. 特点:
|
||
# - 所有 worker 共享
|
||
# - LRU 淘汰策略
|
||
# - 原子操作
|
||
# - 支持过期时间 |