再说说erlang的模块热更新

前面的文章有讲过erlang热更新,只是大概介绍,现在再深入一点讲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().

结果如下:

7> t:start().
true
8> erlang:monitor(process, whereis(t)).  %%进程监控
#Ref<0.0.0.56>
9> whereis(t).
<0.40.0>
10> l(t).                                %%第1次热更
{module,t}
11> whereis(t).
<0.40.0>
12> l(t).                                %%第2次热更
{module,t}
13> whereis(t).
undefined
14> 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]).

loop() ->
    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为何还要本地调用,直接全部都外部调用就好了?

1.外部调用的开销比本地调用大一点。外部调用时通过指针找到这个模块函数的导出地址,当模块热更时,就会修改这个指针指向的地址。内部调用是上下文跳转,对比少了一个指针查找,外加原子锁的开销。

2.外部调用的函数代码加载的时间稍微长一点,需要获取外部函数在导出函数表的地址,避免在执行时才去导出函数表查找函数地址造成开销。

今晚先到这里,有点困了,找时间再谈谈模块加载。 good night !


2015/7/11 修正外部调用和本地调用的的比较说明

参考: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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值