介绍
ActiveMQ是Apache出品的最流行的,强大的开源消息总线服务,它是快速的,支持多种跨语言客户端和协议,具有易于使用的企业集成模式和许多高级功能,完全支持JMS1.1和J2EE1.4。
安装配置
安装都大同小异,主要是Linux和Windows上的安装,Windows上的安装比较简单,下载安装包解压,然后启动即可使用,Linux同Windows安装相似,也是下载安装包解压即可,但是在安装和启动的过程中我有碰到以下几个问题:
1. 启动activemq没有任何信息,并且启动失败,网页无法访问
使用./activemq start 启动不会有启动日志,使用./activemq console 会有启动日志在控制台输出,然后发现同样是权限的问题启动时没办法对一些文件夹进行写操作,然后在安装的顶级目录执行chmod –R 777 目录名 级联应用到目录里的所有子目录和文件
2. 更改启动端口
修改61616端口
在安装目录下找到conf文件夹,切到里面有activemq.xml文件里面配置了端口信息,找到transactionConnector
修改管理页面8161端口
在conf的jetty.xml中可以找到修改的地方
3. 查杀被占用的端口
Netstat –an|grep xxx 查看占用情况
Ps –aux | grep xxx c查看该端口的进程信息
Kill -9 id 按照进程号杀掉该进程
4. 安装完成后访问后台管理页面无法访问,发现端口监听的时tcp6
这种情况有时能访问有时不能访问,修改/ect/sysctl.conf,然后保存退出sysctl -p
5. 端口未对外开放
开放端口的位置在/etc/sysconfig/iptables,开放某一端口只需添加一条记录如下:
-A INPUT -p tcp -m tcp –dport 端口号 -j ACCEPT
保存/etc/rc.d/init.d/iptables save
执行/etc/init.d/iptables restart 命令将iptables服务重启
如果不想修改iptables文件,可以使用如下命令:
Iptables –I INPUT –p tcp –dport 端口号 -j ACCEPT
最后重启iptables服务
注意:在新安装的Linux中不存在iptables,因为防火墙默认是被禁掉的,一般也没有配置过任何防火墙策略,所以不存在/etc/sysconfig/iptables文件
解决:
* 在控制台使用iptables命令随便写一条防火墙规则,如:iptables –P OUTPUT ACCEPT
* 使用service iptables save进行保存,默认就保存到了/eec/sysconfig目录下的iptables文件中
测试Demo
package com.doudou.jms;
import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
* @author: 豆豆
* @date: 2018/9/4 10:34
* @description:
*/
public class ActiveMQ {
private static final String USERNAME = ActiveMQConnection.DEFAULT_USER;
private static final String PASSWORD = ActiveMQConnection.DEFAULT_PASSWORD;
private static final String BROKER_URL = ActiveMQConnection.DEFAULT_BROKER_URL;
/**
* @author: 豆豆
* @date: 2018/9/4 11:32a
* @description: 入口函数,自动生成
*/
public static void main(String[] args) throws InterruptedException {
thread(new HelloWorldProducer(), false);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldProducer(), false);
//这个延时不够会导致运行报错实例已存在的异常
Thread.sleep(2000);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldConsumer(), false);
Thread.sleep(2000);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldProducer(), false);
Thread.sleep(2000);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldProducer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldConsumer(), false);
thread(new HelloWorldProducer(), false);
}
public static void thread(Runnable runnable, boolean daemon){
Thread brokerThread = new Thread(runnable);
brokerThread.setDaemon(daemon);
brokerThread.start();
}
public static class HelloWorldProducer implements Runnable{
@Override
public void run() {
try {
//创建一个ConnectionFactory
//ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost");
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
//创建连接
Connection connection = connectionFactory.createConnection();
connection.start();
//创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//创建目标(topic或者queue)
Destination destination = session.createQueue("TEST.FOO1");
//创建消息生产者从会话到话题或队列
MessageProducer producer = session.createProducer(destination);
producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
//创建消息
String text = "hello world! From:" + Thread.currentThread().getName() + ":" + this.hashCode();
TextMessage message = session.createTextMessage(text);
//通知生产者发送消息
System.out.println("Sent message:" + message.hashCode()+":"+Thread.currentThread().getName());
producer.send(message);
//clean up
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
}
public static class HelloWorldConsumer implements Runnable, ExceptionListener{
@Override
public void run() {
try {
//ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost");
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory();
Connection connection = connectionFactory.createConnection();
connection.start();
connection.setExceptionListener(this);
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination destination = session.createQueue("TEST.FOO1");
MessageConsumer consumer = session.createConsumer(destination);
Message message = consumer.receive(1000);
if (message instanceof TextMessage){
TextMessage textMessage = (TextMessage) message;
String text = textMessage.getText();
System.out.println("Received:" + text);
}else {
System.out.println("Received:" + message);
}
consumer.close();
session.close();
connection.close();
} catch (JMSException e) {
e.printStackTrace();
}
}
@Override
public void onException(JMSException e) {
System.out.println("JMS Exception occured, Shutting down client");
}
}
}
Spring集成
ActiveMQ完全支持Spring配置JMS Client和JMS Message Broker
* JMS Client的配置
在Spring中配置JMS Client,只需要在标准的Spring XML配置文件中配置ActiveMQConnectionFactory实例,同普通的实例bean一样简单。
以下XML配置创建了一个JMS连接工厂,该工厂连接到特定的主机名和端口上的远程代理
<bean id="jmsFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://localhost:61616</value>
</property>
</bean>
下面展示了如何使用零配置类发现可用的brokers去连接
<bean id="jmsFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>zeroconf://_activemq.broker.development.</value>
</property>
</bean>
从1.1开始,支持使用JNDI在Spring中配置ActiveMQ
* 使用Spring
如果您正在使用Spring 2.0 的基于XML Schema的新配置,则可以将ActiveMQ代理XML嵌入到任何常规Spring.xml文件中,而无需上述工厂bean。例如,这里是Spring 2.0中常规Spring XML文件的一个示例,它也配置了代理。
<beans
xmlns="http://www.springframework.org/schema/beans"
xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">
<amq:broker useJmx="false" persistent="false">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://localhost:0" />
</amq:transportConnectors>
</amq:broker>
<amq:connectionFactory id="jmsFactory" brokerURL="vm://localhost"/>
</beans>
这将允许你将JMS的配件同整个broker一起配置(如destination,connection factory)*
* 使用Spring的JmsTemplate
Spring支持一个方便的抽象JMSTemplate,允许你隐藏一些底层的JMS发送消息的细节。
使用JmsTemplate需要记住的一件事是,默认情况下,它会为发送的每条消息创建一个新的连接,会话,生产者 - 然后再将它们全部关闭。这是非常低效的!这样做是为了在EJB容器中工作,这些容器倾向于使用特殊的ConnectionFactory来进行池化。
如果您没有使用JCA容器来管理JMS连接,我们建议您使用库中的池化JMS连接提供程序(org.apache.activemq.pool.PooledConnectionFactory)activemq-pool,它将汇集JMS资源,供Spring的JmsTemplate或EJB高效的使用。配置如下:
<!-- a pooling based JMS provider -->
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://localhost:61616</value>
</property>
</bean>
</property>
</bean>
<!-- Spring JMS Template -->
<bean id="myJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory">
<ref local="jmsFactory"/>
</property>
</bean>
PooledConnectionFaxtory提供Connection,Session和MessageProducer实例的连接池资源,所以我们可以用工具来使用它们,像Camel,Spring的JmsTemplate和MessageListenerContainer。这些实例在使用完之后会被重新放回池内,所以我们可以重复使用这些资源而不用耗费资源去再一次创建它们。
虽然PoolConnectionFactory确实允许创建一组活跃的consumer,但是它不会“pool” consumer,池化对于Connection,sessions和producers是有意义的,它可以使用很小的资源创建这些实例并且使用很小的代价来让这些实例保持闲置。另一方面,消费者通常是在启动的时候创建,然后离开去处理来的消息,当消费者完成时,最好关闭它而不是让它空闲并将它返回池内以供后续重用,这是因为,即使消费者处于空闲状态,ActiveMQ也会继续向消费者的预取缓冲区传递消息,在消费者再次活跃之前,它们会被搁置。
我们还有一个池JMS ConnectionFactory,可以在JCA / MDB容器( org.apac he.activemq.ra.InboundConnectionProxyFactory)中使用,当使用我们的JCA资源适配器时,它将重用与入站消息相同的JMS连接/会话。
* 从Spring的内部消费JMS
Spring的MessagListenerContainer应该用于消息消费。它提供了MDB的所有功能 - 高效的JMS消消费和消息监听器的池化 - 但不需要完整的EJB容器。
您可以使用activemq-pool org.apache.activemq.pool.PooledConnectionFactory来为您的消费者集合高效的池化Connections和Sessions,或者您可以使用Spring JMS org.springframework. jms.connection.CachingConnectionFactory来实现相同的效果。
Spring集成demo
主要做了queue和topic两种类型的消息的发送与接收的测试,首先需要了解下Topic和Queue的区别,在进行测试的过程发现Topic中有的消息在某些情况下无法消费(具体是什么情况接下来会讲)。
JMS中定义了两种消息模型:点对点(Queue)和发布订阅(publish/subscribe, topic)。
在做介绍之前我们需要了解以下几个概念:
* Provider:纯java语言编写的JMS接口的实现(比如ActiveMQ)
* Domains: 消息传递方式,包括点对点(P2P),发布/订阅两种
* Connection factory: 客户端使用连接工厂来创建JMS provider的连接
* Destination:消息被寻址,发送以及接收的对象
P2P消息域使用queue作为destination,消息可以被同步或异步的发送和接收,每个消息只会给每个consumer发送一次,consumer可以使用MessageConsumer.reciver()同步的接收消息,也可以通过使用MessageConsumer.setMessageListener()注册一个MessageListener实现异步接收。多个Consumer可以注册到同一个queue上,但一个消息只能被一个Consumer所接收,然后由改Consumer来确认消息,并且这种情况下,Provider对所有的注册的Consumer以轮询的方式发送消息。
Pub/Sub消息域使用topic作为Destination,发布者向topic发送消息,订阅者注册接收来自topic的消息,发送到topic的任何消息都将自动传递给所有的订阅者。接收方式(同步和异步)与P2P域相同。除非显式指定,否则topic不会为订阅者保留消息。当然,这可以通过持久化(Durable)订阅者来实现消息的保存。这种情况下,当订阅者与Provider断开时,Provider会为它存储消息。当持久化订阅者重新连接时,将会周到所有的断连期间未消费的消息。
Queue中对于一个消息只能有一个消费者可以消费,当消费者不存在时,消息会一直保存,直到有消费者,发布到topic的消息会被所有的消费者消费,当生产者发布消息,不管是否有消费者,都不会保存消息
比较项目 | Topic | Queue |
---|---|---|
概要 | Publish Subscrible message发布订阅消息 | Point-to-Point点对点 |
有无状态 | topic数据默认不落地,是无状态的 | Queue数据默认会在mq服务器上以文件形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面。也可以配置成DB存储 |
完整性保障 | 并不保证发布者发布的每条数据订阅者都能接收到 | queue保证每条数据都能被接收者接收 |
消息是否会丢失 | 一般来说发布者发布到某一个topic时,只有正在监听该topic地址的sub能够接收到消息,如果没有sub在监听,该topic就丢失了 | Sender发送消息到目标Queue,Reciver可以异步接收到这个Queue上的消息。Queue上的消息如果暂时没有Reciver来取也不会丢失 |
消息发布接收策略 | 一对多的消息发布接收策略,监听同一个topic地址的多个sub都能收到publisher发送的消息。Sub接收完通知mq服务器 | 一对一的消息发布接收策略,一个sender发送到额消息,只能有一个receiver接收。receiver接收完后,通知mq服务器已接收,mq服务器对queue里的消息采取删除或其他操作 |
1.Spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:amq="http://activemq.apache.org/schema/core"
xmlns:jms="http://www.springframework.org/schema/jms"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms.xsd">
<!-- <amq:broker useJmx="false" persistent="false">
<amq:transportConnectors>
<amq:transportConnector uri="tcp://10.33.42.93:8162"/>
</amq:transportConnectors>
</amq:broker>
<amq:connectionFactory id="jmsFactory" brokerURL="vm://10.33.42.93"/>-->
<!-- a pooling based JMS provider -->
<bean id="jmsFactory" class="org.apache.activemq.pool.PooledConnectionFactory" destroy-method="stop">
<property name="connectionFactory">
<bean class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL">
<value>tcp://10.33.42.93:61617</value>
</property>
</bean>
</property>
</bean>
<!-- Spring JMS Template -->
<bean id="myJmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory" ref="jmsFactory"/>
</bean>
<!--测试Queue,队列的名字是spring-queue-->
<bean id="destinationQueue" class="org.apache.activemq.command.ActiveMQQueue">
<!--<constructor-arg index="0" value="spring-queue"/>-->
<constructor-arg name="name" value="spring-queue"/>
</bean>
<!--测试Topic-->
<bean id="destinationTopic" class="org.apache.activemq.command.ActiveMQTopic">
<constructor-arg index="0" value="spring-topic"/>
</bean>
<!--<bean id="jmsContainer" class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="destination" ref="destinationQueue"/>
<property name="messageListener" ref="messageListener"/>
<property name="connectionFactory" ref="jmsFactory"/>
</bean>
<!–消息监听器–>
<bean id="messageListener" class="com.doudou.jms.listener.MyMessageListener">
</bean>-->
</beans>
2.主要代码实现(下面只列出queue的sender和Consumer。topic的类似)
package com.doudou.jms.producer;
import com.doudou.jms.DTO.MessageDTO;
import com.doudou.jms.producer.inter.AMQSendService;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.jms.Destination;
@Service
public class AMQSenServiceImpl implements AMQSendService {
@Resource(name = "myJmsTemplate")
private JmsTemplate jmsTemplate;
@Resource(name = "destinationQueue")
private Destination destination;
@Override
public void sendMessage(MessageDTO messageDTO) {
jmsTemplate.send(destination, session -> session.createTextMessage("测试Spring集成ActiveMQ"));
}
}
package com.doudou.jms.consumer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.TextMessage;
/**
* @author: 豆豆
* @date: 2018/9/10 10:26
* @description:
*/
@Service
public class QueueConsumer {
@Autowired
private JmsTemplate jmsTemplate;
@Resource(name = "destinationQueue")
private Destination destination;
public String recive(){
String result = null;
TextMessage textMessage = (TextMessage) jmsTemplate.receive(destination);
try {
result = textMessage.getText();
System.out.println("================================================="+result+"==========================================");
} catch (JMSException e) {
e.printStackTrace();
}
return result;
}
}
3.测试代码
package com.doudou.test;
import com.doudou.base.BaseTest;
import com.doudou.jms.DTO.MessageDTO;
import com.doudou.jms.consumer.QueueConsumer;
import com.doudou.jms.consumer.TopicaSubscribe;
import com.doudou.jms.producer.TopicPublisServiceImpl;
import com.doudou.jms.producer.inter.AMQSendService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
public class TestActiveMQ extends BaseTest {
/* @Autowired
private AMQSendService amqSendService;*/
@Autowired
private QueueConsumer queueConsumer;
@Autowired
private TopicaSubscribe topicaSubscribe;
@Autowired
private TopicPublisServiceImpl topicPublisService;
/* @Test
public void testJMS(){
amqSendService.sendMessage(new MessageDTO("测试spring集成amq"));
}*/
@Test
public void testConsumerQueue(){queueConsumer.recive();}
@Test
public void testTopicPublish(){
topicPublisService.sendMessage(new MessageDTO("发送topic"));
}
@Test
public void testTopicSubscribe(){
topicaSubscribe.recive();
}
}
在测试的过程中发现,当topic的subscriber没有开起来的时候,发布消息之后再开启subscribe无法收到消息,这时候再发一条消息才能被订阅者收到,而且之前发布的消息已经无法收到