JMS基础参见:http://blog.csdn.net/zhangxs_3/article/category/625599
实践中存在的问题(引自《大型网站系统与Java中间件实践》第六章):
一、如何解决消息发送一致性
1.消息发送一致性定义
消息发送一致性是指产生消息的业务动作与发送消息的一致,也就是说,如果业务操作成功了,那么由这个操作产生的消息一定要发送出去,否则就丢失消息了。另一方面,如果业务操作没有发生或者是失败了,就不应该把消息发出去。
2.消息一致性很难保证吗
解决方式一:
JMS中定义的XA系列的接口就是为了实现分布式事务的支持。
缺点:>引入了分布式事务,这会带来一定的开销增加复杂度。
>对于业务操作有限制,要求业务操作的资源必须支持XA协议,才能够与发送消息一起来做分布式事务。但并非所有需要与发送消息一起做成分布式事务的业务操作都支持XA协议。
解决方式二:
(1)业务处理应用首先把消息发送给消息中间件,标记消息状态为待处理。
(2)消息中间件收到消息后,把消息存储到消息存储中,并不投递该消息到Destination(Queue/Topic)。
(3)消息中间件返回接收到的消息处理的结果(仅是入库处理)给业务处理应用,结果是成功或者失败
(4)业务方收到消息中间件返回的结果并进行处理:
a)如果收到的结果是失败,那么就放弃业务处理,结束。
b)如果收到的结果是成功,则进行业务自身的操作。
(5)业务完成后发送业务处理结果给消息中间件。
(6)消息中间件收到业务处理的结果,根据结果进行处理:
a)如果业务失败,则删除消息存储中的消息,结束。
b)如果业务成功,则更新消息存储中的消息状态为可发送,并进行调度,进行消息的投递。
上述方案,如何保证消息发送一致性呢??
我们对于每一个步骤可能发生的异常情况进行分析。
分析只需看:业务操作和消息发送状态是否一样即可,即同时成功或者同时失败。
(1)业务应用发消息给消息中间件。如果这步失败了,无论是网络原因还是消息中间件的原因,或是业务应用自身的原因,我们都会看到业务操作还没有做,消息也还没有存储到消息中间件中,业务操作和消息发送两者状态一致,没有问题。
(2)消息中间件把消息入库。如果这步失败了,无论是消息存储有问题,还是消息中间件收到业务消息后有问题,或者是网络问题,可能造成两个结果。一个是消息中间件失效,那么业务应用收不到消息中间件返回的结果;二是消息中间件插入消息失败,并且有能力返回结果给应用,这时消息存储中都没有消息。
(3)业务应用收到消息中间件返回结果异常。这里出现异常的原因可能是网络、消息中间件的问题,也可能是业务应用自身的问题。如果业务应用自身没有问题,那么业务应用并不知道消息在消息中间件中的处理结果,就会按照消息发送失败来处理,如果这时消息在消息中间件中入库成功的话,就会造成不一致。如果是业务应用有问题,那么如果消息在消息中间件中处理成功的话,也就会造成不一致了;如果未处理成功,则还是一致的。
(4)业务应用进行业务操作。这一步不会差生太大问题。
(5)业务应用发送业务操作的结果给消息中间件。如果这一步出现问题,那么消息中间件啊将不知道如何对消息的状态进行更新,可能造成不一致。
(6)消息中间件更新消息状态。如果这一步出现问题,与上一步造成的结果类似。
从上面的分析可以看出,需要了解的两个主要的控制状态和流程的节点就是业务应用和消息中间件。
从上面的梳理和分析可以看到,对于各种异常情况我们遇到的状态有如下三种:
>业务操作未进行,消息未存储。
>业务操作未进行,消息存入存储,状态为待处理。
>业务操作成功,消息存入存储,状态为待处理。
三种情况中,第一种不需要进行额外处理,因为它本身就是一致的;第二种和第三种都需要了解业务操作的结果,然后来处理已存入消息存储、状态是待处理的消息。
那么如何了解业务操作的结果呢?
下图展示了这个过程。
由消息中间件主动询问业务应用,获取待处理消息所对应的业务操作的结果,然后业务应用需要对业务操作的结果进行检查,并把结果发送给消息中间件(业务处理的结果有失败、成功、等待,等待是多出来的一种状态,代表业务操作还在处理中),然后消息中间件根据返回的业务处理结果更新消息状态。可以说这是发送消息的一个反向流程。
发送消息的正向流程和业务操作结果的反向流程结合起来,就解决了业务操作和发送消息一致性的问题。
该方案的两个限制。
1.需要确定发送的消息的内容。因为我们在业务操作做之前会把状态标记为待处理,这要求先能确定消息内容;这里有一个变通,即先把主要内容也就是也就是能够标记该次业务操作特点的信息发过来,然后等业务操作结束后需要更新状态时再补全内容。不过这还是要求在业务操作之前能够确定一些索引性质的信息。
2.需要实现对业务的检查。为了支持反向流程的工作,业务应用必须能够根据反向流程发送过来的消息内容进行业务检查,确定这个消息所指向的业务操作的状态是完成、待处理,还是正在进行中,否则,待处理状态的消息就无法被处理了。
二、消息模型对消息接收的影响
在JMS中,有Queue(点对点)和Topic(发布/订阅)两种模型,来看一下两种模型的特点。
1.JMS Queue模型
该模型的特点:
>应用1和应用2发送消息到JMS服务器,这些消息根据到达顺序形成一个队列,应用3、应用4进行消息的消费。需要注意的是应用3和应用4收到的是不同的消息,也就是说JMS Queue方式下,如果Queue中的一个消息被一个应用消费了,那么连接到JMS Queue上的另一个应用是收不到这个消息的。即是JMS Queue中的每一个消息只能被一个应用消费,之后就从JMS中删除了。
>消息从发送端发送出来时不能确定最终会被哪个应用消费,但是可以明确的是只有一个应用会去消费这条消息。上图中应用3和应用4所接受到的消息的条数和顺序都不是固定的。
因此,JMS Queue模型也被成为Peer To Peer(PTP)方式。
2.JMS Topic模型
从上图中可以看出,从发送消息的部分和JMS Topic消息服务器内部的逻辑来看,两种模型是一样的,两者最大的区别在于消息的接收部分。在JMS Topic模型中,接收消息的应用3和应用4都可以独立的接收到所有到达Topic的消息。JMS Topic模型也被成为Pub/Sub方式。
3.JMS中客户端连接的处理和带来的限制
在使用JMS时,每个Connection都有一个唯一的ClientId,用于标记连接的唯一性,也就是说刚才对于Queue和Topic的介绍中,我们是默认一个接收应用只用了一个连接。现在来看一下多连接的情况,如下图所示:
从上图可以看到,应用3和JMS服务器建立了两个连接,应用4和JMS建立了一个连接。可以看到这三个连接接收到的消息是完全不同且不重复的,每个连接接收到的消息条数以及收到消息的顺序则不是固定的。
从上图可以看到,应用3和JMS服务器建立了一个连接,应用4和JMS建立了两个连接。两个应用一共建立了三个连接,每个连接都会接收到所有发送到Topic中的所有消息。
4.我们需要什么样的消息模型?
我们对于模型的需求:
.消息发送方和接收方都是集群。
.同一个消息的接收方可能有多个集群进行消息的处理。
.不同集群对同一条消息的处理互不干扰。
分析:对于第一个需求,JSM是可以直接支持的。对于需求二和需求三来说,即是假设有8条消息和两个集群,每个集群有两台机器,那么需要这两个集群中的机器分别处理掉8条消息,不能遗漏也不能重复。如下图所示,
前面我们介绍的JMS只提供了Queue和Topic两种模型,但是这两种模型直接使用在这个场景都是有问题的。
我们需要结合这两种模型做改进,实现满足我们需求的模型。如下图:
具体来说,我们可以把集群和集群之间对于消息的消费当作Topic模型来处理,而集群内部的应用的各个具体实例对于消息的消费当做Queue模型来处理。我们可以引入ClusterId,用这个Id来标识不同的集群,而集群内的各个应用实例的连接也是用同样的ClusterId。当服务器端进行调度时,根据ClusterId进行连接分组,在不同的ClusterId之间保证消息的独立投递,而拥有同样ClusterId的连接则共同消费这些消息。这个策略是分两级来处理,把Queue和Topic两种模型的特点结合起来使用,从而达到多个不同的集群进行消息订阅的目的。