记录一次activemq入门测试
定义提供者类1
public class Provider {
private static final String url = "tcp://192.168.40.132:61616";
private static final String queueName = "yyy-test";
public static void main(String[] args) throws JMSException {
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
//从工厂获取链接
Connection connection = activeMQConnectionFactory.createConnection();
//启动链接
connection.start();
//通过连接获取会话 参数1-是否支持事务, 参数2-消息的确认模式
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//使用会话创建队列
Queue queue = session.createQueue(queueName);
//创建消息生产者对象
MessageProducer producer = session.createProducer(queue);
//创建消息内容
TextMessage textmsg1 = session.createTextMessage("生产者的消息1");
//发送消息
producer.send(queue,textmsg1);
producer.close();
session.close();
connection.close();
}
}
定义提供者类2
public class Provider2 {
private static final String url = "tcp://192.168.40.132:61616";
private static final String queueName = "yyy-test";
public static void main(String[] args) throws JMSException {
//创建工厂
ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(url);
//从工厂获取链接
Connection connection = activeMQConnectionFactory.createConnection();
//启动链接
connection.start();
//通过连接获取会话 参数1-是否支持事务, 参数2-消息的确认模式
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//使用会话创建队列
Queue queue = session.createQueue(queueName);
//创建消息生产者对象
MessageProducer producer = session.createProducer(queue);
//创建消息内容
TextMessage textmsg2 = session.createTextMessage("生产者的消息2");
//发送消息
producer.send(queue,textmsg2);
producer.close();
session.close();
connection.close();
}
}
定义消费者:
public class Consumer {
private static final String url = "tcp://192.168.40.132:61616";
private static final String queueName = "yyy-test";
public static void main(String[] args) throws JMSException {
//1.创建连接工厂
ConnectionFactory factory = new
ActiveMQConnectionFactory(url);
//2.创建连接
Connection connection = factory.createConnection();
//3.启动连接
connection.start();
//4.创建会话
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
//5.创建消息目的地
Queue queue = session.createQueue(queueName);
//创建消费者对象
MessageConsumer consumer = session.createConsumer(queue);
//使用消费者接收消息,采用监听器轮询接收消息
consumer.setMessageListener(new MessageListener() {
@Override
public void onMessage(Message message) {
try {
//将message转换
TextMessage text = (TextMessage)message;
System.out.println("消费者消费了"+text.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
});
}
}
启动提供者1:
四个依次代表:
待处理消息数 消费者数量 入队数据 被消费的数据
可以看到启动消费者1后,入列一条数据,还未被消费
启动提供者2:
然后启动消费者:
数据被消费:
其实从代码就能看出来,mq使用的是队列结构,因此是有序的,消费数据也是先进先出的顺序.
简单demo之后思考的几个问题:
mq的具体使用场景有哪些?
demo中会发现消费者消费消息后,队列中的数据就会消失,如果业务需要多次消费同一条数据如何实现?
测试中监听器是实时监听并消费消息的,若业务需要延时消费,例如提供者提供消息后需要一段时间后再消费如何实现?
当存在多个消费者时,是否会出现并发相关(比如重复消费)问题?
如何通过代码监测到mq里的数据,方便维护与管理?
数据安全性如何,是否有丢失数据的可能性?
先留坑,下次看时再进行记录
-----------------------------------------------------更新--------------------------------------------------------
一. 具体使用场景,典型案例在电商平台中.
商品审核模块分析:
在商品审核通过后,需要做两个事情
1.生成商品详情的静态页
2.将商品信息存入索引库中
这些操作只需要拿到商品id即可:
分析出生产者消费者之后,进行相对应的配置:
1.在审核模块中添加applicationContext-mq-provider.xml
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.40.xxx:61616"/>
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
</bean>
<!-- Spring提供的JMS工具类,它可以进行消息发送、接收等 -->
<!--jmsTemplate:操作mq的api-->
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<!-- 这个connectionFactory对应的是我们定义的Spring提供的那个ConnectionFactory对象 -->
<property name="connectionFactory" ref="connectionFactory"/>
</bean>
<!-- 发布订阅模式, 商品导入索引库和生成静态页面 id为要发送的目的地 -->
<bean id="topicPageAndSolrDestination" class="org.apache.activemq.command.ActiveMQTopic">
<!-- 队列名称 -->
<constructor-arg value="demo_topic_page_solr"/>
</bean>
<!-- 点对点模式,删除索引库-->
<bean id="queueSolrDeleteDestination" class="org.apache.activemq.command.ActiveMQQueue">
<constructor-arg value="demo_queue_solr_delete"/>
</bean>
业务代码:
public class GoodsServiceImpl implements GoodsService {
//注入jms对象,操作mq
@Resource
private JmsTemplate jmsTemplate;
//配置文件中设置的目的地,在xml中指定队列名称的id
@Resource
private Destination topicPageAndSolrDestination;
@Transactional
@Override
public void updateStatus(Long[] ids, String status) {
.... //审核代码
// 将商品id发送到mq中
jmsTemplate.send(topicPageAndSolrDestination, new MessageCreator() {
@Override
public Message createMessage(Session session) throws JMSException {
// 将商品的id封装成消息体进行发送
TextMessage textMessage = session.createTextMessage(String.valueOf(id));
return textMessage;
}
});
}
}
消费者从mq中获取数据:
在静态页模块添加applicationContext-mq-consumer.xml
<!-- 真正可以产生Connection的ConnectionFactory,由对应的 JMS服务厂商提供-->
<bean id="targetConnectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
<property name="brokerURL" value="tcp://192.168.40.xxx:61616"/>
</bean>
<!-- Spring用于管理真正的ConnectionFactory的ConnectionFactory -->
<bean id="connectionFactory" class="org.springframework.jms.connection.SingleConnectionFactory">
<!-- 目标ConnectionFactory对应真实的可以产生JMS Connection的ConnectionFactory -->
<property name="targetConnectionFactory" ref="targetConnectionFactory"/>
</bean>
<!--发布订阅模式, 生成页面-->
<bean id="topicPageAndSolrDestination" class="org.apache.activemq.command.ActiveMQTopic">
<!--必须和provider队列名称保持一致-->
<constructor-arg value="pinyougou_topic_page_solr"/>
</bean>
<!-- 发布订阅模式, 消息监听容器 生成页面 -->
<bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory" />
<property name="destination" ref="topicPageAndSolrDestination" />
<property name="messageListener" ref="pageListener" />
</bean>
<!-- 需要自己定义监听器 -->
<bean id="pageListener" class="com.demo.core.listener.PageListener"></bean>
消费者业务代码: 实现MessageListener
/**
* 自定义的消息监听器:生成商品详情的静态页
*/
public class PageListener implements MessageListener {
@Resource
private StaticPageService staticPageService;
/**
* 获取消息
* @param message
*/
@Override
public void onMessage(Message message) {
try {
// 取出消息
ActiveMQTextMessage activeMQTextMessage = (ActiveMQTextMessage) message;
String id = activeMQTextMessage.getText();
System.out.println("消费者page获取到的id:"+id);
// 调用自己业务代码来消费消息
staticPageService.getHtml(Long.parseLong(id));
} catch (JMSException e) {
e.printStackTrace();
}
}
}
二: 数据多次消费问题:
activemq有两种消息模型
1.PTP:点对点模式 (一个生产者对应一个消费者)
2.订阅模式:一对多(一个生产者对应多个消费者),一个消息可以被多个消费者消费因此如果业务需要多次消费的话,可以使用订阅模式
补充:
对于订阅模式来讲,是消费者通过订阅相对应的队列来实现的,类似于公众号,你需要先关注公众号,才能够接收到该公众号发来的相关消息.
而订阅模式是有持久性订阅和非持久性订阅,
非持久性订阅代表在订阅者下线后是无法接受到mq的消息的,即使重新上线也无法接受,相当于在下线过程中很有可能会导致数据丢失.
而持久性订阅代表则订阅者即便下线,当再次上线时依然会接收到mq发来的消息
三:数据延时消费问题
首先消费者是无法延时消费的,消息到了mq中消费者会实施消费,因此想实现延时操作的话需要从生产者入手,生产者可以延时发送消息来完成延时功能.
mq提供了相应的方法,
以上代码执行后发现并没用,消息还是即时消费的,于是查了一下,这里是需要在本地的activemq/conf目录下的activemq.xml中进行配置的,添加schedulerSupport=“true”,修改完后重启,延时消费成功
四:并发问题
当多消费者监听同一个队列是,mq采用的轮询机制,顺序消费,因此不会发生线程安全问题.而高并发问题就不说了,因为不会…
但是对于重复消费是会出现的,但是也是处于比较极端的情况.
对于mq来说,给消费者发送消息后,消费者消费完毕会给mq返回签收结果,然后才会发送下一条消息,因此在mq发送消息后 签收结果前发生了不可预测的问题(如断网断电)会导致mq没收到返回结果,当再次启动服务时,他会将这条数据重新发送一次,导致重复消费.
解决方案:
每条消息都有自己的id,创建一张表专门记录接收的消息,如果主键发生冲突,就代表消息重复了(同理 redis也可以实现)
五:消息丢失问题
丢失消息情况分很多种:
如消息在队列中服务器宕机
消息多次发送失败被丢死信队列
宕机情况就需要考虑到持久化了
activemq持久化方式有4种
JDBC: 持久化到数据库
AMQ :日志文件(已基本不用)
KahaDB : AMQ基础上改进(默认选择)
LevelDB :谷歌K/V数据库
具体位置在./data/kahadb下,db.data就是持久化的数据
六:监控问题:
activemq状态监控可通过kmx实现
详见http://activemq.apache.org/jmx.html
市场上也有一些图形化界面的监控软件