可行但需谨慎设计:log_by_lua阶段须用cosocket或异步队列推送特征,禁用阻塞api,依赖shared_dict缓冲,提取轻量字段并tab分隔,秒级批量发送,兼顾安全与稳定性。

直接在 Nginx 的 log_by_lua* 阶段提取流量特征并推送至流计算系统是可行的,但需谨慎设计——该阶段不支持阻塞 I/O,不能直接发 HTTP 请求或写磁盘,必须用非阻塞方式(如 cosocket)或异步队列中转。
核心限制与前提条件
Nginx 的 log_by_lua 运行在请求日志记录阶段,此时响应已发出、连接可能即将关闭。该阶段:
- 禁止调用
ngx.sleep、ngx.socket.tcp()等阻塞 API(除非启用 cosocket +set_timeout) - 不支持协程挂起等待远端响应(如同步 POST 到 Kafka 或 Flink)
- 全局共享内存(
shared_dict)是唯一低开销跨请求通信机制 - 推荐搭配
lua-resty-kafka(基于 cosocket 的异步 producer)或 UDP 日志转发
特征提取:轻量、结构化、可索引
避免在 log_by_lua 中做正则解析或 JSON 序列化开销大的操作。只提取确定性字段,例如:
-
ngx.var.remote_addr(客户端真实 IP,注意 X-Forwarded-For 需前置模块解析) -
ngx.var.time_iso8601(ISO 格式时间戳,精度到秒) -
ngx.var.status、ngx.var.request_length、ngx.var.upstream_response_time -
ngx.var.http_user_agent(截取前 128 字符防膨胀) -
ngx.var.uri和ngx.var.args(可选哈希后存,如ngx.crc32_short(uri))
建议统一拼接为 Tab 分隔的字符串(非 JSON),减少序列化 CPU 开销,便于下游流引擎解析。
秒级推送:异步批量 + 内存缓冲
不能每个请求都推一次。正确做法是:
- 用
shared_dict建立环形缓冲区(如 1000 条 slot),每条记录含时间戳 + 特征字符串 - 在
log_by_lua中原子写入(shdict:set或shdict:lpush) - 另起一个
timer协程(通过init_worker_by_lua*启动),每秒从shared_dict批量读出、组装、异步发送 - 发送端使用
lua-resty-kafka的send_batch或ngx.socket.udp()推送到 Kafka/Flink/ClickHouse 的接入端口
安全与稳定性加固要点
生产环境必须考虑:
- 缓冲区满时丢弃旧数据(
shdict:lpush+shdict:llen控制长度),不阻塞 Nginx 主循环 - Kafka 发送失败时本地降级:写入
lua_shared_dict备份区,由后台脚本定期重试 - 特征字段做过滤:剔除敏感 Header(如 Authorization、Cookie)、脱敏 IP(如
192.168.1.100 → 192.168.1.0/24) - 限速控制:用
resty.limit.count限制每秒最大推送条数,防突发流量打垮下游
不复杂但容易忽略。关键不是“能推”,而是“推得稳、推得准、推得安全”。











