【发布时间】:2016-09-18 23:58:45
【问题描述】:
我目前正在开发一个实时媒体服务器,它将允许普通消费者向我们发送实时视频。在我们当前的环境中,我们已经看到发送给我们的广播持续了几天,因此能够在不断开用户连接的情况下修复错误(或添加功能)的想法非常引人注目。
然而,当我编写代码时,我意识到热代码交换没有任何意义,除非我编写每个进程以便所有状态始终在 gen_server 内完成,并且 gen_server 调用的所有外部模块必须尽可能简单.
我们来看下面的例子:
-module(server_template).
-behaviour(gen_server).
-export([start/1, stop/0]).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
init([]) -> {ok, {module1:new(), module2:new()}}.
handle_call(Message, From, State) -> {reply, ok, State}.
handle_cast(any_message, {state1, state2}) ->
new_state1 = module1:do_something(state1),
new_state2 = module2:do_something(state2),
{noreply, {new_state1, new_state2}}.
handle_info(_Message, _Server) -> {noreply, _Server}.
terminate(_Reason, _Server) -> ok.
code_change(_OldVersion, {state1, state2}, _Extra) ->
new_state1 = module1:code_change(state1),
new_state2 = module2:code_change(state2)
{ok, {new_state1, new_state2}}
据我所知,当一个新版本的代码被加载到当前运行的运行时而不使用 OTP 系统时,你可以通过调用你的模块作为外部函数调用来升级到当前的代码版本,所以@987654323 @。
我还看到,当执行热交换时,code_change/3 函数被调用并升级状态,所以我可以使用它来确保我的每个依赖模块都将它们给我的最后一个状态迁移到状态当前代码版本。这样做是因为主管知道正在运行的进程,这允许进程暂停,以便它可以调用代码更改函数。都很好。
但是,如果调用外部模块总是调用该模块的当前版本,那么如果在功能中间完成热交换,这似乎会中断。例如,我的 gen_server 当前正在处理any_message 演员表,比如在运行module1:do_something() 和module2:do_something() 之间。
如果我理解正确,module2:do_something() 现在将调用最新版本的do_something 函数,这可能意味着我将未迁移的数据传递到新版本的module2:do_something() 中。如果它是一个已更改的记录、具有意外数量的元素的数组,或者即使地图缺少代码预期的值,这很容易导致问题。
我是否误解了这种情况的运作方式?如果这是正确的,这似乎表明我必须跟踪可能转换模块边界的任何数据结构的某种类型的版本详细信息,并且每个公共函数都必须检查该版本号并在必要时执行按需迁移。
这似乎是一个非常艰巨的任务,似乎很容易出错,所以我想知道我是否遗漏了什么。
【问题讨论】:
-
你说得对。 OTP 使热代码升级更加可控。它暂停执行符合 OTP 的代码,加载新版本,调用
code_change/3,然后继续工作。它是更受控制的热代码升级。另一件事是,如果发生崩溃,OTP 允许您非常快速地重新启动它,因此如果您的module2:do_something/1因新数据格式而崩溃,它可以恢复。这些东西齐头并进,并且仍然比任何其他运行时环境更简单、更健壮一个数量级。 -
实际上,OTP 流程的暂停对我来说不是很清楚。我假设它在任何正在进行的
handle_call/handle_cast调用正在进行后暂停该进程,否则它将无法使用迁移状态,对吗? -
这里解释(在更新小节中):erlang.org/doc/design_principles/release_handling.html#id78465Release Handler 使用
sys:suspend/1,2、sys:change_code/4,5和sys:resume/1,2来暂停、升级然后恢复进程。 -
这并没有真正解释暂停和恢复如何与
gen_server的code_change回调一起使用,也没有解释如果释放处理程序在调用/转换中暂停进程时管道如何进行.期望sys:code_change将触发my_gen_server:code_change似乎是非常不合理的,这将返回更新状态,该状态将正确应用于暂停期间正在进行的handle_cast调用。这似乎太神奇了(尤其是在不变性的概念下)以至于不能干净。
标签: erlang erlang-otp