MQ 消息队列(一)

消息队列提供一个异步通信机制,消息的发送者不必一直等待到消息被成功处理才返回,而是立即返回。

消息中间件负责处理网络通信,如果网络连接不可用,消息被暂存于队列当中,当网络畅通的时候在将消息转发给相应的应用程序或者服务,当然前提是这些服务订阅了该队列。

如果在商品服务和订单服务之间使用消息中间件,既可以提高并发量,又降低服务之间的耦合度。

MQ典型应用场景:

  • 异步处理。把消息放入消息中间件中,等到需要的时候再去处理。
  • 流量削峰。例如秒杀活动,在短时间内访问量急剧增加,使用消息队列,当消息队列满了就拒绝响应,跳转到错误页面,这样就可以使得系统不会因为超负载而崩溃。
  • 日志处理
  • 应用解耦。假设某个服务A需要给许多个服务(B、C、D)发送消息,当某个服务(例如B)不需要发送消息了,服务A需要改代码再次部署;当新加入一个服务(服务E)需要服务A的消息的时候,也需要改代码重新部署;另外服务A也要考虑其他服务挂掉,没有收到消息怎么办?要不要重新发送呢?是不是很麻烦,使用MQ发布订阅模式,服务A只生产消息发送到MQ,B、C、D从MQ中读取消息,需要A的消息就订阅,不需要了就取消订阅,服务A不再操心其他的事情,使用这种方式可以降低服务或者系统之间的耦合。

activemq

ActiveMQ是apache出品,最流行的,能力强劲的开源消息总线,并且它一个完全支持JMS规范的消息中间件。其丰富的API、多种集群构建模式使得它成为业界老牌消息中间件,在中小型企业中应用广泛。

但是其性能稍差,在面对高并发的情况下,会出现消息阻塞、堆积、延迟等问题。

1、JMS中定义了两种消息模型:点对点(point to point, queue)和发布/订阅(publish/subscribe,topic),区别如下:

(1)queue(点对点):不可重复消费。消息生产者发送消息到queue,然后消息消费者从中取出并消费消息;消息消费后将不再存储在queue中;queue支持存在多个消费者,但是对于一条消息来说,只能有一个消费者进行消费;当消息没有消费者的时候,消息将一直保存,知道有消费者消费。

(2)topic(发布/订阅):可以重复消费。消息生产者发布消息到topic,同时有多个订阅者订阅该消息;发布到topic中的消息会被所有的订阅者消费;当生产者发布消息,不管是否有订阅者,消息都不会保存。

1. 软件下载和安装

1.1在官网下载https://activemq.apache.org/components/classic/download/

activemq获取队列 activemq队列满了之后_spring

1.2 运行

解压后,在bin目录下打开cmd,运行

activemq start

activemq获取队列 activemq队列满了之后_spring_02

此处我的运行报错,有端口被占用,在conf文件夹里修改了配置如下

activemq获取队列 activemq队列满了之后_activemq_03

浏览器输入http://localhost:8161 用户名和密码为 admin

activemq获取队列 activemq队列满了之后_队列_04

2.在springboot中配置activemq

2.1 加入maven依赖

<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>

在配置文件中加入mq的端口号配置

#mq
spring.activemq.broker-url=tcp://localhost:61616
  1. 一个同步式消费的demo

3.1写一个congroller类

@Controller
@RequestMapping("/amq")
public class ProducerTest {
    @Autowired
    private JmsTemplate jmsTemplate;
    /**
     *  将消息放入消息队列
     */
    @RequestMapping("/addtomq")
    @ResponseBody
    public String addToMq(String msg){
        System.out.println("收到消息:"+msg);
        jmsTemplate.convertAndSend("tom",msg);
        return "success";
    }
    /**
     *  从消息队列里取出消息
     */
    @RequestMapping("/pullout")
    @ResponseBody
    public String pullout(){
        Object tom = jmsTemplate.receiveAndConvert("tom");
        System.out.println("取出消息:"+tom);
        return "success";
    }

}

3.2 写一个客户端页面

<div class="form-inline mt-2">
	<input type="text" class="form-control form-control-sm" id="msg"/>
	<button class="btn btn-sm btn-outline-success ml-1" onclick="addToMQ();">放入MQ</button>
	<button class="btn btn-sm btn-outline-danger ml-1" onclick="pullout();">从MQ取出</button>
</div>

<script th:inline="javascript">
		//addToMQ
		function addToMQ() {
			$.post("/amq/addtomq", {msg: $("#msg").val()}, function (data) {
				layer.msg(data);
			});
		}
		//pullout
		function pullout() {
			$.post("/amq/pullout", {}, function (data) {
				layer.msg(data);
			});
		}
	</script>

运行,存入消息

activemq获取队列 activemq队列满了之后_消息队列_05

打开网页显示:

activemq获取队列 activemq队列满了之后_队列_06

取出消息:

activemq获取队列 activemq队列满了之后_队列_07

activemq获取队列 activemq队列满了之后_activemq_08

3.监听式消费(发布订阅模式)

订阅/发布模式有多个接收方和发送方,但是接收方与发送方存在时间上的依赖,如果发送方发送消息时接收方没有监听消息,那么ActiveMQ将不会保存该消息,认为消息已经发送。这个模式还有一个特点就是发送方发送的消息会被所有的接收方接收到,与点对点模式恰恰相反。后面加入的接收方,无法收到之前发送的消息。
发布订阅模式下,当发布者消息量很大时,显然单个订阅者的处理能力是不足的。
实际上现实场景中是多个订阅者节点组成一个订阅组负载均衡消费topic消息即分组订阅,这样订阅者很容易实现消费能力线性扩展。

3.1 监听式消费不用主动取出消息,更改congtroller–注释掉取出消息的代码

@Controller
@RequestMapping("/amq")
public class ProducerTest {
    @Autowired
    private JmsTemplate jmsTemplate;
    /**
     *  将消息放入消息队列
     */
    @RequestMapping("/addtomq")
    @ResponseBody
    public String addToMq(String msg){
        System.out.println("收到消息:"+msg);
        jmsTemplate.convertAndSend("tom",msg);
        return "success";
    }
   /* *//**
     *  从消息队列里取出消息
     *//*
    @RequestMapping("/pullout")
    @ResponseBody
    public String pullout(){
        Object tom = jmsTemplate.receiveAndConvert("tom");
        System.out.println("取出消息:"+tom);
        return "success";
    }*/

}

3.2 在配置文件增加如下配置 开启发布订阅模式

spring.jms.pub-sub-domain=true

3.3 写一个监听器

@Component
public class TestListener {
    @Value("${server.port}")
    private int port;

    @JmsListener(destination = "tom")
    public void onMsg(String msg) {
        System.out.println(port + " : 监听式消费 : msg=" + msg);
    }
}

3.4 页面点击加入队列后,控制台输出

收到消息:你好
8008 : 监听式消费 : msg=你好

activemq获取队列 activemq队列满了之后_消息队列_09

3.5 新建一个项目 复制当前项目下的listener(更改端口号)

当从客户端把消息加入消息队列时,两个项目的控制台都会打印出相同的消息

activemq获取队列 activemq队列满了之后_队列_10

activemq获取队列 activemq队列满了之后_spring_11