Erlang热门代码交换如何在活动中间工作?

时间:2016-05-21 21:52:27

标签: erlang otp

我目前正在开发一个实时媒体服务器,这将允许一般消费者向我们发送实时视频。在我们目前的环境中,我们已经看到了在几天内发送给我们的广播,因此能够在不断开用户连接的情况下修复错误(或添加功能)的想法非常引人注目。

然而,当我编写代码时,我意识到热代码交换没有任何意义,除非我编写每个进程以便所有状态总是在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系统时,您可以通过将模块作为外部函数调用升级到当前代码版本,因此{{ 1}}。

我还看到,当执行热交换时,调用my_module:loop(state)函数并升级状态,因此我可以使用它来确保我的每个依赖模块迁移它们给我的最后状态当前代码版本的状态。这样做是因为主管知道正在运行的进程,它允许暂停进程,因此可以调用代码更改功能。一切都好。

但是,如果调用外部模块总是调用该模块的当前版本,那么如果在函数中间执行热交换,这似乎会中断。例如,我的gen_server当前正在处理code_change/3演员表,例如在any_messagemodule1:do_something()之间。

如果我理解正确,module2:do_something()现在会调用module2:do_something()函数的新当前版本,这可能意味着我将未迁移的数据传递到新版本的{ {1}}。如果它是已更改的记录,具有意外数量的元素的数组,或者即使映射缺少代码所需的值,也很容易导致问题。

我误解了这种情况是如何运作的吗?如果这是正确的,这似乎表明我必须跟踪可能转换模块边界的任何数据结构的某些类型的版本详细信息,并且每个公共函数必须检查该版本号并在必要时执行按需迁移。

这似乎是一个非常高的订单,似乎很容易出错,所以我想知道我是否遗漏了什么。

2 个答案:

答案 0 :(得分:7)

是的,你是完全正确的。没有人说热代码交换很容易。我曾在一家电信公司工作,所有代码升级都是在现场系统上进行的(这样用户在通话过程中就不会断开连接)。正确的做法意味着仔细考虑您提到的所有场景,并为每次失败准备代码,然后测试,然后修复问题,测试等等。要正确测试它,您需要一个在负载下运行旧版本的系统(例如在测试环境中),然后部署新代码并检查是否有任何崩溃。

在您的问题中提到的这个特定示例中,处理此问题的最简单方法是编写两个版本的module2:do_something/1,一个接受旧状态,一个接受新状态。然后相应地处理旧状态,例如将其转换为新状态。

为此,您还需要确保在任何模块有机会使用新状态调用它之前部署新版本的module2

  1. 如果包含module2的应用程序是其他应用程序release_handler的依赖项,则会首先升级该模块。

  2. 否则,您可能需要将部署拆分为两部分,首先升级公共函数以便它们可以处理新状态,然后部署新版本的gen_servers以及其他调用的模块module2

  3. 如果您没有使用发布处理程序,您可以手动指定模块的加载顺序。

  4. 这也是Erlang中模块之间函数调用advised to avoid circular dependencies的原因,例如当modA调用modB中调用modA中的其他函数的函数时。

    对于在发布处理程序的帮助下执行的升级,您可以验证release_handler将基于release_handler生成的relup文件中旧系统上升级模块的顺序old and new release。这是一个包含升级所有说明的文本文件,例如:remove(删除模块),load_object_code(加载新模块),loadpurge等。< / p>

    请注意,没有严格要求所有应用程序必须遵循OTP原则才能使热代码交换工作,但是使用gen_server和正确的supervisor堆栈可以使此任务更容易处理开发人员和发布处理程序。

    如果您未使用OTP版本,则无法使用版本处理程序进行升级,但仍可以强制重新加载系统上的模块并将其升级到新版本。只要您不需要添加/删除Erlang应用程序,这就可以正常工作,因为发布定义需要更改,而且如果没有发布处理程序的支持,则无法在实时系统上完成。

答案 1 :(得分:3)

发布处理调用sys:suspend,它向gen_server发送消息。服务器将继续处理请求,直到它处理挂起消息,此时它基本上只是坐着等待。然后将新模块版本加载到系统中,调用sys:change_code,告诉服务器调用code_change回调进行升级,然后服务器再次坐下等待。当发布处理程序调用{​​{1}}时,它会向服务器发送一条消息,告知它重新开始工作并再次开始处理传入的消息。

发布处理同时为依赖于模块的所有服务器执行此操作。所以首先所有的都被暂停,然后加载新模块,然后告诉所有人升级自己,最后告诉所有人恢复工作。