Erlang基础 -- 介绍 -- Erlang特点

前言

Erlang是具有多重范型的编程语言,具有很多特点,主要的特点有以下几个:

  • 函数式
  • 并发性
  • 分布式
  • 健壮性
  • 软实时
  • 热更新
  • 递增式代码加载
  • 动态类型
  • 解释型

函数式

Erlang是函数式编程语言,函数式是一种编程模型,将计算机中的运算看做是数学中的函数计算,可以避免状态以及变量的概念。

对象是面向对象的第一型,函数式编程语言也是一样,函数是函数式编程的第一型。函数是Erlang编程语言的基本单位,在Erlang里,函数是第一型,函数几乎会被用作一切,包括最简单的计算。所有的概念都是由函数表达,所有额操作也都是由函数操作。

并发性

在上一篇blog中已经说过Erlang编程语言的并发性了,Erlang编程语言可以支持超大量级的并发性,并且不需要依赖操作系统和第三方外部库。Erlang的并发性主要依赖Erlang虚拟机,以及轻量级的Erlang进程。

Erlang进程究竟是怎样轻量?

 1 $ erl
 2 Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [lock-counting] [dtrace]
 3
 4 Eshell V6.3  (abort with ^G)
 5 1> Pid = erlang:spawn(fun() -> receive _ -> ok end end).
 6 <0.34.0>
 7 2> erlang:process_info(Pid).
 8 [{current_function,{prim_eval,‘receive‘,2}},
 9  {initial_call,{erlang,apply,2}},
10  {status,waiting},
11  {message_queue_len,0},
12  {messages,[]},
13  {links,[]},
14  {dictionary,[]},
15  {trap_exit,false},
16  {error_handler,error_handler},
17  {priority,normal},
18  {group_leader,<0.25.0>},
19  {total_heap_size,233},
20  {heap_size,233},
21  {stack_size,9},
22  {reductions,17},
23  {garbage_collection,[{min_bin_vheap_size,46422},
24                       {min_heap_size,233},
25                       {fullsweep_after,65535},
26                       {minor_gcs,0}]},
27  {suspending,[]}]
28 3> erlang:process_info(Pid, memory).
29 {memory,8376}

L5,首先,可以使用Erlang内置的API函数创建一个Erlang进程,并返回这个进程的PID。

L7、L28,再使用Erlang的API函数查看Erlang进程的信息,可以看到以默认参数创建的Erlang进程的heap size是233个字节(嗯,单位是words),占用的内存是8376bytes(计量单位就是bytes),这8376bytes的内存主要包括了这个Erlang进程的调用栈、堆、以及一些内部的数据结构。

那么,在Erlang系统中,可以维持多少Erlang进程,就取决于Erlang可以使用多少计算机内存。

当然,需要注意的是,上述是初始化的heap size和内存占用,在Erlang进程的运行中,Erlang调度器会根据实际情况,给Erlang进程分配需要的内存空间,然后根据相关的算法对Erlang进程进行垃圾回收(GC)。

上图是Erlang虚拟机和Erlang库的关系图,从图中,可以看出,不管是Erlang现有的内部库(kernel、stdlib ... )还是可以自己创建的Erlang库(lager、recon ... ),都是运行在Erlang虚拟机上的,Erlang虚拟机是整个Erlang编程语言的核心所在。

分布式

Erlang的分布式特性是由Erlang在语言层面上支持的,可以使用语言内置的API函数,在远程节点上创建Erlang进程,继而执行指定的模块函数。同样,还可以使用Erlang的RPC模块,调用远程节点的模块函数。

需要注意的一点是,在分布式Erlang系统中,节点指的是,一个可参数分布式Erlang事务的运行着的Erlang系统。

上图是用本地的两个terminal 模拟了两个Erlang节点,一个叫做‘[email protected]‘,另一个叫‘[email protected]‘。

首先,ping一下,确认两个节点是可以建立连接,相互通信的。然后,在其中一个节点上,通过rpc模块,在另一个节点上执行相应的模块函数,并函数执行结果。

Erlang节点之间的相互调用,跨节点远程模块函数执行都是异常方便的,Erlang节点之间的通信完全是由Erlang编程语言在语言层面上支持的(重要的事情,再说一遍),Erlang语言有自己的node(节点,不是nodejs)协议,某些语言,也想实现这种方便的方式(如 https://github.com/goerlang)。

健壮性

健壮性是Erlang编程语言一个非常重要的特点,Erlang编程语言的健壮性,主要依赖于以下几点:

  • 进程隔离
  • 完善的错误异常处理
  • 错误处理哲学
  • 监控者进程

关于Erlang进程资源隔离这一点,在上一个blog中也有说到过。在构建可容错的软件系统过程中,要解决的本质问题就是故障隔离,正因为Erlang进程资源隔离的特点,除了几个特殊性的Erlang进程(Erlang系统的主进程如果死掉的话,Erlang系统肯定没法玩了)之外,某个一般性的进程出现错误异常,对整个Erlang系统造成的影响是很小的,因为资源是隔离的,所以某个进程出现的故障具有隔离性,不会导致整个Erlang系统崩溃。

在Erlang系统中,系统提供了一些错误异常处理的方式,体现在API函数上,常用的有

1 1> erlang:exit("test").
2 ** exception exit: "test"
3 2> erlang:throw("test").
4 ** exception throw: "test"
5 3> erlang:exit("test").
6 ** exception exit: "test"
7 4> 

在Erlang编程语言中,可以使用以上这几个API函数抛出错误异常,这几个API函数都会crash掉调用者进程,这和Erlang的错误处理哲学有关。

(这几个API函数有什么不同,在什么场景下应该用哪个,会在后面的blog中详细介绍)

为了捕获这些错误异常,Erlang同样提供了非常方便的不同的错误异常处理方式,可以使用catch:

1 4> catch 1 + "1".
2 {‘EXIT‘,{badarith,[{erlang,‘+‘,[1,"1"],[]},
3                    {erl_eval,do_apply,6,[{file,"erl_eval.erl"},{line,661}]},
4                    {erl_eval,expr,5,[{file,"erl_eval.erl"},{line,434}]},
5                    {shell,exprs,7,[{file,"shell.erl"},{line,684}]},
6                    {shell,eval_exprs,7,[{file,"shell.erl"},{line,639}]},
7                    {shell,eval_loop,3,[{file,"shell.erl"},{line,624}]}]}}
8 5> 

在上面这个例子中,让1 和 “1” 执行相加操作,系统会爆出异常错误,使用catch来捕获的话,就可以看出错误异常的类型以及调用栈信息,能让码农方便快速的定位究竟是哪里出了问题。

同样,还可以使用try ... catch

5> try 1 + "1" catch Error:Reason -> io:format("Error: ~p, Reason: ~p~n", [Error, Reason]) end.
Error: error, Reason: badarith
ok

try ... catch 这种方式不会显示调用栈信息,和catch 相比的话,overload更小一些。

码农就可以在不同的场景中使用不同的处理方式(如果想知道调用栈信息的话,可以使用catch,如果不关心调用栈信息的话,try ... catch 就OK了),完全自己选择。

至于错误处理哲学,在Erlang系统中,所提倡的方式是,速错,工作进程不成功就成仁,让其他进程来修复错误,尽可能不是用防御式编程(这和Java“有些”不同),这样做,能够让我等码农尽快发现错误异常,避免错误异常真到了生产环境下才被发现(到时候老板扣工资就惨了)。

对于“监控者进程”,Erlang系统提供了link或者是monitor的方式,可以让监控者进程及时发现工作进程的异常故障,进而对异常故障做出相应的处理,速错不是忽略错误异常,而是尽早的发现并修复。在Erlang的OTP框架中,提供了supervisor的behavior,就是基于这种方式的。

 1 6> erlang:process_flag(trap_exit, true).
 2 false
 3 7> erlang:spawn_link(fun() -> 1 + "1" end).
 4 <0.43.0>
 5 8>
 6 =ERROR REPORT==== 18-Aug-2015::23:53:54 ===
 7 Error in process <0.43.0> with exit value: {badarith,[{erlang,‘+‘,[1,"1"],[]}]}
 8
 9
10 8> flush().
11 Shell got {‘EXIT‘,<0.43.0>,{badarith,[{erlang,‘+‘,[1,"1"],[]}]}}
12 ok
13 9> erlang:spawn_monitor(fun() -> 1 + "1" end).
14
15 =ERROR REPORT==== 18-Aug-2015::23:54:09 ===
16 Error in process <0.46.0> with exit value: {badarith,[{erlang,‘+‘,[1,"1"],[]}]}
17
18 {<0.46.0>,#Ref<0.0.0.68>}
19 10> flush().
20 Shell got {‘DOWN‘,#Ref<0.0.0.68>,process,<0.46.0>,
21                   {badarith,[{erlang,‘+‘,[1,"1"],[]}]}}
22 ok

L1,先设置当前进程的trap_exit flag,防止link进程死掉牵连当前进程。然后,分别使用spawn_link(L3)和spawn_monitor(L13)两种方式创建进程,并让创建的进程执行肯定会出现错误异常的函数。等被创建的进程异常退出之后,当前进程就能收到相应的消息(L11和L20),然后就能做出相应的处理了,这些错误信息的具体含义也会在后面的blog详细说明。在此主要是为了说明监控这进程的表现形式。

软实时

Erlang软实时的特点主要依赖于:

  • Erlang虚拟机调度机制
  • 内存垃圾回收策略
  • 进程资源隔离

Erlang系统垃圾回收策略是分代回收的,采用递增式垃圾回收方式,基于进程资源隔离的特点,Erlang内存垃圾回收是以单个Erlang进程为单位的,在垃圾回收的过程中,不会stop the world,也就是不会对整个系统造成影响。结合Erlang虚拟机抢占式调度的机制,保证Erlang系统的高可用性和软实时性。

热更新

哇哈哈,很多人提到Erlang都可能会被Erlang的热更新特点所吸引(其他语言也能实现),但是Erlang的热更新是非常方便并且在电信产品中久经考验。Erlang系统,允许程序代码在运行过程中被修改,旧的代码逻辑能够被逐步淘汰而后被新的代码逻辑替换。在此过程中,新旧代码逻辑在系统中是共存的,Erlang“热更新”的特点,能够最大程度的保证Erlang系统的运行,不会因为业务更新造成系统的暂停。

我司的产品(ptengine.com)现在面向的是全球100+个国家地区,覆盖24+时区(嗯,有半时区,还有四分之一时区),也就是,我们几乎没有停服更新的时间窗口,代码程序啥的,就是靠的Erlang的热更新。(当然,也有失败的时候,后面再细说)

递增式代码加载

Erlang的库,包括Erlang现有的库以及码农自己创建的库是运行在Erlang虚拟机外层的(上面有个图)。可以在Erlang系统运行的过程中,被加载,启动,停止以及卸载,这些,都是码农可以去控制的。

比如:

 1 $ erl -pa ./ebin -pa ./deps/*/ebin
 2 Erlang/OTP 17 [erts-6.3] [source] [64-bit] [smp:8:8] [async-threads:10] [hipe] [kernel-poll:false] [lock-counting] [dtrace]
 3
 4 Eshell V6.3  (abort with ^G)
 5 1> application:load(lager).
 6 ok
 7 2> application:ensure_all_started(lager).
 8 {ok,[syntax_tools,compiler,goldrush,lager]}
 9 3> 00:11:23.274 [info] Application lager started on node [email protected]
10
11 3> application:info().
12 [{loaded,[{goldrush,"Erlang event stream processor","0.1.6"},
13           {kernel,"ERTS  CXC 138 10","3.1"},
14           {lager,"Erlang logging framework","2.0.3"},
15           {syntax_tools,"Syntax tools","1.6.17"},
16           {compiler,"ERTS  CXC 138 10","5.0.3"},
17           {stdlib,"ERTS  CXC 138 10","2.3"}]},
18  {loading,[]},
19  {started,[{lager,temporary},
20            {goldrush,temporary},
21            {compiler,temporary},
22            {syntax_tools,temporary},
23            {stdlib,permanent},
24            {kernel,permanent}]},
25  {start_p_false,[]},
26  {running,[{lager,<0.45.0>},
27            {goldrush,<0.38.0>},
28            {compiler,undefined},
29            {syntax_tools,undefined},
30            {stdlib,undefined},
31            {kernel,<0.9.0>}]},
32  {starting,[]}]
33 4> application:unload(lager).
34 {error,{running,lager}}
35 5> application:stop(lager).
36
37 =INFO REPORT==== 19-Aug-2015::00:11:57 ===
38     application: lager
39     exited: stopped
40     type: temporary
41 ok
42 6> application:unload(lager).
43 ok

以控制lager库为演示示例,(lager库是Erlang的一个第三方库,是一个应用非常广泛的日志组件)。

L5可以使用application:load(lager)加载lager库,然后使用application:ensure_all_started(lager) 启动lager库以及lager库所以来的库(在start时,Erlang系统的处理方式是,如果还没有load的话,会先load,然后再start,所以实际情况下,load使用的机会是比较少的)。

start之后,可以使用application:info() 函数,去检查是否已经启动成功。确认started了,再去unload(好像有点作,仅仅是为了演示一下),然后发现报错了,是因为lager库正在运行,无法unload,那么就先stop 掉lager库吧。

注意,有可能有些人比较疑惑,运行得好好的,为啥要stop呢?在这里可能有这样一种原因,我们自己创建了一个库,然后上线了,运行了一段时间之后,发现,有一个出现几率很小的bug,想修复一下,这个时候可以用热更,也可以用stop -> unload -> 修改代码/编译 -> load -> start 的方式。如果是我想用其中一个库替换掉这个库,那么这个库就已经没有存在的必要了,就必须stop掉。

动态类型

Erlang既是动态语言,又是动态类型。

动态语言指的是,在系统运行过程中,可以改变代码的结构,现有的函数可以被删除或者是被修改,运行时代码可以根据某些条件改变自身结构。这也是Erlang可以热更新的一个基础。

动态类型值得是,在程序原形期间才会检查数据类型,数据类型的绑定不是在编译阶段,而是延后到运行阶段。

举两个例子:

1 8> F = fun(A, B) ->  io:format("-----------------~n"), A + B  end.
2 #Fun<erl_eval.12.90072148>
3 9> F(1, "1").
4 -----------------
5 ** exception error: an error occurred when evaluating an arithmetic expression
6      in operator  +/2
7         called as 1 + "1"

L1,定义一个函数,先输入一个横线(-----------------),然后执行两个参数的相加操作。在L3处调用该函数,传入的两个参数是1 和 “1”,然后,发生了什么?首先输出了横线,也就是函数已经被执行了,而真正运行到相加操作时,才会检查两个参数的数据类型。

再看一个需要编译的例子:

1 $ cat test.erl
2 -module(test).
3 -export([start/0]).
4
5 start() ->
6     add(1, "1").
7
8 add(A, B) ->
9     A + B.

在这个test模块中,定义了两个函数,第一个是start函数,可以被外部调用,在start函数中,调用了一个内部函数,add,add函数执行的是两个变量的相加操作,而在start函数中,向add函数传入了两个参数,第一个是参数是1,第二个是“1”,这明显是会失败的嘛(其他语言可能不会,但是在Erlang语言中,这是会失败的)。

但是在编译的时候,编译器并没有检查start函数中传给add函数的两个参数的数据类型,这个模块是可以编译通过的。(如何编译模块,会在后面的blog中细说)

但是在运行时,就会出现错误。

1 1> c(test).
2 {ok,test}
3 2> test:start().
4 ** exception error: an error occurred when evaluating an arithmetic expression
5      in function  test:add/2 (test.erl, line 8)

L1是编译Erlang模块文件的一种方式,L2调用了test 模块的start函数,然后就出现错误了。

从上面的两个例子中,可以看出,动态类型存在着一定的弊端,潜在的错误异常,只有在运行阶段才能被发现,无法在编译的时候就尽早的发现潜在的错误异常。

解释型

Erlang编程语言是解释型语言,运行在虚拟机上,具有良好的平台兼容性。

总结

Erlang是函数式编程语言,其核心是Erlang虚拟机。Erlang并发进程不同于操作系统进程,是非常轻量的,Erlang内置的分布式特性,异常方便, Erlang编程语言软实时的特性能够在其错误异常处理机制的保护下更加健壮的运行,其热更新能给我们码农带来诸多的方便。

内置,内置,内置,方便,方便,方便。

参考:

  • Erlang的几本书
  • Erlang官方文档
  • 还是上个blog里提到的那篇论文(殿堂级论文)

下一篇blog会对Erlang环境安装一笔带过,重点说一个Wordcount的示例,演示一下Erlang轻量的并发进程以及非常方便好用的分布式特性。

时间: 2024-10-25 12:28:49

Erlang基础 -- 介绍 -- Erlang特点的相关文章

Erlang基础 -- 介绍 -- Wordcount示例演示

在前两个blog中,已经说了Erlang的历史.应用场景.特点,这次主要演示一个Wordcount的示例,就是给定一个文本文件,统计这个文本文件中的单词以及该单词出现的次数. 今天和群友们讨论了一个问题,突然一下子就上升到哲学角度上了,装逼装大发了. PS:图片中有错别字,%s/财务和其他9个月/财务和其他9个人/g 不过真心想说的一点是,把Erlang系统,映射到现实中,很多奇葩问题,就能迎刃而解了.所以,在下面的简要设计中,我就尽可能的代入一下现实世界吧. 环境安装 mac 的话,用brew

Erlang基础 -- 介绍 -- 历史及Erlang并发

前言 最近在总结一些Erlang编程语言的基础知识,拟系统的介绍Erlang编程语言,从基础到进阶,然后再做Erlang编程语言有意思的库的分析. 其实,还是希望越来越多的人关注Erlang,使用Erlang,壮大Erlang编程语言的社区. 说实话,我也没这么高尚,就是看到很多人对Erlang编程语言的误解,Erlang编程语言社区的凋零,招个Erlang开发难之又难,才萌生此念. 这次主要介绍Erlang编程语言.包括Erlang的简要历史以及应用场景,Erlang并发编程,Erlang编程

Erlang基础知识集锦

http://wenku.baidu.com/link?url=or-8mkUYUM0uVeqCYESGe93YIlh2IDLP7lFOwRlwr8Syf3PeHbwJC5DPCErs4NFrb1p4I16eJuHIIFG_tR_jdYGoL5MsJX0YEjdeUmKjkTG 声明:此文档只作为对erlang的认知之用,如果需要学习并使用erlang请系统学习介绍erlang的书. 1.       简介 l  Erlang是一个并行编程语言和运行时系统,最初由爱立信(Ericsson)于19

Archive for the ‘Erlang’ Category 《Erlang编程指南》读后感

http://timyang.net/category/erlang/ 在云时代,我们需要有更好的能利用多核功能及分布式能力的编程语言,Erlang在这方面具有天生的优势,因此我们始终对它保持强烈关注. 按:此为客座文章,投稿人为新浪微博基础研发工程师赵鹏城(http://weibo.com/iamzpc),以下为原文.在对一个分布式KV存储系统的研究过程中,我有幸遇到了Erlang语言.因此,我研究工作的第一目标就是快速入门Erlang语言并在实际研究过程中进一步深入理解Erlang的精髓.在

【简译】jQuery对象的奥秘:基础介绍

本文翻译自此文章 你有没有遇到过类似$(".cta").click(function(){})这样的JavaScript代码并且在想“$('#x')是什么”?如果这些对你想天书一样,请往下读.如果你认为这些代码不可能是真的,请浏览一些jQuery例子,他们都是这种结构. 这篇文章覆盖了像下面一样吓人的代码片段中涉及的关键概念.我们以一个长例子开始,这个长例子是基于一个让一个正方形运动的简单例子(a simple example of animating a square).你可能不需要

Zabbix 3.0 基础介绍 [一]

Zabbix 3.0 基础介绍 [一] zabbix 一.Zabbix介绍 zabbix 简介   Zabbix 是一个高度集成的网络监控解决方案,可以提供企业级的开源分布式监控解决方案,由一个国外的团队持续维护更新,软件可以自由下载使用,运作团队靠提供收费的技术支持赢利   zabbix是一个基于Web界面的,提供分布式系统监控以及网络监视功能的企业级的开源解决方案.   zabbix能监视各种网络参数,保证服务器系统的安全运营,并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题

【OpenGL】“我叫MT”纯手工3D动画制作之1——基础介绍

最近在家研习面经,温习基础,索性花些时间将本科期间完成的一些学习之作整理出来,分享之余顺便水点经验 其实这个事情起源于一门“计算机图形与动画(Computer Graphics & Animation)”的外方课程,当初的外籍教师Tony教的很认真,对于这门课自己也投入了非常多的时间.言归正传,这里先介绍一些涉及的技术,熟悉的同学请跳过哈~ A.几何物体建模 带阴影的后面我会介绍到的哦~ 加下划线的后面我后面会举栗子的哦~ B.涉及的图形学技术与应用 C.动画技术 参考文献 1.王汝传,张登银,

qt model/view 架构基础介绍之QTreeWidget

# -*- coding: utf-8 -*- # python:2.x #说明:QTreeWidget用于展示树型结构,也就是层次结构同前面说的 QListWidget 类似,这个类需要同另外一个辅助类 # QTreeWidgetItem 一起使用.不过,既然是提供方面的封装类,即便是看上去很复杂的树, # 在使用这个类的时候也是显得比较简单的 __author__ = 'Administrator' from PyQt4.QtGui import  * from PyQt4.Qt impor

qt model/view 架构基础介绍之QTableWidget

# -*- coding: utf-8 -*- # python:2.x #说明:QTreeWidget用于展示树型结构,也就是层次结构同前面说的 QListWidget 类似,这个类需要同另外一个辅助类 # QTreeWidgetItem 一起使用.不过,既然是提供方面的封装类,即便是看上去很复杂的树, # 在使用这个类的时候也是显得比较简单的 __author__ = 'Administrator' from PyQt4.QtGui import  * from PyQt4.Qt impor