OpenResty与模块

Lua 中没有常见面向对象语言中所谓类的概念,取而代之使用模块来组织管理代码。关于模块的基础知识大家可以参考「OpenResty 最佳实战」,本文聊点别的。

如何实现一个模块呢?假设我们要实现一个不太安全的房奴模块(houseslave.lua):

local _M = {}


local mt = { __index = _M }


function _M.new(me, bank)
    local t = {
        me = me,
        bank = bank,
    }

    return setmetatable(t, mt)
end


function _M.repay(self, money)
    self.me.money = self.me.money - money
    self.bank.money = self.bank.money + money
end


return _M

如果我们借用类的思维来解释这段代码,那么大概意思就是:类的属性保存在表(t)中,类的方法保存在元表(mt)中,二者通过 setmetatable 关联起来。

实际使用的时候,大致如下所示:

local houseslave = require "houseslave"
local hs = houseslave.new(me, bank)
hs:repay(10000)

学习模块最好的方法就是多看别人是如何搞的,但也不能完全照搬,以很多人都很熟悉的 lua-resty-redis 模块为例,如果通过 luacheck 来检查的话,会发现很多问题:

lua-resty-redis

我们就以 new 方法的问题为例来说明一下,官方文档的描述如下:

red, err = redis:new()

通过冒号语法糖,self 参数被隐式传递了,但这不是重点,要紧的是 self 在这里有没有意义?实际上,new 相当于是类里的构造函数,在调用构造函数之前,还没有实例化出对象,此时 self 是多余的,应该去掉 new 参数中 self 的定义,当然调用方式也要改一下:

red, err = redis.new()

如果你没搞清楚,可以多看看前面房奴的例子,体会一下「点」和「冒号」的差异。

OpenResty 通过 package.path 来查找模块,初学者往往不知道应该把自己写的模块放到哪个目录,此时可以通过 resty-cli 工具来确认你的 package.path 设置:

package.path

package.path

已经装载的模块保存在 package.loaded.* 中,于是我们可以通过 package.loaded.* = nil 的方式卸载对应的模块,如此一来就实现了热装载代码,同把大象放冰箱一样分三步:

把需要动态加载的代码放在一个 Lua 模块文件 foo.lua 中,并标记版本号。 暴露一个 location 以便从外部写最新的版本号到一个 ngx_lua 共享内存字典中。 在 Lua 代码中,首先检查当前 foo 模块的版本号与共享内存字典中的最新版本号是否一致;如果不一致的话,则卸载当前的 foo 模块(package.loaded.foo = nil ),然后再调用 local foo = require “foo”,从而完成热装载代码。

需要说明的是,使用了 LuaJIT FFI 的模块是不能通过清空 package.loaded 中的对应字段卸载的,好在多数时候,需要频繁热装载代码的模块往往是业务相关的模块,我们可以在设计之初,有意识的把 LuaJIT FFI 相关的代码单独剥离出来。

好了,赶在十月底最后一天完成了本月的文章,虽然没什么营养,但习惯不能丢。

文章来源:

Author:老王
link:https://blog.huoding.com/2019/10/31/779