目录

面向容错架构

系统需求

语言需求

库需求

强隔离进程。

层次化的监督者模型,一个进程发生故障时,整个系统可以作出相应的调整。

并发处理和顺序化处理分开。把并发的部分抽象出来。

假设软件包含错误,寻求在软件包含错误的情况下,构建可靠系统的方法。

构建容错系统的要求

  • 可以在语言中解决的要求

  • 可以在标准库中解决的要求

多线程的资源共享模型中,线程很难真正被隔离起来,因此可能导致,一个线程中的错误会传播到另一个线程中,这就破坏了系统内部的坚固性。

架构:一种软件系统组织方式,对系统中的元素、接口、交互进行决策和定义。

思考架构:

  • 问题领域

  • 背后的哲学和原则

  • 构建指南,如何来规划一个系统?

  • 预定义的小部件

  • 描述方式 UML?!

  • 配置方式?如何启动、停止、配置我们的系统?

架构基础设施:用一组相互通信的进程组织起我们的系统。通过枚举系统中所有的进程,并定义出进程间消息传递的通道,就可以很方便的定义良好的组件。并且可以对这些组件进行单独实现和测试。

故障隔离对于编写可容错系统的软件来说,才是本质性的。

构建大型的可靠系统的唯一方法就是:把系统分解成许多独立的并行进程,并为监控和重启这些进程提供一些机制。

使用预先定义好的协议,不同类型的实例之间可以具有相同的消息传递接口。也就实现了多态的效果。

把一个问题分解成多个并发进程时,可以让所有的进程响应同一种消息,并且可以让所有的进程都遵循相同的消息传递接口。

面向并发的语言COPL应该具备的:

  • 支持进程

  • 进程被高度隔离

  • 进程必须有唯一的不可仿照的标识符Pid

  • 进程间没有共享状态,只能互相发消息来通信,只要知道进程的Pid,就可以向它发消息

  • 必须对消息传递进行不可靠假设

  • 一个进程可以检测到另一个进程中的故障,并可以知道发生故障的原因

故障的原因也不一定反映真实情况,比如收到一个进程已经死亡的通知消息,然而事实上是发生来一个网络错误。

进程隔离 意味着消息传递必须是异步的。如果进程通信采用同步方式,那么当消息的接收者偶然发生一个软件错误时,就会永久阻塞住消息的发送者,破坏了隔离的特性。发送方不知道消息什么时候到达接收者,知道消息被正确送达的唯一方法,就是收到针对这条消息的一个确认消息。

顺序化编程语言提供的并发拓展包括了:

  • 信号量

  • 共享数据保护

  • 可靠消息传递

命名进程 所有进程都知道自己的名字,以及由它们创建的子进程的名字。

name distribution question :

消息传递 :

  1. 消息传递是原子化的

  2. 有序

  3. 消息中不能包含指向当前进程中的数据结构的指针

构建可靠系统的操作系统和编程语言要求:

  • 必须支持并发

  • 进程内部错误捕获和 recover

  • 故障检测,包括本地异常和远程异常

  • 可以知道故障的原因

  • 支持热更新

  • 持久存储,以便恢复一个已经奔溃的系统

编写并行系统的编程语言必须包括:

  • 内部可以 recover 错误

  • 轻量级进程

  • 允许一个进程监控另一个进程

  • 知道一个进程的 Pid,就可以向它发消息,无论是在本地还是远程

  • 动态代码升级

库要求:

  • 持久存储

  • 设备驱动程序

  • 代码升级

  • 运行时

Behaviour 库 or 模式

supervisor 监督者模型

gen_server 一种方便实现实现 CS 模式的 模板代码 or 框架代码 or 接口。

gen_event 一种方便实现 事件驱动式应用 的 模式代码 or 框架代码 or 接口。

gen_fsm 一种方便实现 有限状态机 应用 的 模式代码 or 框架代码 or 接口。

编写可容错应用软件的核心部件就是那个 supervisor 。

一个硬件系统要做到容错:

  • 发现故障,立刻停止运行,而不是执行可能不正确的操作

  • 故障曝光,发生的故障,要立刻通知到其他的组件,故障原因必须交代清楚

  • 持久化存储

进程以防御性编程的方式达到“速错”,对所有的输入参数,中间结果,和数据结构进行校验,一旦检测到错误,就立刻报告,并且停止运行。而 Erlang 中,并不建议“防御性编程风格”?!可能是觉得没有必要。

达到软件可容错性的关键在于:

  • 按照进程、消息来划分软件模块

  • 错误限制在速错的软件模块内

  • 由成对的进程来容纳硬件故障和瞬时的软件故障 Erlang 中由监督树实现

  • 由事务机制,来提供数据和消息的完整性,Erlang中由 mnesia 库实现

编译器的设计者总是将硬件系统想的很完美,主张通过静态的编译时类型检查来提供良好的隔离性。

而操作系统的设计者们,主张运行时检查。

Erlang

观念:一切皆进程、进程强隔离、生成销毁轻量级、消息传递是唯一交互方式、独有Pid、知道Pid就可以发消息、错误处理非本地化、let it crash

编写可容错软件的核心在于,限制错误的扩散,进程正是错误扩散的边界。

发送消息

当向一个进程发送消息时,该消息存放在一个属于该进程的邮箱中mailbox ,进程使用recive从邮箱中获取第一条消息,然后和模式一一匹配,如果没有与任何模式匹配成功,则会被转移到一个临时的“保管队列”中。临时保管的消息在下一条消息消费成功时,会放回到邮箱中。

注册进程名

register(Name, Pid)

% 注册后,可以通如下式子向进程发消息
Name ! Msg 

函数调用

  • 要么返回一个值

  • 要么发生异常,使得进程奔溃

我们可以捕捉这个异常,做些补救措施,不让进程奔溃。奔溃的进程,会将它奔溃的消息发送给所有它绑定到的linkermonitor

catch Expr % 假如 Expr 内部有异常, 经过 catch 处理后,会变成 `{'EXIT', W}` 值

抽象出并发,避免在同一个模块里,既有并发的代码,又有顺序化的代码。所有的并发和错误处理都在通用性框架中完成,所有顺序化代码都是插件。框架对插件隐藏起并发和容错机制的细节。

一个包含大量进程的系统中,消息顺序化传递,死锁问题,活锁问题,会使得并发系统非常难以理解和编写。

纯函数 : 是一个不管处于什么上下文,只要使用同样的参数,就能返回相同的结果。它不受环境的影响,也没有副作用改变环境。

一般,一个函数做了下面事情,就会产生副作用:

  • 发送一个消息

  • 接收一个消息

  • 调用exit

  • 调用任何一个会改变进程环境或操作模式的BIF