OTP 23 亮点
OTP 23 已经发布(2020年5月13日)。这是一个漫长的过程,在最终发布之前,分别在二月、三月和四月发布了三个候选版本。我们非常感谢大家对候选版本的反馈,这些反馈揭示了一些我们内部测试没有发现的错误和缺陷。
这篇博客文章将介绍 OTP 23 中的一些新亮点。
您可以在这里下载描述更改的自述文件:OTP 23 自述文件。或者,像往常一样,查看您感兴趣的应用程序的发行说明。例如,这里:OTP 23 Erts 发行说明。
语言 #
在 OTP 23 中,我们为语言和编译器添加了一些新功能,其中一个自从引入位语法以来就一直存在,另一个是来自社区的建议。
匹配语法改进 #
二进制匹配 #
在二进制匹配中,现在允许匹配的段大小是一个保护表达式。在下面的示例中,变量 Size
被绑定到前 8 位,然后在表达式 (Size-1)*8
中用于以下二进制的大小。
example1(<<Size:8,Payload:((Size-1)*8)/binary,Rest/binary>>) ->
{Payload,Rest}.
在映射上匹配 #
在当前的映射匹配语法中,映射模式中的键必须是单个值或文字。如果映射中的键是复杂术语,这会导致不自然的代码。
在 OTP 23 中,映射匹配中的键可以是保护表达式,如您在 new_example2 中看到的那样。
唯一的限制是键表达式中使用的所有变量都必须事先绑定。
以前你必须这样做
example2(M, X) ->
Key = {tag,X},
#{Key := Value} = M,
Value.
现在你可以这样做
new_example2(M, X) ->
#{ {tag,X} := Value} = M,
Value.
下面是一个非法示例,显示仍然不支持使用未绑定的变量作为键模式表达式的一部分。在这种情况下,Key 未绑定,并且要求键表达式中使用的所有变量都必须事先绑定。
illegal_example(Key, #{Key := Value}) -> Value.
带下划线的数字字面量 #
现在允许在数字字面量的数字之间使用下划线以提高可读性。但下划线的位置不是完全自由的,有一些规则。请参见下面允许使用的示例
305441741123_456
1_2_3_4_5
123_456.789_123
1.0e1_23
16#DEAD_BEEF
2#1100_1010_0011
在下面的示例中,我们有一些不允许放置下划线的示例
_123 % variable name
123_
123__456 % only single ‘_’
123_.456
123._456
16#_1234
16#1234_
分布式 spawn 和新的 erpc
模块 #
改进的 spawn #
对于分布式情况,即在另一个节点上生成进程时,spawn 操作在可伸缩性和性能方面得到了改进。
还添加了新功能,例如分布式 spawn_monitor()
BIF。此函数创建一个新进程并原子地设置监视器。
spawn_opt()
BIF 还将支持监视器选项,以便在另一个节点上创建进程时原子地设置监视器。
我们还添加了新的 spawn_request()
BIF,用于异步生成进程。spawn_request()
支持 spawn_opt()
已支持的所有选项。
上述 spawn 改进还可以用于优化和改进 rpc
模块中的许多函数,但由于新函数不是 100% 兼容,我们决定引入一个名为 erpc
的新模块,并保留旧的 rpc
模块。
erpc
模块实现了 rpc
模块提供的操作的增强子集。
增强的意义在于,它可以区分返回值、引发的异常和其他错误。
erpc
也比原来的 rpc
实现具有更好的性能和可伸缩性。这是通过利用新引入的 spawn_request()
BIF 实现的。
rpc
模块现在与 erpc
共享相同的实现,因此 rpc
的用户将自动受益于 erpc
中所做的性能和可伸缩性改进。
下图说明了旧的和新的 rpc:call() 实现,并说明了为什么新的实现更高效且可伸缩。
“旧”的 rpc:call 实现:
新的 rcp:call 实现,在分布协议(spawn 请求)中支持
正如您在上面的“旧”实现中所见,rpc:call
依赖于接收节点上的 rex
进程来生成一个临时进程以执行被调用的函数。如果有许多同时发送到该节点的 rpc:calls
,这将使 rex
成为瓶颈。
新的解决方案根本不使用 rex
,而是让生成的进程解码调用的参数,从而避免了“旧”实现中发生的一些不必要的数据复制。
gen_tcp 和新的 socket 模块 #
在 OTP 22 中,我们引入了新的实验性 socket API。此 API 背后的想法是拥有一个稳定的中间 API,可用于创建不属于更高级别 gen_*
API 的功能。
我们现在在用 socket
作为可选后端来使用 gen_tcp
API 的计划中又前进了一步,从而使替换 inet 驱动程序成为可能。
为了使使用 gen_tcp
的现有代码易于测试,可以使用新的选项 {inet_backend, socket | inet}
来选择 socket
实现而不是默认的 inet
实现。此选项必须放在函数:gen_tcp:listen
、gen_tcp:connect
和 gen_tcp:fdopen
的选项列表中的第一位,这些函数都是创建套接字的函数。例如,像这样
{ok,Socket} = gen_tcp:connect(Addr,Port,[{inet_backend,socket}|OtherOpts])
返回的 Socket
是 '$inet'
标记的 3 元组,而不是端口,因此所有其他 API 函数都将使用正确的套接字实现。
更通用的覆盖方法是使用 Kernel 配置变量 inet_backend
并将其设置为 socket
或 inet
。例如,在 erl
命令行上,如下所示
erl -kernel inet_backend socket
或者使用以下命令设置
ERL_FLAGS="-kernel inet_backend socket"
shell 中的帮助 #
我们已经实现了 EEP 48,它指定了 BEAM 语言使用的 API 文档的存储格式。通过标准化 API 文档的存储方式,可以编写跨语言工作的工具。
普通的文档构建已扩展,为所有 OTP 模块生成 .chunk
文件。您可以运行 make docs DOC_TARGETS=chunks
仅构建 EEP 48 chunk。不设置 DOC_TARGETS 变量仅运行 make docs
将构建所有格式(html、man、pdf、chunks)。
基于这些新功能,我们已经在 shell 中添加了带有以下函数的在线帮助
h(Module)
h(Module,Function),
h(Module,Function,Arity)
还有相应的函数 ht/1,2,3
和 hcb/1,2,3
来获取有关类型和回调函数的帮助
我们在 stdlib
中添加了一个新的模块 shell_docs
,其中包含用于为 shell 呈现文档的函数。这可以例如被基于语言服务器协议 (LSP) 的开发环境使用。
code
模块还获得了一个新函数 get_doc
,该函数返回文档 chunk 而无需加载模块。
请参见下面的示例,以获取 lists:sort/2
的文档
4> h(lists,sort,2).
-spec sort(Fun, List1) -> List2
when
Fun :: fun((A :: T, B :: T) -> boolean()),
List1 :: [T],
List2 :: [T],
T :: term().
Returns a list containing the sorted elements of List1,
according to the ordering function Fun. Fun(A, B) is to
return true if A compares less than or equal to B in the
ordering, otherwise false.
ok
改进的 Tab 补全 #
shell 中的 Tab 补全也得到了改进。以前,模块的 Tab 补全仅适用于已加载的模块,现在已扩展为适用于代码路径中可用的所有模块。补全还扩展为在“help”函数 h、ht 和 hcb 中工作。例如,您可以按下 Tab 键,如下面的示例所示,并获取所有以 l
开头的模块
5> h(l
lcnt leex lists
local_tcp local_udp log_mf_h
logger logger_backend logger_config
logger_disk_log_h logger_filters logger_formatter
logger_h_common logger_handler_watcher logger_olp
logger_proxy logger_server logger_simple_h
logger_std_h
logger_sup
或者,像这样完成 lists
模块中所有以 s
开头的函数
5> h(lists,s
search/2 seq/2 seq/3 sort/1 sort/2 split/2
splitwith/2 sublist/2 sublist/3 subtract/2 suffix/2 sum/1
“容器友好”功能 #
考虑 CPU 配额 #
在决定默认的在线调度程序数量时,现在会考虑 CPU 配额。
因此,自动使 Erlang 成为应用配额的容器环境(例如带有 --cpus
标志的 docker)中的良好公民。
EPMD 独立性 #
在基于云和容器的环境中,在不使用 epmd
的情况下运行分布式 Erlang 节点并使用硬编码端口或替代服务发现可能很有趣。因此,我们引入了一些方法,使在没有 epmd
的情况下启动和配置系统更容易。
握手 #
我们改进了 Erlang 分布式协议中连接设置期间的握手。现在可以在不依赖 epmd
或其他对等节点版本的先验知识的情况下就协议版本达成一致。
动态节点名称 #
与新握手一起引入的另一个功能是动态节点名称。通过使用选项 -name Name
或 -sname Name
并将 Name
设置为 undefined
来选择动态节点名称。
这些选项使 Erlang 运行时系统成为分布式节点。这些标志调用了节点成为分布式节点所必需的所有网络服务器;请参阅 net_kernel
。还确保在 Erlang 启动之前在当前主机上运行 epmd
;请参阅 epmd 和 -start_epmd
选项。
OTP 23 中的新功能是将 Name
设置为 undefined
,然后该节点将以特殊模式启动,该模式优化为另一个节点的临时客户端。启用后,该节点将从它连接的第一个节点请求动态节点名称。此外,还将暗示这些分布设置
erl -dist_listen false -hidden -dist_auto_connect never
因为 -dist_auto_connect
设置为 never
,所以系统必须手动调用 net_kernel:connect_node/1
才能启动分布。如果使用动态节点名称时分布通道关闭,则该节点将停止分布,并且必须再次调用 net_kernel:connect_node/1
。请注意,如果分布被删除然后再次建立,则节点名称可能会更改。
注意! 从 OTP 23 开始支持动态节点名称功能。临时客户端节点和第一个连接的对等节点(提供动态节点名称)都必须至少是 OTP 23 才能正常工作。
控制 epmd
使用的新选项 #
为了让用户更好地控制 epmd
的使用,为 inet 分布添加了一些新选项。
-
-dist_listen false
设置分布通道,但不侦听传入连接。当您想要使用当前节点与同一台机器上的另一个节点交互,而无需它加入整个集群时,这很有用。 -erl_epmd_port Port
配置内置 EPMD 客户端应返回的默认端口。这允许本地节点知道集群中任何其他节点要连接的端口。-
-remsh Node
启动 Erlang,并连接到Node
的远程 shell。如果没有给出-name
或-sname
,则节点将使用-sname undefined
启动。如果Node
使用长名称,则应给出-name undefined
。如果Node
不包含主机名,则会自动从-name
或-sname
选项中获取一个。注意 在 OTP-23 之前,用户需要为
-remsh
提供有效的-sname
或-name
才能工作。如果目标节点运行的不是 OTP-23 或更高版本,则仍然是这种情况。
# starting the E-node test
erl -sname test@localhost
# starting a temporary E-node (with dynamic name) as a remote shell to
# the node test
erl -remsh test@localhost
erl_epmd
回调 API 也已扩展,允许返回 -1 作为创建号,这意味着节点将创建一个随机的创建号。
此外,还添加了一个名为 listen_port_please
的新回调函数,允许回调返回分发应使用的监听端口。如果要从外部服务获取监听端口,则可以使用此函数代替 inet_dist_listen_min/max
。
erl_call
的新选项 #
erl_call
是一个 C 程序,最初作为 erl_interface
应用程序中的示例捆绑在一起。erl_interface
包含用于与 Erlang 节点通信并使 C 程序表现得像 Erlang 节点的 C 库。它们被称为 C 节点。erl_call
已经变得很流行,主要用于产品中,用于管理同一主机上的 Erlang 节点。在 OTP 23 中,erl_call
安装在与 erl
相同的路径下,使其在路径中可用,而无需考虑 erl_interface
版本。erl_call
的另一个新功能是 address
选项,它可用于直接连接到节点,而无需依赖 epmd
来解析节点名称。
据我所知,erl_call
正被用于即将发布的 relx 版本(由 rebar3 使用)的 node_tool 功能中。
TLS 增强和更改 #
现在支持 TLS-1.3(在 OTP 22 中,我们将其归类为实验性的),但尚未完全实现所有功能。支持的关键功能是
- 会话票证
- 刷新会话密钥
- RSASSA-PSS 签名
- 中间件兼容性。
尚不支持“早期数据”功能。早期数据是 TLS 1.3 中引入的优化,允许客户端在连接的第一个往返过程中向服务器发送数据,而无需等待 TLS 握手完成(如果客户端最近与同一服务器通信过)。
在 OTP 23 中,TLS 1.3 默认由客户端和服务器声明为首选协议版本。未显式配置 TLS 版本的用户应注意这一点,因为它可能会影响互操作性。
为 ssl:cipher_suites/2,3
提供了一个新选项 exclusive
,并扩展了 ssl:versions
以更好地反映 Erlang/OTP 当前设置可用的 TLS 版本。
另请注意,我们已删除对旧版 TLS 版本 SSL-3.0 的支持。
SSH #
两个值得注意的 SSH 功能是由开源用户通过 Pull Request 提供的,即支持从 ssh-agents 获取密钥和 TCP/IP 端口转发。端口转发有时称为隧道或 tcp-forward/direct-tcp。在 OpenSSH 客户端中,端口转发对应于选项 -L 和 -R。
Ssh 代理存储的密钥提高了安全性,而端口转发通常用于在两个主机之间获得加密隧道。在密钥处理方面,默认密钥插件 ssh_file.erl
已重写并使用 OpenSSH 文件格式“openssh-key-v1”进行了扩展。到目前为止的一个限制是新格式的密钥无法加密。默认插件现在还使用端口号,这提高了安全性。
现在可以在 Erlang 配置文件中配置 SSH 应用程序。这使得可以例如在不更改代码的情况下更改支持的算法集。
加密 #
在 OTP-22.0 中引入了一个新的加密 API。引入新 API 的主要原因是使用 OpenSSL libcrypto EVP API,该 API 允许在机器支持的情况下进行硬件加速。加密算法的命名也已系统化,现在遵循 OpenSSL 中的模式。
Crypto 应用程序的某些部分正在使用非常旧的 API,而其他部分正在使用最新的 API。事实证明,以新的方式使用旧的 API,同时保持向后兼容性是不可能的。
因此,旧的 API 暂时保留,但它是使用新的原语实现的。旧的 API 在 OTP-23.0 中已被弃用,将在 OTP-24.0 中删除。