从 UNIX 开发人员的角度了解 Erlang

Erlang 非常适合用于开发多核系统时代的强大应用程序,因为它具有独特的进程处理架构和函数编程特性 。在本文中,您可以学习到 Erlang 编程的一些基本知识。

Noah Gift, 助理工程主管, AT&T Interactive

/developerworks/i/p-nogift.jpg

Noah Gift 是 O'Reilly 出版的 Python For UNIX and Linux System Administration 的作者之一,现在还在为 Manning 撰写 Google App Engine In Action 一书。他是一名作家、演说家、顾问和社区负责人,并为 Red Hat MagazineO'ReillyMacTech 撰稿。他的咨询公司的网站是 http://www.giftcs.com,在 http://noahgift.com 可以找到他的许多作品。还可以在 Twitter(http://twitter.com/noahgift)上关注他的近况。

他拥有加州洛杉矶的 CIS 的硕士学位,以及加州 Poly San Luis Obispo 的营养科学学士学位。他是通过 Apple 和 LPI 认证的系统管理员,曾经在许多公司工作过,如加利福尼亚理工学院、Disney Feature Animation、Sony Imageworks、Turner Studios 和 Weta Digital。在空闲的时候,他喜欢和妻子 Leah 以及他们的儿子 Liam 一起度过,谱写钢琴曲、参加马拉松比赛以及积极地参与体育活动。



2011 年 8 月 01 日

简介

如果您是 UNIX 开发人员或系统管理员,很可能会有自己喜欢的一套工具。您可能喜欢 Perl、Python、sed 和 awk 或 Ruby。也可能喜欢 Java™ 或 C++。当您熟悉了一套工具之后,可能不愿意再花时间学习新的语言。必须有强有力的理由证明这么做是值得的。学习 Erlang 就有足够强有力的理由。在本文中,我会解释为什么应该学习 Erlang 以及如何开始。

多核系统的时代已经到来了,但是我们还没有准备好。许多语言依赖于脆弱的并发机制,比如线程和共享的状态,甚至是更糟的全局锁(它导致一个线程只能在单一核上执行)。典型的 *nix 服务器有 24 个核。编程人员很容易编写出过于复杂且容易出错的线程化代码;也很容易编写出只使用 24 核系统上的一个内核的代码,无法充分利用整个 CPU。对于这个问题,一个解决方案是使用专门针对伸缩设计的函数语言。Erlang 没有可变的状态,这是它与 *nix 专业人员使用的大多数流行语言的重要差异。另外,并发是它的固有特性。实际上,其他许多语言都是围绕对象构建的,而 Erlang 是围绕进程构建的。因为由语言(而不是操作系统)控制并发机制,所以生成数千甚至数百万个进程是很普通的。在 Erlang 中,处理问题的方式不一样,这恰恰可以解决当今的计算难题。

交互式的 Erlang shell

喜欢命令行的人会很喜欢 Erlang 的交互式 shell。可以通过交互式 shell 输入表达式、编译代码以及与进程进行通信。它的设计相当优雅,让我觉得就像是在管理使用 Lights Out Management 卡的 *nix 系统和先进的虚拟化系统一样。假设您已经在系统上安装了 Erlang。如果还没有,则需要通过 参考资料 了解如何下载并安装它。安装 Erlang 之后,会在路径中找到 "erl"。输入 "erl" 时,会进入交互式提示,可以在这里输入命令。

交互式 shell:部分 A
lion% erl
Erlang R14B 
(erts-5.8.1) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.1  (abort with ^G)
1>

要想看到帮助菜单,则应该执行下面的 help 命令。注意,每个表达式必须用点号结束,告诉解释器可以执行该表达式了。另外,可以随时输入 "q().",参见下面的帮助菜单输出。

交互式 shell:帮助输出
Eshell V5.8.1  (abort with ^G)
1> help().
** shell internal commands **
b()        -- display all variable bindings
e(N)       -- repeat the expression in query <N>
f()        -- forget all variable bindings
f(X)       -- forget the binding of variable X
h()        -- history
history(N) -- set how many previous commands to keep
results(N) -- set how many previous command results to keep
catch_exception(B) -- how exceptions are handled
v(N)       -- use the value of query <N>
rd(R,D)    -- define a record
rf()       -- remove all record information
rf(R)      -- remove record information about R
rl()       -- display all record information
rl(R)      -- display record information about R
rp(Term)   -- display Term using the shell's record information
rr(File)   -- read record information from File (wildcards allowed)
rr(F,R)    -- read selected record information from file(s)
rr(F,R,O)  -- read selected record information with options
** commands in module c **
bt(Pid)    -- stack backtrace for a process
c(File)    -- compile and load code in <File>
cd(Dir)    -- change working directory
flush()    -- flush any messages sent to the shell
help()     -- help info
i()        -- information about the system
ni()       -- information about the networked system
i(X,Y,Z)   -- information about pid <X,Y,Z>
l(Module)  -- load or reload module
lc([File]) -- compile a list of Erlang modules
ls()       -- list files in the current directory
ls(Dir)    -- list files in directory <Dir>
m()        -- which modules are loaded
m(Mod)     -- information about module <Mod>
memory()   -- memory allocation information
memory(T)  -- memory allocation information of type <T>
nc(File)   -- compile and load code in <File> on all nodes
nl(Module) -- load module on all nodes
pid(X,Y,Z) -- convert X,Y,Z to a Pid
pwd()      -- print working directory
q()        -- quit - shorthand for init:stop()
regs()     -- information about registered processes
nregs()    -- information about all registered processes
xm(M)      -- cross reference check a module
y(File)    -- generate a Yecc parser
** commands in module i (interpreter interface) **
ih()       -- print help for the i module
true
2>

输入这些命令并观察它们的作用,这对学习 Erlang 有很大的意义。在实际编写代码时,需要牢记的是,Erlang 中的状态是不可变的,所以在设置变量时会看到下面的输出,这可能让您很吃惊:

交互式 shell:部分 B
Eshell V5.8.1  (abort with ^G)
1> Var = 1.
1
2> Var = 2.
** exception error: no match of right hand side value 2
3>

当然,在学习新语言时总是应该先研究一下 "Hello World" 示例:

交互式 shell:部分 C
4> io:format("Hello World~n").
Hello World
ok

在全面的 Erlang 课程中还要讨论很多东西,但这里最终只提供了一个常用的数据类型列表示例:

交互式 shell:部分 D
5> List = [1,2,3].
[1,2,3]
6> [Head|Tail] = List.
[1,2,3]
7> Head.
1

在这个示例中,使用模式匹配提取列表的开头和结尾部分。模式匹配在 Erlang 中起到很大的作用,应该通过本文后面的 参考资料 进一步了解它。

编写并编译 Erlang 模块

用 Erlang 编写应用程序需要编写模块,而不只是编写交互式 shell 中看到的表达式。首先注意开发环境。如果打算进行很多 Erlang 开发工作,可以考虑使用带 Erlide 插件的 Eclipse 或 Emacs。在使用 Erlang 进行轻量级编程时,vim 表现得很好。

带 Erlide 插件的 Eclipse
带 Erlide 插件的 Eclipse 的屏幕截图

如果使用带 Erlide 的 Eunit 这样的 IDE,它会在编写模块代码时自动将代码编译为 beam 文件,这相当方便。也可以在 Erlang 交互式解释器中或通过 erlc 命令行编译器执行编译。下面是一个简单的模块,它将模块代码编译为 beam 文件:

fingerprint.erl
%% This is a module that gets the operating system type
-module(fingerprint).
-export([get_os/0]).

get_os ()->
    os:cmd("uname").

要想以交互方式编译模块代码并运行它,则应该再次运行 erl

以交互方式编译模块
lion% erl                                    
Erlang R14B 
(erts-5.8.1) [source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.1  (abort with ^G)
1> c(fingerprint).
{ok,fingerprint}
2> fingerprint:
get_os/0       module_info/0  module_info/1  
2> fingerprint:get_os().
"Darwin\n"
3> q().
ok

如果在 shell 中直接列出文件,那么您会看到已经创建了 fingerprint.beam 文件。也可以通过运行以下命令创建这个 beam 文件:

lion% erlc fingerprint.erl

使用 EUnit 对 Erlang 进行单元测试

如果您在编写 Erlang 代码,很可能是要构建高可用的系统。如果是这样,一定要从一开始就注意细节并为代码编写单元测试。对于前面的模块,相应的测试像下面这样:

EUnit 示例
%% This is a module that gets the operating system type
-module(fingerprint_with_test).
-include_lib("eunit/include/eunit.hrl").
-export([get_os/0]).


get_os ()->
    os:cmd("uname").

get_os_test ()->
    get_os().

要点是,要想用 EUnit 测试模块,则需要包含头文件(如上所示)并按照 "_test" 模式命名函数。如果这样做,则会自动导出所有测试函数并通过下面的代码运行它们。

以交互方式编译和测试 EUnit
Eshell V5.8.1  (abort with ^G)
1> c(fingerprint_with_test).
{ok,fingerprint_with_test}
2> fingerprint_with_test:test().
  Test passed.
ok
3>

使用 Erlang 作为脚本语言或从命令行执行它

还应该注意的是,可以在脚本模式下运行 Erlang,这样就不需要先编译代码。为此,需要运行 R11B4 或更高版本的 Erlang 副本。我们可以略微修改前面的示例,通过添加脚本解释器行和主函数把它变成脚本:

以交互方式编译和测试 EUnit
#!/usr/local/bin/escript

get_os ()->
    os:cmd("uname").

main(_) ->
    io:format("OS Platform: ~p~n", [get_os()]).

下面是输出:

lion% chmod +x fingerprint.escript
lion% ./fingerprint.escript  
OS Platform: "Darwin\n"

最后,与 Perl 或 Ruby 一样,还可以在命令行中编写单行脚本。可以将同一个示例编写为单行脚本,如下所示:

lion% erl -eval 'io:format("OS Platform: ~p~n", [os:cmd("uname")])' -noshell -s init stop
OS Platform: "Darwin\n"

分布式计算变得很容易:Mac OS X 和 Ubuntu 之间的远程消息传递

云计算:一个引人注目的新模型

云计算已被证明是一种强有力的新模型,它的优点之一是使用专门为伸缩设计的抽象性。Erlang 的 Location Transparency 特性也具有类似的优点。这意味着,将消息发送给本地机器上的进程与将消息发送给另一个机器上的进程并没有区别。这是 Erlang 极具竞争力的优点之一。Location Transparency 让我们不必考虑进程所在的位置。现在停一下。好好想想这个特性的意义。
实现它的方法之一是使用异步消息传递,在这种情况下进程之间不共享状态。要将消息发送给系统上的本地进程,可输入以下命令:

将消息发送给远程进程的命令完全与此相同,因为发送消息所需的所有信息都嵌套在消息本身中。

掌握了一些基础知识之后,我们就可以开始做真正有意思的事情了,比如构建相互通信的进程的集群。因为有 Location Transparency 特性,所以很容易在单一系统上测试集群,然后将它转移到大型分布式集群上,其中的系统可以位于世界各地!在下面的示例中,我在 Ubuntu(通过虚拟机)和 Mac OS X 上启动 Erlang,只用几行代码就可以远程地运行代码。注意,对于这个示例,假设已经正确地配置了本地 DNS 和防火墙设置(如果有的话)。

首先,在 Ubuntu 和 OS X 节点上都启动 Erlang:

mac% erl -name mac -setcookie test
ubuntu% erl -name mac -setcookie test

完成上述操作之后,就可以从 OS X 机器建立通信,通过调用 nodes 函数验证是否可以看到远程节点:

(mac@lion.local)1> net_adm:ping('ubuntu@ubuntu.localdomain').
pong
(mac@lion.local)2> nodes(). 
['ubuntu@ubuntu.localdomain']

现在,可以执行任意代码:

(mac@lion.local)15> rpc:call('ubuntu@ubuntu.localdomain', os, cmd, ['uname -a']). 
"Linux ubuntu.local 
2.6.35-24-generic #42-Ubuntu SMP Thu Dec 2 01:41:57 UTC 2010 i686 GNU/Linux\n"

很容易看出 Erlang 是多么强大,使用 Erlang 进行进程间通信是多么容易。尽管这是一个非常简单的示例,但您应该能够通过它体会到 Erlang 的强大之处。


结束语

本文讨论了使用 Erlang 的理论和实践,但是还有很多东西要学习。在今后几年,使用 Erlang 编写的大型生产系统会急剧增加。最近的一篇 Harvard Business Review 文章指出,“短缺” 是促进创新的首要因素。现在短缺的是在处理高度灵活且可伸缩的系统方面的经验丰富的专业人员。您现在应该努力成为 Erlang 语言专家,这会为您提供强大的竞争优势。

如果您有兴趣进一步了解 Erlang,我给出了以下建议。最好阅读一本或所有的 Erlang 书籍,有三本刚出版的书可供选择。这些书都不错。另外,您可以学习 tryerlang.org 上的交互式教程(请参阅 “参考资料”)。最后,还可以试试一些正在发展的 Erlang 项目,比如 CouchDB、RabbitMQ 和 Ejabbrd。

参考资料

学习

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=AIX and UNIX
ArticleID=750007
ArticleTitle=从 UNIX 开发人员的角度了解 Erlang
publish-date=08012011