异步消息机制及使用ActiveMQ(JMS)发送消息

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fanxing1964/article/details/81913681

一,异步消息

    像RMI和Hession/Burlap这些远程调用机制都是同步的,当客户端调用远程方法时,客户端必须等到远程方法完成后,才可以继续执行,但是有些操作是不需要等待,这时就可以用异步消息。

    有两个主要概念,消息代理(message broker)和目的地(destination),当一个应用消息发送时,会将消息发送给消息代理,消息代理可以确保消息被投递到目的地,同时解放发送者,使其可以继续进行其他的业务。

    不同的消息系统会有不同的消息路由模式,但是有两种通用的目的地,队列(queue)和主题(topic),每种类型与特定的消息模型关联,分别是点对点模型和发布/订阅模型。

    点对点模型:每条消息只会发送给一个接收者,消息投递后就会从队列中删除,所以可以确保每条消息只有一个接收者,但是并不意味着一个队列只可以有一个接收者,一个队列可以有多个接收者。

    发布/订阅模型:主题的所有订阅者都可以接收到消息的副本。

二,异步消息相比同步rpc的优点

1,无需等待

2,面向消息和解耦,与面向方法调用rpc通信不同,发送异步消息是以数据为中心的,客户端没有与特定的方法签名绑定

3,与远程服务位置解耦(位置独立),同步rpc服务一般需要网络地址定位,在使用消息后,客户端只需要知道通过那个队列或主题来发送消息,服务只需要能够从队列或主题中获取消息即可。基于此(位置独立),在点对点模型中,当服务过载时,可以添加服务实例来监听相同的队列就可以增加处理能力;在发布/订阅模型中,多个服务可以订阅同一个主题,但是每个服务的处理逻辑可以不同。例如,一组服务共同监听新员工消息这个主题,其中一个服务可以在工资系统中增加该员工,一个系统可以将新员工添加到HR门户中,还有一个服务可以为新员工分配可访问系统权限。

4,确保投递,即使消息发送时服务无法使用,消息也会存储起来,直到服务可用为止。

三,Java消息服务JMS(Java Message Service)

    JMS是Java EE的标准/规范之一,这种规范指出:消息发送应该是一步的,非阻塞的,发送者和接收者可以说是互不影响。jms只是java ee中定义的一组标准的API,它自身并不是一个消息服务系统,它是消息传送服务的一个抽象,也就是它定义了消息传送的接口但是并没有具体的实现,也就是说jms只是一组接口而已。

    而ActiveMQ就是JMS规范的具体实现,它是apache下的一个项目,采用java开发;就像JDBC抽象了关系数据库的访问,JPA抽象了对象与关系数据库的映射,JMS的具体实现由不同的消息中间件厂商提供,而Apache ActiveMQ就是一个,所以ActiveMQ是消息服务系统,而JMS不是。

四,以下是一个使用ActiveMQ的队列消息demo

消息发送端:

package com.uiao.activemq;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;

import javax.jms.*;

/**
 * 点对点消息发送端
 *
 * Created by uiao on 2018/3/10.
 */
public class QueueProducer {

    public static Logger logger = Logger.getLogger(QueueProducer.class);

    public static void main(String[] args) {
        ConnectionFactory connectionFactory;
        Connection connection = null;
        Session session;
        Destination destination;
        MessageProducer messageProducer;
        // 在这可以根据用户名和密码以及url使用有参的构造函数创建链接工厂
        connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);

        try {
            connection = connectionFactory.createConnection();
            connection.start();
          
            session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
            destination = session.createQueue("mq-queue");

            messageProducer = session.createProducer(destination);

            //DeliveryMode.PERSISTENT 当activemq关闭的时候,队列数据将会被保存
            //DeliveryMode.NON_PERSISTENT 当activemq关闭的时候,队列里面的数据将会被清空
            messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

            sendMessage(session, messageProducer);
            messageProducer.setTimeToLive(1000000);
            session.commit();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void sendMessage(Session session, MessageProducer messageProducer) throws Exception {
        for (int i = 0; i < 20; i++) {
            Thread.sleep(100);

            // 消息转化器可以自定义或者用jmsMessagingTemplate
            TextMessage textMessage = session.createTextMessage("第 " + i + " 条文本消息");

            // 传递序列化对象,传递javaBean,TopicProducer就是用的这个
            MqBean bean = new MqBean();
            bean.setAge(24);
            bean.setName("uiao");
            ObjectMessage objectMessage = session.createObjectMessage(bean);

            // 传递流,用来传递文件
            StreamMessage streamMessage = session.createStreamMessage();

            // 传递map
            MapMessage mapMessage = session.createMapMessage();

            // 传递文件
            BytesMessage bytesMessage = session.createBytesMessage();

            logger.info("发送消息:" + textMessage.getText());
            messageProducer.send(textMessage);
        }
    }
}

    Session的方法createSession(boolean var1, int var2) 第一个参数为true是表示支持事务,后面的自动确认为Session.SESSION_TRANSACTED

第一个参数为false时,var2的是可以为Session.AUTO_ACKNOWLEDGE,Session.CLIENT_ACKNOWLEDGE,DUPS_OK_ACKNOWLEDGE其中一个。

消息接收端:

package com.uiao.activemq;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.log4j.Logger;

import javax.jms.*;

/**
 * 点对点消息接收端
 *
 * Created by uiao on 2018/3/10.
 */
public class QueueConsumer {

    private static Logger logger = Logger.getLogger(QueueConsumer.class);

    public static void main(String[] args) {
        ConnectionFactory connectionFactory;
        Connection connection = null;
        Session session;
        Destination destination;
        MessageConsumer messageConsumer;
        connectionFactory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_BROKER_URL);
        try {
            connection = connectionFactory.createConnection();
            connection.start();
            session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); // 需要接收端确认
            destination = session.createQueue("mq-queue");
            messageConsumer = session.createConsumer(destination);
            // 不使用监视器的情况
            while (true) {
                TextMessage textMessage = (TextMessage) messageConsumer.receive(1000); // 设置超时时间,超过不等待消息
                if (textMessage != null) {
                    logger.info("收到的消息:" + textMessage.getText());
                    textMessage.acknowledge();
                } else {
                    break;
                }
            }
            // 使用监视器的情况
            /*messageConsumer.setMessageListener(new MessageListener() {
                // 实现了一个监听器
                @Override
                public void onMessage(Message message) {
                    try {
                        TextMessage textMessage = (TextMessage) message;
                        //MqBean mqBean = (MqBean) ((ObjectMessage) message).getObject();
                        if (null != message) {
                            logger.info("收到的消息:" + textMessage.getText());
                        }
                        // 如果session设置为Session.CLIENT_ACKNOWLEDGE,要加上这一步
                        message.acknowledge();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });*/
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打开浏览器,进入管理页面http://localhost:8161/admin/queues.jsp

密码可以在apache-activemq-5.15.4\conf\jetty-realm.properties设置:

输入密码后进入管理页面

执行完QueueProducer发送端后,可以看到该队列有0个接受端,总共有20条消息,其中0条消息已经处理了,20条消息尚未处理。

然后我们开两个QueueConsumer接收端,再一次执行发送端后查看后台

发送端控制台打印:

接收端1控制台打印:

接收端2控制台打印:

可以看到我们起的两个接收端服务几乎平均的把消息接收了

再看一下管理页面:

接收端从0个变成了2个,消息总数和处理的消息也从20变为40。

主题和队列在使用上一致,只不过主题使用Topic实现Destination接口。

除此之外,针对冗长和复杂的JMS代码,我们还可以使用Spring提供的JMS模板JmsTemplate来创建连接,获得会话,以及发送和接受消息;JmsTemplate还可以处理所有的抛出的JMSException异常。

五,几个常见问题

1,消息确认机制

消息只有被确认之后,才认为是被成功消费,然后消息才会从队列或主题中删除。

客户端接收消息 = 》 消息处理 =》 消息确认

四种确认机制:

(1)、Session.AUTO_ACKNOWLEDGE;客户(消费者)成功从receive方法返回时,或者从MessageListener.onMessage方法成功返回时,会话自动确认消息,然后自动删除消息.

(2)、Session.CLIENT_ACKNOWLEDGE;客户通过显式调用消息的acknowledge方法确认消息,。 即在接收端调用message.acknowledge();方法,否则,消息是不会被删除的.

(3)、Session. DUPS_OK_ACKNOWLEDGE ;不是必须确认,是一种“懒散的”消息确认,消息可能会重复发送,在第二次重新传送消息时,消息头的JMSRedelivered会被置为true标识当前消息已经传送过一次,客户端需要进行消息的重复处理控制。

(4)、 Session.SESSION_TRANSACTED;事务提交并确认。

2,丢消息怎么办?

    可以设置持久化消息,messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

    使用acknowledge()方法,客户端返回确认信息。

3,事务消息

    创建回话Session使用transacted = true,事务消息在发送和接收后必须显示的调用session.commit(),事务消息会自动确认。与设置的确认机制无关

3,消息接收方式

receive方法接收,一次即结束;使用监听器接收,实现onMessage方法,作为一个服务实时监测。

项目地址:https://gitee.com/uiaoadf/myActiveMQ

https://www.jianshu.com/p/b1a5acac15ef

《Spring 实战》

猜你喜欢

转载自blog.csdn.net/fanxing1964/article/details/81913681