erlang的热更新

erlang作为一个为电信级别而出现的语言,热更新是其最重要的特性之一

 热代码升级-Erlang允许程序代码在运行系统中被修改。旧代码能被逐步淘汰而后被新代码替换。在此过渡期间,新旧代码是共存的。

下面我们以最典型的gen_server为例子,讲解一下这个BT的功能

 1 -module(tt13).
 2 -behaviour(gen_server).
 3 
 4 -export([test/0]).
 5 -export([start_link/0, stop/0, init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
 6 
 7 -record(state, {cnt}).
 8 
 9 -define(SERVER, ?MODULE).
10 
11 %%--------------------------------------------------------------------
12 %% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
13 %% Description: Starts the server
14 %%--------------------------------------------------------------------
15 start_link() ->
16     gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]).
17 
18 test() ->
19     gen_server:call(?SERVER, test). 
20 
21 stop() -> 
22     gen_server:cast(?SERVER, stop). 
23 
24 %%--------------------------------------------------------------------
25 %% Function: init(Args) -> {ok, State} |
26 %%                         {ok, State, Timeout} |
27 %%                         ignore               |
28 %%                         {stop, Reason}
29 %% Description: Initiates the server
30 %%--------------------------------------------------------------------
31 init(_) -> {ok, #state{cnt=1}}.
32 %%--------------------------------------------------------------------
33 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
34 %%                                      {reply, Reply, State, Timeout} |
35 %%                                      {noreply, State} |
36 %%                                      {noreply, State, Timeout} |
37 %%                                      {stop, Reason, Reply, State} |
38 %%                                      {stop, Reason, State}
39 %% Description: Handling call messages
40 handle_call(test, _From, #state{cnt=Cnt} = State) ->
41     {reply, {ok, Cnt}, State#state{cnt=Cnt+1}};
42 
43 handle_call(stop, _From, State) ->
44     {stop, normal, ok, State};
45 
46 handle_call(_Unrec, _From, State) ->
47     {reply, {error, invalid_call}, State}.
48 
49 %%--------------------------------------------------------------------
50 %% Function: handle_cast(Msg, State) -> {noreply, State} |
51 %%                                      {noreply, State, Timeout} |
52 %%                                      {stop, Reason, State}
53 %% Description: Handling cast messages
54 %%--------------------------------------------------------------------
55 handle_cast(_Msg, State) ->
56     {noreply, State}.
57 
58 %%--------------------------------------------------------------------
59 %% Function: handle_info(Info, State) -> {noreply, State} |
60 %%                                       {noreply, State, Timeout} |
61 %%                                       {stop, Reason, State}
62 %% Description: Handling all non call/cast messages
63 %%--------------------------------------------------------------------
64 
65 handle_info(_Info, State) ->
66     {noreply, State}.
67 
68 %%--------------------------------------------------------------------
69 %% Function: terminate(Reason, State) -> void()
70 %% Description: This function is called by a gen_server when it is about to
71 %% terminate. It should be the opposite of Module:init/1 and do any necessary
72 %% cleaning up. When it returns, the gen_server terminates with Reason.
73 %% The return value is ignored.
74 %%--------------------------------------------------------------------
75 
76 terminate(_Reason, _State) ->
77     io:format("hello gen server: terminating~n").
78 
79 %%--------------------------------------------------------------------
80 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
81 %% Description: Convert process state when code is changed
82 %%--------------------------------------------------------------------
83 
84 code_change(_OldVsn, State, _Extra) ->
85     {ok, State}.
86 
87 %%====================================================================
88 %%other fun
89 %%====================================================================

 编译运行结果

 1 1> c(tt13).
 2 {ok,tt13}
 3 2> tt13:start_link().
 4 {ok,<0.39.0>}
 5 3> tt13:test().
 6 *DBG* tt13 got call test from <0.32.0>
 7 *DBG* tt13 sent {ok,1} to <0.32.0>, new state {state,2}
 8 {ok,1}
 9 4> tt13:test().
10 *DBG* tt13 got call test from <0.32.0>
11 *DBG* tt13 sent {ok,2} to <0.32.0>, new state {state,3}
12 {ok,2}
13 5> tt13:test().
14 *DBG* tt13 got call test from <0.32.0>
15 *DBG* tt13 sent {ok,3} to <0.32.0>, new state {state,4}
16 {ok,3}

如果修改了函数,可以直接运行

 1 -module(tt13).
 2 -version("1.1").
 3 -behaviour(gen_server).
 4 
 5 %...........
 6 %...........省略若干行
 7 %................
 8 
 9 handle_call(test, _From, #state{cnt=Cnt} = State) ->
10     {reply, {ok, Cnt}, State#state{cnt=Cnt*2}};
11 
12 %...........
13 %...........省略若干行
14 %................

 可以看到我们修改了计数的方法,而且修改了版本号,然后我们继续运行

 1 6> c(tt13).                                    %编译新的代码
 2 {ok,tt13}
 3 7> tt13:test().      
 4 *DBG* tt13 got call test from <0.32.0>
 5 *DBG* tt13 sent {ok,4} to <0.32.0>, new state {state,8}
 6 {ok,4}
 7 8> tt13:test().
 8 *DBG* tt13 got call test from <0.32.0>
 9 *DBG* tt13 sent {ok,8} to <0.32.0>, new state {state,16}
10 {ok,8}
11 9> tt13:test().
12 *DBG* tt13 got call test from <0.32.0>
13 *DBG* tt13 sent {ok,16} to <0.32.0>, new state {state,32}
14 {ok,16}

 可以看到代码就直接替换了,注意编译的时候会用新的代码替换下一次运行的结果,正在运行还是old code,所以不要编译多次(一般在测试环境先进行热更新测试)。

   如果要替换init/1里面的代码?这个方法肯定是不行的,因为init/1代码只运行一次,比如我要修改state结构体,那要怎么弄呢

 1 -module(tt13).
 2 -version("2.0").
 3 -behaviour(gen_server).
 4 
 5 -record(state, {testcheck, cnt}).
 6 %...........
 7 %...........省略若干行
 8 %................
 9 
10 init(_) -> {ok, #state{testcheck='chk', cnt=1}}.
11 %%--------------------------------------------------------------------
12 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
13 %%                                      {reply, Reply, State, Timeout} |
14 %%                                      {noreply, State} |
15 %%                                      {noreply, State, Timeout} |
16 %%                                      {stop, Reason, Reply, State} |
17 %%                                      {stop, Reason, State}
18 %% Description: Handling call messages
19 handle_call(test, _From, #state{cnt=Cnt} = State) ->
20     {reply, {ok, Cnt}, State#state{cnt=Cnt+1}};
21 
22 %...........
23 %...........省略若干行
24 %................
25 
26 code_change("1.1", {state, Cnt}, _Extra) -> 
27     {ok, {state, chk, Cnt}};
28 
29 code_change(_OldVsn, State, _Extra) ->
30     {ok, State}.
31 
32 %...........
33 %...........省略若干行
34 %................

 

  我们修改了state结构体,修改了init/1函数,而且重写了code_change/3,下面我们运行如下

 1 10> compile:file(tt13).          /*编译代码
 2 {ok,tt13}
 3 11> sys:suspend(tt13).           /*暂停服务
 4 ok
 5 12> code:purge(tt13).           /*清除旧的代码,如果有运行的化
 6 false
 7 13> code:load_file(tt13).        /*加载新的代码
 8 {module,tt13}
 9 14> sys:change_code(tt13,tt13,"1.1",[]).    /*修改state状态
10 ok
11 15> sys:resume(tt13).                /*恢复服务
12 ok
13 16> tt13:test().
14 *DBG* tt13 got call test from <0.32.0>
15 *DBG* tt13 sent {ok,32} to <0.32.0>, new state {state,chk,64}
16 {ok,32}
17 17> tt13:test().
18 *DBG* tt13 got call test from <0.32.0>
19 *DBG* tt13 sent {ok,64} to <0.32.0>, new state {state,chk,128}
20 {ok,64}
21 18> tt13:test().
22 *DBG* tt13 got call test from <0.32.0>
23 *DBG* tt13 sent {ok,128} to <0.32.0>, new state {state,chk,256}
24 {ok,128}

 

  整个替换过程是10-15步,注意这个过程中的tt13服务是hang住的,如果这时候使用服务,会出现timeout,所以一般这5步都是同时执行。

       后面可以看到state状态已经改变

 

posted @ 2018-08-14 11:39  土豆008  阅读(797)  评论(0编辑  收藏  举报