Erlang 中的Module级别热部署

Java里面要实现Code Replacement,也就是什么热部署,通常是使用ClassLoader机制。不久前看到了一篇Google的Paper,里面讲解的C++代码热部署更为复杂。
在Erlang里面,实现Code Replacement其实很简单,最方便的方法可以参考 《Erlang Reference Manual》的12.3:

Java代码
  1. -module(m).  
  2. -export([loop/0 ]).  
  3.   
  4. loop() ->  
  5.     receive  
  6.         code_switch ->  
  7.             m:loop();  
  8.         Msg ->  
  9.             ...  
  10.             loop()  
  11.     end.  
-module(m).
-export([loop/0]).

loop() ->
    receive
        code_switch ->
            m:loop();
        Msg ->
            ...
            loop()
    end.



这种简单的HelloWorld例子,不能满足我的疑问。在一个更加复杂的应用里面,比如有多个Process,部分Process的代码更换后,其他没有更新的Process会怎样呢?下面做个试验。
实例代码:codereload.erl

Java代码
  1. -module(codereload).  
  2. -export([main/0 , master_loop/ 2 , worker_loop/ 0 ]).  
  3. -define(VERSION, "0.1" ).  
  4.   
  5. main() ->  
  6.     process_flag(trap_exit, true ),  
  7.     Pid1 = spawn(?MODULE, worker_loop, []),  
  8.     Pid2 = spawn(?MODULE, worker_loop, []),  
  9.     spawn(fun() -> register(master, self()), master_loop(Pid1, Pid2) end).  
  10.   
  11.   
  12. master_loop(Pid1, Pid2) ->  
  13.     io:format("Pid1, Pid2 is alive ~p ~p~n" ,[is_process_alive(Pid1), is_process_alive(Pid2)]),  
  14.     receive  
  15.     refresh ->  
  16.         io:format("Master code reload~n" ),  
  17.         Pid1 ! refresh,  
  18.         Pid2 ! refresh,  
  19.         codereload:master_loop(Pid1, Pid2);  
  20.     Any ->  
  21.         io:format("Master ~p receive message: ~p~n" , [?VERSION, Any]),  
  22.         Pid1 ! Any,  
  23.         Pid2 ! Any,  
  24.         master_loop(Pid1, Pid2)  
  25.     end.  
  26.   
  27. worker_loop() ->  
  28.     receive  
  29.     refresh ->  
  30.         io:format("Worker code ~p reload~n" , [self()]),  
  31.         codereload:worker_loop();  
  32.     Any ->  
  33.         io:format("Worker ~p at ~p also receive message: ~p~n" , [?VERSION, self(), Any]),  
  34.         worker_loop()  
  35.     end.  
-module(codereload).
-export([main/0, master_loop/2, worker_loop/0]).
-define(VERSION, "0.1").

main() ->
    process_flag(trap_exit, true),
    Pid1 = spawn(?MODULE, worker_loop, []),
    Pid2 = spawn(?MODULE, worker_loop, []),
    spawn(fun() -> register(master, self()), master_loop(Pid1, Pid2) end).


master_loop(Pid1, Pid2) ->
    io:format("Pid1, Pid2 is alive ~p ~p~n",[is_process_alive(Pid1), is_process_alive(Pid2)]),
    receive
	refresh ->
	    io:format("Master code reload~n"),
	    Pid1 ! refresh,
	    Pid2 ! refresh,
	    codereload:master_loop(Pid1, Pid2);
	Any ->
	    io:format("Master ~p receive message: ~p~n", [?VERSION, Any]),
	    Pid1 ! Any,
	    Pid2 ! Any,
	    master_loop(Pid1, Pid2)
    end.

worker_loop() ->
    receive
	refresh ->
	    io:format("Worker code ~p reload~n", [self()]),
	    codereload:worker_loop();
	Any ->
	    io:format("Worker ~p at ~p also receive message: ~p~n", [?VERSION, self(), Any]),
	    worker_loop()
    end.



程序里面有三个进程,一个Master和两个Worker,下面使用分布式进程通信来实现Code Replacement。

使用

引用
erl -sname foo@localhost
erl -sname bar@localhost


启动erlang shell

启动codereload:

引用
(foo@localhost)1> codereload:main().
Pid1, Pid2 is alive true true
<0.39.0>



发一个"HI"过去看看:

引用
(bar@localhost)1> {master, 'foo@localhost'} ! "HI".
"HI"



嗯,收到了

引用
(foo@localhost)2> Master "0.1" receive message: "HI"
(foo@localhost)2> Pid1, Pid2 is alive true true
(foo@localhost)2> Worker "0.1" at <0.37.0> also receive message: "HI"
(foo@localhost)2> Worker "0.1" at <0.38.0> also receive message: "HI"




好了,将源程序改一下:

Java代码
  1. -define(VERSION,  "0.1" ). --> -define(VERSION,  "0.2" ).  
-define(VERSION, "0.1"). --> -define(VERSION, "0.2").

 

Java代码
  1. Pid2 ! refresh, --> %Pid2 ! refresh,  
Pid2 ! refresh, --> %Pid2 ! refresh,


这里改了版本号,还有注释掉发送给Pid2的更新通知

先编译新版本

引用
(foo@localhost)2> c(codereload).
{ok,codereload}



发送更新通知:

引用
(bar@localhost)2> {master, 'foo@localhost'} ! refresh.
refresh



收到了:

引用
(foo@localhost)3> Master code reload
(foo@localhost)3> Worker code <0.37.0> reload
(foo@localhost)3> Worker code <0.38.0> reload
(foo@localhost)3> Pid1, Pid2 is alive true true



这时候master, Pid1, Pid2应该都是v0.2的版本,看看是不是这样:

引用
(bar@localhost)3> {master, 'foo@localhost'} ! "HI".
"HI"


引用
(foo@localhost)3> Master "0.2" receive message: "HI"
(foo@localhost)3> Pid1, Pid2 is alive true true
(foo@localhost)3> Worker "0.2" at <0.37.0> also receive message: "HI"
(foo@localhost)3> Worker "0.2" at <0.38.0> also receive message: "HI"



OK,改成了 "0.2" 了,代码替换成功。事情还没有完,留意上面将 Pid2 ! refresh 给注释掉了么,这时候更新会怎样呢?先修改程序:

Java代码
  1. -define(VERSION,  "0.2" ). --> -define(VERSION,  "0.3" ).  
-define(VERSION, "0.2"). --> -define(VERSION, "0.3").

 

Java代码
  1. %Pid2 ! refresh, --> Pid2 ! refresh,  
%Pid2 ! refresh, --> Pid2 ! refresh,



再次编译:

引用
(foo@localhost)3> c(codereload).
{ok,codereload}



发送更新通知:

引用
(bar@localhost)7> {master, 'foo@localhost'} ! refresh.
refresh


引用
(foo@localhost)4> Master code reload
(foo@localhost)4> Worker code <0.37.0> reload
(foo@localhost)4> Pid1, Pid2 is alive true true



可见,现在只有Pid1收到了更新通知进行了更新,会产生怎样的结果呢:

引用
(bar@localhost)8> {master, 'foo@localhost'} ! "HI".
"HI"


引用
(foo@localhost)4> Master "0.3" receive message: "HI"
(foo@localhost)4> Pid1, Pid2 is alive true true
(foo@localhost)4> Worker "0.3" at <0.37.0> also receive message: "HI"
(foo@localhost)4> Worker "0.2" at <0.38.0> also receive message: "HI"


噢,Pid2还停留在v0.2的代码上,可见各个进程的代码是独立的。

发送更新通知,让Pid2加载v0.3的代码:

引用
(bar@localhost)8> {master, 'foo@localhost'} ! refresh.
refresh


引用
(foo@localhost)4> Master code reload
(foo@localhost)4> Worker code <0.37.0> reload
(foo@localhost)4> Worker code <0.38.0> reload
(foo@localhost)4> Pid1, Pid2 is alive true true



这回Pid2更新到新版本了:

Java代码
  1. (bar @localhost ) 9 > {master,  'foo@localhost' } !  "HI" .  
  2. "HI"   
(bar@localhost)9> {master, 'foo@localhost'} ! "HI".
"HI"


引用
(foo@localhost)4> Master "0.3" receive message: "HI"
(foo@localhost)4> Pid1, Pid2 is alive true true
(foo@localhost)4> Worker "0.3" at <0.37.0> also receive message: "HI"
(foo@localhost)4> Worker "0.3" at <0.38.0> also receive message: "HI"




看到头晕了么?我也晕了,还有一种情况呢。在上面,

引用
(foo@localhost)4> Master "0.3" receive message: "HI"
(foo@localhost)4> Pid1, Pid2 is alive true true
(foo@localhost)4> Worker "0.3" at <0.37.0> also receive message: "HI"
(foo@localhost)4> Worker "0.2" at <0.38.0> also receive message: "HI"


这步,如果不输入

引用
{master, 'foo@localhost'} ! refresh.



而是再次编译:

引用
(foo@localhost)4> c(codereload).
{ok,codereload}



发送个消息过去看看:

Java代码
  1. (bar @localhost ) 9 > {master,  'foo@localhost' } !  "HI" .  
  2. "HI"   
(bar@localhost)9> {master, 'foo@localhost'} ! "HI".
"HI"


引用
(foo@localhost)5> Master "0.3" receive message: "HI"
(foo@localhost)5> Pid1, Pid2 is alive true false
(foo@localhost)5> Worker "0.3" at <0.37.0> also receive message: "HI"


噢,怎么回事,is_process_alive(Pid2)返回false了,进程怎么挂了?

《Erlang Reference Manual》的12.3:

引用
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'.

Bot old and current code is valid, and may be evaluated concurrently. Fully qualified function calls always refer to current code. Old code may still be evaluated because of processes lingering in the old code.

If a third instance of the module is loaded, the code server will remove (purge) the old code and any processes lingering in it will be terminated. Then the third instance becomes 'current' and the previously current code becomes 'old'.



可见,Erlang里面,模块的代码只有新旧两个版本,当时Pid1对应于v0.3(Current),Pid2对应于v0.2(Old),当进行编译之后,Pid1的代码就是Old,而Pid2就被强制终止了。


对于以守护进程形式启动的Erlang进程,就不能在shell里面直接编译了,如果在外部编译,这个进程是不认的。我想到的一个方法就是使用rpc来调用编译:
rpc:call('foo@localhost', shell_default, c, [codereload]).


对于热部署,Erlang还有一种更强大的形式,就是使用OTP的Release Handling,我也不会,以后再学习了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值