【问题标题】:Achieving code swapping in Erlang's gen_server在 Erlang 的 gen_server 中实现代码交换
【发布时间】:2010-12-22 21:07:45
【问题描述】:

我希望在 gen_server 上使用 Erlang 的热代码交换功能,这样我就不必重新启动它。我该怎么做?当我搜索时,我只能找到一篇文章,其中提到我需要使用gen_server:code_change 回调。

但是,我真的找不到任何关于如何使用它的文档/示例。非常感谢任何帮助或资源链接!

【问题讨论】:

    标签: erlang erlang-otp hotswap


    【解决方案1】:

    正如我已经提到的,正常的升级方式是创建正确的 .appup 和 .relup 文件,然后让 release_handler 完成需要完成的工作。但是,您可以手动执行所涉及的步骤,如此处所述。抱歉回答太长了。

    以下虚拟 gen_server 实现了一个计数器。旧版本(“0”)只是将整数存储为状态,而新版本(“1”)将 {tschak, Int} 存储为状态。正如我所说,这是一个虚拟的例子。

    z.erl(旧):

    -module(z).
    -version("0").
    
    -export([start_link/0, boing/0]).
    
    -behavior(gen_server).
    -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
    
    start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]). 
    
    boing() -> gen_server:call(?MODULE, boom).
    
    
    init([]) -> {ok, 0}.
    
    handle_call(boom, _From, Num) -> {reply, Num, Num+1};
    handle_call(_Call, _From, State) -> {noreply, State}.
    
    handle_cast(_Cast, State) -> {noreply, State}.
    
    handle_info(_Info, State) -> {noreply, State}.
    
    terminate(_Reason, _State) -> ok.
    
    code_change(_OldVsn, State, _Extra) -> {ok, State}.
    

    z.erl(新):

    -module(z).
    -version("1").
    
    -export([start_link/0, boing/0]).
    
    -behavior(gen_server).
    -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
    
    start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], [{debug, [trace]}]).
    
    boing() -> gen_server:call(?MODULE, boom).
    
    
    init([]) -> {ok, {tschak, 0}}.
    
    handle_call(boom, _From, {tschak, Num}) -> {reply, Num, {tschak, Num+1}};
    handle_call(_Call, _From, State) -> {noreply, State}.
    
    handle_cast(_Cast, State) -> {noreply, State}.
    
    handle_info(_Info, State) -> {noreply, State}.
    
    terminate(_Reason, _State) -> ok.
    
    code_change("0", Num, _Extra) -> {ok, {tschak, Num}}.
    

    启动 shell,并编译旧代码。请注意,gen_server 以调试跟踪启动。

    1> c(z).
    {ok,z}
    2> z:start_link().
    {ok,<0.38.0>}
    3> z:boing().
    *DBG* z got call boom from <0.31.0>
    *DBG* z sent 0 to <0.31.0>, new state 1
    0
    4> z:boing().
    *DBG* z got call boom from <0.31.0>
    *DBG* z sent 1 to <0.31.0>, new state 2
    1
    

    按预期工作:返回 Int,新状态为 Int+1。

    现在将 z.erl 替换为新的,并执行以下步骤。

    5> compile:file(z).
    {ok,z}
    6> sys:suspend(z).
    ok
    7> code:purge(z).
    false
    8> code:load_file(z).
    {module,z}
    9> sys:change_code(z,z,"0",[]).
    ok
    10> sys:resume(z).
    ok
    

    你刚刚做了什么:5:编译了新代码。 6:暂停服务器。 7:清除旧代码(以防万一)。 8:加载新代码。 9:在进程“z”中为模块“z”从版本“0”调用代码更改,其中 [] 作为“Extra”传递给 code_change。 10:恢复服务器。

    现在,如果您运行更多测试,您可以看到,服务器使用新的状态格式:

    11> z:boing().
    *DBG* z got call boom from <0.31.0>
    *DBG* z sent 2 to <0.31.0>, new state {tschak,3}
    2
    12> z:boing().
    *DBG* z got call boom from <0.31.0>
    *DBG* z sent 3 to <0.31.0>, new state {tschak,4}
    3
    

    【讨论】:

    • 在 z.erl 版本 1 中,init 应该返回 {ok, {tschak, 0}} 作为初始状态。
    • 如果虚拟机在加载后使用新版本,为什么还要打电话给code:purge
    • 呃。为什么 sys:change_code 需要停止进程?如果我只是将 ?MODULE:loop(而不是使用 gen_server)放在我的代码中的某个地方,那就没有必要了......
    【解决方案2】:

    您不需要在 gen_server 行为中使用该回调。如果您在代码升级过程中更改状态的内部表示,它就会出现。

    你只需要加载新模块,运行旧版本的gen_server会升级,因为它调用了新模块。只是如果有必要,您没有机会更改表示。

    【讨论】:

    • 你说的“加载新模块”是指我只是重新编译使用genserver的模块,运行的服务器会自动升级吗?
    • 如果你从 Erlang shell 编译它应该会发生这种情况(比如用 c())。否则使用 code:load_file/2 或 code:load_binary/2 来获得类似的效果。
    • 如果您加载新版本的模块而不暂停 gen_server 进程,那么下次运行回调时,它将使用新代码和旧状态运行。对回调模块的所有调用都是外部的,因此始终使用最新加载的模块版本。因此暂停、加载、更改代码、恢复升级过程。没有正在进行的魔法代码升级事件订阅。
    • @archaelus Ugg... 所以,如果我做对了,如果我没有使用 gen_server,那么我就不需要暂停,因为这样我就可以将所有呼叫保持在本地(除了 ?MODULE:code_change)。与“幼稚”的方式相比,这个 gen_server 代码更改看起来超级不酷>:(
    • 是的 - 这种交互有点微妙而且不是真正的想法,但是使用 gen_server 框架的好处远远超过了这个麻烦。我强烈建议使用 gen_server 并处理挂起、加载、change_code、恢复周期。 (仅当您更改#state{} 时才需要 - 如果您没有更改#state{},您可以只加载)
    【解决方案3】:

    最简单的方法是替换 .beam 文件并在 shell 中运行 l(my_server_module).。这绕过了code_change 函数,因此要求状态的表示没有改变。

    如前所述,正确的做法是使用 appup 和 relup 脚本创建一个新版本。然后使用release_handler 安装此新版本。

    【讨论】:

      【解决方案4】:

      如果您想以正确的方式(强烈推荐)进行操作,那么您需要阅读有关 OTP 主管和应用程序的使用。

      您可能比在这里阅读 OTP 设计原则用户指南更糟糕:

      http://www.erlang.org/doc/design_principles/users_guide.html

      【讨论】:

      • 谢谢,我确实阅读了不同的 OTP 行为,但我找不到任何与此相关的信息。
      【解决方案5】:

      如果你在 rebar3 上,一些手动处理已经自动化了(即 appup 和 relup 生成),你可以在这里找到更多信息:http://lrascao.github.io/automatic-release-upgrades-in-erlang/

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-02-27
        • 2016-07-26
        • 2011-10-08
        • 2011-08-09
        • 2014-11-09
        • 1970-01-01
        • 2011-08-15
        • 2016-07-10
        相关资源
        最近更新 更多