activemq学习笔记

记录一次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
市场上也有一些图形化界面的监控软件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值