按照Enterprise Integration Pattern搭建服务系统

  在前一篇文章中,咱们已经对Enterprise Integration Pattern中所包含的各个组成进行了简单地介绍。限于篇幅(20页Word之内),我并无深刻地讨论各个组成。可是若是要真正地按照Enterprise Integration Pattern搭建一个系统,仅仅是了解它们实际上还差得很远。所以在本文中,我将会对Enterprise Integration Pattern中较容易产生混淆的部分以及一些系统搭建时经常使用的一些方法进行介绍。html

 

寻找最优的解决方案数据库

  相信您在读前一篇文章时就已经能感受到,在使用Enterprise Integration Pattern搭建一个系统时,咱们经常能够经过不一样的组成来知足相似的需求。例如在须要对消息进行转换时,咱们经常可使用Content Enricher或Content Filter等组成来添减内容,更可使用Massaging Mapper等组成来完成相似的功能。那么咱们应该何时使用Content Enricher或Content Filter,而何时使用Massaging Mapper呢?编程

  其实答案就存在于过滤器和Endpoint之间的不一样。二者之间的不一样主要在于,过滤器是Pipes and Filters模型之中的一个独立的过滤器,而Endpoint则是过滤器中的一个用来令过滤器内部的业务逻辑实现与消息系统关联的组成。例如咱们有一个应用提供了一系列与消息系统不兼容的API。此时咱们就须要使用一个Endpoint将其与消息系统关联。而它们则共同组成了一个过滤器:浏览器

  在了解了这点不一样以后,相信您就会明白,为何Endpoint中所介绍的那些功能与不少用来完成消息路由及转化的过滤器相似了:一类是单独存在的过滤器,一类则是过滤器中的组成。相较于使用一个独立的过滤器,Endpoint可以减小一次在管道中传输数据的消耗,从而提升了消息的处理速度:缓存

  如上图所示,相较于经由过滤器处理,一个基于Endpoint的具备相同功能的组成能够减小一次消息经过管道进行传递的过程。所以基于Endpoint的解决方案拥有更好的性能。反过来,因为Endpoint是与其后的业务逻辑处于同一个过滤器中的,所以其没法将消息发送到其它子系统之中。也就是说,其灵活性有所降低。甚至说,每次对Endpoint以后的业务逻辑的更新一样须要对Endpoint进行维护,以保证其能正常工做。安全

  这就引出来了一个话题,在开发一个基于Enterprise Integration Pattern的系统,最须要考虑的是什么?服务器

  一般状况下,这些考虑的因素主要有:性能,灵活度,可维护性,高可用性。而最终的解决方案则经常是这些因素相互平衡的产物。网络

  性能没必要多说。在一个基于消息的系统中,对用户请求的处理最终会转化为一系列在管道中传递的消息。因为消息的传递是一个异步操做,所以对单个消息处理时的性能不会比同步操做更好。app

  第一个使系统具备合适性能的前提就是使子系统拥有合适的粒度。若是子系统的粒度较小,那么对一个业务逻辑的处理就须要经由更多的子系统。这既增长了管道的数量,又增长了消息在管道中传输的次数。除此以外,子系统粒度过细也会给消息系统带来很大的压力。在前一篇文章中已经提到过,管道会将消息保存起来,所以每一个管道都会占用一部份内存。若是子系统的粒度过细,那么整个系统就须要更多的管道,对消息系统所在的服务器形成更大的压力,也会提升系统出错的可能。异步

  一个拥有较粗粒度的系统最常出现的问题就是系统过载。这时咱们该怎么作呢?答案就是对其进行横向扩展。在《服务的扩展性》一文中咱们已经提到过,一个服务的扩展方式分为XYZ轴之分。而在处理系统过载问题上,咱们经常须要执行X轴扩展,有时也须要进行Y轴扩展:

  X轴扩展相对简单:使用多个服务实例对消息进行处理。在这些服务以前,咱们可使用Message Dispatcher或Competing Consumers这类Endpoint,或者经过Dynamic Router等过滤器来完成对消息的分发。只是有时经过X轴扩展并不能彻底地解决问题。就像上图显示的那样,对整个子系统进行扩展实际上会致使系统的某些组成利用率很是低。在这种时候,咱们就能够将这个过载的子系统中的各个组成分离出来,并做为一个独立的基于消息的子系统,而后对其真正造成瓶颈的子系统进行扩展:

  上图中展现了如何在一个子系统实例遇到瓶颈的时候执行Y轴扩展。在一开始,该子系统是做为一个独立的系统存在的。在须要处理一个消息的时候,消息从其输入管道流入,并在处理完毕后从其输出管道流出。可是在该子系统成为整个系统的瓶颈时,咱们就须要将该子系统分割为多个粒度稍小的子系统,并对其中成为瓶颈的子系统添加新的实例。这样,咱们就解决了整个系统中的瓶颈。

  可是这样作的坏处则在于,一个消息经常须要通过更多的管道才能被处理完毕。就以上图所展现的子系统分割逻辑为例,能够看到,一个消息在被新系统处理时将首先须要通过绿色的结点,接下来还至少须要通过橘红色的结点。也就是说,对消息的处理至少多出了消息流经一个管道的时间。所以在分割一个对消息处理时间要求较高的系统时,咱们经常须要考虑的是,如何使消息通过较少的管道。

  而在对消息处理吞吐量的要求超过对消息处理时间的要求时,咱们则须要尽可能地使每一个实例最大程度地发挥它的处理能力。这是常识,因此咱们再也不深刻讨论。

  在考虑系统性能时,咱们也经常须要考虑这样一点,那就是有些消息系统提供了建立于内存中的管道。在该管道中传递消息的性能要比经由网络传递消息的性能高出不少。所以在设计基于消息系统的服务时,咱们应尽量地使用这种存在于内存中的管道。

  而从物理结构上来看,从原有子系统中所分割出来的各个子系统实例也须要和消息系统服务所在的实例进行交互。咱们固然能够将管道直接添加到原有的消息系统服务上,而另外一个较为常见的方法则是建立一个新的消息系统服务。这能够带来很是多的好处:使消息系统服务可以承担更多的负载,使得整个系统的物理拓扑逻辑变得更为清晰,更能够在不一样的消息服务上使用不一样的安全配置,例如将其设置为只接受从中心消息服务所发送的消息,进而提升整个系统的安全性:

  而一个与性能经常有冲突的地方就是系统的灵活度。咱们能够回想一下前一篇文章中对Content-Based Router及Dynamic Router的讲解。二者之间的不一样主要在于Dynamic Router能够令一个过滤器注册自身所能接收消息的条件,从而可以动态地加入或离开系统。而这也并不是没有代价。为了提供这种灵活度,咱们须要添加额外的管道,从而为消息服务带来了更多的负载。

  可是过多的灵活性反而也会致使问题的大量出现。例如对于某些服务,咱们能够假设其可能会因为业务的快速增加而达到系统的瓶颈,所以为其设计较强的灵活性是有必要的。而对于某些系统,咱们经常不该该设计有太大的灵活性。例如在当前需求仅仅是针对用户类型来提供相应推荐的推荐系统而言,当天的推荐其实是固定的一系列推荐项的组合,所以也不存在什么须要根据用户偏好动态计算推荐项的功能。对于这样的一个服务,只要需求没有发生变化,整个系统的计算负载也不会高,所以也没必要为它的处理能力扩展留下太多的灵活性。

  而在需求变化时,例如咱们如今须要根据用户的浏览记录来推荐物品,那么咱们就须要考虑系统的灵活性了。由于此时推荐系统的计算结果可能会随着用户的当前浏览而随时发生变化。随着用户的快速增多,这种负载将会愈来愈重,从而形成子系统的过载。

  何况,过多的灵活性也会致使可维护性变得更为困难。仍是以Content-Based Router以及Dynamic Router为例。Dynamic Router之因此出现,就是由于当其中一个参与消息处理的子系统发生变化时,咱们还须要更改Content-Based Router。这会致使Content-Based Router以后的整个子系统暂时不可用。

  这实际上就对参与消息系统中的各个组成之间的耦合性提出了要求。若是对某个组成的修改会致使咱们更改其它一些组成,那么它们之间就是耦合的。对于不常变更的组成关系,这种耦合是正常的,而对于经常会发生变化的组成,尤为是在为整个系统设计高可用性,热插拔功能时,这些耦合就是相对致命的。

  能够这么说,对于一个重要的系统,如何让它在发生变化时不须要中止服务经常是其所最为看重的。高可用性是其中的一种需求,在维护时不须要中止服务也是一种很是重要的需求。所以在设计一个系统时,咱们经常考虑的是:哪里可能会常常发生变化?发生变化以后咱们须要更改哪些组成?若是系统的某个相关组成失效,整个系统是否可以继续正常提供服务?除此以外,是否有没必要要的消息传递?

 

选择合适的组成

  好。上一节咱们已经介绍了在设计一个基于消息的系统时所须要考虑的各类因素。而在本节中,咱们将对Enterprise Integration Pattern中所介绍的一些组成进行分析,从而使您更清楚地了解这些组成之间的优势和缺点,并最终可以正确地使用它们。

  就像Open-Close原则同样,咱们在基于Enterprise Integration Pattern设计一个系统的时候也须要考虑这些系统中各个子系统之间的变与不变。变在这里主要分为两种:系统中的各个子系统之间的关系发生变化,以及路由过程当中消息自身的路由方式发生变化。搞清系统中的变与不变可以提供较高的灵活性和可维护性。可是因为灵活性和可维护性经常须要引入一系列额外的组成,所以其经常会影响整个系统的性能。所以除了须要考虑整个系统的性能以外,咱们还须要考虑各个组成的性能。在这两种思考方式下,消息系统各个组成之间的异同就会显得十分清晰明了。

  这些额外引入的组成经常意味着性能的降低以及维护成本的增长。例如就以使用一个Content-Based Router完成消息的分发这种最为简单的状况为例,它的好处就是能让咱们把全部的路由逻辑都集中在一个组成中完成。这样只要消息中的数据发生了变化,或者有新的子系统添加到路由逻辑之中,咱们只须要更改这些路由逻辑便可。这即是集中管理信息的好处,或者是SRP(笑,Single Responsibility Principle,思想相似,随便扯扯)。可是反过来,若是一个子系统所能接收的消息类型发生了变化,那么咱们就须要同时修改该子系统以及相应的路由器。而这就是一种并不受待见的耦合,尤为是在一个接收端会常常发生变化的系统中,这种变化所带来的困扰远大于咱们集中管理信息所带来的好处。

  反过来,不少消息系统也一样容许咱们建立自定义的各个组成,例如自定义的路由器,自定义的消息转换逻辑等。在这些状况下,咱们也能够经过一系列业界经常使用的思想来解决这些问题。

  就让咱们从最早介绍的Content-Based Router提及。一个Content-Based Router会根据消息中所包含的信息来决定到底由哪一个子系统对该消息进行处理。这也就是说,Content-Based Router知道到底有哪些子系统,同时它还知道如何去分析这些消息。那么一旦这些处理消息的子系统的可见性发生了变化,或者消息中所包含的信息发生了变化,那么咱们就须要对Content-Based Router内的逻辑进行更改。

  而为了不这些维护上的问题,Enterprise Integration Pattern提出了Dynamic Router。其容许各个接收消息的子系统向其注册处理问题的条件。那是否是Content-Based Router就没有任何价值了呢?不是的。相对于Dynamic Router,Content-Based Router是一个更轻量级的解决方案。所以在筛选条件不会发生变化并且参与消息分发的子系统不会发生变化的状况下,其反而是最佳的解决方案。除此以外,如何避免参与分发的各个子系统向Dynamic Router所注册的条件不会发生彼此相互重叠的状况也是一个须要讨论的问题。这也是Dynamic Router的这种灵活性所带来的反作用。

  你仔细想想就会发现,实际上这就是一个依赖注入。只不过咱们不是在具体编程过程当中对其进行使用,而是在整个系统设计时候完成的。所注入的,则是Dynamic Router所须要的做为消息分发依据的逻辑。一样的,Service Locator也会帮咱们解决一系列耦合的问题。在Enterprise Integration Pattern中,典型的借鉴Service Locator的组成彷佛并很少,可是在实际使用中,咱们也的确能够经过这样设计系统来完成各个系统之间的解耦。

  我说的意思实际是,虽说不一样层次上所经常使用的各类方法会存在着一些不一样,例如咱们没法像建立一个派生类同样对子系统进行派生,可是不少时候思想是通用的。

  OK,这段扯得有点远。咱们拉回到如何区分并合理地使用各个组成这样一个话题中。咱们在前面已经讲解过何时使用过滤器,何时使用Endpoint。所以在这里咱们将会把精力主要集中在负责路由的各个过滤器上。由于这经常是不少人产生疑惑的地方。

  在Enterprise Integration Pattern一书中列入了以下的一个用来决定一个系统中所须要使用的路由器的判断逻辑图:

  可是我我的认为这个图是根据各个路由过滤器的特性来去分类的。而在我实际决策过程当中,我更趋向于根据业务逻辑以及消息处理自己的需求来决定到底使用哪一个路由器。该判断逻辑以下所示:

  由于我一直以为,对一个消息如何进行处理才是与业务逻辑关联最密切的。业务逻辑以及某些非功能性需求决定了到底咱们须要什么样的路由逻辑。并且在上图中,我也把Dynamic Router包含进了Content-Based Router中了。由于实际上,Dynamic Router就是一种特殊的Content-Based Router。固然,仁者见仁,智者见智。不是说原书中的决策逻辑很差,而仅仅是将我所使用的决策逻辑介绍给你们。

  而对于用来进行消息转化的Transformer以及各个Endpoint,因为我以为它们实际上仍是很容易区分的,所以在本文中就再也不作细致的讲解了。

 

管理基于消息的系统

  在前面的讲解中,咱们只介绍了应该如何经过Enterprise Integration Pattern所说起的各类组成搭建一个系统。可是除了业务逻辑以外,咱们还须要令咱们的系统知足必定的非功能性需求,例如高可用性,可测试性等。所以在搭建了一个系统以后,咱们还须要作一系列的工做,才能让咱们的系统稳定持续地提供服务。

  可是对这些非功能性需求的保证则没有那么简单。例如,在一个基于消息的系统中,消息的生产者和消费者并不知道彼此,同时对消息的传送经常是一个异步的调用,其只对消息的可靠传递进行了保证,却没有对消息的传递时间进行保证。所以如何知足这些非功能性需求则是更为困难的一件事。

  Enterprise Integration Pattern一书中提供了一系列用以提供这些非功能性需求的解决方案。在本节中,咱们就将对这些解决方案进行简单地介绍。

  首先要介绍的就是Control Bus。在该方案中,Control Bus将使用独立的管道与系统中的各个子系统关联,以动态地监控各组成的运行状态,如子系统是否正常工做,与其运行相关的统计数据,其是否过载,消息的处理是否有较高的延迟等。甚至在监控到了某些异常状态以后,其还须要经过这些管道向这些子系统发送消息,以更改这些子系统的配置:

  那么咱们应该如何经过这些消息来判断一个子系统是否正常工做呢?简单地说,咱们能够令子系统向管道中送入一系列心跳消息的方式来解决。这种心跳消息可能仅仅是一个简单的通知消息,更能够在消息中包含子系统当前的状态信息,如处理了多少消息,每一个消息的处理时间,整个系统的状态等。

  可是这些信息仅仅用来描述子系统的当前运行状态。咱们怎么判断子系统的业务逻辑是否正常执行呢?此时咱们就须要使用Enterprise Integration Pattern中所介绍的Test Message方案:

  从上图中能够看到,Test Message主要包含了四个组成:Generator将首先生成测试消息,接下来,该测试消息将会经过Injector与实际的业务消息发送到子系统中。在子系统处理完毕以后,Separator将会把这些测试消息对应的处理结果分离,并将这些处理结果发送给Verifier进行验证。而这些验证的结果将被发送到Control Bus中,以方便Control Bus管理这些子系统。

  好了,如今咱们已经知道了如何探测一个子系统是否在正常工做。下一步则是为咱们追踪及调试系统做准备。为了可以完成这些功能,咱们首先须要可以侦听在两个子系统之间所传递的消息,才能经过这些侦听到的消息来进行调试。固然,在一个Publish-Subscribe管道上侦听消息是很是简单的:咱们只须要侦听该管道上的消息便可。可是因为Point-to-Point管道将只能对消息进行点对点传输,所以咱们不能简单地对该管道上的消息进行侦听。为了解决这个问题,Enterprise Integration Pattern则提出了Wire Tap方案。该方案会将Point-to-Point管道的一端链接到Wire Tap上,而后由其向目标子系统以及侦听方转发该消息:

  好的,如今咱们可以侦听这些消息了,下一步则是找到一个地方把它们存起来。该功能是经过Message Store来完成的:

  从上图中能够看到,Message Store会要求各个子系统在向输出管道放置消息时也向消息的存储发送一个相同的消息,从而完成对这些消息的持久化。可是咱们怎么才能知道一个消息究竟是如何在系统中流动的呢?答案是经过Message History来记录消息所通过的各个子系统:

  而为了重现并调试某些出错的状况,咱们则须要让某个消息可以通过一系列特殊的子系统,从而容许软件开发人员对出错的状况进行调试。此时咱们就须要使用Detour方案。该方案会使用一个Context-Based Router判断某个消息是否知足特殊条件,若是是,那么将其传递给特定的输出管道:

  可是这里有一个问题,那就是咱们更改了消息的路由路径。这明显会影响Request-Reply类型的消息的执行。为了解决这个问题,咱们须要使用一个Smart Proxy。该组成可以缓存原消息的Return Address。这样当一个消息通过该组成时,其将首先缓存该消息的Return Address,并使用本身的响应输入管道地址替换消息中的返回地址。当消息从该管道返回时,Smart Proxy则会找到原有的Return Address并将消息送回。

  至于Enterprise Integration Pattern中所提到的最后一个组成Channel Purger则很是容易理解。因为消息是在消息系统中缓存的。当咱们从新启动某个子系统,或者对某个子系统进行调试时,其管道中所存留的消息将会明显地影响咱们的调试。Channel Purger则会帮助咱们解决这个问题:其会将管道中的不须要的消息移除。

 

适当地使用EIP

  最后一节,咱们则主要用来讨论您应该如何在合适的时机以合适的方式使用Enterprise Integration Pattern所提供的各类功能。

  首先要明白的就是何时使用Enterprise Integration Pattern。试想一下,若是一个系统对一个用户请求的处理须要5秒钟,那么一个浏览器用户须要很长时间才能完成对页面上全部数据的加载。对于不一样的任务,用户对该行为的忍受能力其实并不相同。例如若是用户加载一个服务的首页都须要2分钟,那么他极有可能放弃使用该服务。可是若是一个功能是在后台作了很是耗时的操做,如部署虚机并在其上安装运行服务所须要的软件,那么对该请求的处理耗时10分钟都不足为过。此时咱们只须要提供给用户一个界面并定时地刷新任务的执行状态,以通知咱们的系统正在工做既可。

  所以,一个本来就须要较长时间耗时的,或者是至少用户可以理解为较为耗时的功能,才能使用Enterprise Integration Pattern对其进行组织。不少直接面向用户的功能,如电子商务,博客,不多直接使用到这些须要长时间耗时的操做,所以使用Enterprise Integration Pattern来组织这些功能只会让您的服务质量变得更差。

  那么咱们应该在何时使用Enterprise Integration Pattern呢?答案实际上就存在于Enterpriese这一个词上。不少企业级应用经常包含一系列很是耗时的操做。就以如今最流行的云来讲吧。我作的产品就是一个云管理软件。这个软件能让用户经过简单地拖拽就能定义其在特定云上所须要部署的服务。接下来,用户只要点击一下部署,在几十分钟后,该应用就将被部署完毕。

  让咱们想想这个云管理软件在部署时作了哪些事情呢?从Amazon上请求资源,对资源进行配置,在这些资源上部署服务所须要的各个软件,配置这些软件,并最终启动服务。能够想象到的是,这里面的每一步都是一个较为耗时的操做。并且它是一个很是典型的按照Pipes and Filters模型组织的业务逻辑:

  而为了能让用户可以知道咱们的应用正在正常工做,咱们则会将当前部署任务的状态回填到数据库中。这样用户在请求查看当前任务的运行状态时,咱们只须要从数据库中将该状态读出返回既可。所以,虽然咱们的部署服务所须要消耗的时间较长,可是用户在请求查看时,咱们就能很是快速地返回,不是么?

  其实这是业内很是常见的一种对耗时任务的一种展现方法。只是因为这可能涉及到咱们公司产品的内部实现,所以为了不一些没必要要的麻烦,我会找机会在介绍其它公司的产品,例如Amazon的CloudFormation,Beanstalk或OpsWorks等再对它的内部执行逻辑进行讲解。

  并且从云这个领域来看,其实如今对云服务提供Enterprise Integration Pattern的原生支持这一要求的呼声也是很高的。这也就是所谓的Cloud Orchestration的一个重要的组成部分。固然啊,这玩艺挺大也挺虚的。我尽可能把它们一步步细化地讲解掉,毕竟我这一系列和Web Service的文章都是一步步地向着这个目标前进的。从前面的负载平衡,后面的扩展性,而后还有之后要讲的高可用性(尤为是基于云的),Amazon云所提供的功能等,我都会抽出时间写成博客。

 

转载请注明原文地址并标明转载:http://www.cnblogs.com/loveis715/p/5185353.html

商业转载请事先与我联系:silverfox715@sina.com

公众号必定帮忙别标成原创,由于协调起来太麻烦了。。。

相关文章
相关标签/搜索