Destination的高级特性
1.组合destinations(虚拟queue)
指的是组合队列,就是虚拟destination。
意思就是说如果一个消息要发给多个destination,可以把他们当作一个,而且这个消息还可以即发给queue,也发给topic。发送端发送到两个队列,接收端是分别从两个队列接受。
(1)第一种是直接使用的方式
下面是演示的例子,将消息发送到一个localhost的broker的两个destination上:
package test2;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
//PTP模式的发送端
public class QueueSender {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory(
"failover:(tcp://localhost:61616,tcp://localhost:61776)?randomize=false");
Connection con = connectionFactory.createConnection();
con.start();
Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue,my-queue2");
MessageProducer producer = session.createProducer(destination);
for(int i=0;i<30;i++){
TextMessage message = session.createTextMessage("message--"+i);
producer.send(message);
}
session.commit();
session.close();
con.close();
}
}
然后写两个接收端类,每个接收端连接的都是同一个broker的不同的destination。测试用的是queue方式,发送端发送3个消息,两个接收端都分别接收到了3个消息,是正确的。
第一个接收端:
package test2;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.activemq.ActiveMQConnectionFactory;
//PTP模式的接收端
public class QueueReceiver {
public static void main(String[] args) throws Exception {
ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("tcp://localhost:61616");
Thread t = new Thread(new T(connectionFactory));
t.setName("t");
t.start();
}
}
class T implements Runnable{
ConnectionFactory connectionFactory;
public T(ConnectionFactory connectionFactory){
this.connectionFactory = connectionFactory;
}
public void run() {
try{
Connection con = connectionFactory.createConnection();
con.start();
final Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("my-queue");
MessageConsumer consumer = session.createConsumer(destination);
consumer.setMessageListener(new MessageListener(){
public void onMessage(Message m) {
TextMessage message = (TextMessage)m;
try{
System.out.println(Thread.currentThread().getName()+"接收到的消息:"+message.getText());
session.commit();
}catch(Exception e){
e.printStackTrace();
}
}
});
}catch(Exception e){
e.printStackTrace();
}
}
}
第二个接收端:接收的Destination是另一个my-queue2。当然接收端也可以不用线程,直接使用就可以了。
两个broker的核心配置文件还是原来的:broker集群、静态双向连接,看一下activemq.xml核心配置文件:
<broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}">
<destinationPolicy>
<policyMap>
<policyEntries>
<policyEntry queue=">" enableAudit="false">
<networkBridgeFilterFactory>
<conditionalNetworkBridgeFilterFactory replayWhenNoConsumers="true"/>
</networkBridgeFilterFactory>
</policyEntry>
<policyEntry topic=">" >
<pendingMessageLimitStrategy>
<constantPendingMessageLimitStrategy limit="1000"/>
</pendingMessageLimitStrategy>
</policyEntry>
</policyEntries>
</policyMap>
</destinationPolicy>
<managementContext>
<managementContext createConnector="false"/>
</managementContext>
<networkConnectors>
<networkConnector name="local network" duplex="true" uri="static://(tcp://localhost:61616,tcp://localhost:61776)" ></networkConnector>
</networkConnectors>
<persistenceAdapter>
<kahaDB directory="${activemq.data}/kahadb"/>
</persistenceAdapter>
<systemUsage>
<systemUsage>
<memoryUsage>
<memoryUsage percentOfJvmHeap="70" />
</memoryUsage>
<storeUsage>
<storeUsage limit="100 gb"/>
</storeUsage>
<tempUsage>
<tempUsage limit="50 gb"/>
</tempUsage>
</systemUsage>
</systemUsage>
<transportConnectors>
<transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="stomp" uri="stomp://0.0.0.0:61613?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
<transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&wireFormat.maxFrameSize=104857600"/>
</transportConnectors>
<shutdownHooks>
<bean xmlns="http://www.springframework.org/schema/beans" class="org.apache.activemq.hooks.SpringContextHook" />
</shutdownHooks>
</broker>
<import resource="jetty.xml"/>
</beans>
运行发送端,运行两个接收端,控制台两个接收端接收到的消息都是一样多的,web界面就有两个目的地,每一个里面的消息都是一样的:
(2)第二种是使用组合的目的地的名字
当然对于在一个broker中配置两个destination的方式也可以用配置文件实现。现在来演示一下。修改核心配置文件,使用组合的destination,提供一个统一的destination的名字,修改两个broker中的核心配置文件,在broker标签里面添加下面的配置:
<destinationInterceptors>
<virtualDestinationInterceptor>
<virtualDestinations>
<compositeQueue name="MY.QUEUE">
<forwardTo>
<queue physicalName="my-queue" />
<queue physicalName="my-queue2" />
</forwardTo>
</compositeQueue>
</virtualDestinations>
</virtualDestinationInterceptor>
</destinationInterceptors>
发送端发送到一个虚拟的destination上,修改发送端的Destination:
Session session = con.createSession(true, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("MY.QUEUE");
两个接收端的destination一个是my-queue,一个是my-queue2。运行结果是两个接收端接收到的消息数量是一样的。运行程序,查看MQ的web管理界面,可以看到总的虚拟队列的名字和对应的两个真正队列的名字:
2.过滤发送到queueu和topic的消息
3.静态连接可以配置避免发送重复消息
4.配置ActiveMQ启动的时候就配置Destination的配置
就是说发送端或者接收端都还没有启动的时候就把队列或者topic的destination创建好:
5.停止不用的destination
现在来操作一下,修改localhost的核心配置文件activemq.xml中的配置,配置2秒就检查一次,配置每5秒就看一次所有的队列,里面没有消息的时候就把队列删除掉:
6.Destination的可选参数
7.虚拟topic
前面介绍了组合的Destination,现在来讲讲虚拟主题。
下面来演示一下。发送端:
接收端,接收端接受的是队列:
把对应的核心配置文件恢复到原来的样子:
然后先运行两个接受端,就是注册一下,然后运行发送端,从web界面的tpoic中看发送端那个队列的名字和发送出去的消息,从web界面的queue中看到两个接收端消息的队列,和接受到的消息(这种配置就是发送端发送的消息直接进入两个消费端的队列中等待被消费)。然后运行两个接收端,从web界面的queue中可以看到两个队列中的消息被消费了。这样就实现了虚拟的Topic。发送端topic发送3个消息,发给了两个队列ConsumerA和ConsumerB,每个Consumer对应的是多个消费者一起消费3个消息。所以实现了Topic端的负载均衡和消息的可靠性。
所以设置虚拟topic的关键是遵循Dastination中名字的前缀的写法。
修改目的地默认前缀
修改对应的核心配置文件:
因为修改了目的地的前缀,所以使用的时候将两个接收端类的目的地的名字前缀改一下就行:
然后运行两个消费端重新注册一下,然后重新运行发送端和两个接受端,从web界面中就可以看到变化了。
8.MQ的queue队列的镜像(实现队列消息的自动通知)
就是说我们希望可以有一种需求,就是我们平时做的发送消息消费消息的这个操作我想记录下来,就想做一个日志文件一样,ActiveMQ实现了这种需求,修改一个配置就可以了。实现的功能就是每次我们做一个队列的操作,这个消息就会发布到一个topic中,所以我们只需要订阅这个topic,每次队列做了什么操作就会通过topic自动的发送给我们。
现在去操作一下,修改核心配置文件:
注意:发送端发送到的一定是一个queue目的地的,接收端用topic的形式:
发送端:
接收端:
先运行接收端注册,然后运行发送端,然后运行接收端,看web界面,在Queues标题下有my-queue的queue名字,在Topic标题下,有名字是my-queue.qmirror的topic的名字。这样就好像把queue消息转为了topic消息,如果想实现持久化订阅,在接收端添加用户注册ID:
然后运行的时候先运行接收端,就是先注册,然后运行queue发送端,然后运行topic的接收端,然后看web界面。这样就实现了镜像队列。
MessageDispatch消息的高级特性
1.Message Cursors游标
消息积压的情况:
(1)消费者挂了
(2)发送端太快了
消息积压之后消费消息就要使用游标,游标分为3种。
(1)第一种
支持持久或非持久的消息。对于非持久的消息临时放在Non-...中,太多的时候放到File中(磁盘),用的时候分页取出放在Pending-X中。
(2)第二种
存储在内存中的。
(3)第三种
内存达到极限的时候:
下面介绍使用topic类型的消息,适合用vm的游标,就是消息存储在内存中,所以消费者必需能够快速的处理消息,消费者必需是比较快的:
Queue类型的消息使用游标:
2.消息的同步和异步发送
支持同步与异步,如果是快消费者,可以使用同步的方式。慢消费者适合用异步发送:
3.消息的分发策略
(1)第一种:严格顺序分发策略
一个发送者,多个接受者的时候默认就可以保证每个消费者得到的消息是能满足严格分发策略的。发送端:
接收端:
配置文件返回到最原来的样子,不要让其他的配置干扰这里:
重新启动重新运行。先启动消费者,保证注册。可以验证这个时候不用配置严格分发策略就可以保证顺序。现在来操作一下严格分发策略,消费者要使用线程的方式。生产者:
消费者:
先执行消费者进行注册,然后执行生产者发送消息,然后运行消费者接收消息。从运行结果中可以看的出来,默认是不能保证多个消费者接受的消息顺序是一样的,所以我们要进行配置。在conf的activemq.xml配置文件中添加内容:
重新启动activemq服务,重新运行我们的消费者和发送者,就可以看到多线程的消费者接收到的消息的顺序是一样的了。
(2)第二种 轮询分发策略
这个策略是保证消息的平均分配的。在大量消息的情况下也使用。
3.禁止批量消息确认
但是一般的情况下是使用默认的,批量确认消息,性能高。
4.生产者流量控制
意思就是如果生产者生产的消息太多了,消费者消费不过来了,会造成Activemq服务端消息的堆积,这是非常影响性能的,所以应该控制生产者流量。
将非持久化消息放到内存中
配置一下核心配置文件,配置一下非持久化内存大小:
运行非持久化发送端,运行完之后控制台就会停止,然后改成3条消息,运行发送端,可以看到控制台不会结束。为啥呢?因为存储消息的内存已经满了放不进去了(我们在配置文件中配置了生产者内存限制),发送端并没有结束,这个时候消费者也没办法可以消费,解决办法就是:重启ActiveMQ服务端,内存就会清空。
现在来讲一下。发送端使用了事务,先启动消费者让他可以接受(消费者是用的监听的方式)。如果每一个发送者发送的是一个消息,不会超过我们配置的内存(上面已经已经测试了),而且能被消费掉,内存会清除消息的空间;同样是使用的事务,如果改成一个生产者一次生产3个消息,那么超过了内存大小生产者结束不了,消费者也消费不了,内存还满了。这个时候唯一可以做的用来清除内存的就是重启ActiveMQ服务器。
所以为了保证消息的正确发送和消费,如果一次想发3条,就把发送端的事务操作取消,这样即使是像一次发送3条,实际上也是一条一条的发:
这样就可以保证发送端正常发送,接收端正常消费了。所以改成非事务的操作之后,消息怎么着都是一条一条的发了。即使我们配置了内存大小,但是因为设置成了非事务的形式,所以多少消息都可以发送出去。可以重启服务器重新测试。除非一条消息的大小已经超过内存大小了,这个时候就只能再重新调整内存大小了。
因为如果内存不够了客户端就不能发出消息了,为了有一个友好的提示,我们在客户端配置一个异常提示,就是内存不够的时候让他抛出一个异常来提示我们:
客户端异常配置
Message的高级特性
1.消息属性
2.MQ系统消息
操作一下,修改核心配置文件:
重新启动MQ服务器,查看web管理界面,可以看到多了一个Advisory相关的:
然后运行一个发送端和接收端。然后再看web界面,可以看到多了很多Advisory相关的对象:
根据从web界面上看到的destination的名字,修改消费端的destination:
运行消费端,运行接收端。通过prod对象可以得到很多的系统的对象。也可以使用下面的方式:
3.Message的延迟和定时消息投递
消费端:
生产端:
不加延迟效果的时候,运行消费端和发送端,消息是可以立刻被发送,被消费的。现在来添加延迟的效果,修改发送端:
发送端希望的效果是:一共发送3条消息(for),每一条消息,(延迟3秒,重复发送5次,每次间隔3秒)。修改消费端(去掉recive中的时间,保证可以一直接受,保证不会关闭接收端session,因为发送端发送过来的是有延迟的):
4.ActiveMQ传递大数据相关内容
现在来演示一下这个过程。发送端:
接收端(接收端用监听器的方式监听):
5.消息转换
从上面的修改中就可以知道是想要进行消息转换的话就需要传入一个MessangeTransfor接口,实现里面的两个方法,里面的方法一个是针对生产者的,一个是针对消费者的,因为这个我们操作的是生产者,所以只需要修改生产者的相应的方法。方法中的Messange参数就是我们的TextMessage消息,在这个方法里面将TextMessage消息转为MapMessage:
接收端:
上面的测试就已经成功的完成了。在消费端修改消息的类型是类似的做法,修改customer为ActiveMQ类型的就行。