【问题标题】:How does Erlang hot code swapping work in the middle of activity?Erlang 代码热交换在活动过程中是如何工作的?
【发布时间】: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,2sys:change_code/4,5sys:resume/1,2 来暂停、升级然后恢复进程。
  • 这并没有真正解释暂停和恢复如何与gen_servercode_change 回调一起使用,也没有解释如果释放处理程序在调用/转换中暂停进程时管道如何进行.期望sys:code_change 将触发my_gen_server:code_change 似乎是非常不合理的,这将返回更新状态,该状态将正确应用于暂停期间正在进行的handle_cast 调用。这似乎太神奇了(尤其是在不变性的概念下)以至于不能干净。

标签: erlang erlang-otp


【解决方案1】:

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

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

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

  1. 如果包含module2 的应用程序是另一个应用程序release_handler 的依赖项,将首先升级该模块。

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

这也是为什么在 Erlang 中它在模块之间的函数调用中是 advised to avoid circular dependencies 的原因,例如当modA 调用modB 中的一个函数时,该函数又调用modA 中的另一个函数。

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

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

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

【讨论】:

    【解决方案2】:

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

    发布处理同时对依赖于模块的所有服务器执行此操作。所以首先全部挂起,然后加载新模块,然后全部被告知升级自己,最后全部被告知恢复工作。

    【讨论】:

      猜你喜欢
      • 2011-02-27
      • 2016-07-10
      • 2014-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-22
      • 1970-01-01
      相关资源
      最近更新 更多