Erlang/OTP 与大多数其他编程环境不同,即使是那些也使用虚拟机的环境。Erlang 对应用程序的结构、隔离级别以及 Erlang 虚拟机可以做什么与您的软件可以做什么之间的分离有着强烈的意见。它不仅仅是一种编程语言,而是一个构建系统的完整框架。理解其核心原则是在快速入门的同时避免后期重写代码的关键:它确保所有应用程序都能很好地协同工作,更新可以实时进行,并且您的代码易于检测和观察。
在本章中,我们将介绍 Erlang 虚拟机和 OTP 在最高级别上的核心概念。
Erlang 运行时系统
所有事物的基础块是 Erlang 虚拟机本身,称为 BEAM。从技术上讲,BEAM 是 Erlang 虚拟机的单个实现,因为可能存在其他实现。例如,Erllvm 是 LLVM 上的实现(使用一些自定义补丁来使一切成为可能),而 90 年代的旧实现称为 JAM。Erlang 虚拟机是用 C 实现的,并且包含许多高级功能:运行进程的调度程序、垃圾回收、内存分配器、用于事件的定时器轮、用于抽象操作系统功能并提供统一接口的一系列智能开关(例如,在时间管理、文件处理驱动程序等方面),一些内置函数比 Erlang 本身运行得更快(BIF)以及与其他语言中本地实现的函数的接口(NIF),以及用于它们的特殊调度程序。显然还有很多其他的东西,但您可以像对待 BSD 或 Linux 中的内核一样看待所有这些东西:构建更高级功能所需的低级内容。
如果您只有虚拟机而没有其他任何东西,则无法运行 Erlang 代码。您没有标准库,甚至没有加载代码的库。为了启动它,有一些我们不需要理解的棘手的引导过程。只需要知道虚拟机附带了一组有限的预加载 Erlang 模块,这些模块可用于设置网络和文件处理功能,而这些功能又用于进一步加载和运行模块。但是,如果您有兴趣了解更多信息,请查阅 BEAM 书籍 或 BEAM 智慧。
如果您采用虚拟机和预加载的内容,以及所有使代码加载成为可能的实用程序,那么您就拥有了基本上称为 Erlang 运行时系统 (ERTS) 的内容。运行时系统在启动时遵循称为 引导脚本 的内容(没有人手动编写)的指令,该指令指定要启动的内容。
Erlang 默认情况下提供引导脚本,这些脚本加载启动 shell 和编写您自己的应用程序所需的最小代码量。完成此操作后,我们可以开始考虑 Erlang,而不仅仅是虚拟机。
Erlang/OTP
到目前为止,我们描述的内容等同于操作系统的内核。现在我们需要用户空间组件的基础块。在 Erlang 中,这基本上就是 OTP 的意义所在。OTP 指定在虚拟机上运行的“组件”应如何构建。该语言不仅仅是“进程和消息”:它有一种定义明确的方法来构建您的代码。
注意
OTP 代表 _开放电信平台_,这是一个毫无意义的名称,在爱立信 Erlang 的早期用于使该软件开源。
Erlang/OTP 系统通过名为 OTP 应用 的组件进行构建。您安装的每个 Erlang 版本或您使用的基于它的系统都附带了一些 OTP 应用。OTP 应用基本上有两种变体:库应用,它只是模块的集合,以及 可运行应用,它包含模块的集合,但还指定了存储在监管树下的有状态进程结构。为了清楚起见,我们将对整本书中 OTP 应用使用以下术语
- 库应用:模块的无状态集合
- 可运行应用:启动有状态监管树结构并在其中运行进程的 OTP 应用
- OTP 应用:库 或 可运行应用,可互换使用
默认情况下,每个人都包含的两个 OTP 应用称为 stdlib
,它是一个包含核心标准库模块(如 list
或 maps
)的库应用,以及 kernel
,它是一个可运行的应用,并为依赖 OTP 应用工作的 Erlang 系统设置核心结构。
节点启动时,所有必需的 OTP 应用中的模块都会加载到内存中。然后启动 kernel
。从那时起,kernel
管理系统的生命周期。所有其他 OTP 应用及其配置都通过它进行处理,分布和热代码更新等独特功能也是如此。如果我们回到操作系统比较,您可以将 kernel
OTP 应用视为 Linux 内核的 systemd
(或者如果您不喜欢 systemd
或使用 BSD 则视为 init
- Windows 用户可以将其视为运行其他服务的服务)
实际上,kernel
和 stdlib
是基本工作 Erlang shell 所需的仅有的两个应用。当您输入 erl
(或在 Windows 上启动 werl
)时,这会启动虚拟机以及 kernel
,并预加载 stdlib
。其他所有内容都是可选的,可以在以后加载。
标准 Erlang 发行版包含以下应用
- kernel
- stdlib
- crypto(加密原语)
- ssl(TLS 终止库)
- inets(网络服务,如 FTP 或 HTTP 客户端)
- ct(通用测试框架)
- wx(图形工具包)
- observer(用于管理 Erlang 节点的控制面板,基于
wx
构建) - compiler(用于构建您自己的项目的 Erlang 编译器)
- 等等
所有这些都组合在一起,形成了所谓的 Erlang 发行版。发行版是 OTP 应用的集合,可能与虚拟机的完整副本捆绑在一起。因此,当您下载并安装 Erlang 时,您只需获得一个发行版,其名称类似于 Erlang/OTP-21.3.4。您可以自由构建自己的发行版,这将使用标准发行版中的一些 OTP 应用,然后将它们与您自己的某些应用捆绑在一起。
因此,如果我们要编写一个名为 proxy
的应用,该应用依赖于 ssh
和 ssl
(它们本身依赖于 public_key
、crypto
、stdlib
和 kernel
),我们将创建一个包含所有这些组件的发行版
- ERTS
- kernel
- stdlib
- crypto
- public_key
- ssl
- ssh
- proxy
可以在图 1 中看到此图的视觉表示。
从本质上讲,构建 Erlang 系统就是重新捆绑虚拟机以及默认发行版提供的一些标准应用,以及您自己的应用和库。
在 Erlang/OTP 中运行
社区开发和使用的标准工具(如 Rebar3)都基于这样的理念:您编写和发布的是 OTP 应用,因此包含处理它们所需的所有功能。这与许多仅要求您在其中一个文件中有一个名为 main()
的函数的编程语言有很大不同。这就是为什么编程语言通常称为 Erlang/OTP
而不是仅仅“Erlang”的原因:它不仅仅是一种编程语言,而是一个通用开发框架,它要求您对所做的一切都进行一些基本结构。
每个人都遵循它,无论他们是在编写嵌入式软件、区块链系统还是分布式数据库。要么使用 OTP,要么就无法运行。虽然其他语言通常不会强制执行任何特定的入门要求,但随后会在以后添加一些要求(例如,在与包管理器集成时),但 Erlang 及其整个社区都期望您编写 OTP 应用,其余工具可以处理这些应用。
因此,快速入门 Erlang 的关键是了解该框架,该框架通常被视为更高级的材料。在这里,我们将颠倒顺序进行操作,从一个功能齐全的发行版开始,然后深入研究其结构。接下来的章节将致力于理解如何在这些要求下工作。