什么是RabbitMQ?

MQ全称为Message Queue,即消息队列, RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message
Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开
发中应用非常广泛。

消息队列的优点:
1、解耦:(一个系统的故障不影响另一个系统的正常运行)

生产者生产的物品只需扔到消息队列中,不需要知道消费者是谁;消费者也不需要知道生产者是谁,有需要就从消息队列中拿就行了。从而将生产者和消费者解耦。

java RabbitMQ丢弃阻塞的消息_持久化


2、异步:(将一些无关紧要的业务投递到队列中稍后处理,然后立即返回响应当前请求)

我只需要把请求扔进消息队列就立即返回,去接收另外的请求,不需要对请求进行处理,避免了即要接收请求后仍需处理请求从而使客户端接收请求速度变慢。所以接收请求和处理请求实现了异步。

java RabbitMQ丢弃阻塞的消息_java_02

3、削峰:

一个时间段大量请求涌入造成服务器崩溃,需要将这些请求放到一定量的消息队列中,超出消息队列容量的请求立即返回请求失败消息。服务器再慢慢从消息队列中处理请求,等短暂的高峰时间段过后消息队列中的请求很快就被处理完毕。

java RabbitMQ丢弃阻塞的消息_持久化_03

市场上其它的消息队列:
ActiveMQ,RabbitMQ,ZeroMQ,Kafka,MetaMQ,RocketMQ、Redis

相关知识
1、什么是AMQP?

AMQP是一套公开的消息队列协议,最早在2003年被提出,它旨在从协议层定义消息通信数据的标准格式,
为的就是解决MQ市场上协议不统一的问题。RabbitMQ就是遵循AMQP标准协议开发的MQ服务

2、什么是JMS?

JMS是java提供的一套消息服务API标准,其目的是为所有的java应用程序提供统一的消息通信的标准,类似java的
jdbc,只要遵循jms标准的应用程序之间都可以进行消息通信。它和AMQP有什么 不同,jms是java语言专属的消
息服务标准,它是在api层定义标准,并且只能用于java应用;而AMQP是在协议层定义的标准,是跨语言的 。

消息发布接收流程(重要):

java RabbitMQ丢弃阻塞的消息_消息队列_04


-----发送消息-----

1、生产者和Broker建立TCP连接。

2、生产者和Broker建立通道。

3、生产者通过通道消息发送给Broker,由Exchange将消息进行转发。

4、Exchange将消息转发到指定的Queue(队列)

----接收消息-----
1、消费者和Broker建立TCP连接
2、消费者和Broker建立通道
3、消费者监听指定的Queue(队列)
4、当有消息到达Queue时Broker默认将消息推送给消费者。
5、消费者接收到消息。

一、用虚拟机开启RabbitMQ服务(docker镜像方便快捷)

java RabbitMQ丢弃阻塞的消息_java_05


启动rabbitmq命令:

docker run -d -p 5672:5672 -p 15672:15672 --name huangRabbitmq f57b7aadf457

进入rabbitmq网站:浏览器输入 虚拟机IP:15672(测试RabbitMQ服务是否开启)

默认用户名:guest

默认密码:guest

注:
1、5672是给服务端开放的端口
2、15672是给Web端开放的端口

二、依赖

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

三、生产者

package com.xuecheng.test.rabbitmq;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 入门程序producer
 * @author Huang
 * @version 1.0
 * @date 2020/4/1 15:06
 */
public class Producer1 {
    //队列
    private static final String QUEUE= "HelloWorld";

    public static void main(String[] args) {
        //设置连接参数
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.93.137");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/"); //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq

        //生产者和队列建立连接
        Connection connection = null;
        Channel channel = null;
        try {
            connection = connectionFactory.newConnection();
            //创建会话通道, 生产者和mq服务通信都在channel通道中完成
            channel = connection.createChannel();
            //这里的交换机用默认的
            /** 声明发送消息目标的队列:如果队列在mq中没有则创建
             *  参数明细
             *  String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments;
             *  1、String queue:队列名称
             *  2、boolean durable:队列持久化,如果要持久化,mq重启后仍在(不持久化队列中的消息)
             *  3、boolean exclusive:
             * 作用1:Connection关闭则自动删除(不用mq重启),设置为true可用于创建临时队列
             * 作用2:一般等于true的话用于一个队列只能有一个消费者来消费的场景
             *  4、boolean autoDelete:自动删除,队列中没有消息(且消费者断开对队列的连接)是否自动删除此队列;如果将该参数和exclusive都设为true就可以实现临时队列
             *  5、Map<String, Object> arguments:可以设置一个队列的额外扩展参数;设置该队列的存在时间
             */
            //声明队列,不存在则创建;如果队列已存在则可以不声明
            channel.queueDeclare(QUEUE, true, false, false, null);
            /** 发送消息
             * 参数明细
             * String exchange, String routingKey, BasicProperties props, byte[] body
             * String exchange:交换机,如果不指定将使用mq的默认交换机
             * String routingKey:路由yey,交换机根据路由key来将消息转发到指定的队列;如果使用默认交换机,routingKey要设置为队列名称,表示默认交换机与队列连接绑定
             * BasicProperties props:消息的属性(设置队列中消息的持久化)
             * byte[] body: 消息内容
             */
            //定义消息内容
            String message = "hello world huang!";
            channel.basicPublish("", QUEUE, MessageProperties.PERSISTENT_BASIC, message.getBytes());
            System.out.println("send to mq:" + message);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            //生产者消息发送完毕后先关闭通道再关闭连接(该程序执行完后立即停止)
            try {
                channel.close();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
            try {
                connection.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

四、消费者(消费者程序一般会持续挂载,保证监听队列获取消息)

package com.xuecheng.test.rabbitmq;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * 入门程序consumer
 * @author Huang
 * @version 1.0
 * @date 2020/4/1 16:03
 */
public class Consumer1 {
    //队列
    private static final String QUEUE= "HelloWorld";

    public static void main(String[] args) throws IOException, TimeoutException {
        //设置连接参数
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.93.137");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/"); //设置虚拟机,一个mq服务可以设置多个虚拟机,每个虚拟机相当于一个独立的mq

        //创建连接
        Connection connection = connectionFactory.newConnection();
        //创建会话通道, 消费者和mq服务通信都在channel通道中完成
        Channel channel = connection.createChannel();
        //声明监听队列,不存在则创建;如果队列已存在则可以不声明(生产者和消费者最好都要声明队列,避免某一方的队列不存在导致一方的发送或接收操作出现异常)
        channel.queueDeclare(QUEUE, true, false, false, null);

        //实现消费方法
        DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
            /**当消费者接受到消息后此方法被调用(ajax的回调函数?)
             * 参数明细:
             * 1、String consumerTag:消费者标签,用来标识消费者的,在监听队列的时候设置channel.basicConsume()
             * 2、Envelope envelope:通过envelope
             * 3、AMQP.BasicProperties properties: 生产者发送消息时额外的消息属性参数
             * 4、byte[] body:消息内容
             */
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                //获取交换机
                String exchange = envelope.getExchange();
                //消息id,mq在channel中用来标识消息的id,如果获取到该id表明也获取到了该id的消息所以可用于确认消息已接收
                long deliveryTag = envelope.getDeliveryTag();
                //将字节类型的消息转换成字符串
                String string = new String(body, "utf-8");
                System.out.println("receive message:" + string);
            }
        };

        /**监听队列
         * 参数明细:
         * String queue, boolean autoAck,  String consumerTag,  Consumer callback
         * 1、String queue: 队列名
         * 2、boolean autoAck:(ACK回复)自动回复;true:当消费者接收到消息后自动告诉mq消息已接收到消息; false:通过编程实现自动回复;
         * mq接收到回复后一般会将队列中对应的消息删除,不回复的话消息会一直在消息队列中存在,队列也会一直给消费者发送该消息
         * 3、指定消费者的名称标签
         * 4、Consumer callback:消费方法;当消费者接收到消息,它要执行该方法
         */

        channel.basicConsume(QUEUE, true, "消费者sms",  defaultConsumer);

        //因为消费者要一直监听队列中的消息(该程序会一直挂载不会停止),所以不能关闭通道或连接

    }

}

运行4次生产者程序,发送4次消息

Ready表示队列中消息的数量,等待消费者获取

java RabbitMQ丢弃阻塞的消息_java_06


运行一次消费者程序,因持续挂载,消息队列中的4条信息都被全部接收了,队列中消息数量为0

java RabbitMQ丢弃阻塞的消息_消息队列_07