Erlang 中的Module級別熱部署

Java裏面要實現Code Replacement,也就是什麼熱部署,通常是使用ClassLoader機制。不久前看到了一篇Google的Paper,裏面講解的C++代碼熱部署更爲複雜。
在Erlang裏面,實現Code Replacement其實很簡單,最方便的方法可以參考 《Erlang Reference Manual》的12.3:

[code]-module(m).
-export([loop/0]).

loop() ->
receive
code_switch ->
m:loop();
Msg ->
...
loop()
end.[/code]

這種簡單的HelloWorld例子,不能滿足我的疑問。在一個更加複雜的應用裏面,比如有多個Process,部分Process的代碼更換後,其他沒有更新的Process會怎樣呢?下面做個試驗。
實例代碼:codereload.erl

[code]-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.[/code]

程序裏面有三個進程,一個Master和兩個Worker,下面使用分佈式進程通信來實現Code Replacement。

使用
[quote]erl -sname foo@localhost
erl -sname bar@localhost[/quote]
啓動erlang shell

啓動codereload:
[quote](foo@localhost)1> codereload:main().
Pid1, Pid2 is alive true true
<0.39.0>[/quote]

發一個"HI"過去看看:
[quote](bar@localhost)1> {master, 'foo@localhost'} ! "HI".
"HI"[/quote]

嗯,收到了
[quote](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"[/quote]


好了,將源程序改一下:
[code]-define(VERSION, "0.1"). --> -define(VERSION, "0.2").[/code]
[code]Pid2 ! refresh, --> %Pid2 ! refresh,[/code]
這裏改了版本號,還有註釋掉髮送給Pid2的更新通知

先編譯新版本
[quote](foo@localhost)2> c(codereload).
{ok,codereload}[/quote]

發送更新通知:
[quote](bar@localhost)2> {master, 'foo@localhost'} ! refresh.
refresh[/quote]

收到了:
[quote](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[/quote]

這時候master, Pid1, Pid2應該都是v0.2的版本,看看是不是這樣:
[quote](bar@localhost)3> {master, 'foo@localhost'} ! "HI".
"HI"[/quote]

[quote](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"[/quote]

OK,改成了 "0.2" 了,代碼替換成功。事情還沒有完,留意上面將 Pid2 ! refresh 給註釋掉了麼,這時候更新會怎樣呢?先修改程序:
[code]-define(VERSION, "0.2"). --> -define(VERSION, "0.3").[/code]
[code]%Pid2 ! refresh, --> Pid2 ! refresh,[/code]

再次編譯:
[quote](foo@localhost)3> c(codereload).
{ok,codereload}[/quote]

發送更新通知:
[quote](bar@localhost)7> {master, 'foo@localhost'} ! refresh.
refresh[/quote]

[quote](foo@localhost)4> Master code reload
(foo@localhost)4> Worker code <0.37.0> reload
(foo@localhost)4> Pid1, Pid2 is alive true true[/quote]

可見,現在只有Pid1收到了更新通知進行了更新,會產生怎樣的結果呢:
[quote](bar@localhost)8> {master, 'foo@localhost'} ! "HI".
"HI"[/quote]

[quote](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"[/quote]
噢,Pid2還停留在v0.2的代碼上,可見各個進程的代碼是獨立的。

發送更新通知,讓Pid2加載v0.3的代碼:
[quote](bar@localhost)8> {master, 'foo@localhost'} ! refresh.
refresh[/quote]

[quote](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[/quote]

這回Pid2更新到新版本了:
[code](bar@localhost)9> {master, 'foo@localhost'} ! "HI".
"HI"[/code]

[quote](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"[/quote]


看到頭暈了麼?我也暈了,還有一種情況呢。在上面,
[quote](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"[/quote]
這步,如果不輸入
[quote]{master, 'foo@localhost'} ! refresh.[/quote]

而是再次編譯:
[quote](foo@localhost)4> c(codereload).
{ok,codereload}[/quote]

發送個消息過去看看:
[code](bar@localhost)9> {master, 'foo@localhost'} ! "HI".
"HI"[/code]

[quote](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"[/quote]
噢,怎麼回事,is_process_alive(Pid2)返回false了,進程怎麼掛了?

《Erlang Reference Manual》的12.3:
[quote]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'.[/quote]

可見,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,我也不會,以後再學習了。

p.s:對本文有任何異議,歡迎拍板:)
發佈了3 篇原創文章 · 獲贊 0 · 訪問量 1241
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章