2021 年你需要知道的关于 Erlang 的一切
- admin
- 07 Jan 2024
今天,我们将看一个相当古老且有些古怪的东西。 你们大多数人可能没有注意到的语言。 虽然 Erlang 不像某些现代编程语言那样流行,但它安静地运行着 WhatsApp 和微信等每天为大量用户提供服务的应用程序。 在这篇文章中,我将告诉你关于这门语言的更多事情、它的历史,以及你是否应该考虑自己学习它。 ## 什么是 Erlang,它在哪里使用? Erl
Read More在我对 rebar 的实验中,我制作了一个简单的示例应用程序来测试upgrades和releases。本文将引导您使用 rebar 创建应用程序、正确布局、打包和部署它,以及创建和安装新版本而无需停机。
本文随附的代码位于github.com/RJ/erlang_rebar_example_project的各个分支中。
注意:如果您想了解 Erlang 应用程序和版本的 OTP 方法概述,OTP 设计原则文档是一个很好的起点。但是,rebar(还)不是 OTP 的一部分,因此请考虑背景阅读。rebar使事情变得更容易。
构建rebar:
$ cd ~/src
$ git clone https://github.com/basho/rebar.git
Initialized empty Git repository in /tmp/rebar/.git/
remote: Counting objects: 2651, done.
remote: Compressing objects: 100% (1344/1344), done.
remote: Total 2651 (delta 1540), reused 2227 (delta 1174)
Receiving objects: 100% (2651/2651), 622.99 KiB | 495 KiB/s, done.
Resolving deltas: 100% (1540/1540), done.
$ cd rebar && make
...snip....
==> rebar (compile)
Congratulations! You now have a self-contained script called "rebar" in
your current working directory. Place this script anywhere in your path
and you can use rebar to build OTP-compliant apps.
现在我们将创建一个名为“dummy_proj”的项目目录,将 rebar 复制到其中,并使用 rebar 生成一个骨架应用程序:
$ mkdir -p ~/src/dummy_proj/apps
$ cd ~/src/dummy_proj/
$ cp ../rebar/rebar .
$ cd apps
$ ../rebar create-app appid=dummy_proj
==> dummy_proj (create-app)
Writing src/dummy_proj.app.src
Writing src/dummy_proj_app.erl
Writing src/dummy_proj_sup.erl
在骨架中,我添加了一个名为 dummy_proj_server 的基本 gen_server,它只是跟踪它被推动的次数,即它保持一些状态,用于演示目的。
我还将 dummy_proj_app.erl 重命名为 dummy_proj.erl,并添加了一个 start/0 函数,该函数在开发期间启动应用程序时很有用,而不是从生成的版本运行。
你需要一个 rebar.conf,把它放在顶层项目目录中:
{sub_dirs, [
"apps/dummy_proj",
"rel"
]}.
{erl_opts, [debug_info, fail_on_warning]}.
{require_otp_vsn, "R14"}.
现在你这样做,进行编译:
$ ./rebar compile
==> dummy_proj (compile)
Compiled src/dummy_proj_sup.erl
Compiled src/dummy_proj.erl
Compiled src/dummy_proj_server.erl
==> rel (compile)
==> dummy_proj (compile)
请注意,您现在在 apps/dummy_proj/ebin/ 中有 .beam 文件,并且 .app.src 为您生成了 apps/dummy_proj/ebin/dummy_proj.app 以及完整的模块列表。
注意:我做了一个简单的 Makefile 调用’rebar compile’,因为我太习惯于输入 make。可以在 git repo 中找到它。
以下是启动应用程序的方法(包含sasl,以便提供良好的错误报告):
$ erl -pa apps/*/ebin -boot start_sasl -s dummy_proj
...snip...
=INFO REPORT==== 16-Mar-2011::14:17:04 ===
Starting dummy_proj application...
=PROGRESS REPORT==== 16-Mar-2011::14:17:04 ===
supervisor: {local,dummy_proj_sup}
started: [{pid,<0.45.0>},
{name,dummy_proj_server},
{mfargs,{dummy_proj_server,start_link,[]} },
{restart_type,permanent},
{shutdown,5000},
{child_type,worker}]
=PROGRESS REPORT==== 16-Mar-2011::14:17:04 ===
application: dummy_proj
started_at: nonode@nohost
Eshell V5.8.1 (abort with ^G)
1> dummy_proj_server:num_pokes().
0
2> dummy_proj_server:poke().
{ok,1}
3> dummy_proj_server:poke().
{ok,2}
4> dummy_proj_server:num_pokes().
2
5>
现在你有一个很好的结构合理的 Erlang 项目,你可以用 rebar 编译它。使用 q() 退出 VM。让我们使用 rebar 将其打包,这样您就可以将其部署到生产环境上。
当您使用 rebar 生成版本时,实际上如果您手动使用 erlang 工具(不推荐,期望使用 rebar工具),您最终会将整个 Erlang VM 和所需的库打包在一个目录下。
这意味着您拥有一个包含 Erlang、您需要的 OTP 库以及所有应用程序代码和依赖项的自包含环境。您可以将其打包,将其传送到另一台机器(具有相同架构,例如 GNU/Linux 64 位),然后在那里运行它
使用 rebar 在 rel 子目录中创建默认节点配置:
$ mkdir rel
$ cd rel/
$ ../rebar create-node nodeid=dummynode
==> rel (create-node)
Writing reltool.config
Writing files/erl
Writing files/nodetool
Writing files/dummynode
Writing files/app.config
Writing files/vm.args
您需要稍微编辑一下 reltool.config;指向您的应用程序目录,并确保版本号与您的 .app.src 文件匹配。您还应该将 dummy_app 添加到作为发布的一部分启动的应用程序列表中。这是我的 v1 标签中的 reltool.conf
回到顶层目录,只需运行:
$ ./rebar generate
==> rel (generate)
现在看看 rel/dummynode。这是包含运行应用程序所需的所有内容的发布目录。
稍后我们将创建更多版本,因此将 rel/dummynode 重命名为 rel/dummynode_first,然后使用 rebar 为我们创建的便捷脚本启动它:
$ cd rel/dummynode_first
$ ./bin/dummynode console
...snip...
Erlang R14B (erts-5.8.1) [source] [64-bit] [smp:8:8] [rq:8] [async-threads:5] [hipe] [kernel-poll:true
=INFO REPORT==== 16-Mar-2011::13:29:59 ===
Starting dummy_proj application...
Eshell V5.8.1 (abort with ^G)
(dummynode@127.0.0.1)1>
(dummynode@127.0.0.1)1> dummy_proj_server:num_pokes().
0
(dummynode@127.0.0.1)2> dummy_proj_server:poke().
{ok,1}
(dummynode@127.0.0.1)3> dummy_proj_server:poke().
{ok,2}
(dummynode@127.0.0.1)4> dummy_proj_server:num_pokes().
2
(dummynode@127.0.0.1)5>
当前的版本正在运行,我们再也不想重新启动它,所以打开另一个控制台,因为我们希望在处理版本 2 时保持它运行。
在生产环境中,您可以从“./bin/dummynode start”开始,以便它在后台运行,然后使用“dummynode attach”来获取控制台。
check github 上的“v1 分支”以获取到目前为止的代码。
将 poke_twice() 函数添加到 dummy_proj_server。
在 apps/dummy_proj.app.src 和 rel/reltool.conf 中将版本从“1”更改为“2”。
这是 v1…v2 之间的 github差异
Erlang 应用程序版本号可以是任何字符串——我倾向于使用带有字母的日期格式:“20110316a”,但你可以使用任何你想要的方案。我在 git 中使用与 erlang 应用程序相同的版本标记发布。为简单起见,我们在这里只使用“1”、“2”、“3”。
注意:如果您在 .app.src 中通过{vsn, git}使用版本,rebar 将从最近的 git 标签中获取版本字符串。
现在构建新版本:
$ ./rebar compile
$ ./rebar generate
所以现在你有了 rel/dummy_proj,其中包含版本 2 的完整版本(包括 VM)。如果你不关心在线升级,你可以杀死你的版本 1 VM,并从这个新的发布目录启动版本 2。
为了进行升级,您必须拥有一个有效的 .appup 文件。这告诉 erlang release_handler 如何在应用程序的特定版本之间升级和降级。
Rebar 有一个(相对较新的)命令,称为“generate-appups”。我将展示它是如何工作的,但最终我们将手动编写我们的 .appup,并将其保存在我们的项目目录中(在 git 中)。
$ ./rebar generate-appups previous_release=dummynode_first
==> rel (generate-appups)
Generated appup for dummy_proj
Appup generation complete
$ cat ./rel/dummynode/lib/dummy_proj-2/ebin/dummy_proj.appup
%% appup generated for dummy_proj by rebar ("2011/03/16 13:37:43")¬
{"2", [{"1", [{update,dummy_proj_server,{advanced,[]}}]}], [{"1", []}]}.¬
摆脱自动生成的文件,并在apps/dummy_proj/ebin/dummy_proj.appup 中手动创建appup 文件:
{"2",
%% Upgrade instructions from 1 to 2
[{"1", [
{load_module, dummy_proj_server}
]}],
%% Downgrade instructions from 2 to 1
[{"1",[
{load_module, dummy_proj_server}
]}]
}.
此 .appup 包含在版本“2”和“1”之间升级和降级的说明。通常,降级说明与升级说明相反。由于我们只是在我们的服务器进程中添加了一个函数,没有改变任何内部状态,我们可以使用 load_module 指令。Appup Cookbook 深入解释了各种升级说明。
现在再次生成,覆盖之前的版本 2。这将确保 .appup 是发布目录的一部分:
$ ./rebar generate -f
现在,创建升级包:
$ ./rebar generate-upgrade previous_release=dummynode_first
==> rel (generate-upgrade)
dummynode_2 upgrade package created
generate-upgrade 命令将查找 rel/dummynode 作为当前版本,并查找 rel/dummynode_first 作为以前的版本。它应该在 rel 中创建了升级 .tar.gz:
$ ls -lh rel/
total 15M
drwxr-xr-x 8 rj rj 4.0K 2011-03-16 13:42 dummynode
drwxr-xr-x 8 rj rj 4.0K 2011-03-16 13:29 dummynode_first
-rw-r--r-- 1 rj rj 14M 2011-03-16 13:45 dummynode_2.tar.gz
drwxr-xr-x 2 rj rj 4.0K 2011-03-16 13:11 files
-rw-r--r-- 1 rj rj 922 2011-03-16 13:36 reltool.config
您仍然应该让虚拟机从 dummynode_first 运行。确保调用了 poke(),因此内部状态不是默认值。这将有助于说明升级工作无缝。
将升级包复制到正在运行的版本的releases目录下:
$ cp rel/dummynode_2.tar.gz rel/dummynode_first/releases
现在,在运行版本 1 的 Erlang 控制台中,我们使用 release_handler 检查当前可用的版本,并安装我们的新版本:
(dummynode@127.0.0.1)5> release_handler:which_releases().
[{"dummynode","1",[],permanent}]
(dummynode@127.0.0.1)6> release_handler:unpack_release("dummynode_2").
{ok,"2"}
(dummynode@127.0.0.1)7> release_handler:install_release("2").
{ok,"1",[]}
(dummynode@127.0.0.1)8> dummy_proj_server:num_pokes().
2
(dummynode@127.0.0.1)9> dummy_proj_server:poke_twice().
{ok,4}
(dummynode@127.0.0.1)10> dummy_proj_server:num_pokes().
4
(dummynode@127.0.0.1)11> release_handler:which_releases().
[{"dummynode","2",
["kernel-2.14.1","stdlib-1.17.1","dummy_proj-2",
"sasl-2.1.9.2","compiler-4.7.1","crypto-2.0.1",
"syntax_tools-1.6.6","edoc-0.7.6.7","et-1.4.1","gs-1.5.13",
"hipe-3.7.7","inets-5.5","mnesia-4.4.15","observer-0.9.8.3",
"public_key-0.8","runtime_tools-1.8.4.1","ssl-4.0.1",
"tools-2.6.6.1","webtool-0.8.7","wx-0.98.7","xmerl-1.2.6"],
current},
{"dummynode","1",[],permanent}]
升级成功;您可以看到 num_pokes() 被保留,并且新的 poke_twice() 函数可用。
release_handler 将我们的版本 2 显示为“当前”,将原始版本 1 显示为“永久”。这意味着虽然版本 2 现在正在运行,但如果您重新启动 VM,版本“1”将启动。
如果您对升级感到满意,请将其永久化,这意味着如果您重新启动 VM,它将启动而不是版本 1:
(dummynode@127.0.0.1)12> release_handler:make_permanent("2").
到目前为止,请check github 上的“v2 分支”以获取代码。
从 v1 升级到 v2 很简单:我们只是在不更改内部 #state 记录的情况下添加了一个乐趣。
Erlang .appup 文件可以做各种聪明的事情,允许您在升级过程中重新连接正在运行的应用程序。
Appup Cookbook详细介绍了您可以在 .appup 中放入的各种命令。
让我们使用更复杂的应用程序进行升级 – 我们将更改 dummy_proj_server 进程中的#state 记录。
对于版本 3,我们将跟踪 prods 和 pokes,这将需要状态记录中的另一个字段。
这是 v2…v3 之间的 github差异。
查看此版本对 .appup 的补充:
{"3",
%% Upgrade instructions
[{"2", [
{update,dummy_app_server,{advanced,[from2to3]}}
]}],
%% Downgrade instructions
[{"2",[
{update,dummy_app_server,{advanced,[from3to2]}}
]}]
}.
此 {update..} 指令将导致在 dummy_app_server 上调用 code_change 函数。code_change 的目的是将状态从旧的 (v2) 格式更改为新的 (v3) 格式。
虽然这不是绝对必要的,但我在 code_change 调用中将“from2to3”作为“Extra”字段传递。这可以进行模式匹配,并在您的 code_change 代码中明确说明预期的版本升级。
移动为 v2 生成的发布目录:
$ mv rel/dummynode rel/dummynode_2
为 v3 编译生成,然后创建升级包:
$ ./rebar compile
$ ./rebar generate
$ ./rebar generate-upgrade previous_release=dummynode_2
和之前一样,将升级包复制到正在运行的版本的releases目录下:
$ cp rel/dummynode_3.tar.gz rel/dummynode_first/releases
现在,在您将 v1 升级到 v2 的 Erlang 控制台中:
(dummynode@127.0.0.1)12> release_handler:unpack_release("dummynode_3").
{ok,"3"}
(dummynode@127.0.0.1)13> release_handler:install_release("3").
{ok,"2",[]}
现在您可以以正确的 OTP 方式部署热代码升级。非常适合更改内部状态或确实需要特殊升级的复杂、大型升级。阅读 Appup Cookbook 几次,并在部署之前在测试环境中测试您的升级包。您可以将实时环境 tar 打包并复制到您的测试环境箱,以获得生产系统的精确克隆以测试升级。
今天,我们将看一个相当古老且有些古怪的东西。 你们大多数人可能没有注意到的语言。 虽然 Erlang 不像某些现代编程语言那样流行,但它安静地运行着 WhatsApp 和微信等每天为大量用户提供服务的应用程序。 在这篇文章中,我将告诉你关于这门语言的更多事情、它的历史,以及你是否应该考虑自己学习它。 ## 什么是 Erlang,它在哪里使用? Erl
Read More这篇文章探讨了 Erlang/OTP 25 中基于类型的新优化,其中编译器将类型信息嵌入到 BEAM 文件中,以帮助JIT(即时编译器)生成更好的代码。 ## 两全其美 OTP 22 中引入的基于SSA的编译器处理步骤进行了复杂的类型分析,允许进行更多优化和更好的生成代码。然而,Erlang 编译器可以做什么样的优化是有限制的,因为 BEAM 文件必须
Read More自从Erlang 存在,就一直有让它更快的需求和野心。这篇博文是一堂历史课,概述了主要的 Erlang 实现以及如何尝试提高 Erlang 的性能。 ## Prolog 解释器 Erlang 的第一个版本是在 1986 年在 Prolog 中实现的。那个版本的 Erlang 对于创建真正的应用程序来说太慢了,但它对于找出Erlang语言的哪些功能有用,哪
Read More