ActiveMQ使用进阶指导

一、回顾Java消息服务(JMS) 


     七大组件:
          
     


     两种模型:
     
          点对点或队列模型:
  • 只有一个消费者将获得消息
  • 生产者不需要在接收者消费该消息期间处于运行状态,接收者也同样不需要在消息发送时处于运行状态。
  • 每一个成功处理的消息都由接收者签收

     
           发布者 订阅者模型:
  • 多个消费者可以获得消息
  • 在发布者和订阅者之间存在时间依赖性。发布者需要建立一个订阅(subscription),以便客户能够购订阅。订阅者必须保持持续的活动状态以接收消息,除非订阅者建立了持久的订阅。在那种情况下,在订阅者未连接时发布的消息将在订阅者重新连接时重新发布。

二、ActiveMQ是一个开源的JMS提供者

          

    名词解释

          Broker:  JMS服务端,接收与保存生产者发送的消息内容,并推送给消费者使用;
          持久化消息:持久性属于消息的一个属性,生产者在发送消息时,可以指定消息属性为持久化消息,broker会将此消息持久化到文件或数据库之后,再推送给消费者;这样即使broker重启也不会丢失消息;JMS规范中,默认消息发送就是持久化的;
           非持久化消息:顾名思义消息不会在broker中持久化到文件,重启可能会产生消息丢失,非持久化消息在异步发送时,性能比持久化消息快20倍(有待考证);

          同步发送:生产者发送消息到broker时,需要同步等待broker的响应;如果你不开启事务、并且发送持久化消息,borker将在消息持久化完成之后,才给生产者发送确认消息;这样做的好处是,broker一旦确认收到消息,消息将不会被丢失;可以在连接工厂上设置,也可以在单个连接上设置;

           异步发送:生产者发送消息到broker时,不用同步等待broker的响应;异步发送效率高,可能会出现消息丢失;可以在连接工厂上设置,也可以在单个连接上设置;
           非持久订阅:消息模型一定是发布者/订阅者模型,只有在消费者在线的情况下,才会收到生产者发送到某个topic的消息,消费者处于离线状态时,这个时间段的消息将不会被收到,即使消费者重新在线也将无法接受到;
          持久订阅:消息模型一定是发布者/订阅者模型,消费者向Broker注册一个自己身份的标识(ClientID+订阅者名字),当这个消费者离线时,broker会为这个标识保存发送到某个topic的消息,当消费者重新在线时,会根据自己标识获取到离线期间的消息;
      Connector Types
     
     
         
         
     
         
      Client Failover
         
          failover:( tcp://host1:61616,tcp://host:61616)?randomize=false
          

三、与Spring整合
      
     加载依赖包


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
         < dependency >
             < groupId >org.apache.activemq</ groupId >
             < artifactId >activemq-all</ artifactId >
             < version >5.9.0</ version >
         </ dependency >
         < dependency >
             < groupId >org.springframework</ groupId >
             < artifactId >spring-context</ artifactId >
             < version >3.2.8.RELEASE</ version >
         </ dependency >
         < dependency >
             < groupId >org.springframework</ groupId >
             < artifactId >spring-jms</ artifactId >
             < version >3.2.8.RELEASE</ version >
         </ dependence >
         < dependence >
             < groupId >org.apache.xbean</ groupId >
             < artifactId >xbean-spring</ artifactId >
             < version >3.12</ version >
         </ dependency >


     
     配置生产者(application_provider.xml )


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
<? xml  version = "1.0"  encoding = "UTF-8" ?>
< beans  default-lazy-init = "true"
 
     <!-- 自动装载com.aqlu包下的所有Bean -->
     < context:component-scan  base-package = "com.aqlu" />
 
     <!-- 加载配置文件 -->
     < context:property-placeholder  system-properties-mode = "OVERRIDE"  ignore-resource-not-found = "true"
                                   location = "classpath:config.properties" />
 
 
     <!-- 定义消息的目的地址(queue)-->
     < bean  id = "testQueue"  class = "org.apache.activemq.command.ActiveMQQueue" >
         < constructor-arg  value = "TestQueue" /> <!-- queue名字 -->
     </ bean >
 
     <!-- 定义消息的目的地址(topic) -->
     < bean  id = "testTopic"  class = "org.apache.activemq.command.ActiveMQTopic" >
         < constructor-arg  value = "testTopic" /> <!-- topic名字 -->
     </ bean >
 
     <!-- 定义消息的目的地址(虚拟topic)-->
     < bean  id = "testVirtualTopic"  class = "org.apache.activemq.command.ActiveMQTopic" >
         < constructor-arg  value = "VirtualTopic.testTopic" /> <!-- topic名字,注意这里必须要带上VirtualTopic. -->
     </ bean >
 
     <!-- 生产者连接工厂 -->
     < amq:connectionFactory  id = "producerConnectionFactory"  brokerURL = "${producer.brokerUrl}"
                            useAsyncSend = "true" />
     
     <!-- 配置具有session缓存给你的连接工厂 -->
     < bean  id = "producerCachingConnectionFactory"  class = "org.springframework.jms.connection.CachingConnectionFactory" >
         < property  name = "targetConnectionFactory"  ref = "producerConnectionFactory" />
         < property  name = "sessionCacheSize"  value = "${producer.sessionCacheSize}" />
     </ bean >
 
     <!-- 简单消息转换器,能够对String、byte[]、Map、Serializable类型的消息自动转换 -->
     < bean  id = "simpleMessageConverter"  class = "org.springframework.jms.support.converter.SimpleMessageConverter" />
 
     <!--配置生产者消息发送模板-->
     < bean  id = "producerJmsTemplate"  class = "org.springframework.jms.core.JmsTemplate" >
         < property  name = "connectionFactory"  ref = "producerCachingConnectionFactory" />  <!-- 指定连接工厂 -->
         < property  name = "explicitQosEnabled"  value = "true" /> <!-- 是否启用Qos; 只有开启Qos时,设置的deliveryMode, priority, timeToLive才能生效 -->
         < property  name = "deliveryPersistent"  value = "true" /> <!-- 设置消息是否需要持久化, 默认为PERSISTENT -->
         < property  name = "priority"  value = "4" /> <!-- 设置优先级, 默认为4 越高优先级越高。kahaDB只支持3种优先级,<4,=4,>4 -->
         < property  name = "timeToLive"  value = "${producer.timeToLive}" /> <!-- 消息有效时间,单位毫秒,默认是不过期;超过消息有效期的消息
将被broker“删除”;强烈建议明确设置此参数,目前我司的业务场景消息如果在一小时还未被处理的话,大部分情况都没有价值了 -->
         < property  name = "messageConverter"  ref = "simpleMessageConverter" /> <!-- 指定消息转换器 -->
     </ bean >
 
</ beans >


          
      发送者代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package  com.aqlu.demo.amq;
 
import  org.springframework.beans.factory.annotation.Autowired;
import  org.springframework.beans.factory.annotation.Qualifier;
import  org.springframework.jms.core.JmsTemplate;
import  org.springframework.stereotype.Component;
 
import  javax.jms.Destination;
import  java.io.Serializable;
 
/**
  * Created by aqlu on 14-6-25.
  */
@Component
public  class  MessageSender {
 
     @Autowired
     @Qualifier ( "producerJmsTemplate" )
     private  JmsTemplate jmsTemplate;
 
     @Autowired
     @Qualifier ( "testTopic" )
     private  Destination destination;
 
     /**
      * 发送消息
      * @param message 消息对象
      */
     public  void  send( final  Serializable message) {
         try {
             jmsTemplate.convertAndSend(destination, message);
         } catch (JmsException e){
             // ....  TODO
         }
     }
 
     public  Destination getDestination() {
         return  destination;
     }
 
     public  void  setDestination(Destination destination) {
         this .destination = destination;
     }
 
     public  JmsTemplate getJmsTemplate() {
         return  jmsTemplate;
     }
 
     public  void  setJmsTemplate(JmsTemplate jmsTemplate) {
         this .jmsTemplate = jmsTemplate;
     }
 
     public  static  void  main(String[] args) {
         ApplicationContext context =  new  ClassPathXmlApplicationContext( "application_provider.xml" );
         MessageSender sender = context.getBean(MessageSender. class );
         sender.send( "Hello broker!" );
     }
}



     消费者配置(application_consumer.xml
     


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<? xml  version = "1.0"  encoding = "UTF-8" ?>
< beans  default-lazy-init = "true"
 
     <!-- 自动装载com.aqlu包下的所有Bean -->
     < context:component-scan  base-package = "com.aqlu" />
 
     <!-- 加载配置文件 -->
     < context:property-placeholder  system-properties-mode = "OVERRIDE"  ignore-resource-not-found = "true"
                                   location = "classpath:config.properties" />
 
     <!-- 定义消息的目的地址(queue)-->
     < bean  id = "testQueue"  class = "org.apache.activemq.command.ActiveMQQueue" >
         < constructor-arg  value = "TestQueue" /> <!-- queue名字 -->
     </ bean >
 
     <!-- 定义消息的目的地址(topic) -->
     < bean  id = "testTopic"  class = "org.apache.activemq.command.ActiveMQTopic" >
         < constructor-arg  value = "testTopic" /> <!-- topic名字 -->
     </ bean >
 
     <!-- 定义消息的目的地址(虚拟topic)-->
     < bean  id = "testVirtualQueue"  class = "org.apache.activemq.command.ActiveMQQueue" >
         < constructor-arg  value = "VirtualTopicConsumers.${consumer.clientId}.VirtualTopic.testTopic" /> <!-- 前缀根据broker配置-->
     </ bean >
 
     <!-- 消费者连接工厂; borkerURL: broker地址; useAsyncSend:是否使用异步发送;clientID:客户端标识;-->
     < amq:connectionFactory  id = "consumerConnectionFactory"  brokerURL = "${consumer.brokerUrl}"
                            clientID = "${consumer.clientId}" />
 
     <!-- 配置具有session缓存给你的连接工厂 -->
     < bean  id = "consumerCachingConnectionFactory"  class = "org.springframework.jms.connection.CachingConnectionFactory" >
         < property  name = "targetConnectionFactory"  ref = "consumerConnectionFactory" />
         < property  name = "sessionCacheSize"  value = "${consumer.sessionCacheSize}" />
     </ bean >
 
     <!-- 配置消费者监听容器 -->
     < bean  id = "taskContainer"  class = "org.springframework.jms.listener.DefaultMessageListenerContainer" >
         < property  name = "connectionFactory"  ref = "consumerConnectionFactory" />  <!-- 指定连接工厂-->
         < property  name = "destination"  ref = "testTopic" />  <!-- 目的地址,queue或者topic -->
         < property  name = "messageListener"  ref = "messageConsumer" />  <!-- 指定消息消费监听对象,实现MessageListener 接口 -->
         < property  name = "concurrentConsumers"  value = "${consumer.concurrentConsumers}" /> <!-- 配置监听对象的并发数 -->
         < property  name = "maxConcurrentConsumers"  value = "${consumer.maxConcurrentConsumers}" /> <!-- 配置监听对象的最大并发数 -->
         <!--<property name="clientId" value="sss"/>-->  <!-- 客户端标识,不配的话会使用连接工程配置的clientID,连接工厂也没陪的话会自动删除一个 -->
         < property  name = "sessionAcknowledgeModeName"  value = "CLIENT_ACKNOWLEDGE" /> <!-- 在使用Spring的情况下,AUTO确认模式会在
消息进入业务方法前进行回应,Client则会在之后,DUL_OK可以延迟消息回应并批量处理,但这可能导致消息重复发送。 -->
         < property  name = "sessionTransacted"  value = "false" />  <!-- 是否开启事务,默认false-->
         < property  name = "subscriptionDurable"  value = "true" /> <!-- 是否持久化订阅,此属性仅在订阅topic时有效;持久化订阅时,监听对象的并发
数只能为1;在使用queue消费时,请注释掉,否则会影响启动 -->
         < property  name = "durableSubscriptionName"  value = "node1" /> <!-- 持久化订阅者名称;持久化订阅开启才生效,持久化订阅时必配;在使用
queue消费时,请注释掉,否则会影响启动 -->
     </ bean >
 
</ beans >



     消费者代码


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package  com.aqlu.demo.amq;
 
import  org.springframework.stereotype.Component;
 
import  javax.jms.*;
 
/**
  * Created by aqlu on 14-6-25.
  */
@Component ( "messageConsumer" )
public  class  MessageConsumer  implements  MessageListener {
     @Override
     public  void  onMessage(Message message)  {
         try  {
             if  (message  instanceof  TextMessage) {
                 System.out.println(Thread.currentThread().getName() +   "接受到消息: "  + ((TextMessage) message).getText());
             } else  if (message  instanceof  ObjectMessage){
                 System.out.println(Thread.currentThread().getName() +   "接受到消息: "  + ((ObjectMessage) message).getObject());
             } else {
                 System.out.println(Thread.currentThread().getName() +  "不能识别此消息:"  + message);
             }
         catch  (JMSException e) {
             e.printStackTrace();
         }
     }
 
     public  static  void  main(String[] args) {
         ApplicationContext context =  new  ClassPathXmlApplicationContext( "application_consumer.xml" );
     }
}




四、业务使用建议
     
     消息内容
          1.消息内容不宜过大,建议尽量使用简单文本消息,每个消息体大小不超过1kb;如有大的消息需要传递,建议使用业务设计避免开,譬如:货品状态发生变化时,消息体里面仅保存货品id以及变化后的状态,如需其他货品信息可以由业务根据货品id调接口查询;
          2. 合理配置消息有效期;
      发送方式
          1.建议所有发送方式设置为持久化发送;非持久化消息在broker重启或故障时,会有消息丢失的可能;
          2.对消息生产者来说,异步发送会会极大的提高系统的吞吐量;ActiveMQ缺省是采用异步发送发送,但按照JMS规范,如果不开启事务且发送持久化消息时,会强制采用同步方式发送,在这种情况下,每一次发送都是同步的,而且阻塞到收到broker的应答,这个应答保证了broker已经成功地将消息持久化,而且不会丢失,但是这样作也严重地影响了性能。如果你的系统可以容忍少量的小事丢失,也可以指定在不开启事务时采用异步方式发送持久化消息;配置方式如下:


1
2
3
4

配置Borker URL参数:  failover:(tcp://host1:61616,tcp://host2:51616)?randomize=false&jms.useAsyncSend=true
配置连接工厂useAsyncSend属性:  <amq:connectionFactory id="producerConnectionFactory" brokerURL="${producer.brokerUrl}" useAsyncSend="true " />


           3.建议消息生产者对于重要消息根据自己业务建立补发机制,针对发送消息异常情况进行补发;
      消费方式
          1.建议消费者在MessageListener的实现类中,异步处理自己的业务逻辑,以达到快消费;处理流程示例:
          
          2.在生产者产能过剩且消费者处理能力很快的情况下,可以通过设置消费者预取值来提升性能;配置方式如下:


1
2
3
4
5

配置所有消费者的预取值:  failover:(tcp://host1:61616,tcp://host2:51616)?randomize=false&jms.prefetchPolicy.all=50
配置所有queue消费者的预取值:  failover:(tcp://host1:61616,tcp://host2:51616)?randomize=false &jms.prefetchPolicy.queuePrefetch=1000
配置所有topic消费者的预取值:  failover:(tcp://host1:61616,tcp://host2:51616)?randomize=false &jms.prefetchPolicy.topicPrefetch=1000


          3.建议消费端处理接口采用幂等设计;
          
      Consumer数量配置
          MessageListenerContainer中允许定义并发的consumer数量,在Queue模式下,通过增大并发consumer数量可以提高消费能力,而且Queue中的消息只会被消费一次;但在Topic模式下,增大并发consumer并不会提升消费能力,因为在Topic模式中,每个consumer都是独立的订阅者,即每个consumer都会收到全量的topic消息,通常情况下concurrentConsumers与maxConcurrentConsumers都配置为1;
     
     模型选择
          1.如何实现消费者在集群环境下,不重复消费Topic消息?
               a. 设置集群中的所有Consumer的ClientID相同;可以在ConnectionFactory中配置,也可以在MessageListenerContainer中配置;  ActiveMQ会保证同一时刻、只有唯一的ClientID的consumer连接上Broker,直到这个consumer关闭,集群中的其他consumer才能连接;优点:生产、消费速度较均衡,broker压力小;弱点:消费者为冷备,压力集中在一个负载上; 推荐使用
               b. 使用virtualtopic;生产者发送特殊的Topic消息到broker,broker负责给topic的订阅者创建Queue,并将消息放入queue,然后消费者从Queue中消费;优点:可以配置多个消费者,消费速度快;弱点:生产速度慢,特别是同步发送持久化消息时,broker压力大;
          2.如何实现消费者下线后,恢复上线时能接受到离线期间的Topic消息?
               a.消费者配置持久化订阅;持久化订阅需要消费者同时满足几个条件:①指定ClientID;②concurrentConsumers与maxConcurrentConsumers都配置为1;③开启subscriptionDurable与设定durableSubscriptionName; 推荐使用;
               b.使用virtualtopic;
      延时与定时投递
          ActiveMQ提供了一种broker端消息定时调度机制,能够满足用户这样的场景:有时候不希望消息马上被broker投递出去,而是想要60秒之后发给消费者,或者我们想让消息每隔一定时间段投递一次,一共投递指定的次数;虽然ActiveMQ提供了满足这样场景的功能,但 不推荐使用;原因如下:①基于目前的AMQ设计,发送持久化延时消息时可能会导致某些持久化文件无法正常清除;②额外增大了broker的负担;③在使用新的leveldb高可靠方案时,由于这些信息依然还存在单独的kahaDB中,apache已明确声明暂不支持;
          如有延时与定时投递的需求场景,建议业务层使用JDK自带的DelayQueue来实现,生产者在到达投递时间后再将消息投递到broker中;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值