Skip to content

Latest commit

 

History

History
48 lines (34 loc) · 3.14 KB

ch06-热更新.md

File metadata and controls

48 lines (34 loc) · 3.14 KB

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

下面就来谈谈Lua代码如何实现热更新。

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

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

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

(loadlib.c)
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的增强版,使用这个函数可以动态更新某个模块的代码:

function require_ex( _mname )
	print( string.format("require_ex = %s", _mname) )
	if package.loaded[_mname] then
		print( 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代码发生改变了。