shell centos Firefox 微软 开源 Ubuntu wordpress Python HTML5 google mysql linux命令 apache Android 程序员 php Windows java nginx linux

Lua5.1.4代码分析 -如何实现Lua代码的热更新

能很好的支持代码热更新机制,是大部分选择要嵌入脚本语言的原因之一。好处很简单,脚本代码可以热更新的话,调试和线上解决问题都可以不用重启程序了,对开发效率有很大的帮助。

今天就来谈谈Lua代码如何实现热更新。

先简单回顾之前提过的模块和require机制。Lua内部提供了一个require函数,来实现模块的加载,它做的事情主要是以下几个:

  • 在registry["_LOADED"]表中判断该模块是否已经加载过了,如果是则返回,避免重复加载某个模块代码。
  • 依次调用注册的loader来加载模块
  • 将加载过的模块赋值给registry["_LOADED"]表。

而如果要实现Lua的代码热更新,其实也就是需要重新加载某个模块,因此就要想办法让Lua认为它之前没有加载过。查看Lua代码发现,registry["_LOADED"]表,实际上对应的是package.loaded表,这在以下函数中有体现:

627 LUALIB_API int luaopen_package (lua_State *L) {
/....
655   /* set field `loaded' */
656   luaL_findtable(L, LUA_REGISTRYINDEX, "_LOADED", 2);
657   lua_setfield(L, -2, "loaded");

因此事情就很简单了,需要提供一个require_ex函数,可以理解为require的增强版,使用这个函数可以动态更新某个模块的代码,这个函数可以在我的开源项目qnode的script/util.lua中找到:

function require_ex( _mname )
  qlog( string.format("require_ex = %s", _mname) )
  if package.loaded[_mname] then
    qlog( string.format("require_ex module[%s] reload", _mname))
  end
  package.loaded[_mname] = nil
  require( _mname )
end

这个函数做的事情一目了然。首先判断是否曾经加载过这个模块,如果有则打印一条日志表示需要重新加载某个模块,然后将该模块原来在表中注册的值赋空,然后再次调用require进行模块的加载和注册。

以上了解了Lua代码热更新的原理,但是还有一些细节需要提醒一下。

第一点,如何组织你的项目中的Lua代码?我在qnode中使用的方式是,单独使用一个叫main.lua的文件调用require_ex函数来加载需要用到的lua模块,而lua虚拟机创建之后执行的是这个文件,这样的话,当你需要热更新项目中的lua代码时,只需要重新执行这个main.lua就行了。如何通知热更新代码呢?我在qnode中使用的信号机制,当服务器收到USR1信号时,通知所有工作进程,由工作进程来重新对main.lua进行重新加载,这样就完成了lua代码的热更新,为此我写了一个简单的脚本reload.sh,就是根据当前qnode的服务器进程ID来对其发送USR1信号量的。

第二点,一般热更新的都是函数的实现,所以需要对全局变量做一些保护。比如当前某全局变量为100,表示某个操作已经进行了100次,它不能因为热更新重新置0,所以要对这些不能改变的全局变量做一个保护,最简单的方式就是这样:

a = a or 0

很简单的原理,只有当前a这个变量没有初始值的时候才会赋值为0,而后面不管这个Lua文件被加载多少次,a都不会因为重新加载了Lua代码发生改变了。

延伸阅读

评论