发布/订阅消息传送模型

1、发布/订阅模型概览

    发布/订阅(publish-and-subscribe)模型通常被简写为pub/sub模型。在这个模型中,消息生产者成为发布者(publisher),而消息消费者则称为订阅者(subscribe)。在点对点模型中,是将消息发送到一个队列中,而发布/订阅模型则是将消息发布给一个主题。发布/订阅模型最重要的特性如下:

  • 消息通过一个称为主题的虚拟通道进行交换。
  • 每条消息都会传送给称为订阅者的多个消息消费者。订阅者有许多类型,包括持久性、非持久性和动态性。
  • 发布者通常不会知道、也也意识不到哪一个订阅者正在接收主题消息。
  • 消息被推送给消费者,这意味着消息会传送给消费者,而无需请求。消息通过一个称为主题的虚拟通道进行交换。主题就是生产者发布消息和订阅者消费消息的目的地。传送给一个主题的消息被自动推送给所有合格的消费者。
  • 生产者和消费者之间没有耦合。订阅者和发布者可以在运行时动态添加,这使得系统的复杂性可以随时间的推移而增长。
  • 订阅一个主题的每个客户端都会接收到发布该主题的消息副本。发布者生产的单条消息可以复制并分发给成百上千的订阅者。


    使用发布订阅模型时,JMS提供者会立即将发布到一个主题的消息传送给各个订阅者。因此,与点对点模型不同,订阅者并不是通过"扫描主题"来寻找属于它们的消息。相反,是由JMS提供者将消息的一个副本传送给各个订阅者。

    发布/订阅模型和点对点的另一个主要区别是,发布/订阅模型时在把消息复制给每个订阅者时,使用消息选择器;而点对点模型则是在已将消息添加到队列之后,再使用消息选择器。

    订阅者既可以是持久性的,也可以是非持久型的。非持久订阅者只有在当前订阅者是活动的,而且已经连接到主题的时候,才会接收到消息;而持久订阅者会接收发送到该主题的、它所需要的所有消息,而不管该订阅者活动与否。

    订阅者还可以是动态的,或者是受管的。动态持久型订阅者可以实时创建,而受管型订阅者则是静态的,并且JMS提供者也知道这个受管订阅者的存在。

    何时使用发布/订阅消息传送模型:如果要将事件或消息广播到多个消息消费者,就会用到发布/订阅模型。这里最重要的一条就是,这条消息可供多个消费者消费。发布/订阅模

                                               型的原理是将消息的副本推送给多个订阅者。

发布/订阅消息模型的使用:

 JMS初始化

    在TLender类示例中,suoyoudeJMS初始化逻辑都在构造函数中处理。TLender构造函数的代码,除了两处重要的区别外,几乎和QBorrow构造函数完全相同。

    首先,请注意TLender类的连接工厂、连接及会话对象,都和QBorrow类非常类似,除了它使用的是基于主题的接口而不是基于队列的接口:

//连接到提供者、并获得和JMS的连接
Context ctx = new InitialContext();
TopicConnectionFactory qFactory = (TopicConnectionFactory)ctx.lookup(topiccf);
tConnection = qFactory.createTopicConnection();

//创建JMS会话
tSession = tConnect.createTopicSession(false,Session.AUTO_ACKNOWLEGE);
//查找请求和响应队列
topic = (Topic)ctx.lookup(topicName);
//现在,创建已经完成,启动连接
tconnection.start();

   这里需要重点说明的是,尽管现在使用的是基于主题的API,但它的流程和点对点模型所用的基于队列的API是相同的:

  1. 获得JMS提供的一个初始上下文。
  2. 查找连接工厂。
  3. 创建一个JMS连接。
  4. 创建一个JMS会话。
  5. 查找目的地。
  6. 启动连接。

发布消息

    一旦TLender类被初始化,利率就会通过命令行输入进来。这事,系统从main方法中调用了publishRate方法,利率被发布到该主题智商。与点对点的例子不同,一旦消息发布以后,TLender类将不再等待响应。这事根据发布/订阅模型的去耦本质而为之;TLender类并不知道或并不关心谁在订阅利率、它们怎样处理数据,或者由多少订阅者在接收利率信息。也可能会出现这样的情况:一些订阅者正在接收利率数据,并由这个特定的贷方进行抵押利率波动的趋势分析,而其他订阅者(比如TBorrower类)则在进行分析利率,以决定是否再提供贷款。

    在publishRate方法的一开始,我们创建了一个BytesMessage来保存利率数据。可以选择5中JMS消息类型的任何一种,不过我们选择了可移植性最强的BytesMessage:

BytesMessage msg = tSession.createBytesMessage();
msg.writeDouble(newRate);

    在创建消息之后,我们接着又创建了TopicPublisher对象,它指定了希望发布消息的主题,然后,我们使用了publish方法发布了消息:

Topicpublisher publisher = tSession.createPublsher(topic);
publisher.publish(msg);

    像点对点模型的send方法一样,在TopicSender对象中也有若干种重写的publish方法可用。我们正在使用的这种方法,只接受JMSMessage对象作为唯一的参数。其他重写的方法则允许您指定主题、传送模型、消息优先级,还有消息有效期。由于我们并未制定任何其他值,所有消息优先级被设置为普通(4),传送模型被设置为持久性消息(DeliveryMode.PERSISTENT),而有效期则被设置为0,表示消息将永不过期。所有这些参数都可以使用其他publish方法来重写。

    这里有一点要说明:虽然请求/应答肯定能够适用于发布/订阅模型,但是它在当今基于主题的消息传送模型中并不常见,这主要是因为发布/订阅模型的本质特性:发布/订阅模型通常用于广播事件或信息,它并不期望对该广播作出响应。

持久订阅者和非持久订阅者

    如果您要运行TBorrower类,并随后发布若干种利率,TBorrower类就会得到新利率,并判断这个利率是否合适。不过,如果您要终止TBorrower类,发布一些新利率,随后再重启TBorrower类,您就不会获得在TBorrower类未运行期间发布到该主题的利率。这事为什么?原因就在于您所创建的TBorrower类是一个非持久订阅者:

TopicSubscriber subscribe = tSession.createSubscriber(topic);

    只有非持久订阅者在主动侦听一个主题时,才会接收到消息。否则,它们将会错过这些消息。在发布/订阅模型中,并不存在保存所有消息的"主题"这样一个真实的概念;确切的说,当JMS提供者接收到一条消息时,提供者将为每个订阅者制作该消息的一个副本。如果订阅者不是活动的,它就不会接收该消息的副本。这个概念如图所示5-3所示。

  

    另一方面,持久订阅者会接收发送到该主题的所有消息(依靠该订阅者使用的消息选择器),无论该订阅者活动与否。这通常称为"保存并转发(store-and-forward)"消息传送。持久订阅者”保存并转发"的概念如下:

 

 

        持久订阅者是通过在JMS提供者中指定订阅名称(通过配置或通过管理界面)并使用createDurableSuscriber方法来创建的,它接受订阅者名称作为参数之一:

TopicSubscriber subscriber = tSession.createDurableSubscriber(topic,"Borrower1");

      如何在持久订阅者和非持久订阅者之间作出选择,通常由业务需要决定,但是还要考虑其他一些注意事项,包括数据易失性和消息耗费的存储容量等。首先,除非是做某些

     趋分析,否则,您只会关注一只股票的当前价格,而不是20分钟或30分钟之前的价格。其次,如果持久订阅者长时间处于非活动状态,那么,这种模型就会为订阅者存储数以千

      计的无用消息,浪费JMS数据仓库(datastore)的宝贵空间。在选择持久订阅者和非持久订阅者时,必须要考虑这些问题。

动态订阅者和受管订阅者

         上一节,我们创建了一个名为Borrower1的持久订阅者,一些JMS提供者允许您在配置文件或管理界面中静态地定义持久订阅者。这时,持久订阅者称为受管持久订阅者。

    这意味着可以静态定义持久订阅者。而且JMS也已经知道了这个订阅者。

         JMS规范允许在运行时动态地定义持久订阅者,而无需在JMS提供者配置文件中对它们进行静态定义。这种类型的持久订阅者称为动态持久订阅者。例如,如果要定义一个新

    持久订阅者BorrowerA,只须输入:

TopicSubscriber subscriber = tSession.createDurableSubscriber(topic,"BorrowerA");

   在这个例子中,BorrowA持久订阅者不是在JMS的提供者中定义的,因此,它并不是一个受管持久订阅者。不过一旦执行上面的代码行,就会在JMS提供者中创建一个新的持

  久订阅者BorrowerA。该订阅者将保持持久订阅者的身份,直到它被取消订阅(unsubscribe)为止。

    尽管这种特性提供了很大的灵活性,但它也是有代价的。每个持久订阅者,无论它是受管的还是动态的,都会接收发布到该主题的一个消息副本。

    这意味着当持久订阅者未被激活时,就得为它们存储那些消息。从容量规划的角度来看,动态持久订阅者存在某种程度的风险,其原因在于,它难以控制使用该系统的持久订

    阅者的数量。

取消持久订阅者的订阅状态

     有时候,您应该在客户端应用程序中显示取消一个持久订阅者的订阅状态,可以调用Session.unsubscriber方法来删除一个动态订阅者:

private void exit(){
    try{
        subscriber.close();
        tSession.unsubscribe("BorrowerA");
        tConnection.close():       
     }catch(Exception e){
       e.printStackTrace();
    }
}

   对于非持久订阅者,调用TopicSubscriber类的close()方法就足够了。而对于持久订阅,则应该使用TopicSession对象的unsubscribe(String name)方法,它采用订阅名称

    作为参数。这会通知JMS提供者,不要再为这个客户端存储消息。如果没有先把该订阅关闭,那么,您无法调用unsubscribe()方法。因此,对于持久订阅来说,要调用这两个方

    法。

临时主题

    临时主题时JMS提供者动态创建的一个主题,它是使用TopicSession对象的createTemporaryTopic方法创建的。临时主题与创建该主题的TopicSession的连接关联在一

   起。它仅在连接期间活动,而且可以保证在所有连接中是唯一的。由于是临时主题,所以它不具有持久性:只有和它关联的客户端连接是活动的,它才被激活。在其他所有方面,

   它就像是一个"正常的"主题。

       因为临时主题时通过调用在客户端会话对象上的一种方法而动态获得的,所以临时主题是唯一跨所有客户端连接的主题。但是除非使用JMSReplyTo消息头传送该主题的身

   份,否则,对于其他JMS客户端来说,它是不可用的。虽然任何客户端都能够向其他客户端的临时主题发布消息,不过,只有与创建临时主题的JMS客户端连接关联的会话,才能

  订阅这些消息。当然,JMS客户端还可以向自己的临时主题发布消息。

posted @ 2014-07-19 22:20  皓若繁星  阅读(1800)  评论(0)    收藏  举报