在 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/OTP 应用程序中使用 gen_server
Erlang:如何在主管中正确调度带有 start_child 的 gen_server 并调用 API