OpenResty的文件IO操作

OpenResty提供了很好的网络IO能力,甚至高性能的访问MySQL、Memcached 以及 Redis 等能力,但对文件的IO,却缺少高效的封装,只能写原生的lua文件处理函数。也可能是我孤陋寡闻,如果知道OpenResty的文件操作函数,麻烦告诉我一声。

为什么OpenResty需要写文件,不是有ngx.log(level,message)函数提供写日志的功能吗?因为有些时候需要对数据进行落盘,只靠ngx.log数据,格式有限,而且与log混在一起,数据不好处理,所以需要单独的写文件功能。

比如,统一采集接口,原先是收到数据后,OpenResty异步写kafka,所谓的异步写kafka,意思是数据先存在内存,再去写kafka。正常情况下,是没有问题的,极端情况下,如kafka服务挂了,内存的容量又有限,很快内存就会存不下,新上报的数据就会丢失。为了避免这种情况,我们设计成数据先落磁盘,再异步写kafka。

lua操作文件的函数很简单。

//打开文件
filehandle = io.open(filePath, "a")
//写文件
fileHandle:write(message, "\n")
//数据刷到磁盘
fileHandle:flush()
//文件关闭
fileHandle:close()

问题一:如果每个请求都这么做,都要打开,关闭,效率太低。

思考: 只要想办法把打开的文件描述符存到table里,要写文件时先到table检查文件描述符是否存在,存在即用,不存在则先创建。
遇到难题: 编写在location块里的lua是没能创建全局变量的。
解决难题: 用init_worker_by_lua_block提前创建woker级别的变量。在http块里增加:init_worker_by_lua_block { fileHandleList = {} }//增加worker级别的table变量声明
init_worker_by_lua_block说明:

语法 init_worker_by_lua_block {lua-script-str}
配置环境 http
阶段 starting-worker
含义 当master进程被启动后,每个worker进程都会执行Lua代码。如果Nginx禁用了master进程,init_by_lua将会直接运行。

location的代码对应更新为:

local filePath = "/data/" .. filename .. ".log"
if io.type(fileHandleList[filePath]) ~= 'file' then
    fileHandleList[filePath] = assert(io.open(filePath, "a"))
end
local fileHandle = fileHandleList[filePath]
fileHandle:write(message, "\n")
fileHandle:flush()
local res = { code = 1, msg = "send data success!" }
ngx.say(cjson.encode(res))

问题二:在OpenResty多进程的情况下,如果同时写同一个文件,会造成数据混乱。

思考: 为了避免同时写,需要加锁。
遇到难题: 需要一个跨worker的全局锁。
解决难题: 用lua_shared_dict设置一块共享内存区域,可以被各个worker共享。在http块里增加:lua_shared_dict file_locks 1m;//声明全局锁的空间为1M
lua_shared_dict说明:

语法 lua_shared_dict
默认值
上下文 http
阶段 取决于使用
含义 声明一个共享内存区域,作为基于shm的Lua字典的存储空间ngx.shared.。
共享内存区域始终由当前nginx服务器实例中的所有nginx工作进程共享。
该参数接受大小的单位,如k和m:

location的代码对应更新为:

local resty_lock = require('resty.lock')
local lock, err = resty_lock:new("file_locks")
local filePath = "/data/" .. filename .. ".log"
if io.type(fileHandleList[filePath]) ~= 'file' then
    fileHandleList[filePath] = assert(io.open(filePath, "a"))
end
local fileHandle = fileHandleList[filePath]
local elapsed, err = lock:lock(filename)
if not elapsed then
    ngx.log(ngx.WARN, "failed to lock: " .. err)
end
fileHandle:write(message, "\n")
fileHandle:flush()
local ok, err = lock:unlock()
if not ok then
    ngx.log(ngx.WARN, "failed to unlock: " .. err)
end
local res = { code = 1, msg = "send data success!" }
ngx.say(cjson.encode(res))

你可能感兴趣的:(Openresty,openresty,lua,开发语言)