openresty中使用lua输出json日志到磁盘文件串行问题
背景
公司客户端日志上报模块,使用openresty+lua 先将json日志文件写到磁盘,然后flume采集走。
最近发现, 落到磁盘的文件日志,存在串行情况, 即第二个json覆盖了第一个json的部分内容, 导致这两个json串都无法正常解析。
测试文本内容如下:
1 |
|
问题分析
因为json文本不合法,且openresty输出的是合法json文本。 只能有一个原因。 数据被覆盖。 重点关注日志写入逻辑
1 |
|
使用方式为
1 |
|
引入log包, 调用write写入方法
write写入方法逻辑有:
- 获取待写入的文件路径
- 打开文件
- 将文件顺序写入到磁盘上
- 关闭文件句柄
逻辑较清晰, 这里出问题的大概率是io.open
, write
上, 因为存在多个请求并发写, 是否并发写入导致的问题呢?
经过google排查,发现 确实有反馈 io.open自身缓存(lua的io层还是调用fopen), 会导致覆盖原有数据。 给出的解决方案也比较简单, 换成直接系统调用方法(open,write方法)。
但并没有给出如何在lua代码中调用 systemcall 的open, write方法。
lua程序中,需要借助ffi
库,直接调用c
的系统调用方法, ffi库允许Lua代码调用外部C函数。
如何在lua代码中调用外部C函数?
- 加载ffi库
- 调用
ffi.cdef
为使用的C函数进行声明, 在lua中,只能调用已声明的 - 调用步骤2中声明的C函数
借助ffi包实现直接调用write写文件
1 |
|
重新上线后, 没有再出来新的串行问题
在Nginx Worker中共享变量
上述写文件中, 会持续的open, close 文件句柄, 造成性能的损耗。 我们试想, 怎么才能共用文件句柄?
这里就需要讲到nginx的worker方式, worker即工作线程, 每个worker会处理多个网络请求(epoll网络模型),使用require
导入模块后, 此模块下的局部变量和数据和代码都归此worker下的所有网络请求共享。
数据共享 是基于 worker下的所有请求, 而不是说所有worker共享此数据。通常建议是worker下,只共享只读数据,也可以共享可修改的数据, 但必须保证模块中没有 非阻塞IO操作, 即不主动交出Nginx 事件循环和nginx_lua模块的轻量级线程调度,要十分小心共享可修改数据,出现bug非常不容易排查。
如果想在不同worker之间,共享数据,有以下方式:
- 使用ngx.shared.DICT 方式
- 使用1个worker和1个server
- 借助第三方缓存方式, 比如redis
那么这里我们就可以声明local fd
, 1个worker下多个请求共享文件句柄