在 Erlang 的 gen_server 中实现代码交换

Posted

技术标签:

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

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

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

【问题讨论】:

【参考方案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/

【讨论】:

以上是关于在 Erlang 的 gen_server 中实现代码交换的主要内容,如果未能解决你的问题,请参考以下文章

Erlang 源码分析之 Gen_Server

何时在 Erlang/OTP 应用程序中使用 gen_server

Erlang:如何在主管中正确调度带有 start_child 的 gen_server 并调用 API

Erlang:无法创建 gen_server:call()

Erlang,尝试制作 gen_server: 调用有很多响应

难以理解 Erlang Gen_Server 架构