http://www.aboutyun.com/thread-12921-1-1.html
1、集成模式中的消息模式
归根结底,企业应用系统就是对数据的处理,而对于一个拥有多个子系统的企业应用系统而言,它的基础支撑无疑就是对消息的处理。与对象不同,消息本质上是一种数据结构(当然,对象也可以看做是一种特殊的消息),它包含消费者与服务双方都能识别的数据,这些数据需要在不同的进程(机器)之间进行传递,并可能会被多个完全不同的客户端消费。在众多分布式技术中,消息传递相较文件传递与远程过程调用(RPC)而言,似乎更胜一筹,因为它具有更好的平台无关性,并能够很好地支持并发与异步调用。对于Web Service与RESTful而言,则可以看做是消息传递技术的一种衍生或封装。在《面向模式的软件架构(卷四)》一书中,将关于消息传递的模式划归为分布式基础设施的范畴,这是因为诸多消息中间件产品的出现,使得原来需要开发人员自己实现的功能,已经可以直接重用。这极大地降低了包括设计成本、实现成本在内的开发成本。因此,对于架构师的要求也就从原来的设计实现,转变为对业务场景和功能需求的判断,从而能够正确地进行架构决策、技术选型与模式运用。
常用的消息模式
在我参与过的所有企业应用系统中,无一例外地都采用(或在某些子系统与模块中部分采用)了基于消息的分布式架构。但是不同之处在于,让我们做出架构决策的证据却迥然而异,这也直接影响我们所要应用的消息模式。
- 消息通道(Message Channel)模式
我们常常运用的消息模式是Message Channel(消息通道)模式,如图1所示。

消息通道作为在客户端(消费者,Consumer)与服务(生产者,Producer)之间引入的间接层,可以有效地解除二者之间的耦合。只要实现规定双方需要通信的消息格式,以及处理消息的机制与时机,就可以做到消费者对生产者的“无知”。事实上,该模式可以支持多个生产者与消费者。例如,我们可以让多个生产者向消息通道发送消息,因为消费者对生产者的无知性,它不必考虑究竟是哪个生产者发来的消息。
虽然消息通道解除了生产者与消费者之间的耦合,使得我们可以任意地对生产者与消费者进行扩展,但它又同时引入了各自对消息通道的依赖,因为它们必须知道通道资源的位置。要解除这种对通道的依赖,可以考虑引入Lookup服务来查找该通道资源。
例如,在JMS中就可以通过JNDI来获取消息通道Queue。若要做到充分的灵活性,可以将与通道相关的信息存储到配置文件中,Lookup服务首先通过读取配置文件来获得通道。
消息通道通常以队列的形式存在,这种先进先出的数据结构无疑最为适合这种处理消息的场景。微软的MSMQ、IBM MQ、JBoss MQ以及开源的RabbitMQ、Apache ActiveMQ都通过队列实现了Message Channel模式。因此,在选择运用Message Channel模式时,更多地是要从质量属性的层面对各种实现了该模式的产品进行全方位的分析与权衡。例如,消息通道对并发的支持以及在性能上的表现;消息通道是否充分地考虑了错误处理;对消息安全的支持;以及关于消息持久化、灾备(fail over)与集群等方面的支持。因为通道传递的消息往往是一些重要的业务数据,一旦通道成为故障点或安全性的突破点,对系统就会造成灾难性的影响。在本文的第二部分,我将给出一个实际案例来阐释在进行架构决策时应该考虑的架构因素,并由此做出正确地决策。
- 发布者-订阅者(Publisher-Subscriber)模式
一旦消息通道需要支持多个消费者时,就可能面临两种模型的选择:拉模型与推模型。拉模型是由消息的消费者发起的,主动权把握在消费者手中,它会根据自己的情况对生产者发起调用。如图2所示:
<ignore_js_op>
图2 拉模型
拉模型的另一种体现则由生产者在状态发生变更时,通知消费者其状态发生了改变。但得到通知的消费者却会以回调方式,通过调用传递过来的消费者对象获取更多细节消息。
在基于消息的分布式系统中,拉模型的消费者通常以Batch Job的形式,根据事先设定的时间间隔,定期侦听通道的情况。一旦发现有消息传递进来,就会转而将消息传递给真正的处理器(也可以看做是消费者)处理消息,执行相关的业务。
推模型的主动权常常掌握在生产者手中,消费者被动地等待生产者发出的通知,这就要求生产者必须了解消费者的相关信息。如图3所示:
<ignore_js_op>

图3 推模型
对于推模型而言,消费者无需了解生产者。在生产者通知消费者时,传递的往往是消息(或事件),而非生产者自身。同时,生产者还可以根据不同的情况,注册不同的消费者,又或者在封装的通知逻辑中,根据不同的状态变化,通知不同的消费者。
两种模型各有优势。拉模型的好处在于可以进一步解除消费者对通道的依赖,通过后台任务去定期访问消息通道。坏处是需要引入一个单独的服务进程,以Schedule形式执行。而对于推模型而言,消息通道事实上会作为消费者观察的主体,一旦发现消息进入,就会通知消费者执行对消息的处理。无论推模型,拉模型,对于消息对象而言,都可能采用类似Observer模式的机制,实现消费者对生产者的订阅,因此这种机制通常又被称为Publisher-Subscriber模式,如图4所示:
<ignore_js_op>

图4 Publisher-Subscriber模式(图片来自eaipatterns )
通常情况下,发布者和订阅者都会被注册到用于传播变更的基础设施(即消息通道)上。发布者会主动地了解消息通道,使其能够将消息发送到通道中;消息通道一旦接收到消息,会主动地调用注册在通道中的订阅者,进而完成对消息内容的消费。
对于订阅者而言,有两种处理消息的方式。一种是广播机制,这时消息通道中的消息在出列的同时,还需要复制消息对象,将消息传递给多个订阅者。另一种方式则属于抢占机制,它遵循同步方式,在同一时间只能有一个订阅者能够处理该消息。
- 消息路由(Message Router)模式
无论是Message Channel模式,还是Publisher-Subscriber模式,队列在其中都扮演了举足轻重的角色。然而,在企业应用系统中,当系统变得越来越复杂时,对性能的要求也会越来越高,此时对于系统而言,可能就需要支持同时部署多个队列,并可能要求分布式部署不同的队列。这些队列可以根据定义接收不同的消息,例如订单处理的消息,日志信息,查询任务消息等。这时,对于消息的生产者和消费者而言,并不适宜承担决定消息传递路径的职责。事实上,根据S单一职责原则,这种职责分配也是不合理的,它既不利于业务逻辑的重用,也会造成生产者、消费者与消息队列之间的耦合,从而影响系统的扩展。
既然这三种对象(组件)都不宜承担这样的职责,就有必要引入一个新的对象专门负责传递路径选择的功能,这就是所谓的Message Router模式,如图5所示:
<ignore_js_op>

图5 Message Router模式(图片来自eaipatterns )
通过消息路由,我们可以配置路由规则指定消息传递的路径,以及指定具体的消费者消费对应的生产者。例如指定路由的关键字,并由它来绑定具体的队列与指定的生产者(或消费者)。路由的支持提供了消息传递与处理的灵活性,也有利于提高整个系统的消息处理能力。同时,路由对象有效地封装了寻找与匹配消息路径的逻辑,就好似一个调停者(Meditator),负责协调消息、队列与路径寻址之间关系。