面向容错架构
系统需求
语言需求
库需求
强隔离进程。
层次化的监督者模型,一个进程发生故障时,整个系统可以作出相应的调整。
并发处理和顺序化处理分开。把并发的部分抽象出来。
假设软件包含错误,寻求在软件包含错误的情况下,构建可靠系统的方法。
构建容错系统的要求
可以在语言中解决的要求
可以在标准库中解决的要求
多线程的资源共享模型中,线程很难真正被隔离起来,因此可能导致,一个线程中的错误会传播到另一个线程中,这就破坏了系统内部的坚固性。
架构:一种软件系统组织方式,对系统中的元素、接口、交互进行决策和定义。
思考架构:
问题领域
背后的哲学和原则
构建指南,如何来规划一个系统?
预定义的小部件
描述方式 UML?!
配置方式?如何启动、停止、配置我们的系统?
架构基础设施:用一组相互通信的进程组织起我们的系统。通过枚举系统中所有的进程,并定义出进程间消息传递的通道,就可以很方便的定义良好的组件。并且可以对这些组件进行单独实现和测试。
故障隔离对于编写可容错系统的软件来说,才是本质性的。
构建大型的可靠系统的唯一方法就是:把系统分解成许多独立的并行进程,并为监控和重启这些进程提供一些机制。
使用预先定义好的协议,不同类型的实例之间可以具有相同的消息传递接口。也就实现了多态的效果。
把一个问题分解成多个并发进程时,可以让所有的进程响应同一种消息,并且可以让所有的进程都遵循相同的消息传递接口。
面向并发的语言COPL
应该具备的:
支持进程
进程被高度隔离
进程必须有唯一的不可仿照的标识符Pid
进程间没有共享状态,只能互相发消息来通信,只要知道进程的Pid,就可以向它发消息
必须对消息传递进行不可靠假设
一个进程可以检测到另一个进程中的故障,并可以知道发生故障的原因
故障的原因也不一定反映真实情况,比如收到一个进程已经死亡的通知消息,然而事实上是发生来一个网络错误。
进程隔离 意味着消息传递必须是异步的。如果进程通信采用同步方式,那么当消息的接收者偶然发生一个软件错误时,就会永久阻塞住消息的发送者,破坏了隔离的特性。发送方不知道消息什么时候到达接收者,知道消息被正确送达的唯一方法,就是收到针对这条消息的一个确认消息。
顺序化编程语言提供的并发拓展包括了:
锁
信号量
共享数据保护
可靠消息传递
命名进程 所有进程都知道自己的名字,以及由它们创建的子进程的名字。
name distribution question :
消息传递 :
消息传递是原子化的
有序
消息中不能包含指向当前进程中的数据结构的指针
构建可靠系统的操作系统和编程语言要求:
必须支持并发
进程内部错误捕获和 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
函数调用
要么返回一个值
要么发生异常,使得进程奔溃
我们可以捕捉这个异常,做些补救措施,不让进程奔溃。奔溃的进程,会将它奔溃的消息发送给所有它绑定到的linker
和monitor
。
catch Expr % 假如 Expr 内部有异常, 经过 catch 处理后,会变成 `{'EXIT', W}` 值
抽象出并发,避免在同一个模块里,既有并发的代码,又有顺序化的代码。所有的并发和错误处理都在通用性框架中完成,所有顺序化代码都是插件。框架对插件隐藏起并发和容错机制的细节。
一个包含大量进程的系统中,消息顺序化传递,死锁问题,活锁问题,会使得并发系统非常难以理解和编写。
纯函数 : 是一个不管处于什么上下文,只要使用同样的参数,就能返回相同的结果。它不受环境的影响,也没有副作用改变环境。
一般,一个函数做了下面事情,就会产生副作用:
发送一个消息
接收一个消息
调用exit
调用任何一个会改变进程环境或操作模式的BIF