阅读Erlang官方文档经常可以读到下面两句:
-
"xxx implemented using this module will have a standard set of interface functions and include functionality for tracing and error reporting. It will also fit into an OTP supervision tree. Refer to OTP Design Principles for more information."
-
" xxx comply to the OTP design principles".
一句是说某模块包含了一系列标准的接口函数来实现跟踪和错误报告,这个模块符合加入监控树的要求,去OTP设计原则相关的章节查询更多信息.后面一句就简单了,说xx模块的实现时符合OTP设计原则的.但是Erlang文档组织的问题,文档中并没有对OTP设计原则给出严格的定义和解释.你可能会搜索到下面的链接:
http://www.erlang.org/documentation/doc-5.6/pdf/design_principles.pdf
呵呵,这个仅仅是Erlang老版本的文档而已.本文是我学习OTP设计原则的一点总结:
OTP设计原则定义
The OTP Design Principles is a set of principles for how to structure Erlang code in terms of processes, modules and directories.
这句来自http://www.erlang.org/doc/design_principles/des_princ.html 给出了OTP设计原则的大致定义即:OTP设计原则是如何按照进程,模块,文件夹的概念来组织代码的一系列原则.无论用什么技术实现,设计都是解决代码如何组织的问题.还记得我们曾经提到过设计的三层视角吗?(猛击这里穿越:http://www.cnblogs.com/me-sa/archive/2008/04/15/ooview.html)从概念层确定要做什么,到规约视角确定各部分之间的关系,实现视角最终通过代码实现功能,经过这三个视角从宏观到微观的设计过程,代码结构逐步确定.在Erlang的特定语境下,组织代码的方式就是:进程,模块,文件夹;
Erlang代码的物理组织方式
模块和文件夹是代码的物理组织方式,代码组织的最小单元是模块(module),模块放在不同的文件夹中,下面是一个典型的文件夹布局:
doc
存放程序文档和配置文件
include 存放头文件.hrl
priv
类似于Reference文件夹,存放需要引用的第三方类库
src
源代码文件夹
ebin 编译的目标文件夹,存放beam文件
Erlang代码的功能逻辑组织方式
Erlang的设计哲学是把独立的活动通过进程表达;Erlang代码的功能逻辑组织方式,其实就是在讲如何组织进程.
Erlang/OTP进程组织的方式是监控树(supervision tree),监控树用worker和supervisor的概念把进程分成两类.worker完成实际的运算工作,supervisor的职责就是监控worker的行为,如果worker出错就通过重启的方式使worker正常工作.监控树将代码组织成层级结构,我们可以基于这种架构泛型建立起一个逐级可控的容错系统.下面是开源项目log4erl的监控树结构:
不是所有监控树中的进程都可以成为supervisior和worker,对进程还是有特殊要求的:A.支持调试sysdebugfacilities B.能够响应系统消息. 所谓系统消息是在监控树中使用的有特殊意义的消息,比如trace输出,挂起或恢复进程执行.要满足上面的两个要求对于开发者需要做什么呢?我们有两个选择:
-
使用标准behavior实现
-
使用sys和proc_lib模块
在stdlib文档的behavior章节可以看到gen_server gen_fsm gen_event supervisior都实现了跟踪和错误报告的功能,并可以处理系统消息.监控树中进程都按照相同的模式编写,结构上有很多类似.比如supervisor的区别仅仅在于监控哪个进程.很多worker也都是server-client,fsm(有限状态机),和event handler的关系.Behaviours 规范化了这些通用模式,把一个进程的实现代码分成两部分:通用部分和回调接口.通用部分由Erlang/OTP类库实现,其实我觉得应该称之为框架更合适一些,回调接口函数由我们来完成,并把这些函数导出.由于回调函数往往不会被开发者直接调用,开始的时候很容易出现的问题就是忘掉导出回调函数,特别是初始化函数init.使用behavior开发是通过牺牲一点点效率来获得通用性和一致性.对于复杂性的系统来说,一致性至关重要,一致性带来的是更高的可读性,可维护性.标准的Erlang的behavior有:
gen_server 实现通用client-server关系
gen_fsm 实现有限状态机
gen_event 实现事件处理功能
supervisor 实现监控树中的supervisor
我们在代码中显示添加-behaviour(Behaviour),编译的时候编译器会检查对应behavior的回调函数,如果缺失或者没有导出会有警告.
这些标准的behavior内部实现使用的是sys和proc_lib,所以我们可以直接使用这两个模块达到同样的效果,后面我会有专门的文章说这两个模块.
Erlang/OTP中的能完成特定功能集合的组件被称为application. 监控树是进程的组织方式,application是Erlang功能的组织方式.
总结一下:
-
Erlang OTP设计原则关注的是Erlang代码的组织方式
-
Erlang代码分割存放在不同的模块,模块存放在不同的文件夹
-
Erlang代码的逻辑组织方式是监控树
-
可以成为supervisior和worker需要支持跟踪,错误报告以及响应系统消息
-
标准behavior实现了上述要求,也可以通过sys和proc_lib模块实现
-
监控树是进程的组织方式,application是Erlang功能的组织方式
纯理论的东西总是显得空洞,后面的文章中我们会不断回顾这部分内容,将理论和编程实践做对应.
周末愉快!