0

我正在探索 Elixir/Erlang 热重载并试图了解 Erlang 热重载是如何工作的。

一些帖子在这里给出了热插拔的一瞥,在这里,来自 elixir,给出了热插拔的关键步骤。

此外,我尝试了著名的 tcp 池库 Erlang 牧场,以了解热插拔如何在开发和部署环境中保持 tcp 连接。代码在 github 上(readme.md 里有一些中文,随意用mix runoriex -S mixtelnet localhost 8000测试一下)。

热重载时一个非常有影响的事情是当持有代码的进程被删除时,一个进程将被杀死。在这个阶段,我应该给它一个恢复策略,或者确保在做热插拔时代码不能被删除。我认为一个好的做法是将逻辑代码删除到另一个与套接字连接循环代码不同的文件中。

我感到困惑的是EVM如何识别进程中的代码版本并在更新新代码时删除旧版本?

我经常听说 OTP 将有助于进行热重载?
文档描述了升级的步骤,它如何处理运行环境中的热重载?

谢谢。

4

1 回答 1

1

下一个模块是一个超级基础的服务器,来说明没有OTP机制的代码更改。答案仅针对 erlang 代码。

-module (modtest).

-export ([init/0,loop/1]).

init() ->
    spawn(?MODULE, loop, [version()]).

loop(Version) ->
    receive
        reload ->
            io:format("external call state is ~p, current version is ~p~n",[Version,version()]),
            ?MODULE:loop(version());
        stop ->
            io:format("stopped~n");
        Message ->
            io:format("receive message ~p~n---> local call state is ~p, current version is ~p~n",[Message,Version,version()]),
            loop(version())
    after 10000 ->
        io:format("timeout, state is ~p, current version is ~p~n",[Version,version()]),
        loop(version())
    end.

version() -> version1.

首先,尝试版本 1 中的模块

1> c(modtest).
{ok,modtest}
2> P = modtest:init().
<0.66.0>
timeout, state is version1, current version is version1
3> P! message1.
receive message message1
---> local call state is version1, current version is version1
message1
timeout, state is version1, current version is version1
4> P ! reload.
external call state is version1, current version is version1
reload
timeout, state is version1, current version is version1

接下来,进行一次巨大的进化

version() -> version2.

在 VM 外部编译模块并返回到正在运行的应用程序

5> % compile outside version 2
timeout, state is version1, current version is version1
5> P! message1.               
receive message message1
---> local call state is version1, current version is version1
message1
6> P ! reload.                
external call state is version1, current version is version1
reload
7> P! message1.               
receive message message1
---> local call state is version1, current version is version1
message1
timeout, state is version1, current version is version1

什么都没发生,模块没有自动加载,让我们在VM中加载模块

8> % load new version
timeout, state is version1, current version is version1
8> l(modtest).
{module,modtest}
9> P! message1.      
receive message message1
---> local call state is version1, current version is version1
message1
10> P ! reload.       
external call state is version1, current version is version1
reload
11> P! message1.
receive message message1
---> local call state is version1, current version is version2
message1
12> P! message1.
receive message message1
---> local call state is version2, current version is version2
message1
timeout, state is version2, current version is version2
13> P! stop.   
stopped
stop
14>

很好,新代码在模块中第一次“完全合格”调用后已更新,不幸的是,您无法控制何时考虑新代码。在示例中,即使有重新加载功能,新代码也会在下一个循环中使用,如果需要对状态数据进行任何修改,则为时已晚。下一个代码使用中间完全限定调用以允许修改状态数据。

-module (modtest).

-export ([init/0,loop/1,code_change/1]).

init() ->
    spawn(?MODULE, loop, [version()]).

loop(Version) ->
    receive
        reload ->
            NewVersion = ?MODULE:code_change(Version),
            io:format("external call state is ~p, current version is ~p~n",[Version,NewVersion]),
            ?MODULE:loop(NewVersion);
        stop ->
            io:format("stopped~n");
        Message ->
            io:format("receive message ~p~n---> local call state is ~p, current version is ~p~n",[Message,Version,version()]),
            loop(version())
    after 10000 ->
        io:format("timeout, state is ~p, current version is ~p~n",[Version,version()]),
        loop(version())
    end.

version() -> version3.

code_change(Version) ->
    io:format("it is possible here to do any action on the state: ~p before the code change is completed~n",[Version]),
    % It is possible to have different adaptation depending on the current version
    version().

在虚拟机中检查这个新版本

1> c(modtest).
{ok,modtest}
2> P = modtest:init().
<0.66.0>
3> P ! message.
receive message message
---> local call state is version3, current version is version3
message
4> P ! message.
receive message message
---> local call state is version3, current version is version3
message
5> P ! reload.
it is possible here to do any action on the state: version3 before the code change is completed
reload
external call state is version3, current version is version3
6> P ! reload.
it is possible here to do any action on the state: version3 before the code change is completed
reload
external call state is version3, current version is version3
timeout, state is version3, current version is version3
7> % new version

做一个新版本

...
version() -> version4.
...

并返回虚拟机

7> c(modtest).        
{ok,modtest}
timeout, state is version3, current version is version3
8> P ! message.
receive message message
---> local call state is version3, current version is version3
message
9> P ! message.
receive message message
---> local call state is version3, current version is version3
message
10> P ! reload.  
it is possible here to do any action on the state: version3 before the code change is completed
reload
external call state is version3, current version is version4
11> P ! message.
receive message message
---> local call state is version4, current version is version4
message
12> P ! stop.   
stopped
stop
13>

很好,它按预期工作。但是仍然有一个巨大的限制,“服务器”不能使用任何其他完全限定的调用,否则无法保证在加载新代码后立即调用函数 code_change。

这是 OTP 在版本升级或降级时带来的行为(参见release_handling )。

于 2018-03-28T12:46:53.373 回答