【问题标题】:Erlang/elixir: Difference between module redefine and hot code swapErlang/elixir:模块重新定义和热代码交换之间的区别
【发布时间】:2016-07-10 09:35:42
【问题描述】:

代码交换:Achieving code swapping in Erlang's gen_server

模块重新定义:

iex(node2@127.0.0.1)6> Code.load_file("mesngr.ex", "./lib")
[{Mesngr,
  <<70, 79, 82, 49, 0, 0, 12, 72, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 255, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 4, 104, 2, 100, 0, ...>>}]
iex(node2@127.0.0.1)8> Code.load_file("mesngr.ex", "./lib")
lib/mesngr.ex:1: warning: redefining module Mesngr
[{Mesngr,
  <<70, 79, 82, 49, 0, 0, 12, 72, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 0, 255, 131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115, 95, 118, 49, 108, 0, 0, 0, 4, 104, 2, 100, 0, ...>>}]
iex(node2@127.0.0.1)9>

我已经注意到某些差异,例如在模块重新定义的情况下不会调用 GenServer 的 code_change 回调(因为我假设只是覆盖,而不是从新 -> 当前和当前 -> 旧的转换)。但我也注意到像这样重新定义模块确实会改变底层代码(这在 FP 语言中是有意义的)。

我想我的问题归结为以下几点:

  1. 在开发和生产期间简单地重新定义模块会有多好/坏/丑?
  2. 在版本管理和回滚之外进行适当的代码交换有什么好处?是否有关于该主题的优秀教程/手册/文章?
  3. 与模块重新定义相比,热代码交换如何在后台工作?

【问题讨论】:

    标签: elixir


    【解决方案1】:

    在任何给定时间点,在 Erlang 中,您最多可以同时运行给定模块的两个版本。

    例子:

    • 加载一个模块
    • 使用代码启动 gen_server
    • 更改某些内容并加载模块' - 现在 gen_server 仍在运行旧代码
    • 启动第二个 gen_server - 它将运行模块中的新代码'
    • 再次更改某些内容并加载模块'' - 因为在任何时间点同一代码只能有两个版本,所以第一个模块被清除;所有运行它的进程都被杀死了,所以你的第一个 gen_server 被杀死了

    您可以通过从该模块调用完全限定的函数,从运行旧代码过渡到在进程内运行新代码。因此,与其调用function(Args),不如调用module:function(Args)。我不确定相同的机制是否适用于 Elixir。

    要手动进行代码热升级,您必须在您的第一个模块版本中具有此完全限定的函数调用。

    如果您更改了gen_server 中的状态表示,这可能会导致服务器崩溃。

    所以回答你的问题:

    1. 那会很糟糕。手动执行此操作意味着仅手动测试升级。它可能导致随机进程死亡、丢失状态,在极端情况下会丢失数据。

    2. 与 appups 和 relups 的热代码交换是完全自动化的。您不需要像第一个代码版本中的完全限定函数那样的“钩子”,因此当您需要进行代码热升级时,您可以开始担心它。代码更改将被正确调用,让您有机会将旧状态转换为新状态。如果您修改了多个进程,您可以定义在升级期间应该冻结的模块组。我认为这里有一个很好的教程:http://learnyousomeerlang.com/relups

    3. 热代码交换刚刚开始按使用先前模块定义的进程使用新模块定义。进行适当的 appup 和 relup 处理代码加载、交换、更改状态和清除旧内容。使其更具可测试性。

    热代码交换并不容易,在生产环境中,当您拥有分布式系统时,使用新代码启动全新实例可能会更好。负载均衡器可以将新连接推送到新实例,我们只需等待旧连接终止,然后再终止旧实例。

    但是,在嵌入式系统中,您只有一个实例并且需要在线 24/7 热代码交换可能是唯一的选择。

    【讨论】:

      【解决方案2】:

      “因此,您应该调用 Module.function(Args),而不是调用函数 (Args)。” @tkowal 提到的是,在 Elixir 中 imports 仍然是完全限定的调用,即使它们在 Elixir 中编写的文字源代码中看起来并不像它。

      defmodule Foo do
        import Bar, only: [baz: 0]
      
        def foo do
          # reloads Bar
          baz
        end
      end
      

      【讨论】:

      • 不完全是这个问题的答案,但真的很好知道。谢谢!
      • 是的,我不能在评论中做一个代码块,所以我不得不把它作为一个答案。
      猜你喜欢
      • 2016-07-10
      • 2011-02-27
      • 2016-08-31
      • 2017-04-14
      • 1970-01-01
      • 2014-10-30
      • 2011-06-13
      • 2016-06-01
      • 1970-01-01
      相关资源
      最近更新 更多