Erlang OTP 之 Application

235 阅读9分钟

一名Erlang初学者,文章是看了官方文档后的总结,官方文档地址:www.erlang.org/doc/system/…

1️⃣介绍

在 OTP 中,application表示实现某些特定功能的组件,可以作为一个单元启动和停止,并且可以在其他erts中重用

start(StartType, StartArgs) ⭐

start/2 在启动应用程序时调用,是通过启动顶级supervisor来创建监督树。预计返回顶级supervisor的 pid 和可选术语 State ,默认为 [] 。 StartType:   通常为normal, 仅在接管或故障转移的情况下才具有其他值  normal: 以正常方式启动 {failover, Node} : 故障转移 如果运行应用程序的节点出现故障,则应用程序将在 distributed 配置参数的节点列表中列出的第一个操作节点重新启动(在指定的超时后)。这称为故障转移。 {takeover, Node}: 接管 如果启动的节点根据 distributed 比运行分布式应用程序的节点具有更高的优先级,则应用程序将在新节点重新启动并在旧节点停止。这称为接管。

StartArgs: 由.app中的键 mod 定义。

start_phase(Phase, StartType, PhaseArgs)

 start_phase(Phase, StartType, PhaseArgs) -> ok | {error, Reason}

当启动期间不同应用程序中的进程之间需要同步时,启动包含应用程序的应用程序。

prep_stop(State)

prep_stop(State) -> NewState when State :: term(), NewState :: term().

当应用程序即将停止时,在关闭应用程序的进程之前,将调用此函数。 该功能是可选的。如果未定义,则进程将终止,然后调用 Module:stop(State) 。

stop(State)

在应用程序停止后调用,用于进行任何必要的清理。

config_change(Changed, New, Removed)

config_change(Changed, New, Removed) -> ok
                           when
                               Changed :: [{Par, Val}],
                               New :: [{Par, Val}],
                               Removed :: [Par],
                               Par :: atom(),
                               Val :: term().
  • 如果配置参数已更改,则在代码替换后应用程序将调用此函数。
  • Changed 是参数值元组的列表,包括具有更改值的所有配置参数。
  • New 是参数值元组的列表,包括所有添加的配置参数。
  • Removed 是所有已删除参数的列表。

📑 .app

{application, Application,
  [{description,  Description},
   {id,           Id},
   {vsn,          Vsn},
   {modules,      Modules},
   {maxP,         MaxP},
   {maxT,         MaxT},
   {registered,   Names},
   {included_applications, Apps},
   {applications, Apps},
   {env,          Env},
   {mod,          Start},
   {start_phases, Phases},
   {runtime_dependencies, RTDeps}]}.



             Value                Default
             -----                -------
Application  atom()               -
Description  string()             ""
Id           string()             ""
Vsn          string()             ""
Modules      [Module]             []
MaxP         int()                infinity
MaxT         int()                infinity
Names        [Name]               []
Apps         [App]                []
Env          [{Par,Val}]          []
Start        {Module,StartArgs}   []
Phases       [{Phase,PhaseArgs}]  undefined
RTDeps       [ApplicationVersion] []

Module = Name = App = Par = Phase = atom()
Val = StartArgs = PhaseArgs = term()
ApplicationVersion = string()


  • description - 注释

  • id - app的标识

  • vsn  - 应用程序的版本。

  • modules -此应用程序引入的所有模块。一个模块只能在一个应用程序中定义。

  • maxP  - 已弃用 - 被忽略 应用程序中允许的最大进程数。

  • maxT  - 允许应用程序运行的最长时间(以毫秒为单位)。指定时间后,应用程序将自动终止。

  • registered  - 在此应用程序中启动的已注册进程的所有名称。

  • included_applications - 此应用程序包含的所有应用程序。当此应用程序启动时,所有包含的应用程序都会由应用程序控制器自动加载,但不会启动。假设所包含的应用程序的最顶层管理程序是由该应用程序的管理程序启动的。

  • applications - 必须在此应用程序之前启动的所有应用程序。所有应用程序至少都依赖于Kernel和 STDLIB

  • env - 指定的 {Par,Val} 元组列表
    Par 是一个原子。  Val 是 term() 应用程序可以通过调用 application:get_env(App, Par) 来获取参数。 ***Note: .app 文件中的env可以被.config配置文件中的值覆盖

  • mod - 指定application回调模块和启动参数

  • start_phases -  应用程序的启动阶段和相应的启动参数的列表。如果存在此键,则应用程序主机除了通常调用 Module:start/2 之外,还会为键 start_phases 定义的每个启动阶段调用 Module:start_phase(Phase,Type,PhaseArgs)

  • runtime_dependencies - 应用程序所依赖的应用程序版本列表。此类应用程序版本的一个示例是 "kernel-3.0" 。指定为运行时依赖项的应用程序版本是最低要求。

🌲 Included application

介绍:

  • 一个application可以包括其他application, 被包含的application,被称为included applicationincluded application有自己的应用程序目录和 .app 文件。
  • 没有被任何application包含的应用成为 primary application
  • 一个应用程序只能被另一个应用程序包含。
  • 在启动primary application时,会加载子应用和回调 start_phase, 但不会启动included application,需要主动去启动子应用

定义:

  • 在.app中使用定义: {included_applications, [App]},
  • 有包含其他应用时必须在.app中这样定义回调模块: {mod, {application_starter, [Mod, [ ]]}}
  • 必须包含 {start_phases, [{Phase,PhaseArgs}]} 选项,并且指定的start_phases必须是为主应用程序指定的start_phases的子集。 定义start_phases的目的是包含和包含的应用程序中的进程之间需要同步

启动

在启动primary application时, 启动顺序如下:

application:start(prim_app)
 => prim_app_cb:start(normal, [])
 => prim_app_cb:start_phase(init, normal, [])
 => prim_app_cb:start_phase(go, normal, [])
 => incl_app_cb:start_phase(go, normal, [])
ok

⚖️ Distributed Applications

介绍

在具有多个 Erlang 节点的分布式系统中,可能需要以分布式方式控制应用程序。如果运行某个应用程序的节点发生故障,则该应用程序将在另一个节点上重新启动。 这样的应用程序称为Distributed Application。请注意,这是分布式应用程序的控制。所有应用程序都可以是分布式的,例如,它们使用其他节点上的服务。

定义

分布式应用程序是通过使用 .config 配置内核应用程序来指定的

[{kernel,
	  [{distributed, [{Application, Timeout, NodeDesc}],
	   {sync_nodes_mandatory, [Node]},
	   {sync_nodes_optional,  [Node]},
	   {sync_nodes_timeout,  Time}
	  }]
 }]

distributed:

  • Application = atom() : 指定application
  • Timeout = integer() : 指定在另一个节点重新启动应用程序要等待的毫秒数。默认为 0
  • NodeDesc = [Node | {Node,...,Node}] : 对应的分布式节点 第一个Node代表运行app的节点

sync_nodes_mandatory

  • Node = [Node] : 指定必须启动哪些其他节点 (在 sync_nodes_timeout 指定的超时内)

sync_nodes_optional

  • Node = [Node] : 指定可以启动哪些其他节点 (在 sync_nodes_timeout 指定的超时内)

sync_nodes_timeout

  • Time = integer() | infinity : 指定等待节点启动的毫秒数。

as:


[{kernel,
  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
   {sync_nodes_timeout, 5000}
  ]
 }
].

启动

当所有涉及的(强制)节点都已启动时,可以通过在所有这些节点上调用 application:start(Application) 来启动分布式应用程序。

Failover故障转移

介绍

如果运行应用程序的节点出现故障,则应用程序将在 distributed 配置参数的节点列表中列出的第一个操作节点重新启动(在指定的超时后), 这称为故障转移。

通过调用star回调来启动应用程序:这里 Node 是终止的节点。 这时 StartType将变成 : {failover, Node}

Module:start({failover, Node}, StartArgs)

如果 cp1 出现故障,系统会检查其他节点 cp2 或 cp3 中哪一个节点正在运行的应用程序数量最少,但会等待 5 秒 cp1 重新启动。如果 cp1 未重新启动,并且 cp2 运行的应用程序少于 cp3 ,则 myapp 将在 cp2 上重新启动。现在假设 cp2 也出现故障并且在 5 秒内没有重新启动。 myapp 现已在 cp3 上重新启动。

Takeover接管

介绍

如果启动的节点根据 distributed 比运行分布式应用程序的节点具有更高的优先级,则应用程序将在新节点重新启动并在旧节点停止, 这称为takeover

通过调用star回调来启动应用程序:这里 Node 是终止的节点。 这时 StartType将变成 : {takeover, Node}

Module:start({takeover, Node}, StartArgs)

例子: 如果 myapp 正在 cp3 运行,并且 cp2 现在重新启动,则它不会重新启动 myapp ,因为 cp2 和 cp3 节点未定义。

如果 cp1 也重新启动,则可以使用函数 application:takeover/2myapp 移动到 cp1 ,因为 cp1 具有更高的此应用程序的优先级高于 cp3 。 在本例中, Module:start({takeover, cp3@cave}, StartArgs) 在 cp1 处执行以启动应用程序。

🖇️API

permit

-spec permit(Application, Permission) -> ok | {error, Reason}
  when Application :: atom(), 
	   Permission :: boolean(), 
	   Reason :: term().

  • 更改 Application 在当前节点运行的权限。必须使用 load/1,2 加载应用程序才能使该功能生效。

  • 如果已加载但未启动的应用程序的权限设置为 false ,则 start 返回 ok 但应用程序不会启动,直到权限设置为 true 。

  • 如果正在运行的应用程序的权限设置为 false ,则应用程序将停止。如果稍后将权限设置为 true ,则会重新启动。

  • 如果应用程序是分布式的,则将权限设置为 false 意味着应用程序将根据其分布的配置方式在另一个节点上启动或移动到另一个节点.

  • 默认情况下,所有应用程序在所有节点上都加载有权限 true 。

takeover

-spec takeover(Application, Type) -> ok | {error, Reason}
when Application :: atom(), 
	 Type :: restart_type(), 
	 Reason :: term().

接管分布式应用程序 Application ,该应用程序在另一个节点 Node 上运行。

 application:takeover(mapp, temporary).

start

-spec start(Application, Type) -> ok | {error, Reason}               
when Application :: atom(), 
	 Type :: restart_type(), 
	 Reason :: term().

Type有三种类型:

  • permanent : 如果应用程序终止,所有其他应用程序和整个 Erlang 节点也会终止。
  • transient : 对于 Reason == normal ,会报告此情况,但不会终止其他应用程序。 异常情况下,所有其他应用程序和整个 Erlang 节点也会终止。
  • temporary : 如果临时应用程序终止,则会报告此情况,但不会终止其他应用程序。

Note:

  • 请注意,应用程序始终可以通过调用 stop/1 显式停止。无论应用程序的类型如何,其他应用程序都不会受到影响。
  •  transient几乎没有实际用途,因为当监督树终止时,原因被设置为 shutdown ,而不是 normal 。

ensure_all_started

(since OTP 26.0)

-spec ensure_all_started(Applications, Type, Mode) -> {ok, Started} |                                                       {error, AppReason}
when
	Applications :: atom() | [atom()],
	Type :: restart_type(),
	Mode :: serial | concurrent,
	Started :: [atom()],
	AppReason :: {atom(), term()}.

Mod :

  • concurrent : 在并发模式下,构建依赖图并同时递归地启动图的叶子。
  • serial : 一次一个逐渐启动

demo 🧪

application:ensure_all_started([mapp,incl,incl2], transient, concurrent).