消息中间件--JMS--ActiveMQ--01

10 篇文章 0 订阅
10 篇文章 0 订阅

消息中间件–JMS–ActiveMQ–01

1、什么是MQ

​ MQ全称Message Queue,消息队列的意思,是面向消息的中间件的一种(message-oriented middleware),它底层使用Queue储存消息,遵循先进先出的原则。系统与系统之间进行信息交流时,可以把消息发送到MQ中,然后由MQ完成消息的推送,消息队列可以在分布式环境下提供应用解耦,弹性伸缩,冗余存储、流量削峰,异步通信,数据同步等功能。

​ 大致的过程是这样的:
​ 发送者把消息发送给消息服务器,消息服务器将消息存放在若干队列/主题topic中,在合适的时候,消息服务器回将消息转发给接受者。在这个过程中,发送和接收是异步的,也就是发送无需等待,而且发送者和接受者的生命周期也没有必然的关系;尤其在发布pub/订阅sub模式下,也可以完成一对多的通信,即让一个消息有多个接受者。

2、为什么使用MQ

​ 以电商系统举个例子:
在这里插入图片描述

​ 在没有MQ情况下,当用户完成下订单成功之后,订单系统需要调用支付系统完成支付,还需要调用仓储系统来安排物流,一个下订单的操作可能需要级联的调用许多个微服务,当某一时刻订单系统的并发量急剧增大,比如秒杀活动,此时因为后台要调用的服务较多,造成订单系统的整体负荷急剧增加,最终可能会导致订单系统的崩溃。

​ 那么,我们可不可以把仓储系统及之后的微服务剥离开来,因为用户订单下发成功之后是不用马上就安排物流配送的,整个物流链路可以与订单系统异步执行,只需要订单系统发送过来一个确认消息即可。

​ 这种情况下就可以引入MQ,当用户下订单成功之后,订单系统向MQ发送一个消息,通知仓储系统安排物流配送,MQ不必等待仓储系统链路的完成,只需要接着处理支付系统链路的业务即可,这样可以很大程度的减轻订单系统乃至整个应用的压力。

​ 那么,我们可不可以绕过MQ,直接把消息发送给仓储系统呢?

​ 原理上是行得通的,但是这是就要求仓储系统和订单系统必须同时在线,否则消息是无法发送成功的。并且这样一来,上下游的耦合度较高,如果后续增加了一个XX系统,也需要接收订单系统的消息,此时就需要修改订单系统发送消息的代码,不符合开闭原则。

​ 而加入了消息中间件之后,上下游之间的信息交流就不用直接耦合了,消息生产者把消息发送给MQ,MQ在合适的时机把消息推送给消息消费者,消费者和生产者之间是异步进行的,并且当大流量来袭时,消费者可以根据自身的能力消费MQ中的信息,不会被大流量冲垮,达到流量削峰的目的。

​ 总的来讲,在分布式系统中引入MQ有以下几点好处:

系统解耦

​ 当新的模块接进来时,可以做到代码改动最小;因为消息生产者是把消息发送到一条虚拟的通道(主题或者队列)上的,当有新的模块接入时,只需要在新的模块中设置要监听/订阅的通道即可,原来的上游模块是不需要进行改动的。

执行异步

​ MQ采用异步处理模式,通过梳理服务之间的强弱依赖关系,将非关键调用链路的操作异步化,提升整体系统的吞吐能力;消息发送者可以发送一个消息而无须等待响应。消息发送者将消息发送到一条虚拟的通道(主题或者队列)上;消息接收者则订阅或者监听该爱通道。一条消息可能最终转发给一个或者多个消息接收者,这些消息接收者都无需对消息发送者做出同步回应。整个过程都是异步的。
​ 案例:
​ 也就是说,一个系统跟另一个系统之间进行通信的时候,假如系统A希望发送一个消息给系统B,让他去处理。但是系统A不关注系统B到底怎么处理或者有没有处理好,所以系统A把消息发送给MQ,然后就不管这条消息的“死活了”,接着系统B从MQ里面消费出来处理即可。至于怎么处理,是否处理完毕,什么时候处理,都是系统B的事儿,与系统A无关。

流量削峰

​ 在MQ中,可以设置流量缓冲池,可以让后端系统按照自身吞吐能力进行消费,不被冲垮;

​ 但是,引入MQ后,也会产生一些问题

​ 1)系统更复杂,多了一个MQ组件

​ 2)消息传递路径更长,延时会增加

​ 3)消息可靠性和重复性互为矛盾,消息不丢不重难以同时保证

​ 4)上游无法知道下游的执行结果,这一点是很致命的。所以请牢记, 调用方实时依赖执行结果的业务场景,请直接调用,不能使用MQ。

​ 权衡利弊之后,可以得到适用MQ的场景:

上游不关注下游的处理结果,并且同步调用耗时较长;

​ 不适用的场景:

上游实时关注下游的处理结果。

3、MQ的常用产品

​ MQ指的是消息队列,放置在系统之间的消息中间件,是一种理念,常用的落地实现有一下几种:
在这里插入图片描述

​ 从整体上来讲,ActiveMQ属于Apache公司,是比较老牌的MQ产品,并且底层使用java编写;RabbitMQ使用的也比较多,但是它底层采用的是Erlang语言;Kafka主要用在大数据领域,RocketMQ是ailibaba参考Kafka推出的一个MQ产品,使用的比较多。

​ 本次仅介绍ActiveMQ的使用,其他产品的使用大致思路相同,因为它们都是基于MQ理念的产品,只不过一些落地的实现有所区别。

4、ActiveMQ安装

​ 第一步:进入官网下载Linux系统安装包,官网下载地址:

	http://activemq.apache.org/download-archives 

​ 选择版本后,进入选择操作系统页面:
在这里插入图片描述
​ 第二步:下载完成之后,将tar包上传到Linux操作系统/opt目录下,并解压

	tar -zxvf apache-activemq-5.15.9-bin.tar.gz	

​ 第三步:安装成功,进入 apache-activemq-5.15.9/bin目录下,执行activemq命令:

    ./activemq start  	----  启动activemq
    ./activemq stop	  	----  停止activemq
    ./activemq restart	----  重启activemq

    #将启动、停止、重启日志重定向至日志文件(文件名自定义)
    ./activemq start >> run_activemq.log
    ./activemq stop >> run_activemq.log
    ./activemq restart >> run_activemq.log

​ 第四步:启动之后,可以通过ps命令查看是否启动成功:

    ps -ef | grep activemq

​ 第五步:ActiveMQ启动之后,默认占用两个端口号,一个提供JMS服务(61616),一个提供管理控制台服务(8161),如果需要访问这两个端口,请设置linux防火墙,开放这两个端口。

​ 第六步:开启防火墙之后,访问http://192.168.234.133:8161(Linux主机ip+port),即可进入ActiveMQ的管理控制台。得到此页面也意味着ActiveMQ安装和启动成功。
在这里插入图片描述

5、ActiveMQ的入门程序

​ 在ActiveMQ中,消息通道有两种,一种是Queue,一种是Topic,二者的比较如下:
在这里插入图片描述
​ Queue是一种一对一的方式,生产者将消息发送到MQ中的一个Queue中,MQ再将消息推送给监听此Queue的消费者。注意,这种方式的消息只会被推送一次,也就是说,消费者应当只有一个。当消费者是一个集群时,默认采用轮询的方式进行消息推送,所有服务实例均分消息。

​ Topic是一种一对多的方式,消息发布者将消息发送到MQ中的一个Topic中,MQ再将消息推送给订阅此Topic的订阅者。注意,当Topic的订阅者不止一个时,这种方式的消息会被重复推送,总推送次数=订阅者个数*消息数。

​ 更详细的比较如下:

比较项目Queue队列模式Topic队列模式
工作模式"负载均衡"模式,如果当前没有消费者,消息也不会丢弃;如果有多个消费者,那么一条消息也只会发送给其中一个消费者,并且要求消费者ack信息"订阅-发布"模式,如果当前没有订阅者,消息将会被丢弃,如果有多个订阅者,那么这些订阅者都会收到消息
有无状态Queue数据默认会在mq服务器上已文件形式保存,比如Active MQ一般保存在$AMQ_HOME\data\kr-store\data下面,也可以配置成DB存储无状态
传递完整性消息不会被丢弃如果没有订阅者,消息会被丢弃
处理效率由于一条消息只发送给一个消费者,所以就算消费者再多,性能也不会有明显降低。当然不同消息协议的具体性能也是有差异的由于消息要按照订阅者的数量进行复制,所以处理性能会随着订阅者的增加而明显降低,并且还要结合不同消息协议自身的性能差异

​ 下面我们分Queue和Topic两种模式来书写入门程序,但是二者的整体书写思路是一致的:
在这里插入图片描述

​ JMS(java message service)开发的基本步骤:

​ 1:创建一个connection factory
​ 2:通过connection factory来创建JMS connection
​ 3:启动JMS connection
​ 4:通过JMS connection创建JMS session
​ 5:创建JMS destination(目的地 队列/主题)
​ 6:创建JMS producer或者创建JMS consume并设置destination
​ 7:创建JMS consumer或者注册一个JMS message listener
​ 8:发送(send)或者接收(receive)JMS message
​ 9:关闭所有JMS资源

5.1、Queue模式

​ 第一步:环境搭建

​ 创建Maven工程,引入ActiveMQ依赖:

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

​ 第二步:书写生产者主程序

package com.demo.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;

public class JmsProduce {
    private static final String ACTIVEMQ_URL = "tcp://192.168.234.133:61616";
    private static final String QUEUE_NAME = "queue01";
    
    public static void main(String[] args) throws JMSException {
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(queue);
        //6、启动连接
        connection.start();
        //7.通过使用消息生产者,生产三条消息,发送到MQ的队列里面
        for (int i = 0; i < 3; i++) {
            //8.创建消息
            TextMessage textMessage = session.createTextMessage("msg---hello" + i);//理解为一个字符串
            //9.通过messageProducer发送给MQ队列
            messageProducer.send(textMessage);
        }
        //10.关闭资源
        messageProducer.close();
        session.close();
        System.out.println("****消息发布到MQ队列完成");
    }
}

​ 第三步:书写消费者主程序

​ 两种书写方式,一种是阻塞式,一种是监听式。

package com.demo.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
 * 阻塞式消息消费者
 */
public class JmsConsumer {
    private static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616";
    private static final String QUEUE_NAME = "queue01";
    
    public static void main(String[] args) throws JMSException {
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消息的消费者,指定消费哪一个队列里面的消息
        MessageConsumer messageConsumer = session.createConsumer(queue);
        //6、启动连接
        connection.start();
        //循环获取
        while (true) {
            //7.通过消费者调用方法获取队列里面的消息(发送的消息是什么类型,接收的时候就强转成什么类型)
            //receive()方法为阻塞方法,如果没有接受到消息,就会一致阻塞等待
            TextMessage textMessage = (TextMessage) messageConsumer.receive();
            if (textMessage != null) {
                System.out.println("****消费者接收到的消息:  " + textMessage.getText());
            }else {
                break;
            }
        }
        //8.关闭资源
        messageConsumer.close();
        session.close();
        connection.close();
    }
}
package com.demo.activemq.queue;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
import java.io.IOException;
/**
 * 监听模式下的消费者
 */
public class JmsConsumer2 {
    private static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616";
    private static final String QUEUE_NAME = "queue01";
    
    public static void main(String[] args) throws JMSException, IOException {
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Queue queue = session.createQueue(QUEUE_NAME);
        //5.创建消息的消费者,指定消费哪一个队列里面的消息
        MessageConsumer messageConsumer = session.createConsumer(queue);
        //6、启动连接
        connection.start();
        //7.通过监听的方式消费消息
        /*
        异步非阻塞式方式监听器(onMessage)
        订阅者或消费者通过创建的消费者对象,给消费者注册消息监听器setMessageListener,
        当消息有消息的时候,系统会自动调用MessageListener类的onMessage方法
        我们只需要在onMessage方法内判断消息类型即可获取消息
         */
        messageConsumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                if (message != null && message instanceof TextMessage) {
                    //8.把message转换成消息发送前的类型并获取消息内容
                    TextMessage textMessage = (TextMessage) message;
                    try {
                        System.out.println("****消费者接收到的消息:  " + textMessage.getText());
                    } catch (JMSException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        System.out.println("执行了39行");
        //保证控制台不关闭,阻止程序关闭
        System.in.read();
        //关闭资源
        messageConsumer.close();
        session.close();
        connection.close();
    }
}

​ 第四步:启动生产者,观察ActiveMQ管理控制台,得到如下:
在这里插入图片描述
​ Number Of Pending Messages=等待消费的消息,这个是未出队列的数量,=总接收数-总出队列数。
​ Number Of Consumers=消费者数量,消费者端的消费者数量。
​ Messages Enqueued=进队消息数,进队列的总消息量,包括出队列的。这个数只增不减。
​ Messages Dequeued=出队消息数,可以理解为是消费者消费掉的数量。

​ 第五步:启动消费者JmsConsumer,观察控制台
在这里插入图片描述
​ 可以看到当前的消费者数量为1,待消费为0,总进队列数为3,已经消费为3.

​ 如果我们先启动JmsConsumer和JmsConsumer2,再启动生产者,此时生产者生产的消息将通过负载均衡的方式分发到JmsConsumer和JmsConsumer2中。

5.2、Topic模式

​ 第一步:环境搭建

​ 创建Maven工程,引入ActiveMQ依赖:

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

​ 第二步:书写生产者主程序

package com.demo.activemq.topic;
 
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
 
public class JmsProducer_Topic {
    public static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616";
    public static final String TOPIC_NAME = "topic01";
 
    public static void main(String[] args) throws JMSException {
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Topic topic = session.createTopic(TOPIC_NAME);
        //5.创建消息的生产者
        MessageProducer messageProducer = session.createProducer(topic);
        //6、启动连接
        connection.start();
        //7.通过使用消息生产者,生产三条消息,发送到MQ的队列里面
        for (int i = 0; i < 3; i++) {
            //8.通过session创建消息
            TextMessage textMessage = session.createTextMessage("TOPIC_NAME---" + i);
            //9.使用指定好目的地的消息生产者发送消息
            messageProducer.send(textMessage);
        }
        //10.关闭资源
        messageProducer.close();
        session.close();
        connection.close();
        System.out.println("****TOPIC_NAME消息发布到MQ完成");
    }
}

​ 第三步:书写消费者主程序

​ 两种书写方式,一种是阻塞式,一种是监听式。

package com.demo.activemq.topic;
import org.apache.activemq.ActiveMQConnectionFactory;
import javax.jms.*;
/**
 * 阻塞式消息消费者
 */
public class JmsConsumer_Topic {
    private static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616";
    private static final String TOPIC_NAME = "topic-ly";
    
    public static void main(String[] args) throws JMSException {
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Topic topic = session.createTopic(TOPIC_NAME);
        //5.创建消息的消费者,指定消费哪一个队列里面的消息
        MessageConsumer messageConsumer = session.createConsumer(topic);
        //6、启动连接
        connection.start();
        //循环获取
        while (true) {
            //7.通过消费者调用方法获取队列里面的消息(发送的消息是什么类型,接收的时候就强转成什么类型)
            //receive()方法为阻塞方法,如果没有接受到消息,就会一致阻塞等待
            TextMessage textMessage = (TextMessage) messageConsumer.receive();
            if (textMessage != null) {
                System.out.println("****消费者接收到的消息:  " + textMessage.getText());
            }else {
                break;
            }
        }
        //8.关闭资源
        messageConsumer.close();
        session.close();
        connection.close();
    }
}
package com.demo.activemq.topic;
 
import org.apache.activemq.ActiveMQConnectionFactory;
 
import javax.jms.*;
import java.io.IOException;
 
public class JmsConsumer2_Topic {
    public static final String ACTIVEMQ_URL = "tcp://192.168.10.130:61616";
    public static final String TOPIC_NAME = "topic-ly";
 
    public static void main(String[] args) throws JMSException, IOException {
        System.out.println("我是2号消费者");
        //1.创建连接工厂,按照给定的URL,采用默认的用户名密码
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2.通过连接工厂,获得connection并启动访问
        Connection connection = activeMQConnectionFactory.createConnection();
        //3.创建会话session
        //两个参数transacted=事务,acknowledgeMode=确认模式(签收)
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4.创建目的地(具体是队列queue还是主题topic)
        Topic topic = session.createTopic(TOPIC_NAME);
        //5.创建消息的消费者
        MessageConsumer messageConsumer = session.createConsumer(topic);
        //6、启动连接
        connection.start();
        //7.创建消息的消费者,指定消费哪一个队列里面的消息
        messageConsumer.setMessageListener(message -> {
            if (message instanceof TextMessage){
                try {
                    String text = ((TextMessage) message).getText();
                    System.out.println(text);
                } catch (JMSException e) {
                    e.printStackTrace();
                }
            }
        });
        System.in.read();
    }
}

​ 第四步:先启动消费者JmsConsumer_Topic和JmsConsumer2_Topic,此时观察ActiveMQ管理控制台,得到如下:
在这里插入图片描述
​ 第五步:启动生产者,观察控制台在这里插入图片描述
​ 可以看到当前的消费者数量为2,总进队列数为3,已经消费为6(2*3)。

​ 因为Topic是发布/订阅模式,生产者发布一次,所有的订阅此主题的消费者都能够接收全部消息,类似于微信公众号。

​ 但是要注意的是,在Topic模式下,消费者只能消费它存在之后生产者发布的消息,所以必须要先启动消费者,再启动生产者,如果生产者发布的消息没有消费者,则这些消息会成为废消息,永远不会有消费者接收。

​ 而Queue模式下就不存在这种问题,因为ActiveMQ会储存Queue消息,等有消费者时再进行推送。

下接:消息中间件–JMS–ActiveMQ–02

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值