一、前言:为什么要在 OpenResty 中查 Redis?
在高并发场景下,传统架构面临巨大挑战:
- ❌ 所有请求穿透到后端 → 数据库压力剧增
- ❌ 通用逻辑(鉴权、限流)重复开发
- ❌ 响应延迟高(网络往返 + 后端处理)
OpenResty = Nginx + LuaJIT ,让你在网关层直接操作 Redis ,实现:
✅ 毫秒级响应 (缓存命中时无需调用后端)
✅ 降低 70%+ 后端负载
✅ 统一业务逻辑入口
本文将手把手教你从零实现 OpenResty 查询 Redis 缓存 ,并提供生产级封装方案。
二、核心依赖:lua-resty-redis
OpenResty 官方提供高性能 Redis 客户端 `lua-resty-redis`,特点:
- ✅ 基于 cosocket,非阻塞 I/O
- ✅ 支持 连接池复用 (
set_keepalive) - ✅ 自动重连机制
- ✅ 支持 Pipeline 批量操作
💡 确认已安装(官方 OpenResty 默认包含):
bashopenresty -V 2>&1 | grep -o 'lua-resty-redis'
三、基础实现:单次 Redis 查询
场景:用户 Token 验证
Lua
location /api/secure {
access_by_lua_block {
-- 1. 引入 Redis 模块
local redis = require "resty.redis"
local red = redis:new()
-- 2. 设置超时(单位:毫秒)
red:set_timeout(1000)
-- 3. 连接 Redis
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "Redis connect failed: ", err)
ngx.exit(500)
end
-- 4. 查询 Token
local token = ngx.var.http_authorization
local user_id = red:get("auth:token:" .. token)
-- 5. 处理结果(注意:Redis nil 返回 ngx.null)
if not user_id or user_id == ngx.null then
ngx.status = 401
ngx.say('{"error":"invalid token"}')
return ngx.exit(401)
end
-- 6. 透传用户 ID 给后端
ngx.req.set_header("X-User-ID", user_id)
-- 7. 关键:放入连接池复用(避免连接泄漏!)
local ok, err = red:set_keepalive(10000, 100)
if not ok then
ngx.log(ngx.WARN, "Failed to set keepalive: ", err)
end
}
proxy_pass http://backend;
}
🔑 关键点:
- 必须调用
set_keepalive实现连接复用- 使用
user_id == ngx.null判断 Redis 返回 nil
四、生产级封装:Redis 工具类
避免重复代码,创建 /usr/local/openresty/lualib/lib/redis_util.lua:
Lua
-- lib/redis_util.lua
local redis = require "resty.redis"
local ngx = ngx
local _M = {}
-- 配置(建议从共享内存读取)
local config = {
host = "127.0.0.1",
port = 6379,
timeout = 1000,
pool_size = 100,
pool_idle_timeout = 10000
}
-- 获取 Redis 连接
local function get_connection()
local red = redis:new()
red:set_timeout(config.timeout)
local ok, err = red:connect(config.host, config.port)
if not ok then
return nil, "connect failed: " .. tostring(err)
end
return red, nil
end
-- 释放连接到池
local function release_connection(red)
local ok, err = red:set_keepalive(config.pool_idle_timeout, config.pool_size)
if not ok then
ngx.log(ngx.WARN, "set_keepalive failed: ", err)
red:close()
end
end
-- GET 方法
function _M.get(key)
local red, err = get_connection()
if not red then return nil, err end
local res, err = red:get(key)
release_connection(red)
if err then return nil, err end
if res == ngx.null then return nil, "not found" end
return res, nil
end
-- SET 方法
function _M.set(key, value, exptime)
local red, err = get_connection()
if not red then return nil, err end
local ok, err = exptime and red:set(key, value, "EX", exptime) or red:set(key, value)
release_connection(red)
if not ok then return nil, err end
return true, nil
end
return _M
五、使用封装工具:简洁 & 安全
Lua
location /api/user-profile {
content_by_lua_block {
local redis_util = require "lib.redis_util"
local cjson = require "cjson.safe"
local user_id = ngx.var.arg_userId
if not user_id then
ngx.exit(400)
end
-- 1. 先查缓存
local cache_key = "user:profile:" .. user_id
local cached, err = redis_util.get(cache_key)
if cached then
ngx.say(cached)
return
end
-- 2. 缓存未命中:回源查询后端
local res = ngx.location.capture("/internal/user/" .. user_id)
if res.status ~= 200 then
ngx.status = res.status
ngx.say(res.body)
return
end
-- 3. 写入缓存(TTL=300秒)
redis_util.set(cache_key, res.body, 300)
ngx.print(res.body)
}
}
✅ 优势:
- 代码简洁,逻辑清晰
- 自动连接管理
- 统一错误处理
六、高级技巧:Pipeline 批量查询
场景:一次获取多个用户资料
Lua
-- 在 redis_util.lua 中添加 pipeline 方法
function _M.pipeline(commands)
local red, err = get_connection()
if not red then return nil, err end
red:init_pipeline()
for _, cmd in ipairs(commands) do
red[cmd[1]](red, unpack(cmd, 2))
end
local results, err = red:commit_pipeline()
release_connection(red)
if err then return nil, err end
return results, nil
end
使用示例:
Lua
local commands = {
{"get", "user:profile:1001"},
{"get", "user:profile:1002"},
{"expire", "session:abc", 1800}
}
local results, err = redis_util.pipeline(commands)
📊 性能提升:
- 单条查询:10ms × 100 = 1000ms
- Pipeline:~15ms(网络往返仅 1 次)
七、生产环境最佳实践
7.1 连接池配置(nginx.conf)
Lua
http {
lua_socket_connect_timeout 1s;
lua_socket_send_timeout 1s;
lua_socket_read_timeout 1s;
lua_socket_pool_size 100; # 每个 worker 的连接池大小
}
7.2 故障降级
Lua
local profile, err = redis_util.get(key)
if err then
ngx.log(ngx.WARN, "Redis error, fallback to backend: ", err)
-- 直接走后端,不中断服务
end
7.3 监控指标
-
记录缓存命中率:
Luaif cached then ngx.shared.stats:incr("cache_hit", 1) else ngx.shared.stats:incr("cache_miss", 1) end
八、常见问题排查
| 问题 | 原因 | 解决 |
|---|---|---|
connection refused |
Redis 未启动或防火墙 | 检查 telnet 127.0.0.1 6379 |
timeout |
网络慢或 Redis 阻塞 | 优化 Lua 脚本,设置合理超时 |
| 连接泄漏 | 未调用 set_keepalive |
使用封装工具类强制管理 |
| 中文乱码 | 编码不一致 | 确保 Redis 和 OpenResty 均使用 UTF-8 |
九、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!