周末读了两篇论文"On Designing and Deploying Internet-Scale Services"和Joe Armstrong的论文"面对软件错误构建可靠的分布式系统",这两篇论文实战内容相当多,整理笔记于此,备忘.
On Designing and Deploying Internet-Scale Services [HTML]
英文版"Making reliable distributed systems in the presence of sodware errors" [PDF]
中文版 《面向软件错误构建可靠的分布式系统》 [PDF]
架构的定义
An architecture is the set of significant decisions about the organization of a sodware system, the selection of the structural elements and their interfaces by which the system is composed, together with their behaviour as specified in the collaborations among those elements, the composition of these structural and behavioural elements into progressively larger subsystems, and the architectural style that guides this organization—these elements and their interfaces, their collaborations, and their composition.
Booch, Rumbaugh, and Jacobson [19]
在我接触到的架构定义中,Joe Armstrong论文中提到的定义相当务实,相当具有实践指导价值;首先和Martin Fowler等人对架构的共识是架构是一组系统组织方式的重要决策;决策包含系统有哪些构成元素,元素之间的接口,元素之间的协作方式;是一种把这些结构和行为元素构建为更大子系统的的合成方式;也是一种构建风格,在其指导下把元素,元素接口,元素协作和合成方式组织起来.
把架构的抽象概念映射到具体实践过程,可以从六个具体方面去考量,这时候我们已经从万里高空下降到陆地上了.
1.问题领域——我们的架构是为解决什么类型的问题而设计的?软件架构一定不是通用的,而是为解决某一类特定问题而设计的.缺少了关于用来解决哪类问题的描述的架构是不完整的.
笔记:同样的,缺少上下文描述的编程语言比较也只是一场关公战秦琼的乱斗而已;遇到过一些"技术牛人"身居要位,硬是把之前项目的设计方案强制应用在当前的项目上,这都不是偷懒那样简单,而是缺少对项目负责的专业态度.而更为糟糕的是一旦项目做失败了,就甩手走人,留下一个烂尾的工程留给团队;对团队的伤害,需要更长时间进行修复.
2.哲学—— 软件构造方法背后的原理是什么?架构的核心思想是什么?
笔记:在解决了问题或者完成了一个设计之后,我们一定要回顾一下解决问题的思路和设计的原理;具体的技术细节可能无法服用,但是思维方式和设计方法都是可以复用的.在阅读开源项目时,学习语言技巧是一方面,更为重要的是看这个项目是解决了什么问题,如何解决的,整体思路是怎样的.
笔记:这个还是深有感触的,现在有一些项目比如PlayStation®Suite SDK把C#当作脚本使用,但是你会发现在这些项目里面虽然同是C#语言但无论是类库组织还是命名规范都是有自己的规则和风格的.
4.预先定义好的部件——以“从一组预先定义好的部件中选择”的方式进行设计远比“从头设计”的方式要来得容易.Erlang 的OTP库包含了一个完整的现成部件集(称之 behaviour 库),一些常用的系统都可以使用这些部件构建起来.例如 gen_server 这种 behaviour 就可以用来构建client-server 系统,gen_event 这种 behaviour 可以用来构建基于事件(event-based)的程序.
笔记:学习语言,其中一个重要的部分就是掌握这个语言所提供的类库;一方面是不重复造*,能够直接使用高质量类库;另一方面从类库中可以学习这种语言的编程风格.
6.配置方式——我们如何来启动,停止和配置我们的系统?我们可以在系统工作过程中进行重配置吗?
• Everything is a process.
• Processes are strongly isolated.
• Processes have unique names.
• If you know the name of a process you can send it a message.
• Processes share no resources.
• Error handling is non-local.
5.描述方式——我们如何描述某一部件的接口?我们如何描述系统中两个部件之间的通信协议?我们如何来描述系统中的静态和动态结构?为了回答这些问题,我们将介绍一些专门的符号.其中一些用来描述程序的API,而其他的则用来描述协议和系统结构。
笔记:学习一种新知识,其中的门槛一方面是来自于概念,另一方面就是来自于符号系统;这两种都是对信息进行了简化,背后包含了较大的信息量,没有额外的信息支持难以理解.在最初接触一种新知识的时候,由于不了解这个领域的术语和概念,我们甚至都不能正确的描述遇到的问题.
6.配置方式——我们如何来启动,停止和配置我们的系统?我们可以在系统工作过程中进行重配置吗?
笔记:编程完成业务功能并没有万事大吉,还要考虑到配置,部署,这些问题的考虑时机甚至在设计之初就要考虑;针对不同的硬件设备,配置也有所不同;考虑到日常维护,还要准备一些给运维使用的脚本.
Erlang 故障隔离
为了构建出在软件存在错误的时候仍具有合理行为的可容错软件系统,Erlang的解决问题的思路是:
1.任务层次化,尽力完成顶层任务
2.如果完成任务过程中出现错误,如果无法纠正错误,就立即取消重新启动新的任务
这其中最本质的问题就是故障隔离,操作系统通过进程的概念实现了故障隔离,进程提供了保护区域,一个进程出错,不会影响到其他进程的运行.不同程序员编写的不同应用程序分别跑在不同的进程中; 一个应用程序的错误不会对系统中运行的其他应用程序产生副作用.这种选择当然满足了初步的要求.然而因为所有进程使用同一片 CPU,同一块物理内存,所以当不同进程争抢 CPU 资源或者使用大量内存的时候,还是可能对系统中的其他进程产生负面影响. 进程间的相互冲突程度取决于操作系统的设计特性.
Erlang进程和并发编程是语言的一部分,而不是由宿主操作系统提供的,Erlang应用程序是通过大量互相通信的并行进程构建起来的,这样做的原因是:
架构基础设施——我们可以用一组相互通信的进程组织起我们的系统,并定义出进程间消息传递的通道,我们就可以很方便地把系统划分成定义良好的子部件,并可以对这些子部件进行单独实现和测试.
巨大的潜在效率——设计成以许多独立的并行进程来实现的系统,可以很方便地实现在多处理器上,或者运行在分布式的处理器网络上. 注意这种效率的提升只是潜在的,只有当应用程序可以被分解成许多真正独立的任务时,才能产生实效.如果任务之间有很强的数据依赖,这种提升往往是不可能的.
故障隔离——没有共享数据的并发进程提供了一种强大的故障隔离方法.一个并发进程的软件错误不会影响到系统中其他进程的运行.
在并发的这三种用法中,前两条并不是其本质特性,可以由某种内置的调度程序通过在进程间提供不同的伪并行(pseudo-parallel)时分方式来获得.第三个特性对于编写可容错系统的软件来说,则是本质性的.每一项独立的活动都在一个完全独立的进程中来执行.这些进程没有共享数据,进程之间只通过消息传递的方式进行通信,这就限制了软件错误造成的影响.
一旦进程之间共享有任何公共资源,譬如内存,或指向内存的指针,或互斥体等等,一个进程中的一个软件错误破坏共享资源的可能性就存在.因为消除大型软件系统中的这类软件错误仍然是一个未解的难题,所以我认为构建大型的可靠系统的唯一现实的方法就是把系统分解成许多独立的并行进程, 并为监控和重启这些进程提供一些机制,
从某种意义上讲,操作系统提供了“被编程语言设计者遗忘了的东西”.但是在 Erlang 这样的编程语言中, 操作系统是几乎不需要的.OS真正提供给 Erlang的只是一些设备驱动程序,而 OS提供的诸如进程,消息传递,调度,内存管理等等机制都不需要.
面向并发编程语言COPL
COPL编写这样的并发程序相当容易,它有三个步骤:
1.从真实世界中的活动中识别出真正的并发活动
2.识别出并发活动之间的所有消息通道
3.写下能够在不同的消息通道中流通的所有消息
1.从真实世界中的活动中识别出真正的并发活动
2.识别出并发活动之间的所有消息通道
3.写下能够在不同的消息通道中流通的所有消息
值得注意的是,COPL 提供的并发性一定是真正的并发性,因此以进程的形式存在的对象都是真正并发的,进程间的消息传递也是真正的异步消息,而不像许多面向对象语言中一样是通过远程过程调用(remote procedure call)来冒充.
Erlang世界观概览
• Processes are strongly isolated.
• Process creation and destruction is a lightweight operation.
• Message passing is the only way for processes to interact.• Processes have unique names.
• If you know the name of a process you can send it a message.
• Processes share no resources.
• Error handling is non-local.
• Processes do what they are supposed to do or fail.
思维导图