RabbitMQ消息队列(一)《Java-2021面试谈资系列》

本文深入介绍了RabbitMQ作为消息中间件的角色,包括其分布式架构、AMQP协议、消息持久化、分发策略和高可用机制。通过实例展示了RabbitMQ的安装、基本使用、消息模式以及在SpringBoot中的整合。此外,还探讨了RabbitMQ在同步异步问题、高内聚低耦合、流量削峰等场景中的应用。
摘要由CSDN通过智能技术生成

RabbitMQ消息队列

一、中间件

1.什么是中间件

中间件(Middleware)是处于操作系统和应用程序之间的软件,也有人认为它应该属于操作系统中的一部分。人们在使用中间件时,往往是一组中间件集成在一起,构成一个平台(包括开发平台和运行平台),但在这组中间件中必须要有一个通信中间件,即中间件=平台+通信,这个定义也限定了只有用于分布式系统中才能称为中间件,同时还可以把它与支撑软件和实用软件区分开来。

2.中间件技术及架构概述

在这里插入图片描述

单体架构

在早期开发过程中,大都是采用单体架构,如图
在这里插入图片描述

问题:如果项目中一个模块出现了问题,那么整个项目都要重启。

缺点

  • 耦合度太高
  • 运维的成本过高
  • 不易维护
  • 服务器的成本高
  • 升级架构的复杂度也会增大

分布式架构

在这里插入图片描述

一个请求由服务器端的多个服务协同处理完成

存在问题

1:学习成本高,技术栈过多
2:运维成本和服务器成本增高
3:人员的成本也会增高
4:项目的负载度也会上升
5:面临的错误和容错性也会成倍增加
6:占用的服务器端口和通讯的选择的成本高
7:安全性的考虑和因素逼迫可能选择RMI/MQ相关的服务器端通讯。

优点:

1:服务系统的独立,占用的服务器资源减少和占用的硬件成本减少,
确切的说是:可以合理的分配服务资源,不造成服务器资源的浪费
2:系统的独立维护和部署,耦合度降低,可插拔性。
3:系统的架构和技术栈的选择可以变的灵活(而不是单纯的选择java)
4:弹性的部署,不会造成平台因部署造成的瘫痪和停服的状态。

3.消息中间件

1.消息中间件的分布式架构

在这里插入图片描述
从上图中可以看出来,消息中间件的是
1:利用可靠的消息传递机制进行系统和系统直接的通讯
2:通过提供消息传递和消息的排队机制,它可以在分布式系统环境下扩展进程间的通讯

2.消息中间件使用场景

1:跨系统数据传递
2:高并发的流量削峰
3:数据的分发和异步处理
4:大数据分析与传递
5:分布式事务

3.常见的消息中间件

ActiveMQ、RabbitMQ、Kafka、RocketMQ等。

4.消息中间件的本质和设计

它是一种接受数据,接受请求、存储数据、发送数据等功能的技术服务。

MQ消息队列:负责数据的传接受,存储和传递,所以性能要过于普通服务和技术。
在这里插入图片描述
谁来生产消息,存储消息和消费消息呢?
在这里插入图片描述

二、消息队列的理解

1.消息队列协议

什么是协议?

在这里插入图片描述
我们知道消息中间件负责数据的传递,存储,和分发消费三个部分,数据的存储和分发的过程中肯定要遵循某种约定成俗的规范,你是采用底层的TCP/IP,UDP协议还是其他的自己取构建等,而这些约定成俗的规范就称之为:协议

#所谓协议是指:
1:计算机底层操作系统和应用程序通讯时共同遵守的一组约定,只有遵循共同的约定和规范,系统和底层操作系统之间才能相互交流。
2:和一般的网络应用程序的不同它主要负责数据的接受和传递,所以性能比较的高。
3:协议对数据格式和计算机之间交换数据都必须严格遵守规范。

网络协议三要素

1.语法。语法是用户数据与控制信息的结构与格式,以及数据出现的顺序。
2**.语义**。语义是解释控制信息每个部分的意义。它规定了需要发出何种控制信息,以及完成的动作与做出什么样的响应。
3.时序。时序是对事件发生顺序的详细说明。

比如我MQ发送一个信息,是以什么数据格式发送到队列中,然后每个部分的含义是什么,发送完毕以后的执行的动作,以及消费者消费消息的动作,消费完毕的响应结果和反馈是什么,然后按照对应的执行顺序进行处理。如果你还是不理解:大家每天都在接触的http请求协议:

1:语法:http规定了请求报文和响应报文的格式。
2:语义:客户端主动发起请求称之为请求。(这是一种定义,同时你发起的是post/get请求)
3:时序:一个请求对应一个响应。(一定先有请求在有响应,这个是时序

而消息中间件采用的并不是http协议,而常见的消息中间件协议有:OpenWire、AMQP、MQTT、Kafka,OpenMessage协议。

面试题:为什么消息中间件不直接使用http协议?

1: 因为http请求报文头和响应报文头是比较复杂的,包含了cookie,数据的加密解密,状态码,响应码等附加的功能,但是对于一个消息而言,我们并不需要这么复杂,也没有这个必要性,它其实就是负责数据传递,存储,分发就行,一定要追求的是高性能。尽量简洁,快速。
2:大部分情况下http大部分都是短链接,在实际的交互过程中,一个请求到响应很有可能会中断,中断以后就不会就行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取消息的过程,出现问题和故障要对数据或消息就行持久化等,目的是为了保证消息和数据的高可靠和稳健的运行。

AMQP协议

AMQP:(全称:Advanced Message Queuing Protocol) 是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。
特性
1:分布式事务支持。
2:消息的持久化支持。
3:高性能和高可靠的消息处理优势。
在这里插入图片描述

MQTT协议

MQTT协议:(Message Queueing Telemetry Transport)消息队列是IBM开放的一个即时通讯协议,物联网系统架构中的重要组成部分。
特点
1:轻量
2:结构简单
3:传输快,不支持事务
4:没有持久化设计。
应用场景
1:适用于计算能力有限
2:低带宽
3:网络不稳定的场景。
在这里插入图片描述

OpenMessage协议

是近几年由阿里、雅虎和滴滴出行、Stremalio等公司共同参与创立的分布式消息中间件、流处理等领域的应用开发标准。
特点
1:结构简单
2:解析速度快
3:支持事务和持久化设计。
在这里插入图片描述

Kafka协议

Kafka协议是基于TCP/IP的二进制协议。消息内部是通过长度来分割,由一些基本数据类型组成。
特点
1:结构简单
2:解析速度快
3:无事务支持
4:有持久化设计
在这里插入图片描述

2.消息队列持久化

简单来说就是将数据存入磁盘,而不是存在内存中随服务器重启断开而消失,使数据能够永久保存
在这里插入图片描述
常见持久化方式

ActiveMQRabbitMQKafkaRocketMQ
文件存储支持支持支持支持
数据库支持///

3.消息的分发策略

MQ消息队列有如下几个角色
1:生产者
2:存储消息
3:消费者
那么生产者生成消息以后,MQ进行存储,消费者是如何获取消息的呢?一般获取数据的方式无外乎推(push)或者拉(pull)两种方式,典型的git就有推拉机制,我们发送的http请求就是一种典型的拉取数据库数据返回的过程。而消息队列MQ是一种推送的过程,而这些推机制会适用到很多的业务场景也有很多对应推机制策略。

场景一
在这里插入图片描述

比如我在APP上下了一个订单,我们的系统和服务很多,我们如何得知这个消息被那个系统或者那些服务或者系统进行消费,那这个时候就需要一个分发的策略。这就需要消费策略。或者称之为消费的方法论。

场景二
在这里插入图片描述

在发送消息的过程中可能会出现异常,或者网络的抖动,故障等等因为造成消息的无法消费,比如用户在下订单,消费MQ接受,订单系统出现故障,导致用户支付失败,那么这个时候就需要消息中间件就必须支持消息重试机制策略。也就是支持:出现问题和故障的情况下,消息不丢失还可以进行重发。

消息分发策略的机制与对比

ActiveMQRabbitMQKafkaRocketMQ
发布订阅支持支持支持支持
轮询分发支持支持支持/
公平分发/支持支持/
重发支持支持/支持
消息拉取/支持支持支持

4.消息队列高可用、高可靠

什么是高可用机制

所谓高可用:是指产品在规定的条件和规定的时刻或时间内处于可执行规定功能状态的能力。
当业务量增加时,请求也过大,一台消息中间件服务器的会触及硬件(CPU,内存,磁盘)的极限,一台消息服务器你已经无法满足业务的需求,所以消息中间件必须支持集群部署。来达到高可用的目的。

集群部署1:主从共享

img

生产者将消费发送到Master节点,所有的都连接这个消息队列共享这块数据区域,Master节点负责写入,一旦Master挂掉,slave节点继续服务。从而形成高可用。

集群部署2:主从复制

img

这种模式写入消息同样在Master主节点上,但是主节点会同步数据到slave节点形成副本,和zookeeper或者redis主从机制很类同。这样可以达到负载均衡的效果,如果消费者有多个这样就可以去不同的节点就行消费,以为消息的拷贝和同步会暂用很大的带宽和网络资源。在后续的rabbitmq中会有使用。

集群部署3:多主集群同步

img

和上面的区别不是特别的大,但是它的写入可以往任意节点去写入。

集群部署4:多主集群转发

img

如果你插入的数据是broker-1中,元数据信息会存储数据的相关描述和记录存放的位置(队列)。
它会对描述信息也就是元数据信息就行同步,如果消费者在broker-2中进行消费,发现自己几点没有对应的消息,可以从对应的元数据信息中去查询,然后返回对应的消息信息,场景:比如买火车票或者黄牛买演唱会门票,比如第一个黄牛有顾客说要买的演唱会门票,但是没有但是他会去联系其他的黄牛询问,如果有就返回

集群部署5:Master-slave与Breoker-cluster组合的方案

img

实现多主多从的热备机制来完成消息的高可用以及数据的热备机制,在生产规模达到一定的阶段的时候,这种使用的频率比较高

总结

1:要么消息共享
2:要么消息同步
3:要么元数据共享

什么是高可靠机制?

所谓高可用是指:是指系统可以无故障低持续运行,比如一个系统突然崩溃,报错,异常等等并不影响线上业务的正常运行,出错的几率极低,就称之为:高可靠。
在高并发的业务场景中,如果不能保证系统的高可靠,那造成的隐患和损失是非常严重的。
如何保证中间件消息的可靠性呢?可以从两个方面考虑:
1:消息的传输:通过协议来保证系统间数据解析的正确性。
2:消息的存储可靠:通过持久化来保证消息的可靠性。

三、RabbitMQ入门

官方地址:https://www.rabbitmq.com/download.html
在这里插入图片描述

1.安装

RabbitMQ是一个开源的遵循AMQP协议实现的基于Erlang语言编写,支持多种客户端(语言)。用于在分布式系统中存储消息,转发消息,具有高可用,高可扩性,易用性等特征。

这里说明一下,我这边采用Docker安装!!!

#获取镜像
docker pull rabbitmq:management

#创建并运行容器
docker run -di --name=myrabbit -p 15672:15672 rabbitmq:management
  • hostname:指定容器主机名称
  • name:指定容器名称
  • -p:将mq端口映射到本地或运行时设置用户和密码
#建议采用这个
docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management

查看日志

docker logs -f myrabbit

测试访问
在这里插入图片描述

浏览器访问—> http://ip地址:15672

2.RabbitMQ角色分类

1.none:

  • 不能访问management plugin

2.management:查看自己相关节点信息

  • 列出自己可以通过AMQP登入的虚拟机
  • 查看自己的虚拟机节点 virtual hosts的queues,exchanges和bindings信息
  • 查看和关闭自己的channels和connections
  • 查看有关自己的虚拟机节点virtual hosts的统计信息。包括其他用户在这个节点virtual hosts中的活动信息。

3.Policymaker:

  • 包含management所有权限
  • 查看和创建和删除自己的virtual hosts所属的policies和parameters信息

4.Monitoring:

  • 包含management所有权限
  • 罗列出所有的virtual hosts,包括不能登录的virtual hosts。
  • 查看其他用户的connections和channels信息
  • 查看节点级别的数据如clustering和memory使用情况
  • 查看所有的virtual hosts的全局统计信息。

5.Administrator:

  • 最高权限
  • 可以创建和删除virtual hosts
  • 可以查看,创建和删除users
  • 查看创建permisssions
  • 关闭所有用户的connections

3.入门案例

实现步骤

1:jdk1.8
2:构建一个maven工程
3:导入rabbitmq的maven依赖
4:启动rabbitmq-server服务
5:定义生产者
6:定义消费者
7:观察消息的在rabbitmq-server服务中的过程

新建一个maven工程
在这里插入图片描述
导入依赖

Java原生依赖:

<dependency>    
    <groupId>com.rabbitmq</groupId>    
    <artifactId>amqp-client</artifactId>    
    <version>5.10.0</version>
</dependency>

spring依赖:

<dependency>    
    <groupId>org.springframework.amqp</groupId>    
    <artifactId>spring-amqp</artifactId>    
    <version>2.2.5.RELEASE</version>
</dependency>

<dependency>    
    <groupId>org.springframework.amqp</groupId>    
    <artifactId>spring-rabbit</artifactId>    
    <version>2.2.5.RELEASE</version>
</dependency>

springboot依赖:

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

这里我们只是做一个入门案例,仅仅导入原生的依赖即可

启动服务

systemctl start rabbitmq-server或者docker start myrabbit

代码测试

public class Producer {

    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("生产者");
            //4.通过连接建立通道
            channel = connection.createChannel();
            //5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            String queueName = "queue1";
            channel.queueDeclare(queueName, false, false, false, null);
            //6.发送消息的内容
            String message = "hello Rabbitmq";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routing
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish("", "queue1", null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述
显然消息已经发送出去,我们此时去可视化界面看看
在这里插入图片描述

我们去写一下消费者代码,测试

public class Consumer {
    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("生产者");
            //4.通过连接建立通道
            channel = connection.createChannel();

            channel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery message) throws IOException {
                    System.out.println("收到的消息是" + new String(message.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收失败");
                }
            });

            System.out.println("开始接收消息");
            System.in.read();


        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述
此时,我们再去可视化界面查看
在这里插入图片描述
消息已经被消费掉了!!!

4.AMQP

什么是AMQP?

AMQP全称:Advanced Message Queuing Protocol(高级消息队列协议)。是应用层协议的一个开发标准,为面向消息的中间件设计。

AMQP生产者流程

img

AMQP消费者流程

img

四、RabbitMQ核心组成

1.RabbitMQ核心组成概念

在这里插入图片描述

核心概念:
Server:又称Broker ,接受客户端的连接,实现AMQP实体服务。 安装rabbitmq-server
Connection:连接,应用程序与Broker的网络连接 TCP/IP三次握手和四次挥手
Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各Channel,每个Channel代表一个会话任务。
Message :消息:服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。
Virtual Host :虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exchange和Queue,同一个虚拟主机里面不能有相同名字的Exchange
Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)
Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key.
Routing key:是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
Queue:队列:也成为Message Queue,消息队列,保存消息并将它们转发给消费者。

2.RabbitMQ架构设计

在这里插入图片描述

3.RabbitMQ运行流程

在这里插入图片描述

4.RabbitMQ具体模式操作

1.简单模式

上述入门已经讲解过了

2.工作模式
  • 类型:无
  • 特点:分发机制
    在这里插入图片描述
轮询分发:
  • 生产者
public class Producer {

    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("生产者");
            //4.通过连接建立通道
            channel = connection.createChannel();
            for (int i = 0; i <= 10; i++) {
            String message = "zjdzka:" + i ;
                channel.basicPublish("","queue1", null, message.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
  • 消费者1
public class Consumer {
    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("消费者1");
            //4.通过连接建立通道
            channel = connection.createChannel();

            channel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery message) throws IOException {
                    System.out.println("消费者1收到的消息是" + new String(message.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收失败");
                }
            });

            System.out.println("开始接收消息");
            System.in.read();


        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
  • 消费者2
public class Consumer2 {
    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("消费者2");
            //4.通过连接建立通道
            channel = connection.createChannel();

            channel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery message) throws IOException {
                    System.out.println("消费者2收到的消息是" + new String(message.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收失败");
                }
            });

            System.out.println("开始接收消息");
            System.in.read();


        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述

公平分发:

公平分发机制生产者代码没变,则是将消费者代码有所改动

  • 消费者1
public class Consumer {
    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("消费者1");
            //4.通过连接建立通道
            channel = connection.createChannel();
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery message) throws IOException {
                    System.out.println("消费者1收到的消息是" + new String(message.getBody(), "UTF-8"));
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    finalChannel.basicAck(message.getEnvelope().getDeliveryTag(),false);
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收失败");
                }
            });

            System.out.println("开始接收消息");
            System.in.read();


        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
  • 消费者2
public class Consumer2 {
    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("消费者2");
            //4.通过连接建立通道
            channel = connection.createChannel();
            Channel finalChannel = channel;
            finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery message) throws IOException {
                    System.out.println("消费者2收到的消息是" + new String(message.getBody(), "UTF-8"));
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    finalChannel.basicAck(message.getEnvelope().getDeliveryTag(),false);
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收失败");
                }
            });

            System.out.println("开始接收消息");
            System.in.read();


        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述
能者多劳,性能好的可能收到的消息就多,但是注意是必须采用手动应答

3.发布订阅模式
  • 类型:fanout
  • 特点:Fanout—发布与订阅模式,是一种广播机制,它是没有路由key的模式。
    在这里插入图片描述
生产者:
/**
 * fanout 发布订阅
 */
public class Producer {

    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("生产者");
            //4.通过连接建立通道
            channel = connection.createChannel();
            //5: 申明队列queue存储消息
            //6.发送消息的内容
            String message = "hello Rabbitmq";
            String exchangeName = "fanout-exchange";
            String routingKey = "";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routing
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

测试前
在这里插入图片描述
测试后
在这里插入图片描述

消费者:
public class Consumer {
    public static Runnable runnable = ()-> {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("消费者");
            //4.通过连接建立通道
            channel = connection.createChannel();

            channel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery message) throws IOException {
                    System.out.println("收到的消息是" + new String(message.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收失败");
                }
            });

            System.out.println("开始接收消息");
            System.in.read();


        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    public static void main(String[] args) {
        new Thread(runnable,"queue1").start();
        new Thread(runnable,"queue2").start();
        new Thread(runnable,"queue3").start();
    }
}

在这里插入图片描述

4.路由模式
  • 类型:direct
  • 特点:有routing-key的匹配模式
    在这里插入图片描述
    测试之前,我现在可视化页面创建一个交换机并设置路由key
    在这里插入图片描述
    我设置队列1和3key一致
生产者:
/**
 * routing 匹配路由
 */
public class Producer {

    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("生产者");
            //4.通过连接建立通道
            channel = connection.createChannel();
            //5: 申明队列queue存储消息
            //6.发送消息的内容
            String message = "hello Rabbitmq";
            String exchangeName = "direct-exchange";
            String routingKey = "email";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routing
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述

消费者:

消费者代码同发布订阅,由此可见只接受到了两个
在这里插入图片描述

5.主题Topic模式
  • 类型:topic
  • 特点:模糊的routing-key的匹配模式

在这里插入图片描述

在这里插入图片描述

生产者:
public class Producer {

    public static void main(String[] args) {
        //1.创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //2.设置连接信息
        connectionFactory.setHost("8.133.189.144");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/"); //虚拟访问路径

        Connection connection = null;
        Channel channel = null;

        try {
            //3.创建连接
            connection = connectionFactory.newConnection("生产者");
            //4.通过连接建立通道
            channel = connection.createChannel();
            //5: 申明队列queue存储消息
            //6.发送消息的内容
            String message = "hello Rabbitmq";
            String exchangeName = "topic-exchange";
            String routingKey = "com.order.test";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routing
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("消息发送异常");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述

因为我设置的路由key为com.order.test,所以只要队列1,2符合,因此队列1和2 可以介绍到消息

消费者:

在这里插入图片描述

6.参数模式
  • 类型:headers
  • 特点:参数匹配模式

5.RabbitMQ使用场景

1.同步异步问题

串行方式:将订单信息写入数据库成功后,发送注册邮件,再发送注册短信。以上三个任务全部完成后,返回给客户端
在这里插入图片描述
代码

public void makeOrder(){
    // 1 :保存订单 
    orderService.saveOrder();
    // 2: 发送短信服务
    messageService.sendSMS("order");//1-2 s
    // 3: 发送email服务
    emailService.sendEmail("order");//1-2 s
    // 4: 发送APP服务
    appService.sendApp("order");    
}

并行方式:将订单信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
*
代码

public void makeOrder(){
    // 1 :保存订单 
    orderService.saveOrder();
   // 相关发送
   relationMessage();
}
public void relationMessage(){
    // 异步
     theadpool.submit(new Callable<Object>{
         public Object call(){
             // 2: 发送短信服务  
             messageService.sendSMS("order");
         }
     })
    // 异步
     theadpool.submit(new Callable<Object>{
         public Object call(){
              // 3: 发送email服务
            emailService.sendEmail("order");
         }
     })
      // 异步
     theadpool.submit(new Callable<Object>{
         public Object call(){
             // 4: 发送短信服务
             appService.sendApp("order");
         }
     })
      // 异步
         theadpool.submit(new Callable<Object>{
         public Object call(){
             // 4: 发送短信服务
             appService.sendApp("order");
         }
     })
}

问题

1:耦合度高
2:需要自己写线程池自己维护成本太高
3:出现了消息可能会丢失,需要你自己做消息补偿
4:如何保证消息的可靠性你自己写
5:如果服务器承载不了,你需要自己去写高可用

2.异步消息队列

在这里插入图片描述

优点

1:完全解耦,用MQ建立桥接
2:有独立的线程池和运行模型
3:出现了消息可能会丢失,MQ有持久化功能
4:如何保证消息的可靠性,死信队列和消息转移的等
5:如果服务器承载不了,你需要自己去写高可用,HA镜像模型高可用。
按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20 QPS。比串行提高了3倍,比并行提高了两倍

代码

public void makeOrder(){
    // 1 :保存订单 
    orderService.saveOrder();   
    rabbitTemplate.convertSend("ex","2","消息内容");
}
3.高内聚低耦合

在这里插入图片描述

4.流量的削峰

在这里插入图片描述

分布式事务的可靠消费和可靠生产
索引、缓存、静态化处理的数据同步
流量监控
日志监控(ELK)
下单、订单分发、抢票

五、整合SpringBoot

1.目标Fanout

在这里插入图片描述

2.整合目录

在这里插入图片描述

3.代码展示

配置文件:
=======================生产者=================================
# 服务端口
server:
  port: 8080
# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 8.133.189.144
    port: 5672

=======================消费者=================================
# 服务端口
server:
  port: 8081
# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 8.133.189.144
    port: 5672
生产者:
  • 订单服务
@Service
public class OrderService {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 1: 定义交换机
    private String exchangeName = "fanout_order_exchange";
    // 2: 路由key
    private String routeKey = "";

    public void makeOrder(String userid,String productid,int num){
        // 1: 模拟用户下单
        String orderNumer = UUID.randomUUID().toString();

        System.out.println("用户 " + userid + ",订单编号是:" + orderNumer);
        // 发送订单信息给RabbitMQ fanout
        rabbitTemplate.convertAndSend(exchangeName, routeKey, orderNumer);
    }
}
  • 配置文件绑定
@Configuration
public class RabbitMQConfig {
    @Bean
    public Queue emailQueue() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        //   return new Queue("TestDirectQueue",true,true,false);
        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("email.fanout.queue", true);
    }
    @Bean
    public Queue smsQueue() {
        return new Queue("sms.fanout.queue", true);
    }
    @Bean
    public Queue weixinQueue() {
        return new Queue("weixin.fanout.queue", true);
    }
    @Bean
    public FanoutExchange fanoutOrderExchange() {
        return new FanoutExchange("fanout_order_exchange", true, false);
    }


    //绑定  将队列和交换机绑定
    @Bean
    public Binding smsBinding() {
        return BindingBuilder.bind(smsQueue()).to(fanoutOrderExchange());
    }
    @Bean
    public Binding weixinBinding() {
        return BindingBuilder.bind(weixinQueue()).to(fanoutOrderExchange());
    }
    @Bean
    public Binding emailBinding() {
        return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange());
    }
}
  • 测试
@SpringBootTest
class SpringbootRabbitmqApplicationTests {

    @Autowired
    private OrderService orderService;

    @Test
    void contextLoads() {
        orderService.makeOrder("1","1",12);
    }

}

在这里插入图片描述

消费者:
@Component
@RabbitListener(queues = {"email.fanout.queue"})
public class FanoutEmail {

    @RabbitHandler
    public void messagerevice(String message){
        // 此处省略发邮件的逻辑
        System.out.println("Email-------------->" + message);
    }
}
============================================================================================================
@Component
@RabbitListener(queues = {"sms.fanout.queue"})
public class FanoutSms {

    @RabbitHandler
    public void messagerevice(String message){
        // 此处省略发邮件的逻辑
        System.out.println("sms-------------->" + message);
    }
}
============================================================================================================
@Component
@RabbitListener(queues = {"weixin.fanout.queue"})
public class FanoutWeixin {

    @RabbitHandler
    public void messagerevice(String message){
        // 此处省略发邮件的逻辑
        System.out.println("weixin-------------->" + message);
    }

}

ilder.bind(weixinQueue()).to(fanoutOrderExchange());
}
@Bean
public Binding emailBinding() {
return BindingBuilder.bind(emailQueue()).to(fanoutOrderExchange());
}
}


- 测试

```java
@SpringBootTest
class SpringbootRabbitmqApplicationTests {

    @Autowired
    private OrderService orderService;

    @Test
    void contextLoads() {
        orderService.makeOrder("1","1",12);
    }

}

在这里插入图片描述
学习视频出自:https://www.bilibili.com/video/BV1dX4y1V73G

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值