Posts Tagged ‘jms’

ActiveMQ使用经验

June 18th, 2009

ActiveMQ是apache的一个开源JMS服务器,不仅具备标准JMS的功能,还有很多额外的功能。公司里引入ActiveMQ后,ActiveMQ成里我们公司业务系统中最重要的一个环节。所有应用都通过jms集成,如果ActiveMQ出了故障,整个系统就瘫痪了。因此,头对ActiveMQ的性能,可靠性,以及如何正确使用,是非常的关心的,而我就被指派来做关于ActiveMQ的调研,本文对此做了些总结。

1 使用jms需要注意的问题

一下所述的问题,不仅是对ActiveMQ,对于其他的JMS也一样有效。

1.1 不要频繁的建立和关闭连接

JMS使用长连接方式,一个程序,只要和JMS服务器保持一个连接就可以了,不要频繁的建立和关闭连接。频繁的建立和关闭连接,对程序的性能影响还是很大的。这一点和jdbc还是不太一样的。

1.2 Connection的start()和stop()方法代价很高

JMS的Connection的start()和stop()方法代价很高,不能经常调用。我们试用的时候,写了个jms的connection pool,每次将connection取出pool时调用start()方法,归还时调用stop()方法,然而后来用jprofiler发现,一般的cpu时间都耗在了这两个方法上。

1.3 start()后才能收消息

Connection的start()方法调用后,才能收到jms消息。如果不调用这个方法,能发出消息,但是一直收不到消息。不知道其它的jms服务器也是这样。

1.4 显示关闭Session

如果忘记了最后关闭Connection或Session对象,都会导致内存泄漏。这个在我测试的时候也发现了。本来以为关闭了Connection,由这个Connection生成的Session也会被自动关闭,结果并非如此,Session并没有关闭,导致内存泄漏。所以一定要显示的关闭Connection和Session。

1.5 对Session做对象池

对Session做对象池,而不是Connection。Session也是昂贵的对象,每次使用都新建和关闭,代价也非常高。而且后来我们发现,原来Connection是线程安全的,而Session不是,所以后来改成了对Session做对象池,而只保留一个Connection。

2 集群

ActiveMQ有强大而灵活的集群功能,但是使用起来还是会有很多陷阱。

2.1 broker cluster和 master-slave

ActiveMQ可以做broker的集群,也可以做master-slave方式的集群。前者能在多个broker之前fail-over和load-balance,但是在某个节点出故障时,可能导致消息丢失;而后者能实时备份消息,和fail-over,但是不能load-balance。broker cluser的方式,在一个broker上发送的消息可以在其它的broker上收到。当一个broker失效时,客户端可以自动的转到别的broker上运行,多个broker可以同时提供服务,但是消息只存储在一个broker上,如果那个broker失效了,那么客户端直到它重新启动后才能收到该broker上的消息,假如很不幸,那个broker的存储介质坏了,那么消息就丢失掉了。
Master-slave方式中,只有master提供服务,slave只是实时的备份master的数据,所以消息不会丢失。当master失效时,slave会自动升为master,客户端会自动转到slave上工作,所以能fail-over。由于只有master提供服务,所以不能将负载分到多个broker上。
其实单个broker的性能已经是相当的惊人了,在我们公司的机器上能达到每秒收发4000个消息,没个消息4K字节这样的速度,足够公司目前的需要了,而公司并不希望丢失任何数据,所以我们选择使用master-slave模式。

2.2 多种master-slave模式

master-slave也有多种实现方式。它们的不同只是在共享数据和锁机制上。

2.2.1 Pure master-slave

Pure master-slave,显示的在配置文件中指定一个broker做为另一个broker的slave。运行时,slave同过网络自动从master出复制数据,同时在和master失去连接时自动升级为master。当master失效,slave成为master后,如果要让原先的master重新投入运行,需要停掉运行中的slave(现在升级为master了),手动复制slave中的数据到master中。再重新启动master和slave。这种方式最简单,效率也不错,但是只能有两台做集群,只能fail-over一次,而且需要停机回复master-slave结构。

2.2.2 JDBC master-slave

这种方式不需要特殊的配置,只要让所有的节点都把数据存储到同一个数据库中。先拿到数据库表的锁的节点成为master,一旦它失效了,其它的节点获得锁,就可以成为master。因为数据通过数据库共享,放在一个地方,不需要停机恢复master-slave。这种方式,需要额外的数据库服务器,如果数据库失效了,那么就全失效了,而且速度不是很快。我们在用mysql测试时,并没有成功,master失效后,其他的节点始终没有升级成slave,可能是数据库配置的问题。

2.2.3 Share file master-slave

这种方式类似于前者,也不需要特别的配置,只是通过共享文件系统来共享数据,靠文件锁实现只有一台成为master。共享文件系统的方式有很多,我们测试了nfs v4 (v3有bug,不行), 最终在稳定性,效率等方面不是很满意,可能是通过网络太慢了。

测试过众多master-slave模式后发现,pure方式管理起来麻烦,jdbc方式成本高,效率低,share file方式需要高性能的共享文件,都有缺点。鉴于单台activeMQ很可靠,而我们的基础平台组愿意用硬件备份,最终还是决定不用master-slave了,也不用broker cluster,就用单台,通过硬件冗余保证数据不会丢失,并找另外一台刀片机做冷备,在主服务器失效时顶替。

  • Share/Bookmark

如何保证jms消息的顺序性

June 3rd, 2009

JMS提供的queue和topic两种工作方式,其中queue能保证消息在传输中的顺序性,这是队列先入先出(first in first out)的特性。JMS本身不能保证多个队列里的消息的顺序性,比如 先放入queue1的消息m1,并不一定总是比后放入queue2的消息m2,先到达同样的目的地。

并发地发送和接收消息不能保证消息按正确顺序进入队列和被消费。比如,有两个线程分别发送创建订单和支付订单消息,由于线程运行的不确定性,即使我们按顺序先开始发送创建订单的线程,后开始支付订单的线程,如果两个线程开始时间间隔不够长,还是有可能后开始的线程先发送掉消息。同样的情况发生在并发的接收处理消息时。

那么如何保证消息的顺序性呢?

一种方案是,乱序接收,顺序处理。也就是说,消息在发送,传输和接收过程中可能是乱序的,但消费者在接收到消息之后,并不立即处理,而是先将消息排序,然后在处理。JMS消息头部的 JMSCorrelationID可以帮助我们完成这个工作。JMSCorrelationID存放了另一个消息的id。消息的发送者,如果要保证消息的顺序性,要将后发送的消息的JMSCorrelationID设置成前一个消息的id。消费者接收消息后,如果发现其头部有JMSCorrelationID,则查看该消息是否已被处理过,如果没有,则等待该消息,至到该消息被处理后,才处理这个消息。这一工作需要发送者和接收者都记住已经发送和接收过的消息,以便于给后来的消息参考。

另一种方案是,顺序发送,顺序传输,顺序接收,顺序处理。其中传输可以由queue来保证,但是发送接收和处理则需要应用程序来控制。简单的说,直到前一个消息发送成功,才能发送后一个消息,同样的,直到前一个消息被接受和处理结束,才能接收和处理后一个消息。这样的做法无疑会降低并发带来的好处。

以上所说的方案,是用来严格控制消息的顺序性的,然而,如果消息的发送的间隔时间足够长,不需要做过多控制,也可以控制消息的顺序性。假设,一个消息正常情况下,由发送,传输,接收到处理成功,最大时间耗费是2秒,那么只要我们保证消息发送的时间隔达不低于2秒,那么这两个消息就可以被正确的处理。这一条件咋听起来很苛刻,但事实上大部分的消息都满足了。比如,同一个订单的创建,和请求支付,最快也要数秒的时间,而退款的开始和结束,可能要数天。在典型的web应用中,操作人员不可能那么快的点击系统,而系统也不可能那么快的响应。进一步的,如果两个需要顺序处理的业务事件可能在极端的时间里连续发生,我们只要在程序上控制,人为将间隔拉开,就可以保证顺序性。

在系统的各各环节控制消息的顺序,代价高昂,而带来的好处却只是针对那极少数极端情况;通过业务或程序的方式,保证消息的有效时间间隔,代价较小,也能有效保证顺序。

如果系统中有很多的间隔极端,又需要保证顺序的消息,那你就要考虑是否将这两个消息合并成一个,或则不该采用jms了。

  • Share/Bookmark

企业应用集成–java

April 28th, 2009

java企业应用最终往往运行在某种应用服务器中,比如tomcat,jboss等。部署的单元是war, jar 或者 ear文件,开始的时候,我们可能会将多个应用放到一个单元里来开发管理和部署。随着应用的增多,为了能够让每个应用能够独立的运行不受干扰,也为了开发工作能很好的安排,往往会将各应用分开,于是会有多个war, jar,或ear文件。试想一下,假设你有订单系统,产品系统,客服系统,等,如果这些应用都被放在同一个部署单元里,当需要更新客服系统时,会短暂的导致订单系统不能正常工作,而这种情况往往是不能被接受的。另外,如果客服系统非常繁忙,由于部署在同一台物理服务器上,共享硬件资源,订单系统会受到干扰,而这也不是我们想要的。将单独的应用单独打包,单独部署成为必然。那么就面临着让这些应用通信,将它们集成的问题。


与java和php之间集成相比,java与java之间的集成,不仅可以使用共享数据库,soap,或基于http的简单的文档交换,还可以使用jms,rmi等多种方案。其中jms是j2ee众多的标准中最为成功的协议之一,很少有人针对它发表过不满意见。我们在工作中就采用了jms。和http相比,它有如下优势:

为java量身定做的

jms不仅可以传输普通的字符串,还可以直接传输java对象,并和java的其它服务结合起来,接口api也符合java的使用习惯。如果用http,我们还得要考虑如何把对象序列化等问题,比较麻烦。

使用长连接

http主要用在客户端和服务器交互上,为了能让服务器支持大量的客户端,http往往都以一种无状态和短连接的方式工作。而jms主要用在java应用之间的交互,不需要支持那么多的客户端,用长连接更加适合。典型的http服务器,可能同时要给数千数万人提供服务,如果服务器和每个客户端保持一个连接,那对服务器来说,显然是一个非常大的负担。所以,通常客户端和http的一次交互完成后,连接就断开,而下次访问时,还得要重新建立连接。jms服务器面对的客户端一般只有几个,至多几十个,和每个客户端保持连接的开销是可以接受的。典型的企业可能有如下系统:订单系统,产品系统,结算系统,等等,这些系统,每个都和集中的jms服务器保持一个连接,总连接数也是很少的,所以可以保持长连接。这客户端和jms服务器通信的时候,不必每次都建立连接,减少了网络操作,提升了性能。

异步

http操作的过程是同步的,如果要使用异步的方式工作,需要程序员再做些编码工作。而jms天生就以异步的方式来工作的,不必做过多的编码。消息的生产者一旦把消息发送出去,就可以立刻返回不必等待消息的消费结果,后续的消息消费可以自动的异步执行。将不必要立刻执行的动作异步执行,可以大大降低系统的响应时间。

可靠的消息传递

在使用http通信的过程中,如果发生了网络异常,是否要做重发,以及如何重发,会是一件很痛苦的事情。jms提供了persitent的传递模式,在这种模式时,客户端代理在发送消息之前,会先把消息持久,如果发生网络异常,客户端代理会自动处理重发,而不需要应用程序关心。jms消息的唯一编号,可以有效的阻止一个消息被重复处理的问题。

在java应用集成时,jms比http更适合。成熟的jms服务器软件很多,除了ibm的mq,jboss mq, apache activemq 都是很不错的选择。

  • Share/Bookmark

如何让jboss下的消息驱动bean消费远程JMS消息

December 10th, 2008

用消息驱动bean来处理本地的JMS消息太容易不过了,但是如何处理远程的消息呢?翻遍了Java EE手册和API也找不到。原来各种应用服务器都有各自的实现。那么如何让jboss下的消息驱动bean消费远程JMS消息呢?把下面的代码复制到一个文件里,并重名为以-service.xml结尾的文件,放到jboss的deploy目录,就可以在本地得到一个从远程获取消息的JMS provider。

    <mbean code="org.jboss.jms.jndi.JMSProviderLoader"
name="jboss.mq:service=JMSProviderLoader,name=RemoteJMSProvider,server=remotehost">
        <attribute name="ProviderName">RemoteJMSProvider</attribute>
        <attribute name="ProviderAdapterClass">org.jboss.jms.jndi.JNDIProviderAdapter</attribute>
        <!-- The connection factory -->
        <attribute name="FactoryRef">UIL2XAConnectionFactory</attribute>
        <!-- The queue connection factory -->
        <attribute name="QueueFactoryRef">UIL2XAConnectionFactory</attribute>
        <!-- The topic factory -->
        <attribute name="TopicFactoryRef">UIL2XAConnectionFactory</attribute>
        <!-- Connect to JNDI on the host "the-remote-host-name" port 1099-->
        <attribute name="Properties">
            java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
            java.naming.factory.url.pkgs=org.jnp.interfaces
            java.naming.provider.url=the-remote-host-name:1099
        </attribute>
    </mbean>



注意,你要把上面的 the-remote-host-name 改成你的。部署完成后,确认在jboss的JndiViewer里能不能看到 java:/RemoteJMSProvider 。然后再MDB类上加上:

    @MessageDriven(activateConfig = {
        @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
        @ActivationConfigProperty(propertyName="destination", propertyValue="queue/testQueue"),
        @ActivationConfigProperty(propertyName="providerAdapterJNDI", propertyValue="java:/RemoteJMSProvider")
    })
    public class MDB implements MessageListener {
        ...
    }



providerAdapterJNDI属性使这个MDB从刚才部署好的JMS provider那里读取消息。

  • Share/Bookmark