erlang热更新的实现与原理
erlang的热更新是模块级别的,就是一个模块一个模块更新的。
热更新是什么,就是在不停止系统的情况下对运行的代码进行替换。
如何进行热更新?
c(Mod) ->
compile:file(Mod),
code:purge(Mod),
code:load_file(Mod).
以上就是shell c(Mod) 的主要代码,3个步骤:编译新的代码,清除旧代码,加载新代码
同样, l(Mod) 的主要代码如下,少了编译过程:
l(Mod) ->
code:purge(Mod),
code:load_file(Mod).
热更新原理
erlang文档有说明:
The code of a module can exist in two variants in a system: current and old. When a module is loaded into the system for the first time, the code becomes 'current'. If then a new instance of the module is loaded, the code of the previous instance becomes 'old' and the new instance becomes 'current'.
意思是,erlang每个模块都能保存2份代码,当前版本'current'和旧版本'old',当模块第一次被加载时,代码就是'current'版本。如果有新的代码被加载,'current'版本代码就变成了'old'版本,新的代码就成了'current'版本
这样,就算代码在热更新,有进程在调用这个模块,执行的代码也不会受影响。热更新后,这个进程执行的代码没有改变,只不过代码被标记成'old'版本。而新的进程调用这个模块时,只会访问'current'版本的代码。而'old'版本的代码如果没有进程再访问,就会在下次热更新被系统清除掉。
erlang用两个版本共存的方法来保证任何时候总有一个版本可用,这样,对外服务就不会停止。
热更新问题
有个问题,如果'old'版本一直都有进程在调用,在此期间,代码热再更新了会发生什么情况?
热更新时,如果模块存在'old'版本代码,erlang会kill掉所有调用这个'old'版本代码的进程,然后移除掉'old'版本代码,'current'版本变成了'old'版本,新的代码就成了'current'版本。
热更新问题重现
-module(t).
-compile(export_all).
start() ->
Pid = spawn(fun() -> do_loop() end),
register(t, Pid).
do_loop() ->
receive
Msg ->
io:format("~p~n", [Msg])
end,
do_loop().
结果如下:
t:start().
true
erlang:monitor(process, whereis(t)). %%进程监控
Ref<0.0.0.56>
whereis(t).
<0.40.0>
l(t). %%第1次热更
{module,t}
whereis(t).
<0.40.0>
l(t). %%第2次热更
{module,t}
whereis(t).
undefined
flush().
Shell got {'DOWN',#Ref<0.0.0.56>,process,<0.40.0>,killed}
ok
热更新2次后,进程就被kill掉了。(想知道在哪被kill,可在code_server中do_purge/3找到,参考[ 1])
解决热更新问题
如果进程一直在自己loop里面,就会一直跑着'old'版本的代码,这样的后果就是新的代码没有被使用,而且在下一次热更新时进程会被系统kill掉。
怎么解决这个问题,erlang文档还是能找到答案:
To change from old code to current code, a process must make a fully qualified function call. Example:
-module(m).
-export([loop/0]).
->
receive
code_switch ->
m:loop();
Msg ->
do_something(),
loop()
end.
就是在热更新后,给这个进程发消息 code_switch ,这样进程会调用 m:loop()
这里,loop()和m:loop()有什么区别呢?
erlang根据模块划分,函数分本地调用和外部调用,其中,本地调用是调用本模块内的函数,函数可以不导出,调用形式为 Atom(Args) ;外部调用就是调用别的模块函数,函数必须导出,调用形式为 Module:Function(Args).
在erlang VM中,进程调用模块的过程是先加载这个模块当前版本的代码再执行,如果进程一直都是本地调用,那么所有操作都是在进程当前运行的代码中完成。换句话,这个过程中进程不会去加载新的代码。打破这种局面的就是外部调用。
看这张图的时候先把绿色的内容去掉,进程就一直都是本地调用,加入绿色内容后,进程会重新加载这个模块的代码再运行。
那么有些同学会好奇,既然这样,erlang为何还要本地调用,直接全部都外部调用就好了?
参考:http://blog.csdn.net/mycwq/article/details/41175237
http://learnyousomeerlang.com/designing-a-concurrent-application#hot-code-loving
http://www.erlang.org/doc/reference_manual/code_loading.html#id86381
————————————————
版权声明:本文为CSDN博主「没有开花的树」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/mycwq/article/details/41175237