ActiveMQ_基础API_C_2021-03-09

这篇博客主要在讲ActiveMQ基础API和配置的特性

编号说明
C_2021-03-09第一次创建

ActiveMQConnectionFactory

在通过基础API对ActiveMQ进行操作的时候,第一步就是实例化出连接工厂。在demo里,创建实例工厂的时候,只是给出了ActiveMQ在transportConnectors中配置的一个连接地址。除此之外,在构建ActiveMQConnectionFactory的时候,还可以传递用户名,密码,如下所示。
源码截图默认情况下,对于ActiveMQ中是没有设置name和password。要想使用这个功能,可以通过在activemq.xml里简单配置。

注意,下面的plugins是配置在broker标签内。

<plugins>
      <simpleAuthenticationPlugin>
          <users>
              <authenticationUser username="admin" password="admin" groups="admins,publishers,consumers"/>
              <authenticationUser username="publisher" password="publisher"  groups="publishers,consumers"/>
              <authenticationUser username="consumer" password="consumer" groups="consumers"/>
              <authenticationUser username="guest" password="guest"  groups="guests"/>
          </users>
      </simpleAuthenticationPlugin>
 </plugins>

在上面的配置文件中,定义了用户名,密码,还有对应的组别。
需要留意,这里的配置信息是用于创建连接工厂的时候使用,而非web控制台的账号密码。

ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
				"admin",
				"admin",
				"tcp://localhost:61616"
				);

connection & session

事务 & 持久化

在demo中,有这么一行代码。

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

对于 Session.AUTO_ACKNOWLEDGE 签收模式,暂且不提,我们来看第一个参数,这个boolean值。
方法的源码如下。

public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException {
        checkClosedOrFailed();
        ensureConnectionInfoSent();
        if (!transacted) {
            if (acknowledgeMode == Session.SESSION_TRANSACTED) {
                throw new JMSException("acknowledgeMode SESSION_TRANSACTED cannot be used for an non-transacted Session");
            } else if (acknowledgeMode < Session.SESSION_TRANSACTED || acknowledgeMode > ActiveMQSession.MAX_ACK_CONSTANT) {
                throw new JMSException("invalid acknowledgeMode: " + acknowledgeMode + ". Valid values are Session.AUTO_ACKNOWLEDGE (1), " +
                        "Session.CLIENT_ACKNOWLEDGE (2), Session.DUPS_OK_ACKNOWLEDGE (3), ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE (4) or for transacted sessions Session.SESSION_TRANSACTED (0)");
            }
        }
        return new ActiveMQSession(this, getNextSessionId(), transacted ? Session.SESSION_TRANSACTED : acknowledgeMode, isDispatchAsync(), isAlwaysSessionAsync());
    }

从源码来看,这个boolean的参数表示,是否开启事务。我们暂且只关注最后return的那一句。如果这个boolean是true,则签收模式一律设置成Session.SESSION_TRANSACTED。否则,会把签收模式设置为程序员传入的类型。

不论是provider或者consumer,当设置了开启事务的时候,在操作的末尾都需要进行session.commit,对于异常情况处理中,都可以用session.rollback进行回滚。
对生产者,如果在send之后没有commit,message不会被发送到消息队列中。
对消费者,如果在收到消息后没有进行commit,那么消息队列就没有收到对应消息的ack,不算消息消费完成。

session.commit();
session.rollback();

事务控制很简单,就是上面这两行代码进行控制。

签收模式

Connection创建session时的第二个参数,就是签收模式。一共有四种签收模式。签收代表接收端的session已收到消息的一次确认,反馈给broker。ActiveMQ支持自动签收与手动签收。

  • Session.AUTO_ACKNOWLEDGE:当客户端从receiver或onMessage成功返回时,Session自动签收客户端的这条消息的收条。
  • Session.CLIENT_ACKNOWLEDGE:客户端通过调用消息(Message)的acknowledge方法签收消息。在这种情况下,签收发生在Session层面:签收一个已经消费的消息会自动地签收这个Session所有已消费的收条。
  • Session.DUPS_OK_ACKNOWLEDGE:Session不必确保对传送消息的签收,这个模式可能会引起消息的重复,但是降低了Session的开销,所以只有客户端能容忍重复的消息,才可使用。
  • Session.SESSION_TRANSACTED:开启事务后使用的签收模式。在这个模式下,需要通过commit方法进行确认,如果发生异常,也可以通过rollback进行回滚。

Producer

持久化

在前面讲到过,持久化方式可以在activemq.xml中进行配置,在broker标签下面的persistenceAdaptor标签中进行配置。在代码中,也可以进行控制。
在producer对象上,可以通过setDeliveryMode方法来开启和关闭持久化。

producer.setDeliveryMode(DeliveryMode.PERSISTENT);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

优先级

在Queue这种传输模式中,每个消息默认的优先级是4。优先级取值是0~9之间,其中0最低,9最高。对于优先级更高的消息会被提前消费。
在使用优先级的时候,有两处需要进行修改。

  1. 需要在activemq.xml中配置要使用优先级的Queue
    在进行配置的时候,需要把配置信息配置在broker -> destinationPolicy -> policyMap -> policyEntries标签下,在policyEntries中的子标签是 policyEntry,对应的配置信息如下所示。queue属性表示要使用优先级的Queue,prioritizedMessages表示使用优先级,设置成 true
<policyEntry queue="queue1" prioritizedMessages="true" />
  1. 在代码中设置优先级。
    配置好上述信息后,通过 producer 的 send 方法来控制每一个消息的优先级。如下,第三个参数就是优先级。
void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException;

同样,也可以通过producer上的方法,来对当前producer要发送的所有消息设置优先级。

producer.setPriority(6);

超时时间

在ActiveMQ中,默认是没有开启超时时间。如果有这些需要,可以根据业务自行设置。与上面说的优先级一样,也有两个方法来设置超时时间。

  1. 通过producer对象设置超时时间。参数是 long 型,单位是毫秒。
producer.setTimeToLive(1000L);
  1. 通过producer的send方法设置超时时间。在说优先级的时候也涉及了下面的方法。第四个参数就是这个消息的超时时间。
void send(Message message, int deliveryMode, int priority, long timeToLive) throws JMSException;

设置了消息超时的消息,消费端在超时后无法在消费到此消息。这些超时消息会进入死信队列。

死信队列

在ActiveMQ中,超时消息会进入到ActiveMQ.DLQ中去。这个队列内容的消息不会自动清空。此处会有消息堆积的风险。
强调一个,死信队列并非只将Queue中的超时消息加到队列中,对于Topic里的超时消息也会加入其中。

配置

常规操作

如果不想使用默认的死信队列 ActiveMQ.DLQ,可以在 activemq.xml 文件中进行配置。
与之前配置优先级的时候类似,死信队列是配置在 policyEntry标签内,是他的子标签。

<policyEntry queue="test_msg" prioritizedMessages="true" >
	<deadLetterStrategy> 
		<individualDeadLetterStrategy   queuePrefix="DMS." useQueueForQueueMessages="true" /> 
	</deadLetterStrategy> 
 </policyEntry>

解读一下上面的配置。
queue=,表示这是一个Queue,名字是test_msg;
prioritizedMessages = true,表示这个队列可以使用优先级;
deadLetterStrategy标签成对出现,里面的内容是 针对test_msg这个队列而存在的死信队列配置。
queuePrefix=“DMS.”,表示死信队列的前缀名,最终的死信队列就会是DMS.test_msg
useQueueForQueueMessages=“true”,表示这个死信队列是用来接收这个Queue的信息的。

同样,对于Topic也会有相同的配置信息,其中的区别在于useQueueForTopicMessages=“true”

其他操作

对于非持久化信息,如果设置了超时时间,并且希望消息超时后进入死信队列,则需要在individualDeadLetterStrategy中增加这么一个属性。

<individualDeadLetterStrategy   queuePrefix="DMS." useQueueForQueueMessages="true" processNonPersistent="true"/> 

processNonPersistent=“true”,这个属性会让非持久化信息进入死信队列。

同样的,对于设置了超时时间的消息,但是并不想让这些消息进入死信队列的话,可以进行如下配置。

<individualDeadLetterStrategy   processExpired="false"  /> 

processExpired=“false”,表示不让超时的消息进入死信队列。

独占消费者

独占消费者存在的意义是这样的。消息生产端producer生产消息,并按照顺序推给ActiveMQ,consumer从MQ提取消息进行消费,这本身是保证了消息的顺序性。假设,现在针对这一个Queue有多个Consumer存在。每一次信息过来后,一旦MQ收到ACK,就会推送下一个消息让consumer消费。但是由于每个consumer消费信息的速度不一样,无法保证顺序性。(当然,为了保证顺序性不是只有这一个办法)。

独占消费者的意义在于,现在provider把数据推送给Queue,有很多个consumer从Queue中去提取这个信息。此时,会有一个Consumer成为第一个获取消息成功的消费者,在之后,这个Queue的消息就只能由这一个消费者进行消费。要使用独占消费者的时候,需要在代码中进行如下编写。
在创建Queue的时候,传入如下参数,consumer.exclusive=true

Queue queue = session.createQueue("xxoo?consumer.exclusive=true");

此外,还可以在多个consumer中设置优先级,如下:consumer.exclusive=true&consumer.priority=10
注意,这里说的优先级不是前面提到的,消息的优先级,而是在独占消费者的时候,挑选消费者的优先级。

Queue queue = session.createQueue("xxoo?consumer.exclusive=true&consumer.priority=10");

当然,独占消费者的使用,会产生新的问题,比如慢速消费,消费倾斜,消息堆积。这些问题和优化,都会在后续进行讲解。

Message

Message,消息。作为消息中间件要传递的对象,他很常用也很重要。常见的Message类型分为下面这几种。
ActiveMQ实现了JMS规范,因此就用下面这个类图来列举Message种类。
注意,JMS规范对应Java的javax.jms
在这里插入图片描述根据上面类图来看,直接的子接口有这么几种,下面先挑选几类最常用的进行讲解。然后再通过Message内提供的方法讲解一些配合着的用法。

TextMessage

细心的同志们可能已经注意到,在导读中基础应用的demo里,除了ConnectionFactory这个接口是new 了一个ActiveMQConnectionFactory之外,其他的对象都是通过 createXXX进行的实例化。这是因为ActiveMQ实现了JMS规范,而我们在日常使用中都是接口类型。
同理,这些Message也是需要通过Create方法来进行实例化。
实例化TextMessage
message是通过session对象进行实例化的,提供了有参和无参两个方法,参数是message对应的text。
使用ActiveMQ后,对应的实现类就是ActiveMQTextMessage,下面是这个类的结构。
ActiveMQTextMessage图上面画横线的两个方法,分别是set内容和get内容的方法。

MapMessage

MapMessage 也是通过Create方法创建出来的, 方法无参。
在使用中,MapMessage与普通的Map不太一样。
set的时候,需要选择与数据类型匹配的方法,然后第一个参数是map里的key,第二个是value。
mapMessage与之对应的就是各种各样的get方法。
get方法结构

ObjectMessage

相同的路数,先上实例化。
实例化方法从上面有参的方法可以看出,能被装载到ObjectMessage的对象,需要实现 Serializable 接口。
核心方法就两个,分别是getObject和setObject。

但是,在Consumer端去消费相应信息的时候,这里有个坑。
我准备了User类,并且已经实现了 Serializable接口。省略provider端代码,直接看下面的consumer端。通过监听器监听消息,并且对收到的消息进行强制类型转换。

public String receive() throws JMSException {
        ActiveMQConnectionUtil util = new ActiveMQConnectionUtil();
        MessageConsumer consumer = util.getConsumer();
        consumer.setMessageListener((x)->{
            if(x instanceof ObjectMessage){
                try {
                    User info =  (User)((ObjectMessage) x).getObject();
                    System.out.println(info.getName());
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });

        return "";
    }

一切看的都挺完美的,但是这么会报错。详细的异常信息就不完全输出了,我们看个重要的。

Exception in thread "main" javax.jms.JMSException: Failed to build body from content. Serializable class not available to broker. 
java.lang.ClassNotFoundException: Forbidden class com.phl.activemq_demo.bean.User!  This class is not trusted to be serialized as ObjectMessage payload. Please take a look at http://activemq.apache.org/objectmessage.html for more information on how to configure trusted classes.

根据上面的信息,虽然实现了接口,两端用同样的类去序列化和反序列化。但是这套本身简单的操作,在JMS规范里,是不允许的。报错原因是因为,这个类没有被信任,感觉很像是Iphone中需要设置对未知企业的信任一样。

解决方法也很简单,如下所示:
这是信任在连接工厂上两个属性:
信任属性要么,指明被信任的package,要么就告诉他,信任所有的包。
因此,针对这个问题,我们可以通过给 连接工厂对象设置信任的包,或者信任所有包去解决。等设置了信任之后,先前consumer的代码示例就不会出错了。

BytesMessage

和上面一样,通过Create方法进行实例化。核心方法是 writeBytesreadBytes。此外,还提供了便捷方法,比如 readBoolean writeBoolean 这类指定数据类型的简便方法。

Message

Message作为他们的父类,除了刚才的特性方法之外,还有其他的方法。下面粗略阐述一下这些方法,与之匹配的高级场景会在后面介绍高级应用的时候再说。

property

在上面那些类型中,都有各式各样的setProperty和getProperty方法。这些方法是给Message的Properties中设置属性信息,这些属性最常用的是selector。

acknowledge

在之前介绍过签收模式。其中 Session.CLIENT_ACKNOWLEDGE 这种签收模式需要用户自己进行确认,确认信息的方法就是 acknowledge 。

JMSCorrelationID

CID,用于模拟出会话效果,适用于分类消息,并且颗粒度是单条消息的情况。后续会讲。

JMSDeliveryMode

消息是否持久化,提供了对应的get,set方法。

JMSExpiration

消息超时时间,提供了对应的get,set方法。

JMSMessageID

消息ID,提供了对应的get,set方法。

JMSPriority

消息优先级,提供了对应的get,set方法。前面也说过,在使用的时候,需要在activemq.xml中policyEntry进行配置。

JMSReplyTo

生产者可以收到消费者的回复,这里设置回复地址。

上述一些高级特性的使用,后续会补上来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值