Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1535061
  • 博文数量: 386
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4121
  • 用 户 组: 普通用户
  • 注册时间: 2016-07-04 13:04
  • 认证徽章:
文章分类

全部博文(386)

文章存档

2019年(24)

2018年(135)

2017年(159)

2016年(68)

我的朋友

分类: 嵌入式

2017-11-27 22:29:56

 

经典软件架构模式

经典软件架构模式


  (一)      架构模式是什么
  (二)      分层模式案例
  (三)      微核模式案例
  (四)      管道与过滤器案例
  (五)      MVC模式案例
  (六)      REST模式案例
  (七)      SOA模式案例
  (八)      如何选择架构模式案例
  (九)      业界应用模式的经典案例

  架构模式是什么

  软件架构模式,诞生于软件开发的最大难题——需求变更。由于需求变更,导致了大量项目因为超出预算的人力、时间而归于失败。软件开发成本有限的,但需求变更似乎是无限的,这成为了一个非常难解决的问题。

经典软件架构模式



  软件需求变更的结果,基本上就是对于软件代码的修改。而软件代码的修改却是程序员们最头疼的事情。因为一些大型系统,其代码根本就无法完全看懂,即便 能了解部分细节,在着手修改的时候,也会碰到“触一发而动全身”的问题:因为有些功能的修改,需要修改整个系统的很多部分,导致了无穷的BUG。另外一个 致命的问题,就是在紧迫的时间内,对于代码的修改往往只能依赖有限的一个或几个程序员,只有他们对系统是最熟悉的。但是面临巨大的工作量,几乎无法让更多 的程序员参与进来,其他人只能干瞪眼。一旦熟悉系统的程序员离职,有可能就代表了整个系统无法维护。即便是系统能分割给几个人负责,在“集成”几个部分的 代码的时候,其调试和除错的工作,又常常是旷日持久的,因为那些从来没协作过的代码,隐藏着大量的误解和不兼容问题。——这一切的根源,其实只是一个最简 单的事实,就是系统中对于“代码耦合”的结构问题。糟糕的代码耦合让整个系统变得难以理解、难以修改、难以分工、难以集成。

经典软件架构模式



  针对代码耦合的问题,软件界进行了大量的理论研究和实践,最后发现:系统的架构设计,是改善耦合的最好方式。架构设计的本质,就是:

  划分耦合的单位——也就是划分模块。系统应该划分成什么样的模块,代表了设计者对于系统应对的需求的基本理解。一旦能清晰的划分出模块了,其代码耦合就有了最基本的范围。而模块本身也是提示程序员理解系统的基本单位。

  规范耦合的形式——代码耦合的形式有很多种,如直接调用、事件响应、消息队列等等,这些形式提供了代码耦合的不同特征。直接调用的代码在静态阅读的时 候非常容易理解,而事件响应则提供了运行时耦合的好处。耦合的形式还有另外一层含义,就是代码耦合的规范:那些模块之间应该直接耦合,哪些不能耦合,是否 应该加入中间层次等等。

  这两个关于耦合的设计,就形成了各种架构设计。

经典软件架构模式



  在软件界多年的架构设计工作之后,人们总结出一些经验,这些经验被成为“架构模式”。架构模式包含了“名称”“适应场景”“模块定义”“模块关系”这 几个部分。没有任何一种架构模式是万能的,所以每个模式都必须有“适应场景”。而模式本身的内容,就是通过“模式定义”和“模块关系”两个层面的规定来表 现出来。除了模式的本身内容,为了理解某种特定的架构模式,人们还会附带上一些应用案例,好让学习者能通过实例来理解这种设计的真谛。最后,还会有些关于 某种设计模式的实现手段——在面向对象的设计思想下,往往会使用一种或者多种“设计模式”来作为实现。当然只要是符合架构模式的耦合规定的,都可以成为某 种架构模式的实践,但是使用软件业常见的“设计模式”,还是能比较轻松的去用代码来实现这些架构。

经典软件架构模式



  分层模式

  下面我们开始第一个架构模式的介绍,先看一个案例:

经典软件架构模式


【此案例并非完全真实情况,有一定提炼修改成分】


  这是一个有着复杂功能的多人在线社区,其服务器端是我们需要讨论的重点。这个产品的服务器端必须满足多样的功能:玩家移动到不同的场景中,玩家可以换 上不同的服装,可以互相加好友并且聊天,同时还有广播频道的聊天,每个玩家还有自己的资料库和背包,另外还有各种运营活动。

  在最初的开发过程中,我们针对每个需要开发的功能,建立了一个模块。这些模块通过单独和客户端、数据库的操作,完成所需功能。如果要开发新功能,就重 新写一个这样的模块。这种架构设计在一开始是非常有效的,产品功能被不断的开发出来,模块的数量也在增多,但是也潜藏了一个问题。

  这个问题的爆发是随着一个叫做“任务系统”的功能而出现的。因为任务系统本质上是需要很多其他模块的功能提供支持。如需要玩家去某个场景(场景模 块),获得某个东西(背包模块),然后添加一个好友(好友模块),或者换上某个服装,说一句话等等……这样的任务功能的实现,被迫要修改很多个模块的代 码,因为每个模块都只有最基本的“自由使用”功能的代码,编程接口都仅仅是面向客户端的,而数据结果都是直接SQL到数据库的。这种需要组合的功能请求, 以及获得功能的结果状况,都是其接口上没有的。这导致了非常复杂的,持续的代码修改。因为任务的内容可是时常会变化的哦!

经典软件架构模式



  在分析了问题之后,我们决定重构整个架构,我们把架构从一字排开的设计,修改成为可以多个层次互相调用的模块。这些模块直接的接口,有面向客户端的, 也有面向其他模块的,这样我们就能直接调用那些现成的功能,组合开发出更复杂强大的功能。不管任务系统如何变化,我们都可以不用重写那些已经实现的功能, 这让整个系统成为可以应对这种需求变化的关键。

经典软件架构模式



  实际上,这样的架构正是一个著名的架构模式——分层模式。

  分层模式的规定非常简单而有效:

  每个模块都必须属于某个层次,提供给“第N 1层”(上层)服务;同时委派任务给“第N-1层”的模块。

  任何一个模块,都不得逆层次调用:属于第N层的模块,不得调用(耦合)第N 1层或以上层次的模块。

  任何一个模块,都不得跨层调用:属于第N层的模块,不应该调用第N-2层或更下层的模块。

经典软件架构模式



  分层模式是架构中最基本的模式,但是也是我们开发中最被忽视的模式。我们开发中往往没有去定义代码的“层次”,仅仅以“功能”纵向划分模块,没有按实现层次横向切分。

  门面模式、策略模式都常常用来实现分层架构。

  上文所说的例子,后来改造成了分层模型:

  1)重复代码减少了,功能开发更快了——一顶层开发只需要学习下层类库,就可以开始开发;近似功能模块被统一,修BUG覆盖面更好。

  2)性能提升代码能更好的覆盖了——集中了通用实现代码,也集中了优化部分。

  3)可以并行开发,资深人员负责底层,一般人员负责上层。

  总结一下分层模式:

  “模式的模式”——强大同时灵活

  适应:集成不同类型功能 ——当我们需要把很多不同功能的代码集成起来的时候,这种模式提供了最合理的结构。能让我们的代码有足够的灵活性去应对需求变更。

  不适应:简单业务模型——如果系统本身不复杂(或者叫做可预期的修改很少),建立各种层次,然后为了符合层次间调用的规定,会增加很多不必要的代码,陷入过度设计的泥坑。

  方法论:以业务逻辑特征建模 ——使用分层模式,往往需要我们在大脑里对问题领域进行层次抽象,这种抽象最可信赖的原则,就是按照业务逻辑去做。比如现实业务中有一个角色,我们就建立 一个角色的模块;如果我们有一个场景,就以此为名建立一个这样的模块……。以业务逻辑建立的模块,本身也会让系统更容易被理解,因为在代码里能找到和现实 中一一对应的概念。

  设计模式实现:

  门面模式 ——我们对于每个模块或者每个层次都会设计一个“门面”来降低耦合的复杂程度。
  策略模式——抽象层次会隐藏底层的实现细节,这就是策略模式最基本的设计,我们往往会把上层作为功能接口,下层作为可选的策略来实现。

  微核模式

  我们来看第二个案例,这是一个多人在线的游戏社区。在这个社区中,除了可以聊天、换衣服之外,还有大量的小型、中型、甚至大型游戏可以玩。这些游戏的内容,都会和整个社区以及角色集合起来,有点像几十个不同的游戏融合成的一个大游戏。

经典软件架构模式


【此案例并非完全真实情况,有一定提炼修改成分】


  我们这次要讨论的是客户端的架构。由于这个游戏,有一个统一的社区场景,还有大量的游戏内容,再加上覆盖在场景上的UI,所以我们针对每个功能,设计 了模块。如“场景:码头”就是一个模块,包含了场景中的美术资源和其他所有功能。每个游戏也是一个单独模块,玩家开始这个游戏,就整个进入这个单独的模 块。退出游戏则返回到之前的场景模块中。而UI也是分成主工具栏,聊天界面等模块,每个可以打开的面板,都是一个单独的模块,比如背包就是一个模块。

  这种设计在初期有很大的好处,因为每个模块都可以单独的开发、修改和测试。一个模块包含了所有的代码和资源,是一个完整的整体。对于添加功能,只需要增加一个这样的模块,然后把连接代码组合到就的模块中就大功告成了。

  但是这种架构并没能维持很久,因为策划要求加入一个“商店系统”,需求是:玩家能够在场景中触发商店的界面进行购买操作,同时在每个游戏中,也要能购 买物品,但是界面却不能是商店本地的UI界面,因为要和游戏中的风格结合。同时,玩家还要能从聊天界面中,通过点击文字连接,去打开针对物品的商店界面。 这种需求同样需要修改多个模块,导致工作量很大。加上每个模块都是单独开发的,连开发人员都不是同一个人,所以这种需求要实现起来似乎困难重重。同时作为 修改速度非常快的客户端部分,策划和程序员们也都迫切希望能维持之前的能单独调试的结构。

经典软件架构模式



  因此,我们通过改进架构,实现了一个“不可见”的客户端模块——“统一处理框架”,这个框架负责处理网络通信、界面事件、模块事件三个主要的模块关联 操作。每个原来的模块,都必须使用这个“统一处理框架”来和其他模块互动。另外,我们还对所有的游戏整合了一个“游戏系统”的模块,对游戏中统一的开始游 戏、结束游戏、物品产出、物品消耗等功能做了接口定义。所有的游戏都需要符合“游戏系统”的需求,实现其要求的接口,这样整个系统任意的代码,都可以使用 这些接口去控制一个具体的游戏行为。最后,我们把场景的基础代码和具体美术资源、具体逻辑代码做了拆分,形成了一个“场景播放器”模块,这个模块能让任意 的代码,实现对任何一个场景的切入和退出。通过这样的修改,对于商店系统的实现,仅仅是调用“统一处理框架”的API,发送几个功能事件,具体的图形播放 和逻辑处理都会由提供功能的模块实现。同样,这些每个模块还是可以单独运行和单独调试,他们之间的关系,都通过发送事件和接收事件来处理就可以了。你可以 发送一个没人处理的事件,也可以自己虚拟一个需要接收处理的事件,这样就可以调试任何一个模块。

经典软件架构模式



  这个架构,实际上就是参考了著名的“微核模式”而构成的。微核模式的核心是:

  基本服务封装到微核 ——主要是一些每个模块都会用到的功能。

  内、外服务器负责功能实现(插件系统)——外服务器负责整合某个特定领域的抽象。内部服务器负责通用的功能抽象,如网络功能、日志等。

  应用程序、服务器通过微核通信 ——这是最核心的部分,一个基于“事件”的运行时交互系统,用来沟通各个不同的模块。

  外部服务和应用程序的差别,在于是否通过一个适配器来和微核耦合。这个适配器实际上能让应用程序模块更换不同的微核,这在于可移植系统上很重要。

  微核模式实际上是一种特化的分层模式,他把最底层的功能封装层“微核”,同时把各个模块的交互规定为“运行时的事件”。这样简化了的3层架构,提供能非常好的模块独立性。

经典软件架构模式



  微核模式也就是我们常见的“插件系统”——模块高度独立,可移植。

  适应:运行时多模块协作系统 ——如果我们需要系统可以运行起来之后,动态的加载和运行不同的模块,微核将是最合适的架构。在许多需要运行时扩展的系统中,比如一些IM软件想要带上各种和好友关系有关的功能;或者是希望同样的代码能在不同的“平台”、“环境”、“操作系统”下运行,都会使用这种架构。

  不适应:无需运行时多模块协作系统——如果软件本身不会分为多个需要不定时启动、运行的模块,就不必要实现这个稍嫌复杂的架构。

  方法论:实现运行时耦合——这个架构的核心,是把代码的直接耦合,变成运行时的动态调用,因此我们会使用事件机制、消息队列等手段,把代码的调用和具体的“数据”关联起来,从而避免了代码直接写死。

  设计模式实现:

  观察者模式——观察者模式是最常用的运行时耦合的编码手段,在其他的架构中,我们还将见到他的身影。他是如此的常见,以致于JAVA直接把这个模式的实现代码收纳标准库JDK当中了。

  管道和过滤器模式

  第三个案例是一个WEB的例子,但并不是简单的CGI加数据库,而是一个在网站上点播图文铃声短信、订阅各种短信服务的系统。从界面上就可以看到,这 个网站可以下发不同的歌曲铃声,各种手机格式的图片,还有一些特别的文字短信,这些称之为“点播”服务。网站上搜集了大量的铃声、图片、文字来提供此功 能。另外一类称之为“订阅”服务,就是每天会不定时发一些最新的新闻、黄历、心理测试等短信内容给订阅了服务的手机号。最后一类比较复杂,属于交互性服 务,比如彩票、交友、股市、邮箱,这些服务让用户可以通过发送短信来和系统互动,比如查某个股票的价格,购买彩票,发布自己的交友信息,查收邮件内容等。 总体上此网站的功能比较复杂,但大部分是把信息从互联网发送到手机上。

经典软件架构模式


【此案例并非完全真实情况,有一定提炼修改成分】


  为了建设一个这样的网站,一般来说最开始,都是从两个模块开始:一个WEB网站,一个收发短信的进程。WEB网站最常见的方案,就是用PHP或者其他 类CGI的程序,连接一个数据库,然后把页面展现给用户。因此我们按照业务模块,把这些PHP划分成图片、铃声、文字短信、新闻、黄历、邮件……等等的模 块,每个模块负责展现业务内容。当这些模块需要发送短信的时候,就把要发的短信的内容写到数据库的某个表里面。然后短信收发进程就从数据库里读取内容,发 往各个不同的运营商的短信网关。

经典软件架构模式



  这样的系统本身是问题不大的,但是随着用户量的增长,首先出现了一个要命的情况:数据库负载很高。因为用户访问WEB网站需要读写数据库,发送短信也 要先写数据库,短信收发进程也要先读后删数据库记录,这让数据库不堪重负。或者说,本身让数据库来承担这种“消息队列”通讯就是错误的用法。同时,也发生 了另外一个问题:由于接入的手机运营商越来越多,当时都是分省接入的,短信收发进程变的非常复杂,其原因是发送短信的内容并非只有收、发手机号和发送文本 这3个字段而已,而是还有很多其他的逻辑要处理。譬如说每条短信都要填一个“业务代码”,代表了短信的资费信息,然而每个省运营商申请的代码都是不同的, 一条新闻发往不同的短信网关,需要填写不同的“业务代码”。有时候还会需要“套用”和“借用”一些业务代码。这样的复杂逻辑代码都塞到每个短信发送进程 里,随着频繁的商务关系的变化,全部修改起来非常麻烦。

  因此我们做了一次重构,核心思想是把发送短信的各个流程,划分到不同的模块里,形成一个工作流。然后使用文件代替数据库,作为消息队列;加入专门处理 短信商务关系的业务模块,统一解决类似“业务代码”的问题。这样处理之后,所有的处理过程都被分割在单一的模块里,可以很方便的单独修改。

经典软件架构模式



  这种架构,实际上就是“管道和过滤器”架构:把一个需要多重步骤处理的数据流程,分割为多个独立的处理模块,称之为“过滤器”,然后根据业务需要,把 “过滤器”连接成处理数据的“管道”。所有的代码,都要符合“过滤器”的要求,也就是要针对特定接口“数据”的输入端和输出端。而且这些输入和输出端是可 以互相连接的。

经典软件架构模式



  “管道和过滤器”——非常适合分布式系统

  适应:面向数据处理。在很多情况下,我们都是需要处理某种特定格式的输入数据,然后结果输出成某种数据。这种“单进单出”的模型是非常常见的情况,比 如在编译器、Shell脚本系统、网络处理系统。特别是如果你需要让多个服务器共同工作,最简单的方式就是写很多个进程充当“过滤器”,然后监听 TCP/IP端口,等待需要处理的网络包,结果则是通过发送网络包给另外一个后续步骤处理的进程。由于过滤器进程天然的可以使用操作系统的网络功能,因此 很容易让多个不同服务器上的进程系统工作起来。

  不适应:输入输出复杂系统。假如是做一个人机交互的GUI程序,或者是一个输入有多个输出的程序,这些都打破了“单进单出”的模型,就不太适合这种模式了。当然你也可以强行这么做,结果就是要写很多代码把大量的输入和输出数据整理成单个输入、输出的的数据模型。

  方法论:关注数据处理步骤。构造这种模式的方法,最重要的是切法数据的处理步骤,一般来说我们会根据处理的计算消耗,以及处理步骤所需的数据模型复杂 程度来切分。在分布式系统中,我们会希望不要让一个过滤器的负担过重,影响服务器负载的均衡;在其他的系统中,我们希望每个过滤器的编程接口尽量简单,而 不是要面临大堆复杂的状态值。

  设计模式实现:

  装饰器模式——我们可以通过组装多个过滤器对象,构造出一条处理管道。java.io库就是一个最典型的例子。

  责任链模式 ——责任链就是一个典型的处理管道,我们可以用这种模式几乎一一对应的实现出管道和过滤器模式。

  MVC模式

  当我们要写一个GUI程序的时候,基本上都想到这种著名的架构模式。因此我们举一个简单的例子:MP3播放器。这个系统有播放按钮、进度条、播放状态、播放列表、歌词等部分,这些部分都要结合到一起来适应正在播放的一首歌。

经典软件架构模式


【此案例并非完全真实情况,有一定提炼修改成分】


  如果我们只是简单的思考如何实现,往往会做的比较简单:我们按照可以显示的部分,划分出几个模块,譬如播放按钮模块,进度条模块,播放状态(歌曲标 题)面板,歌词面板等等。然后在所有可以操作的GUI控件上增加操作事件函数。比如按下播放按钮,各个面板和进度条就开始显示对应的信息。但是,如果面板 和按钮变多了,我们会发现写在事件函数中的代码会越来越复杂,因为整个界面的各个部分,都是关联的。按下一个按钮,可能需要修改多个面板,一旦写漏了一个 逻辑,那个面板的显示就是错误的。随着UI界面变得复杂,这种关系会呈几何式增长,最终代码会变得不可维护。

经典软件架构模式



  但是实际上,我们只要稍加思考就会发现,只要加入一个表示“歌曲列表”的不可见的状态对象,就能大大简化这种关系。

经典软件架构模式



  由于操作UI基本上都是去修改播放的状态的,所以他们只需要管理这个对象即可。而所有的显示UI,都是从这个播放状态对象读取数据,不管具体要如何操 作。而这个结构里的“播放状态对象”,就是MVC模型里面的Model,操作UI中的事件函数,就属于Controllor,而显示UI的模块,就是 View了。

经典软件架构模式



  MVC模型本身并非是只有一种标准方案,而是存在多种不同的描述的。但是他们基本上都会把系统的模块定义为M\V\C三类。对于这三类模块之间的耦 合,定义了一些必须遵守的原则。一般来说,会把程序的状态放在M型模块里,而供用户操作的代码,放在Controllor类型的模块,View类型的模块 就专心的负责根据Model的数据来做效果显示。注意MVC模型并非“三层结构”!因为彼此之间形成对客户程序的屏蔽,而是互相之间都有双向的管理。

  “GUI首选”——交互界面频繁改

  适应:多变的交互界面。此模式1978年由Xerox PARC发明,用来配合Xerox发明的GUI界面的开发。是一种非常专用的架构。

  不适应:非交互领域。需要注意的是PC程序中除了GUI部分,譬如游戏里面的场景——往往不是界面UI的,坚持使用MVC往往容易造成不必要的复杂。

  方法论:以交互特征划分模块。区分出系统中哪些是需要“人”来操作的,哪些是用来单纯“输出信息”的,哪些是内部的状态,就是构造MVC系统的关键。

  设计模式实现:

  观察者模式——一般来说在View对Controllor的触发上,为了避免直接的耦合关系,都会使用观察者模式。有些做法下,Model会和对应View的“同步绑定”,他们的刷新事件,也是通过观察者模式的Update事件来通知。

  REST模式

  让我们回到服务器端开发。一直以来,互联网服务就以数据互通为最重要的业务特性。我们来看看一个微博系统的案例。

经典软件架构模式


【此案例并非完全真实情况,有一定提炼修改成分】


  微博作为一个非常常用的“用户制造内容”服务,一直都是各种互联网网站最喜欢的项目之一。微博本身的功能抽象并不复杂:发微博、读微博、发评论、看评 论。但是需要微博数据的外部系统却很多,比如微博自己就有WEB平台、手机平台、Pad平台,在各种合作厂商那里,又要提供可以发微博晒产品、真人秀、炫 耀成就……等等。可以说微博是一个结合大量其他应用系统的信息中心。初期的产品设计,可能会比较简单:

经典软件架构模式



  在这个模型里面,我们一般把功能分层两组,一组是本系统的服务器,如WEB平台和手机平台。另外一组是开放给第三方的接口服务器。我们希望这样能分流 负载,并且隔离不同平台的故障。但是,随着业务的发展,策划有可能对最简单的微博功能,要求增加一下活动,比如“集赞抽奖”之类的,那么我们就要增加一些 专门的“游戏活动”服务器。但是为了让第三方也能参加,自然就要部署多套,而且其中功能可能还有一些不同。——这就造成了积累下来的业务逻辑重复代码增多 的问题。随着第三方的接入商越来越多,除了会剧烈增加第三方TCP接口服务器的负载外,还有针对外部厂商的开发语言提供越来越多格式的API,这些维护工 作量往往会占据掉开发团队大量的开发时间。有没有一种一蹴而就的方法呢?答案是有的。在互联网数据共享和互联的服务里面,一种叫REST的模型迅速超越了 古老的corba RPC方案,战胜了JAVA专用的RMI技术,也干掉了各种WebService方案(包括SOAP),登上了最流行互联网接口的宝座。因此当我们改成使 用REST模型的方案后,我们终于可以集中精力在微博系统本身的业务功能开发上了。

经典软件架构模式



  由于我们把微博的功能都集中到REST功能服务器上,我们可以把各种用户界面相关的代码独立出去,集中精力做好核心功能逻辑。同时由于所有的请求都集 中到REST服务器上,在此的负载均衡和故障维护都变得统一。也无需维护多份相关的逻辑代码了。由于REST是一种跨语言的标准协议(基于HTTP),所 以各种语言都有开源的REST API,这样就无需另外开发很多语言的API。为了提高外部业务的性能,还可以专门构造一个缓存系统,减少对主题功能服务器的压力。

  REST模式是在Roy Thomas Fielding于2000年的博士论文提出。这篇论文的名字叫《架构风格与基于网络的软件架构设计》,在论文中,作者详细的回顾了架构模式的发展例程, 并且以互联网服务为案例,逐步推演出REST架构模式,论证了其合理性和必然性。这篇论文本身就是一片对于架构模式的很好的说明(当然包括了本文前面介绍 的几种模式),并且在互联网的环境下,提出了新的解决方案。这个模式的出现,说明了软件架构模式并非一层不变,而是在持续发展中的新兴科学。

经典软件架构模式


  “最通用的接口”——最常用于互联网公共接口 。现在的主要实现方案,一般使用HTTP协议和URI标准。

  适应:资源型公开服务。一般互联网服务都可以抽象成“对一个由URI指定的资源”进行“增删查改”操作的模型。这种模式和按照REST定义的HTTP命令字天衣无缝的结合在一起。并且这些“资源”还可以利用缓存,来提高其性能。

  不适应:大量临时状态服务。由于REST模式规定了“资源”必须是无状态的、可缓存的,否则就会造成大量的性能问题。因为我们如果以HTTP协议去实 现这个模式,短连接和大量文本协议字的解析,在没有缓存支持下运行,将会很消耗服务器性能。况且,资源状态变化过于频繁,必定造成接口调用的频繁,这也会 消耗大量网络连接。

  方法论:以业务资源为核心设计。我们需要把我们的业务功能,抽象成可以用“资源”描述的模型,才能很好的使用REST模式。而不是简单的把URI作为某种类似“命令字”的方式来使用,那是一般的Web Service。一般我们可以把本服务的核心数据,作为“资源”来描述,然后围绕这些不同种类的资源来设计接口。以微博服务为例,微博文和评论就是这种“资源”。

  设计模式实现:

  命令模式:一般来说我们会把一次REST调用,看成是一个命令。而REST一共有四种命令:PUT/DELETE/GET/POST,我们只需要扩展这四个基本“命令”类型,就能很方便的实现REST模式了。

  SOA模式

  现代互联网服务,往往都是服务器集群来支撑的。单纯的在一个进程、一个服务器内的架构,往往不能满足需求。那么针对海量服务的服务器集群,有什么样的架构模式是可以参考的呢?我们可以来看一个案例。

经典软件架构模式


【此案例并非完全真实情况,有一定提炼修改成分】


  假设我们要做一个电子商务的系统,这个系统无疑需要展示商品、提供购买流程。同时这个系统也需要有针对供应商的订货、发货、入账等流程。除了交易处 理,还需要有评价系统管理信用;统计和推荐系统增加销售量;用户帐号系统保障安全和便利等等。这些模块和功能一般比较复杂,但是一般涉及到买家和卖家两个 角色,因此我们往往根据这个规则,划定了两类模块,分别实施其功能,最后通过一个数据平台来存储这些数据。

经典软件架构模式



  这样的系统什么看起来问题不大,但是有两个潜在的问题:

  不能应对复杂的需求变化。比如需要在同样的数据上重新开一个网店,但是仅仅售卖其中一部分货品。这就可能需要拷贝代码或者复杂的代码重构工作。如果需要增加一些促销活动,可能会涉及大量代码的修改。

  不能应对自动容灾和伸缩的需求。由于用户访问量可能很大,因此我们往往需要准备大量的服务器来运行这个网站。当访问量变化的时候,没有预定的架构设 计,可能会被用户量的增加打个措手不及;又或者当服务器出现故障的时候,明明有可以分担任务的服务器,但是用不上,白白给用户造成故障体验。

  为了解决这两个问题,我们经过一番设计,能比较好的解决。其中最简单的是还是利用前文提到的“分层模式”,把业务分解成“顶层:面对用户的流程处理” 和“底层:面对业务的系统能力”两个层面。这样的分层能让复杂的需求变化大部分集中于“顶层”部分,由于可以重用“底层”的能力,这种需求变化仅仅需要编 写变化相关的代码,大大减轻了开发的工作量。然后,为了解决集群系统的性能和稳定性问题,我们把顶层到底层的调用,改成:“先通过一个【中心节点】查询, 再具体调用”的方式。这样我们只要把运行状态同步到中心节点上,就能实现自动的容灾和伸缩。最后我们再启用分布式的数据存储,解决最底层数据的安全性问 题。

经典软件架构模式



  这样的模型,实际上就是SOA模式。这种模式最大的特点,是关注服务模块之间的调用:在运行期根据负载、路由策略等用户设定,来决定模块间的调用关 系。这比起以前单服务器中的观察者模式,是一种升级——让多台服务器和多个进程之间,可以在运行时协作。这种模式可以很好的实施集群的负载均衡和容灾策 略。对于复杂的服务期间通信问题,也通过规定的交互接口和交互流程实现了高度的简化。很多SOA模式采用RPC框架实现,跨服务器调用往往和调用本地函数 一样方便。

经典软件架构模式



  SOA模型的标准内容,包括一个核心服务代理模块、一批服务消费者模块、一批服务提供者模块。当然消费者本身也可以提供者。这些模块都可以是集群中的 分布在不同服务器上的进程。而服务提供者通过一种叫“服务合同”的接口定义数据,来发布自己提供的服务;消费者则使用这个接口定义,发起对服务的调用,从 而屏蔽底层服务交互的细节。
在集群启动后,服务提供者先发布自己的服务信息到服务代码模块进程上。服务消费者在需要时,对服务代理查找所需服务,得到能提供服务的地址、接口等信息,直接对服务提供者发起服务请求。

  在以Web Service框架实现的时候,一般“服务合同”由WSDL提供,具体的服务都是Web Service,具体的编码格式有一些使用SOAP格式。 WSDL可以生成各Service Broker在实践中往往提供了负载均衡、容灾扩容等运营功能。 通常“服务代理”由一个目录服务器充当,而SOAP协议则可以直接序列化、反序列化为对象。

  “云模式”——分布式业务系统。SOA是提供云服务最好的模式之一,因为云服务对于容灾和伸缩性有较高的要求,并且也需要考虑后续功能开发的便利性。

  适应:异种系统集成。在多个不同的业务系统需要“集成”或者数据共享时,SOA模式提供了很好的伸缩性、容灾模型,以及简便的接口集成方案。

  不适应:实时系统。由于SOA模式存在一个“查询-调用”的过程,如果对于程序响应要求很高,则不适合增加这样一个复杂的模型。SOA模式可以提供几 乎无限的吞吐量,但是对于降低响应延迟却有死穴。虽然我们常常利用“缓存”中心节点数据的方式来减少服务延迟,但这也同样要付出服务器提供者状态不同步, 导致请求失败的风险。

  方法论:抽象业务逻辑为“服务”。由于SOA的核心是面对服务的,所以我们在分布式系统中,传统的面向“命令字”的思维需要扭转过来:服务是有自己的 名字,规定的接口形式和固定的返回值类型的,而命令字则只有自己的类型和数据;服务是以请求-应答为标准模型的,而命令处理则只有“发送-处理”。从灵活 性上来说,命令字会更好,但是从代码的可理解度来说,无疑抽象成“服务”更容易被阅读。

  设计模式实现:

  命令模式——尽管我们把功能抽象成服务,但是其底层实现,还是一个个的命令字往返收发实现的。使用命令模式可以很好的封装服务的底层实现。

  如何选择架构模式

  上面我们探讨了6种经典的架构模式,那么在实践中,我们到底应该如何选择呢?在次我们可以用一个案例来初步的讨论。

  我们的案例是开发一个大型网站的发布(CMS)系统。大家知道,现在的门户网站,每天的访问量都是上亿次PV的;同时网站每天也要发布数以百计的新闻 和专题。这些新闻除了简单的放上网站以外,互相直接的内容关系,还是网站编辑们组织栏目,整理专题所需要的。因为我们的网站是由大大小小很多经过分类的文 章链接组成的。而CMS就是一套专门用来发布、管理网站文章内容的系统。

经典软件架构模式



  这个系统的主要使用者有2个,对于网站访问者来说,他们看到是编辑人员的工作成果:一个经过良好组织(通过超链接)的网站,里面包含了很多页面。对于网站编辑者来说,这个系统要提供他们的工作界面,包括工作人员自己的帐号权限管理、内容组织编辑等软件功能界面。

经典软件架构模式



  面对这样的一个需求,我们首先要做需求的细化:

  编辑功能

  a)新建、修改、删除文章
  b)对各级文章模版进行修改
  c)制作各种专题页面和嵌入式链接专区

  用户需求

  a)快速响应用户的点击
  b)当新文章发布后,快速更新首页、专题页面等链接区块
  c)承载海量用户并发访问

  以上两个需求抽象总结来说:

  要应对编辑复杂的交互操作

  要应对快速的高性能数据发送

  在需求确定了之后,我们就要进入模式的选项。我们可以先按利于功能开发和适用服务器端(交互操作少)还是适用于客户端(交互操作多)两个维度来整理一下已知的架构模式。

经典软件架构模式



  我们可以看到,分层模式是最基本的模式,其他的5种模式多少都算是分层模式的一种变种。所以他处于模式分类的中心地位。而MVC和微核模式由于方便在 运行时提供复杂的模块交互,所以更多用于客户端方向。而REST/SOA/管道和过滤器则更倾向于特定的模块处理接口(请求-应答),所以更适合于服务器 端。

  在功能性和性能(非功能需求)上看,REST由于实现层大部分是HTTP协议承载,所以一般性能较差,好处是很方便开发功能。而MVC模式的实现代码 也因为需要复杂的事件机制甚至依赖语言的反射机制,所以性能也不能算非常好。相比之下,微核模式能更接近基本底层代码,其性能表现要好的多。而SOA模式 因为可以组合大量的系统提供高吞吐量,所以也在可用性、承载量上有更好的表现。

  最后,我们来比较下SOA和“管道与过滤器”这两者。SOA对于服务的描述更适合与开发复杂的逻辑,而“管道和过滤器”由于功能语义更底层,所以更方便能以最有效的实现方法来处理数据。所以SOA强于功能处理,而“管道与过滤器”则偏重性能(承载量)。

  不过我们这个案例的需求非常明显,分层模式的功能太弱了,应该有更好的选择。由于我们需要有“复杂的交互的操作”,所以MVC模式应该是值得考虑的一 个。由于同时也有“高性能数据发送”的需求,而且“用户读文章”的功能非常单一,所以“管道和过滤器”模式也应该使用,而无需去使用针对复杂功能的SOA 模式。

  通过以上的论证,我们可以明确一个观念:万能的模式一定是无能的模式;模式的限制越多,能提供的特性也越强。

  需求分析结论

  价值取向
        万能即无能
       限制越多,功能越强

  需求及变化
    范围一:编辑使用,逻辑多变
    内容页、栏目页、专题页
    相关文章、每页推荐、模板升级
    PC浏览器、手机浏览器、手机APP
       范围二:浏览者使用,性能要求高
    高可用、高承载
    只读、非实时

  最后,我们在建立方案之前,还应该参考一下业界的实现,以比较和印证我们的分析。

经典软件架构模式



  StoryServer
        Tcl语言模板——无法二次开发工具
        自带cache系统——不好分布
        按CPU收费——费用高

  Zope
       Python语言模板
        自带数据库、Web服务器——性能差

  Midgard
        PHP语言模板——灵活性差
        安装在Apache上——部署复杂,性能差

  可以看出,参考产品中,在以上两个需求都满足的很好的确实不多。所以我们应该提供高性能、高可用性,同时也是维护使用简单的发布系统。现在,我们分两个层面来描述我们的系统架构设计:

  整体系统架构

经典软件架构模式



  我们把整个系统分为两个过滤器,一个是用来生成内容的“内容管理器子系统”,另外一个是“内容缓冲器”子系统。文章内容从编辑人员处通过“内容管理 器”输入和处理,然后发布到“内容缓冲器”系统中,由它提供高性能、分布式的内容发布功能。我们可以综合使用内存缓冲更新方法和文件静态存储方法等一系列 手段,尽量的提高内容缓冲器的性能。由于内容缓冲器的写入很少,而读取量很大,针对这个特性,我们的分布集群也很好做。

  内容管理器子系统架构

经典软件架构模式



  在内容管理器内,包含了一个网站内容的内部复杂逻辑关系,因此要提供方便的软件工具给策划人员,最好是用MVC模式来实现。在编辑对于网站页面内容需 求的变化中,我们可以根据MVC的原则,不断修正和增加内容管理器的功能。而核心的数据逻辑部分,则会较少需要修改,这样可以提供给编辑人员更自由的内容 创作空间。

  针对上述案例,我们可以发现,一个系统并非只能用一个架构模式,而是可以复合的使用不同的架构模式。而在选择模式的过程中,我们需要建立一个核心价值 观:紧紧把握业务需求,以及需求的变化。我们通过把握需求,就可以抽象出业务领域的名词作为核心概念,从而导出系统架构中模块的名称;然后我们通过对业务 规则的总结,则可以未这些模块添加上行为。这样整个系统的各个模块都能严格对应上业务需求。但是,有时候我们会无法决定模块应该如何划分,比如这个功能究 竟应该由A模块来独立完成,还是应该由B/C两个模块协作完成。我们就可以使用“探讨需求变化的原因”这个标准来做决定。如果这个模块的需求是预计不会变 化的,那么就只用一个模块就好了;如果由于不同的原因,导致这个模块需要修改的话,那么就应该为“不同的原因”专门设计“不同的模块”。而最终我们选择模 块的组织形式——架构的时候,正式根据这些需求变化造成的问题来决定。比如这些需求变化会造成性能、承载上的难题吗?这些需求变化会造成用户界面修改频繁 吗?能处理好这些需求变化,正是考验一个人的软件架构能力的地方。

经典软件架构模式



  业界经典架构模式范例

  在软件界发展的长河里,使用各种架构模式的经典案例非常多。我们可以从学习这些案例的过程中,体会和掌握架构模式的含义。

经典软件架构模式



  分层体系里,最出名的是TCP/IP协议,这是构造互联网的基石。TCP/IP协议通过对于不同层次的定义,从而兼容各种不同的网络硬件、操作系统、应用软件。正是因为TCP/IP协议严格遵守分层的设计,所以在底层复杂的实现下依然可以提供良好的兼容性。

经典软件架构模式



  网游的鼻祖MUD系统,由于采用良好的分层模型,其底层MudOS被无数个不同的游戏世界所共用。而MudLib本身的良好分层,也让构造一个虚拟世 界变得简单有趣,而不是复杂艰辛。通过同一套MudLib改造出的完全不同的游戏非常常见。现在很多大型网游的架构,都是来源于MUD的这套分层架构。

经典软件架构模式



  Windows窗口模型是最经典的微核案例。它通过一套消息队列,让运行时的各个窗口模块可以互相调用。而Windows系统本身,则负责核心的用户 输入、通信、维护消息队列等操作。这样每个窗口程序,只需要针对Windows消息做编程,就能拥有对Windows系统下各个应用、服务的使用能力。

经典软件架构模式



  著名的开源IDE:Eclipse,现在已经被用在各种不同语言和系统的开发上,包含了JAVA/C /LUA/PYTHON/PHP/Android…等任何你想得到的地方。这种如此丰富的特性,又能完美的结合在一起,是由于它本身对微核模式做了一个优秀的实现:OSGi

经典软件架构模式



  OSGI四层实际上就是个微核:为了解决组件之间的通信,OSGi约定每个组件通过提供各自开放的服务(Services)实现相互间的协作。但如何知道哪个组件拥有哪些服务呢,OSGI规范通过服务注册表(Service Registration)来解决服务的查询、定位和调用问题。在OSGI的世界里,Bundle即可理解成为组件。

  管道和过滤器的例子在软件界则更加的多,比如我们最常见的Unix Shell系统,每个进程都被赋予了stdin/stdout/stderr这样的三个“出口”,因此每个进程都可以作为一个过滤器存在,而竖线“|”符号则可以把这样多个“过滤器”直接组合成一个处理管道。

经典软件架构模式



  在网络服务器领域,Apache MINA是一个很流行的IO框架。这个框架的特色,就是你可以用代码构建对一个网络包的处理管道。你可以定义很多个IoFilter类的对象,然后组合起 来成为一个管道,这些过滤器有些负责对消息解码,有的负责对消息鉴权,有的负责解压数据……最终消息会送到用户定义的IoHandler类型的对象那里, 进行最终的业务逻辑处理。这对于需要“统一的增加某个网络处理特性”来说非常好用。比如我们需要修改网络的编码协议,比如从JSON格式换成Google Protocol Buffer,我们只需要修改一个过滤器就好了;或者我们需要让消息变得更小,我们只需要增加一个zip算法压缩、解压的过滤器就完成了。这些特性的修改 完全无需修改任何业务逻辑代码。

经典软件架构模式



  MVC模式中,在工具类库上支持的最彻底的莫过于苹果的iOS界面类库,他直接把整个UI类库都按MVC模式来设计。你必须要编写Controller对象,关联View对象,才能让UI模型运行起来。

经典软件架构模式



  在Web应用开发领域,由于基于HTML/JS的页面也是用户交互操作的范畴,所以也出现大量的使用MVC模式设计的框架。其中最经典的就是Apache Struts框架。

经典软件架构模式



  在这个框架里,JSP充当View模块的载体,而Servlet则是Controller的载体。Model就是用户编写的JAVA类对象。 Controller和Model之间通过一个配置文件关联。而Model和View之间则直接通过JSP自定义Tag来绑定。这样只要是用户的操作,就 能自动根据配置struts-config.xml转发到用户对象的方法上,而结果的更新,通过JSP的标记自动的刷新出来。

  SOA系统一般用在大型的企业内部中。由于企业内部的各个部门系统很多,而且数据又需要互相关联,所以SOA模型就能很好的对这些复杂的结构进行管理。

经典软件架构模式



  在这个例子中,大家可以看到,业务流程系统需要大量对业务服务层进行调用,而业务服务层的每个子系统(供应商、配送、保险、银行……),都是通过“注册中心”发布的。业务流程本身会先查找,然后调用这些业务服务子系统,而无需实际与复杂的业务子系统耦合到一起。

  最后关于REST的案例,这里提供全球最多人使用的facebook提供的公开业务API是最好有说服力的。

经典软件架构模式



  这是一个针对facebook消息读取的API,URI中的object-id部分对应着一条facebook消息,而comments则对应所有的评论资源。使用GET命令字就是拉取这些评论资源。

经典软件架构模式



  而对于写评论,也是同样的,以URI中的object-id对应消息,comments对应评论资源,使用PUT就是把内容添加进去了。REST的核心就是以URI作为资源的对应,使用GET/PUT等HTTP命令字做操作。

  在本文最后,我想推荐一些让我得到以上知识的书,这些书都是软件架构知识的宝库。其中第一本POSA是最古老的经典,是架构模式书籍的开山之作;第二 本是中国人写的架构设计的方法学,详细介绍了架构设计的整个过程,以及里面所涉及的文档格式、方法,也提到了架构模式;第三本论文总结了软件架构中的知 识,推导出REST模型;第四本讲解了如何在细节上以分层架构来设计复杂的库,虽然其中内容主要针对.NET体系,但是对于任何一个想设计框架或者复杂系 统架构的人来说,这里面的原则、规范、最佳实践都是可以照搬的。

  《面向模式的软件体系结构(POSA)》
  《软件架构设计》
  《架构风格和基于网络软件架构设计》
  《.NET设计规范:约定、惯用法与模式》
阅读(666) | 评论(0) | 转发(0) |
0

上一篇:安装 SCons

下一篇: BitNami一键安装Redmine

给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册