SpringBoot+ActiveMq实现点对点(Queue)消息队列

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

上篇博文主要分析了三种不同的请求方式,其中提到了基于消息队列的请求,当然只是从理论的角度去进行了分析,本篇博文就再次结合具体实现来说说消息队列。

一、什么是消息队列?

作为中间件,消息队列是分布式应用间交换信息的重要组件。消息队列可驻留在内存或磁盘上, 队列可以存储消息直到它们被应用程序读走。通过消息队列,应用程序可以在不知道彼此位置的情况下独立处理消息,或者在处理消息前不需要等待接收此消息。所以消息队列可以解决应用解耦、异步消息、流量削锋等问题,是实现高性能、高可用、可伸缩和最终一致性架构中不可以或缺的一环。

简单的来说,消息队列就是独立于客户端与服务端,将消息(请求)以队列的形式存储起来,等待服务端进行读取。

二、消息队列的原理分析

如下图所示,用户1、2、3同时向服务端系统发送请求,三个请求会先被分配到队列中存储起来,服务端会监听队列中的消息,一旦系统空闲,并且监听到队列中有消息,系统就会从队列中取出消息,并进行处理。如此设计,系统可以按照自己的节奏去处理请求,从而减轻服务端的压力,保证业务处理的流畅;即使系统由于某些原因停止运行,由于未处理的请求仍保存在队列中,这些请求也不会丢失。

三、消息队列的架构

消息队列主要分为点对点(Queue)模式和订阅(Topic)模式两种,点对点模式的整体流程如下图所示:

主要角色有生产者、消息、队列、消费者

生产者将生产出来的消息塞入到队列中,消费者从队列中取出消息并消费,被消费完的消息不会存在在队列中,一条消息只会被一个消费者消费一次;

订阅模式与点对点模式整体流程相似,不同在于,由于是订阅关系,生产者生产出来的消息,会被所有的消费者接收到;(由于本文以主要分析点对点模式,这里仅大致介绍一下订阅模式,之后会对订阅模式进行补充)

常用的消息中间件有:ActivieMq、RabbitMq、RocketMq和kafka这几种,本文是以ActivieMq作为消息中间件的;

四、具体实现

1、安装ActiveMq

本文使用的是5.15.5版本的,下载地址是http://activemq.apache.org/activemq-5155-release.html

下载解压之后,进入到bin目录下,如果是windows版本,可以直接点击activemq.bat启动;如果是linux版本,则执行./activemq start;

2、在项目中导入相应jar包

在pom中添加maven:

<dependency>
	<groupId>org.apache.activemq</groupId>
	<artifactId>activemq-all</artifactId>
	<version>5.15.5</version>
</dependency>

3、创建生产者:

Message(消息)有TextMessage(文本消息)、MapMessage(键值对消息)、StreamMessage(流消息)、BytesMessage(字节消息)和ObjectMessage(对象消息),由于MapMessage是一种结构比较灵活的消息类型,因此本文以此为例;

/**
 *
 * @author yuyan
 * @create 2018-08-28 16:09
 **/
@Service
public class Producer {

    public void sendMessage(String msg){
        try {
            //创建连接工厂,三个参数分别是用户名、密码以及消息队列所在地址
            ActiveMQConnectionFactory connFactory = new ActiveMQConnectionFactory(
                    ActiveMQConnection.DEFAULT_USER,
                    ActiveMQConnection.DEFAULT_PASSWORD,
                    "tcp://localhost:61616");

            //连接到JMS提供者
            Connection conn = connFactory.createConnection();
            //开启连接
            conn.start();

            //事务性会话,自动确认消息
            Session session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE);
            //消息的目的地,创建队列"queue"
            Destination destination = session.createQueue("queue");
            //消息生产者
            MessageProducer producer = session.createProducer(destination);

//           //文本消息
//          TextMessage textMessage = session.createTextMessage("这是文本消息");
//          producer.send(textMessage);

            //键值对消息
            MapMessage mapMessage = session.createMapMessage();
            //将消息内容放入到消息里
            mapMessage.setString("reqDesc", msg);
            //生产者传送消息
            producer.send(mapMessage);
//
//            //流消息
//            StreamMessage streamMessage = session.createStreamMessage();
//            streamMessage.writeString("这是流消息");
//            producer.send(streamMessage);
//
//            //字节消息
//            String s = "BytesMessage字节消息";
//            BytesMessage bytesMessage = session.createBytesMessage();
//            bytesMessage.writeBytes(s.getBytes());
//            producer.send(bytesMessage);
//
//            //对象消息
//            User user = new User("obj_info", "对象消息"); //User对象必须实现Serializable接口
//            ObjectMessage objectMessage = session.createObjectMessage();
//            objectMessage.setObject(user);
//            producer.send(objectMessage);


            session.commit(); //提交会话,该条消息会进入"queue"队列,生产者也完成了历史使命
            producer.close();
            session.close();
            conn.close();

        }catch (Exception e){
            e.printStackTrace();

        }

    }

}

4、创建消费者:

消费者监听消息的方式有两种,一种是通过Api提供的监听器去实现监听;另一种是通过循环的方式去主动接收消息。这里将消费者设计成一个组件,在服务器启动时,消费者就会被创建,并监听队列。

/**
 *
 * @author yuyan
 * @create 2018-08-28 18:39
 **/
@Component
public class Comsumer implements ApplicationRunner{

    @Override
    public void run(ApplicationArguments args) throws Exception {
        init();
    }
     
    public  void init() throws JMSException {
        //前期的初始化工作与生产者相同
        ConnectionFactory factory = new ActiveMQConnectionFactory(
                ActiveMQConnectionFactory.DEFAULT_USER,
                ActiveMQConnectionFactory.DEFAULT_PASSWORD,
                "tcp://localhost:61616"
        );

        Connection conn = factory.createConnection();
        conn.start();

        Session session = conn.createSession(Boolean.FALSE, Session.AUTO_ACKNOWLEDGE);
        //与生产者的消息目的地相同
        Destination dest = session.createQueue("queue");

        MessageConsumer messConsumer = session.createConsumer(dest);
        //方式1设置消息监听
        messConsumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                try {
                    MapMessage m = (MapMessage)message;
                    System.out.println("consumer接收到"+m.getString("reqDesc")+"的请求并开始处理,时间是"+new Date());
                    System.out.println("这里会停顿5s,模拟系统处理请求,时间是"+new Date());
                    Thread.sleep(5000);
                    System.out.println("consumer接收到"+m.getString("reqDesc")+"的请求并处理完毕,时间是"+new Date());
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
      //方式2主动接收消息
//        while(true){
//            try {
//                MapMessage m = (MapMessage) messConsumer.receive();
//              
//                Thread.sleep(1000);
//                System.out.println(m.getString("reqDesc"));
//            }catch (Exception e){
//                e.printStackTrace();
//            }
//
//        }

//        if(conn != null)conn.close();
    }


}

5、写一个接口并进行测试

/**
 *
 * @author yuyan
 * @create 2018-08-28 16:32
 **/
@Controller
@RequestMapping(value = "MessageCenter")
public class MessageController {

    @Autowired
    //创建一个生产者,消费者在系统运行时已经创建
    Producer producer;

    @RequestMapping(value = "/SendMessageByQueue", method = RequestMethod.GET)
    @ResponseBody
    public void send(String msg) {
        try {
            System.out.println(msg+"开始发出一次请求,时间是"+new Date());
            producer.sendMessage(msg);
            System.out.println(msg+"请求发送完成,时间是"+new Date());


        }catch (Exception e){
            e.printStackTrace();
        }
    }


}

测试结果如下:

通过观察两个红框可以发现:1、2两个请求是几乎同时发出的,用户2先进入队列,随后1进入,通过观察两个绿框可以得知,由于2先进入队列,2先执行,5s执行完后,1才开始执行;

我们也可以通过http://localhost:8161/admin/queues.jsp,也就是ActiveMq的管理界面去观察变化:

Name:队列的名称

Number Of Pending Messages:队列中还未被处理的消息数量

Numer Of Consumers:消费者的数量

Messages Enququed:入队列的消息数量(包括已出队的)

Messages Deququed:出队列的消息数量

这是队列的初始状态:

当两个请求入队后:

此时队列中有两条入队(未被处理)的消息,

当一个请求并服务端(消费者)取走后:

此时队列中还未被处理的消息数量为1,出队列的消息数量为1;

当另一个请求也被服务端取走后:

队列中再无其他消息,两条消息均已出列;

那么,一个消息队列的流程就已经全部走完了。

五、总结

消息队列并不能提高系统的运行速度(如果想提高速度,还是需要用到多线程等方式),消息队列作为中间件的作用是降低应用间的耦合,在高并发、高流量的情况下保证服务端的稳定,保证业务流程的顺畅和数据的完整(请求不丢失)。

猜你喜欢

转载自blog.csdn.net/superyu1992/article/details/82380880
今日推荐