
软件架构设计策略.docx
20页架构设计则为满足架构需求的质量属性寻找适当的战术对如何实现特定 的质量属性感兴趣质量需求指定了软件的响应,以实现业务目标我们感 兴趣的是设计使用设计模式、架构模式或架构策略创建设计的“战术“是什么使一个设计具有了可移植性,一个设计具有了高性能,而另一个设 计具备了可集成性?实现这些质量属性依赖于基本的设计策略我们将对这 些称之为“战术”的设计决策进行分析战术就是影响质量属性响应控制的 设计决策战术集合称为“架构策略”架构模式以某种方式将战术打包在 一起系统设计是由决策集合组成对设计师来说,每个战术都是一个设计选择 例如,其中一个战术引入了冗余,以提高系统的可用性这是提高可用性的 一个选择但是不是唯一选择我们将每个系统质量属性的战术组织为层次形式,但是每个层次只是为了 说明一些战术,而且任何战术列表都肯定是不完成的1. 可用性战术恢复和修复是可用性的重要方面,为了阻止错误发展成故障,至少能够把 错误限制在一定的范围内,从而使修复成为可能维持可用性的所有方法包 括某种类型的冗余,用来检测故障的某种类型的健康监视,以及当检测到故 障时某种类型的恢复有些情况下,监视或恢复是自动进行的,有时需要手 动。
我们事项考虑错误检测,然后分析错误恢复,最后讨论错误预防1> 错误检测用于识别错误的3个战术是命令/响应、心跳和异常⑴命令/响应一个组件发出一个命令,并希望在预定义的时间内收到一个来自审查组件的响应可以把该战术用在共同负责某项任务的一组组件 内客户机也可以使用这种战术,以确保服务器对象和到服务器的通信路 径在期望的性能边界内操作可以用一种层级形式组织“命令/响应”错误 探测器,其中最底层的探测器对与其共享一个处理器的软件进程发出命 令,较高层的错误探测器对较低层的探测器发出命令与所有进程发出命 令的远程错误探测器相比,这种战术所使用的通信带宽更少一个组件定期发出一个心跳消息,另一个组件接收听该信息如 果心跳失败,则假定最初的组件失败,并通知错误纠正组件心跳还可以 传递数据例如,自动柜员机定期向服务器发送一次交易日志该消息不 仅起到心跳的作用,而且传送了要处理的数据识别错误的一个方法就是遇到了异常命令/响应和心跳战术在不同的进程中操作,异常战术在一个进程中操作异常处理程序通常将错误在语义上转换为可以被处理的形式2> 错误恢复错误恢复由准备恢复和修复系统两部分组成运行在冗余处理器上的每个进程都具有相同的输入,它们计算发 送给表决者的一个简单的输出值。
如果表决者检测到单处理器的异常行 为,那么就中止这一行为表决算法可以是“多数规则”或“首选组件“或其他算法该方法用于纠正算法的错误操作或者处理器的故障,通常 用在控制系统每个冗余组件的软件可以由不同的小组开发,并且在不同 平台上执行稍微好一点情况是在不同平台上开发一个软件组件,但是这样的开发和维护费用非常昂贵⑵主动冗余(热重启)所有的冗余组件都以并行的方式对事件做出响应 因此他们都处在相同的状态仅使用一个组件的响应,丢弃其他组件的响 应错误发生时,使用该战术的系统停机时间通常是几毫秒,因为备份是 最新的,所以恢复所需要的时间就是切换时间⑶被动冗余(暖重启/双冗余/三冗余)一个组件(主要的)对事件做出响应,并通知其他组件(备用的)必须进 行状态更新当错误发生时,在继续提供服务前,系统必须首先确保备用 状态是最新的该方法也用在控制系统中,通常情况是在输入信息通过通 信通道或传感器到来时,如果出现故障必须从主组件切换到备用组件时使 用⑷备件备用件是计算平台配置用于更换各种不同的故障组件出现故障时,必须 将其重新启动为适当的软件配置,并对其状态进行初始化定期设置持久 设备的系统状态的检查点,并记录持久设备的所有状态变化能够使备件设 置为适当的状态。
这通常用作备用客户机工作站,出现故障时,用户可以 离开该战术的停机时间通常是几分钟⑸Shadow操作以前出现故障的组件可以在短时间内以“shadow模式”运行,以确保在恢复该组件前,模仿工作组件行为⑹状态再同步主动和被动冗余战术要求恢复的组件在重新提供服务前更 新其状态更新的方法取决于可以承受的停机时间、更新的规模以及更新所要求的消息的数量⑺检查点/回滚检查点就是记录所创建的一致状态,或者是定期进行,或者是对具体事件做出响应有时系统会以一种不同寻常的方式出现故 障,可检测到其状态不一致在这种情况下,应该使用上一个一致状态检 查点和拍了快照后所发生的事务日志来恢复系统3> 错误预防⑴从服务中删除该战术从操作中删除了系统的一个组件,以执行某些活 动来防止预期发生的故障一个示例就是重新启动组件,以防止内存泄露 导致故障的发生如果从服务中删除是自动的,则可以设计架构策略来支 持它如果是人工进行的,则必须对系统进行设计以对其提供支持事务就是绑定几个有序的步骤,以能够立刻撤销整个绑定如果 进程中的一个步骤失败的话,可以使用事务来防止任何数据受到影响,还 可以使用事务来防止访问相同数据的几个同时线程之间发生冲突。
⑶进程监视器一旦检测到进程中存在着错误,监视进程就可以删除非执 行进行,并为该进程创建一个新的实例,就像在备件战术中一样,初始化 为某个适当的状态总结了上面讨论的战术2, 可修改性战术可修改战术的目标是控制实现、测试和部署变更的时间和成本把可修改 性战术根据其目标进行分组一组可修改性战术目标是减少由某个变更直 接影响的数量这组称为“局部化修改”另一组可修改战术的目标是限 制对局部化的模块的修改这组称为“防止连锁反应”两组之间的差别 是有直接受变更影响的模块(那些调整其责任来完成变更的模块)间接受 变更影响的模块(那些责任保持不变,但必须改变其实现来适应直接受影 响的模块)第三组战术的目标是控制部署时间和成本我们把这组战术 叫做“延迟绑定时间”1>局部化修改目标是在设计期间为模块分配责任,以把预期的变更限 制在一定范围内其战术有:维持语义的一致性、预期期望的变更、泛化该 模块、限制可能的选择⑴维持语义的一致性语义的一致性是在模块中责任之间的关系目标是 确保所有这些责任都能够协同工作,不需要过多地依赖其他模块该目标是 通过选择具有语义一致性的责任来实现的耦合和内聚指标是度量语义一致 性的尝试,但它们遗漏了变更的上下文。
相反根据一组预期的变更来度量语 义一致性其中一个子战术就是“抽象通用服务”通过专门的模块提供通 用服务通常被视为支持重用但是抽象通用服务也支持可修改性如果已经 抽象出了通用服务,那么对这些通用服务的修改只需要进行一次,而不需要 在使用这些服务的每个模块中都进行修改此外,对使用这些服务的模块的 修改不会影响到其他用户不仅支持局部化修改,而且还能够防止连锁反应 抽象通用服务的示例就是应用框架的使用和其他中间件软件的使用⑵预期期望的变更考虑所预想的变更的集合提供了一个评估特定的责任 分配的方法基本的问题是“对于每次变更,所建议的分解是否限定了为完 成变更所需要修改的模块的集合?”一个相关的问题是“根本不同的变更会 影响相同模块吗?”这与语义一致性有什么不同呢?根据语义一致性分配责 任,假定期望的变更在语义上是一致的预测期望变更的战术不关心模块责 任的一致性,它所关心的是使变更的影响最小在实际中很难单独使用该战 术,因为不可能预期所有变更基于此原因,我们通常结合语义一致性来使 用该战术⑶泛化该模块使一个模块更通用能够使它根据输入计算更广泛的功能 可以该输入看作是为该模块定义了一种语言,这可能会如同使常数成为输入 参数一样简单;也可能如同把该模块实现为解释程序,并使输入参数成为解 释程序的语言中的程序一样复杂。
模块越通用,越有可能通过调整语言而非 修改模块来进行请求变更⑷限制可能的选择修改(尤其是在产品线中的修改)的范围可能非常大, 因此可能会影响很多模块限制可能的选择将会降低这些修改所造成的影响 例如,产品线的某个变化点可能允许处理器的变化将处理器变更限制为相 同家族的成员就限制了可能的选择2>防止连锁反应修改所产生的一个连锁反应就是需要改变该修改并没 有直接影响到的模块例如,改变了模块A以完成某个特定的修改,那么 必须改变模块B,这仅仅是因为改变了 A,在某种意义上来说,是因为它 依赖于模块A确定的8中类型的依赖① 语法• 数据要使B正确编译或执行,由A产生并由B使用的数据类型 或格式必须与B所假定的数据的类型或格式一致• 服务要使B正确编译和执行,由A提供并且由B调用的服务的签名必须与B的假定一致② 语义• 数据要使B正确执行,由A产生并由B使用的数据语义必须与 B所假定的数据的语义一致• 服务要使B正确执行,由A提供并且由B调用的服务的语义必 须与B的假定一致③ 顺序• 数据要使B正确执行,它必须以一个固定的顺序接收由A产生 的数据• 控制要使B正确执行,A必须在一定的时间限制内执行。
④ A的一个接口身份A可以有多个接口要使B正确编译和执行, 该接口的身份(名称或句柄)必须与B的假定一致⑤ A的位置(运行时)要是B正确执行,A运行的位置必须与B的假 定一致⑥ A提供的服务/数据的质量要是B正确执行,设计A所提供的数据 或服务的质量的一些属性必须与B的假定一致例如,某个特定的传 感器所提供的数据必须有一定的准确性,以使B的算法能够正常运行⑦ A的存在要是B正常执行,A必须存在例如,如果B请求对象A提供服务,而A不存在并且不能动态创建,那么B就不能正常执行⑧ A的资源行为要使B正常执行,A的资源行为必须与B的假定一 致这可以是A的资源使用(A使用与B相同的内存)或资源拥有(B 保留了 A认为属于它的资源)没有任何一个战术一定能够防止语义变更的连锁反应首先讨论与特定模 块的接口相关的那些战术一一信息隐藏和维持现有的接口一一然后讨论 一个违反了依赖链的战术一一仲裁者的使用⑴信息隐藏信息隐藏就是把某个实体(一个系统或系统的某个分解) 的责任分解为更小的部分,并选择使哪些信息成为公有的,哪些信息成为 私有的可以通过指定的接口获得公有责任信息隐藏的目的是将变更隔 离在一个模块内,防止变更扩散到其他模块。
这是防止变更扩散的最早的 技术它与“预期期望的变更有很大关系”,因为它使用那些变更作为分 解的基础⑵维持现有的接口如果B依赖于A的一个接口的名字和签名,则维持 该接口及其语法能够使B保持不变当然如果B对A有语义依赖性,那么 该战术不一定会起作用,因为很难屏蔽对数据和服务的含义的改变此外, 也很难屏蔽对服务质量、数量质量、资源使用和资源拥有的依赖性还可 以通过将接口与实现分离来实现该接口的稳定性这使得能够创建屏蔽变 化的抽象接口变化可以包含在现有的责任中,或者可以通过用模块的一 个实现代替另一个实现来包含变化实现该战术的模式包括:• 添加接口大多数编程语言允许多个接口可以通过新接口提供最 新的可见的服务或者数据,从而使得现有的接口保持不变并提供相同 的签名• 添加适配器给A添加一个适配器,该适配器把A包装起来,并提 供原始A的签名• 提供一个占位程序A如果修改要求删除A,且B仅依赖于A的签名,那么为A提供一个占位程序能够使B保持不变⑶限制通信路径限制与一个给定的模块共享数据的模块也就是说, 减少使用由该给定模块所产生的数据的模块的数量,以及产生由该模块所使 用的数据的模块的数量这会减少连锁反应,因为数据产生/使用引入了导致 连锁反应的依赖。












