RabbitMQ

根据以下学习视频,个人整理的笔记

https://www.bilibili.com/video/BV1dX4y1V73G?spm_id_from=333.999.0.0&vd_source=7a8946d22777450e46486d5fd60d8d4d

消息队列-RabbitMQ

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

什么是中间件

我国企业从20世纪80年代开始就逐渐进行信息化建设,由于方法和体系的不成熟,以及企业业务和市场需求的不断变化,一个企业可能同时运行着多个不同的业务系统,这些系统可能基于不同的操作系统、不同的数据库、异构的网络环境。现在的问题是,如何把这些信息系统结合成一个有机地协同工作的整体,真正实现企业跨平台、分布式应用。中间件便是解决之道,它用自己的复杂换取了企业应用的简单。

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

举例:

  • RMI(Remote Method Invocations, 远程调用)
  • Load Balancing(负载均衡,将访问负荷分散到各个服务器中)
  • Transparent Fail-over(透明的故障切换)
  • Clustering(集群,用多个小的服务器代替大型机)
  • Back-end-Integration(后端集成,用现有的、新开发的系统如何去集成遗留的系统)
  • Transaction事务(全局/局部)全局事务(分布式事务)局部事务(在同一数据库联接内的事务)
  • Dynamic Redeployment(动态重新部署,在不停止原系统的情况下,部署新的系统)
  • System Management(系统管理)
  • Threading(多线程处理)
  • Message-oriented Middleware面向消息的中间件(异步的调用编程)
  • Component Life Cycle(组件的生命周期管理)
  • Resource pooling(资源池)
  • Security(安全)
  • Caching(缓存)

为什么需要使用消息中间件

具体地说,中间件屏蔽了底层操作系统的复杂性,使程序开发人员面对一个简单而统一的开发环境,减少程序设计的复杂性,将注意力集中在自己的业务上,不必再为程序在不同系统软件上的移植而重复工作,从而大大减少了技术上的负担。中间件带给应用系统的,不只是开发的简便、开发周期的缩短,也减少了系统的维护、运行和管理的工作量,还减少了计算机总体费用的投入。

中间件的特点

为解决分布异构问题,人们提出了中间件(middleware)的概念。中间件是位于平台(硬件和操作系统)和应用之间的通用服务,如下图所示,这些服务具有标准的程序接口和协议。针对不同的操作系统和硬件平台,它们可以有符合接口和协议规范的多种实现。

在这里插入图片描述

也许很难给中间件一个严格的定义,但中间件应具有如下的一些特点:

  • 满足大量应用的需要
  • 运行于多种硬件和OS平台
  • 支持分布计算,提供跨网络、硬件和OS平台的透明性的应用或服务的交互
  • 支持标准的协议
  • 支持标准的接口

**由于标准接口对于可移植性和标准协议对于互操作性的重要性,中间件已成为许多标准化工作的主要部分。**对于应用软件开发,中间件远比操作系统和网络服务更为重要,中间件提供的程序接口定义了一个相对稳定的高层应用环境,不管底层的计算机硬件和系统软件怎样更新换代,只要将中间件升级更新,并保持中间件对外的接口定义不变,应用软件几乎不需任何修改,从而保护了企业在应用软件开发和维护中的重大投资。

简单说:中间件有个很大的特点,是脱离于具体设计目标,而具备提供普遍独立功能需求模块的能力。这使得中间件一定是可替换的。如果一个系统设计中,中间件是不可替换的,不是架构、框架设计有问题,那么就是这个中间件,在别处可能是个中间件,在这个系统内是引擎。

在项目中什么时候使用中间件技术

在项目的架构和重构中,使用任何技术和架构的改变我们都需要谨慎斟酌和思考,因为任何技术的融入和变化都可能人员,技术,和成本的增加,中间件的技术一般现在一些互联网公司或者项目中使用比较多,如果你仅仅还只是一个初创公司建议还是使用单体架构,最多加个缓存中间件即可,不要盲目追求新或者所谓的高性能而追求的背后一定是业务的驱动和项目的驱动,因为一旦追求就意味着你的学习成本,公司的人员结构以及服务器成本,维护和运维的成本都会增加,所以需要谨慎选择和考虑。

**但是作为一个开放人员,一定要有学习中间件技术的能力和思维,**否则很容易当项目发展到一个阶段在去掌握估计或者在面试中提及,就会给自己带来不小的困扰,在当今这个时代这些技术也并不是什么新鲜的东西,如果去掌握和挖掘最关键的还是自己花时间和花精力去探讨和研究。

常见的中间件

  • 消息中间件 ActiveMQ
  • 消息中间件 RabbitMQ
  • 消息中间件 Kafka(性能最快,但不支持事务)
  • 消息中间件 RocketMQ
  • 负载均衡中间件(Nginx/Lvs)
  • 缓存中间件(Memcache/Redis)
  • 数据库中间件(ShardingJdbc/Mycat)

学习中间件的方式和技巧

  • 理解中间件在项目架构中的作用,以及各中间件的底层实现。
  • 可以使用一些类比的生活概念去理解中间件
  • 使用一些流程图或者脑图的方式去梳理各个中间件在架构中的作用
  • 尝试用java技术去实现中间件的原理
  • 静下来去思考中间件在项目中设计的和使用的原因
  • 找到对应的替代总结方案
  • 尝试编写博文总结类同中间件技术的对比和使用场景
  • 学会查看中间件的源码以及开源项目和博文

中间件技术及架构的概述

在这里插入图片描述

单体架构

在实际的项目中,大部分的企业项目开发中,在早期都采用的是单体的架构模式,如下图:

在这里插入图片描述

在企业开发当中,大部分的初期架构都采用的是单体架构的模式进行架构,而这种架构的典型的特点:就是把所有的业务和模块,源代码,静态资源文件等都放在同一个工程中,如果其中的一个模块升级或者迭代发生一个很小变动都会重新编译和重新部署项目。这种架构存在的问题就是:

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

分布式架构

在这里插入图片描述

分布式系统:通俗来讲,就是一个请求由服务器端的多个服务(服务或者系统)协同处理完成

和单体架构不同的是,单体架构是一个请求发起jvm调度线程(确切的是tomcat线程池)分配线程Thread来处理请求直到释放,而分布式架构是:一个请求是由多个系统共同来协同完成,jvm和环境都可能是独立。如果拿生活中的建楼房作比喻的话,单体架构就想建设一个小房子很快就能够搞定,如果你要建设一个鸟巢或者大型的建筑,你就必须是各个环节的协同和分布,这样目的也是项目发展到后期的时候要去部署和思考的问题。

分布式架构系统存在的问题

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

分布式架构系统的好处

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

基于消息中间件的分布式系统的架构

在这里插入图片描述

从上图中可以看出来,消息中间件

  • 利用可靠的消息传递机制进行系统和系统间的直接通讯
  • 通过提供消息传递和消息的排队机制,它可以在分布式系统环境下扩展进程间的通讯。

消息中间件应用的场景

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

比如你有一个数据要进行迁移或者请求并发过多的时候,比如你有10W的并发请求下订单,我们可以在这些订单入库之前,我们可以把订单请求堆积到消息队列中,让它稳健可靠的入库和执行。

常见的消息中间件

ActiveMQ、RabbitMQ、Kafka、RocketMQ等。

消息中间件的本质及设计

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

  • MQ消息队列:负责数据的接受,存储和传递,所以性能要高于普通服务和技术

    在这里插入图片描述

  • 谁来生产消息,存储消息和消费消息呢?

    在这里插入图片描述

消息中间件的核心组成部分

  • 消息的协议
  • 消息的持久化机制
  • 消息的分发策略
  • 消息的高可用,高可靠
  • 消息的容错机制

小结

其实不论选择单体架构还是分布式架构都是项目开发的一个阶段,在什么阶段选择适合的架构方式,而不能盲目追求,最后造成的后果和问题都需要自己买单。但是作为一个开发人员学习和探讨新的技术是我们每个程序开发者都应该去保持和思考的问题。当我们没办法去改变社会和世界的时候,我们为了生活和生存那就必须要迎合企业和市场的需求,发挥你的价值和所学的才能,创造价值和实现自我。

消息队列的协议

什么是协议

在这里插入图片描述

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

所谓协议是指:

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

网络协议的三要素

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

比如我MQ发送一个信息,是以什么数据格式发送到队列中,然后每个部分的含义是什么,发送完毕以后的执行的动作,以及消费者消费消息的动作,消费完毕的响应结果和反馈是什么,然后按照对应的执行顺序进行处理

如果你还是不理解,举个大家每天都在接触的http请求协议的例子说明一下:

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

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

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

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

AMQP协议

AMQP(全称:Advanced Message Queuing Protocol) 是高级消息队列协议。由摩根大通集团联合其他公司共同设计。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。

特性:

  • 分布式事务支持
  • 消息的持久化支持
  • 高性能和高可靠的消息处理优势

AMQP协议的支持者:

  • RabbitMQ
  • ActiveMQ

MQTT协议

MQTT(Message Queueing Telemetry Transport)协议是IBM开放的一个即时通讯协议,物联网系统架构中的重要组成部分。

特点:

  • 轻量
  • 结构简单
  • 传输快,不支持事务
  • 没有持久化设计

应用场景:

  • 适用于计算能力有限
  • 低带宽
  • 网络不稳定的场景。

MQTT协议的支持者:

  • RabbitMQ
  • ActiveMQ

OpenMessage协议

OpenMessage协议是近几年由阿里、雅虎和滴滴出行、Stremalio等公司共同参与创立的分布式消息中间件、流处理等领域的应用开发标准。

特点:

  • 结构简单
  • 解析速度快
  • 支持事务和持久化设计

OpenMessage协议的支持者:

  • RocketMQ

Kafka协议

Kafka协议是基于TCP/IP的二进制协议。消息内部是通过长度来分割,由一些基本数据类型组成

特点是:

  • 结构简单
  • 解析速度快
  • 无事务支持
  • 有持久化设计

小结

消息中间件协议:是在tcp/ip协议基础之上构建的一种约定成俗的规范和机制。它的主要目的是可以让不同的客户端之间进行沟通和通讯。并且这种协议下规范最好具有持久性,高可用,高可靠的性能。

消息队列的持久化

持久化

简单来说就是将数据存入磁盘,而不是存在内存中随服务器重启断开而消失,使数据能够永久保存

在这里插入图片描述

常见的持久化方式

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

消息的分发策略

MQ消息队列有如下几个角色

  • 生产者
  • 存储消息
  • 消费者

那么生产者生成消息以后,MQ进行存储,消费者是如何获取消息的呢?

一般获取数据的方式无外乎推(push)或者拉(pull)两种方式,典型的git就有推拉机制,我们发送的http请求就是一种典型的拉取数据库数据返回的过程。而消息队列MQ是一种推送的过程,而这些推机制会适用到很多的业务场景也有很多对应推机制策略

场景分析一

在这里插入图片描述

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

场景分析二

在这里插入图片描述

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

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

ActiveMQRabbitMQKafkaRocketMQ
发布订阅支持支持支持支持
轮询分发(不会有数据倾斜)支持支持支持/
公平分发(有数据倾斜)/支持支持/
重发支持支持/支持
消息拉取/支持支持支持

消息队列的高可用和高可靠

什么是高可用机制

所谓高可用:是指产品在规定的条件和规定的时刻或时间内处于可执行规定功能的状态

当业务量增加时,请求也变多,一台消息中间件服务器会触及硬件(CPU,内存,磁盘)的极限,一台消息中间件服务器已经无法满足业务的需求,所以消息中间件必须支持集群部署。来达到高可用的目的。

集群模式:Master-Slave(主从共享数据的部署方式)

在这里插入图片描述

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

集群模式:Master- slave(主从同步数据的部署方式)

在这里插入图片描述

这种模式写入消息同样在Master主节点上,但是主节点会同步数据到Slave节点形成副本,和zookeeper或者redis主从机制很类同。这样可以达到负载均衡的效果,如果消费者有多个,不同的消费者就可以去不同的节点消费。

但是消息的拷贝和同步会占用很大的带宽和网络资源

集群模式:多主集群同步数据的部署方式

在这里插入图片描述

和上面的主从同步数据的部署方式区别不是特别的大,但是它的写入可以往任意节点去写入,不一定是主节点

集群模式:多主集群转发数据的部署方式

在这里插入图片描述

如果你插入的数据是在broker-1中,那么元数据信息会存储数据的相关描述和记录存放的位置(队列)。它会对描述信息也就是元数据信息进行同步,如果消费者在broker-2中进行消费,broker-2发现自己节点没有对应的消息,可以从对应的元数据信息中去查询,然后返回对应的消息信息

集群模式:Master-Slave与Broker-Cluster组合的方案

在这里插入图片描述

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

集群模式的总结

集群模式最终目的都是为了保证消息服务器不会挂掉,出现了故障依然可以保证消息服务继续使用

反正终归三句话:

  • 要么消息共享
  • 要么消息同步
  • 要么消息转发(元数据共享)

什么是高可靠机制

所谓高可用是指系统可以无故障地持续运行,比如一个系统突然崩溃,报错,异常等等并不影响线上业务的正常运行,出错的几率极低,就称之为:高可靠

在高并发的业务场景中,如果不能保证系统的高可靠,那造成的隐患和损失是非常严重的。

如何保证中间件消息的可靠性呢?可以从两个方面考虑:

  • 消息的传输:通过协议来保证系统间数据解析的正确性
  • 消息的存储可靠:通过持久化来保证消息的可靠性

RabbitMQ的安装及入门

概述

RabbitMQ官网:https://www.rabbitmq.com/

官网介绍:

With tens of thousands of users, RabbitMQ is one of the most popular open source message brokers. From T-Mobile to Runtastic, RabbitMQ is used worldwide at small startups and large enterprises.

RabbitMQ is lightweight and easy to deploy on premises and in the cloud. It supports multiple messaging protocols. RabbitMQ can be deployed in distributed and federated configurations to meet high-scale, high-availability requirements.

RabbitMQ runs on many operating systems and cloud environments, and provides a wide range of developer tools for most popular languages.

‎翻译后:

RabbitMQ拥有数以万计的用户,是最受欢迎的开源消息代理之一。从‎‎T-Mobile‎到‎Runtastic,RabbitMQ在全球范围内用于小型初创企业和大型企业。‎

‎RabbitMQ是轻量级的,易于在本地和云中部署。它支持多种消息传递协议。RabbitMQ可以部署在分布式和联合配置中,以满足高规模、高可用性的需求。‎

‎RabbitMQ 在许多操作系统和云环境上运行,‎并为大多数流行语言提供了广泛的开发人员工具‎。‎

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

安装RabbitMQ

下载地址:https://www.rabbitmq.com/download.html

因为RabbitMQ是Erlang语言编写的,所以还需要Erlang的相关配置才能运行RabbitMQ

查看RabbitMQ和Erlang的兼容性矩阵:https://www.rabbitmq.com/which-erlang.html,RabbitMQ和Erlang的版本一定要互相兼容才可以

1、下载RabbitMQ和Erlang

在这里插入图片描述
在这里插入图片描述

分别点击rabbitmq-server可选择你需要的RabbitMQ版本下载,点击erlang同样可选择与RabbitMQ版本兼容的版本下载

在这里插入图片描述

el7一般代表是兼容Centos7版本的,el8一般代表是兼容Centos8版本的,不要大意选错了!!!

在这里插入图片描述

在这里插入图片描述

2、把下载的两个rpm包上传到阿里云服务器

在这里插入图片描述

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# pwd
/usr/rabbitmq
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# ll
total 35292
-rw-r--r-- 1 root root 20295948 Aug 25 22:20 erlang-23.2.7-2.el7.x86_64.rpm
-rw-r--r-- 1 root root 15836975 Aug 25 22:20 rabbitmq-server-3.8.14-1.el7.noarch.rpm
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 

3、安装Erlang

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# yum -y install erlang-23.2.7-2.el7.x86_64.rpm 
............
............
............
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# erl -v # 执行命令,显示如下内容则安装成功
Erlang/OTP 23 [erts-11.1.8] [source] [64-bit] [smp:1:1] [ds:1:1:10] [async-threads:1] [hipe]

Eshell V11.1.8  (abort with ^G)
1> 

4、在RabbitMQ安装过程中需要依赖socat插件,所以我们还要安装socat

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# yum install -y socat
............
............
............
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 

5、安装RabbitMQ

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# yum -y install rabbitmq-server-3.8.14-1.el7.noarch.rpm 
............
............
............
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 

6、启动RabbitMQ测试

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# systemctl status rabbitmq-server # 查看RabbitMQ服务状态
● rabbitmq-server.service - RabbitMQ broker
   Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
   Active: inactive (dead)
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# systemctl start rabbitmq-server # 启动RabbitMQ
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# systemctl status rabbitmq-server # 再次查看RabbitMQ服务状态
● rabbitmq-server.service - RabbitMQ broker
   Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
   Active: active (running) since Thu 2022-08-25 22:54:06 CST; 3s ago
 Main PID: 16857 (beam.smp)
   Status: "Initialized"
    Tasks: 22
   Memory: 71.7M
   CGroup: /system.slice/rabbitmq-server.service
           ├─16857 /usr/lib64/erlang/erts-11.1.8/bin/beam.smp -W w -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -P 1048576 -t 5000000 -stbt db -zdbbl 128000...
           ├─16872 erl_child_setup 32768
           ├─16897 /usr/lib64/erlang/erts-11.1.8/bin/epmd -daemon
           ├─16917 inet_gethost 4
           └─16918 inet_gethost 4

Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: ##########  Licensed under the MPL 2.0. Website: https://rabbitmq.com
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Doc guides: https://rabbitmq.com/documentation.html
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Support:    https://rabbitmq.com/contact.html
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Tutorials:  https://rabbitmq.com/getstarted.html
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Monitoring: https://rabbitmq.com/monitoring.html
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Logs: /var/log/rabbitmq/rabbit@iZwz9cj1ytrolpgw50tiksZ.log
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: /var/log/rabbitmq/rabbit@iZwz9cj1ytrolpgw50tiksZ_upgrade.log
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Config file(s): (none)
Aug 25 22:54:05 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Starting broker... completed with 0 plugins.
Aug 25 22:54:06 iZwz9cj1ytrolpgw50tiksZ systemd[1]: Started RabbitMQ broker.
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# systemctl stop rabbitmq-server # 停止RabbitMQ服务
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# systemctl status rabbitmq-server # 再次查看RabbitMQ服务状态
● rabbitmq-server.service - RabbitMQ broker
   Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
   Active: inactive (dead)

Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: /var/log/rabbitmq/rabbit@iZwz9cj1ytrolpgw50tiksZ_upgrade.log
Aug 25 22:54:02 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Config file(s): (none)
Aug 25 22:54:05 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Starting broker... completed with 0 plugins.
Aug 25 22:54:06 iZwz9cj1ytrolpgw50tiksZ systemd[1]: Started RabbitMQ broker.
Aug 25 22:55:49 iZwz9cj1ytrolpgw50tiksZ systemd[1]: Stopping RabbitMQ broker...
Aug 25 22:55:49 iZwz9cj1ytrolpgw50tiksZ rabbitmqctl[17071]: Shutting down RabbitMQ node rabbit@iZwz9cj1ytrolpgw50tiksZ running at PID 16857
Aug 25 22:55:49 iZwz9cj1ytrolpgw50tiksZ rabbitmq-server[16857]: Gracefully halting Erlang VM
Aug 25 22:55:49 iZwz9cj1ytrolpgw50tiksZ rabbitmqctl[17071]: Waiting for PID 16857 to terminate
Aug 25 22:55:53 iZwz9cj1ytrolpgw50tiksZ rabbitmqctl[17071]: RabbitMQ node rabbit@iZwz9cj1ytrolpgw50tiksZ running at PID 16857 successfully shut down
Aug 25 22:55:53 iZwz9cj1ytrolpgw50tiksZ systemd[1]: Stopped RabbitMQ broker.
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 


设置开机启动RabbitMQ的命令如下(了解一下,如果需要就执行命令)

# 开机启动服务
# systemctl enable rabbitmq-server

RabbitMQ的配置

**RabbitMQ默认情况下有一个配置文件,定义了RabbitMQ的相关配置信息,默认情况下能够满足日常的开发需求。如果需要修改需要,需要自己创建一个配置文件进行覆盖。**具体操作,可以百度一下

相关端口

  • 5672:RabbitMQ的通讯端口
  • 25672:RabbitMQ的节点间的CLI通讯端口
  • 15672:RabbitMQ HTTP_API的端口,管理员用户才能访问,用于管理RabbitMQ,需要启动Management插件
  • 1883、8883:MQTT插件启动时的端口
  • 61613、61614:STOMP客户端插件启用的时候的端口
  • 15674、15675:基于webscoket的STOMP端口和MOTT端口

一定要注意:RabbitMQ 在安装完毕以后,会绑定一些端口,如果你购买的是阿里云或者腾讯云相关的服务器一定要在安全组中把对应的端口打开,并且打开防火墙相关的端口

RabbitMQ的Web管理界面及授权操作

RabbitMQ的管理界面

默认情况下,RabbitMQ没有开启web端的客户端插件,需要手动开启才可以

1、开启web端的客户端插件

rabbitmq-plugins enable rabbitmq_management

2、重启RabbitMQ服务

systemctl restart rabbitmq-server

3、浏览器访问测试

在这里插入图片描述

4、RabbitMQ有一个默认的账号和密码都是:guest。但是这个只能在localhost本机下访问时登录使用。如果像我们这样远程登录,则需要添加一个远程登录的用户。执行如下命令

# 新增用户,账号和密码都是:admin
# rabbitmqctl add_user admin admin
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# rabbitmqctl add_user admin admin
Adding user "admin" ...
Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more.
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 

5、给用户授予 administrator 权限

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# rabbitmqctl set_user_tags admin administrator
Setting tags for user "admin" to [administrator] ...
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 

权限等级如下(权限由高到低):

  • 1、administrator(拥有最高权限):可以登录控制台、查看所有信息、可以对RabbitMQ进行管理
  • 2、monitoring:监控者,可以登录控制台,查看所有信息
  • 3、policymaker:策略制定者,可以登录控制台,指定策略
  • 4、managment:普通管理员,可以登录控制台

6、输入账号密码,登录即可

在这里插入图片描述

如果访问RabbitMQ失败,可以查看如下解决方法,把80改成15672尝试解决:

在这里插入图片描述

一定要注意:RabbitMQ 在安装完毕以后,会绑定一些端口,如果你购买的是阿里云或者腾讯云相关的服务器一定要在安全组中把对应的端口打开,并且打开防火墙相关的端口

相关命令

rabbitmqctl add_user 账号 密码
rabbitmqctl set_user_tags 账号 权限
rabbitmqctl change_password 账号 新密码
rabbitmqctl delete_user 账号 # 删除用户
rabbitmqctl list_users # 查看用户清单
rabbitmqctl set_permissions -p / 账号 ".*" ".*" ".*" # 为用户设置administrator角色

Docker安装RabbitMQ

Docker的安装

Docker的具体安装可以查看Docker的笔记

Docker的相关命令

# 启动docker:
systemctl start docker
# 停止docker:
systemctl stop docker
# 重启docker:
systemctl restart docker
# 查看docker状态:
systemctl status docker
# 开机启动:  
systemctl enable docker
systemctl unenable docker
# 查看docker概要信息
docker info
# 查看docker帮助文档
docker --help

在Docker上安装RabbitMQ

直接执行如下命令:

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

直接用浏览器测试访问: 阿里云IP地址:15672 即可成功!

Linux相关的排查命令

netstat -naop | grep 5672 # 查看端口是否被占用

ps -ef | grep 5672 # 查看进程

systemctl stop # 服务

RabbitMQ角色的分类

以下角色的权限由低到高

1:none

  • 不能访问management plugin

2:managment

  • 列出自己可以通过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

RabbitMQ入门案例

简单模式

在这里插入图片描述

1、新建一个普通Maven项目

在这里插入图片描述

在这里插入图片描述

2、导入Java的RabbitMQ的原生依赖

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

3、编写生产者的代码

package com.xuexiangban.rabbitmq.simple;

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

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

public class Producer {
    public static void main(String[] args) {
        // 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范
        // 只不过RabbitMQ遵循的是AMQP协议
        // 1、创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        // 2、创建连接connection
        try {
            connection = connectionFactory.newConnection("生产者");
            // 3、通过连接获取通道channel
            channel = connection.createChannel();
            // 4、通过通道创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
            String queueName = "queue1";
            // 以下四个参数的含义:队列名字,是否要持久化,是否为独占队列,是否自动删除队列,携带的附属参数
            channel.queueDeclare(queueName,false,false,false,null);
            // 5、准备消息
            String message = "Hello xuexiangban!!!";
            // 6、发送消息给我们的队列queue
            // 以下四个参数的含义:交换机,队列、路由key,消息的状态控制,消息主题
            channel.basicPublish("",queueName,null,message.getBytes());

            System.out.println("消息发送成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 7、关闭通道
            if(channel!=null && channel.isOpen()){
                try {
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 8、关闭连接
            if(connection.isOpen() && connection!=null){
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4、编写消费者的代码

package com.xuexiangban.rabbitmq.simple;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {
    public static void main(String[] args) {
        // 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范
        // 只不过RabbitMQ遵循的是AMQP协议
        // 1、创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        // 2、创建连接connection
        try {
            connection = connectionFactory.newConnection("生产者");
            // 3、通过连接获取通道channel
            channel = connection.createChannel();
            // 4、通过通道创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
            channel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println("收到的消息是:" + new String(delivery.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();
        } finally {
            // 7、关闭通道
            if(channel!=null && channel.isOpen()){
                try {
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 8、关闭连接
            if(connection.isOpen() && connection!=null){
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5、先运行生产者生产消息,再运行消费者消费消息。结合RabbitMQ的管理界面分析即可!

工作模式

在这里插入图片描述

当有多个消费者时,我们的消息会被哪个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢?

工作模式下又分为两种模式

  • 轮询分发(默认):一个消费者一条,按均分配
  • 公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少,按劳分配

轮询分发

1、编写生产者的代码

package com.xuexiangban.rabbitmq.work.lunxun;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
 * @author: 学相伴-飞哥
 * @description: Producer 简单队列生产者
 * @Date : 2021/3/2
 */
public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "学相伴:" + i;
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.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、消费者1

package com.xuexiangban.rabbitmq.work.lunxun;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
 * @author: 学相伴-飞哥
 * @description: Consumer
 * @Date : 2021/3/2
 */
public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            // finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

3、消费者2

package com.xuexiangban.rabbitmq.work.lunxun;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
 * @author: 学相伴-飞哥
 * @description: Consumer
 * @Date : 2021/3/2
 */
public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            // finalChannel.basicQos(1);
            finalChannel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

4、结合控制台信息分析即可!

公平分发

1、编写生产者的代码

package com.xuexiangban.rabbitmq.work.fair;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * @author: 学相伴-飞哥
 * @description: Producer 简单队列生产者
 * @Date : 2021/3/2
 */
public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            //===============================end topic模式==================================
            for (int i = 1; i <= 20; i++) {
                //消息的内容
                String msg = "学相伴:" + i;
                // 7: 发送消息给中间件rabbitmq-server
                // @params1: 交换机exchange
                // @params2: 队列名称/routingkey
                // @params3: 属性配置
                // @params4: 发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.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、消费者1

package com.xuexiangban.rabbitmq.work.fair;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author: 学相伴-飞哥
 * @description: Consumer
 * @Date : 2021/3/2
 */
public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work1");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
//            channel.queueDeclare("queue1", false, false, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;

            finalChannel.basicQos(1);// 消费者一次接收一条消息
            // 下一行的第二个参数是关闭自动应答
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(2000);
                        // 开启手动应答
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work1-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

3、消费者2

package com.xuexiangban.rabbitmq.work.fair;

import com.rabbitmq.client.*;

import java.io.IOException;

/**
 * @author: 学相伴-飞哥
 * @description: Consumer
 * @Date : 2021/3/2
 */
public class Work2 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("消费者-Work2");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, true, false, null);
            // 同一时刻,服务器只会推送一条消息给消费者
            //channel.basicQos(1);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;

            finalChannel.basicQos(1);// 消费者一次接收一条消息
            // 下一行的第二个参数是关闭自动应答
            finalChannel.basicConsume("queue1", false, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try{
                        System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                        Thread.sleep(200);
                        // 开启手动应答
                        finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);

                    }catch(Exception ex){
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println("Work2-开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}

4、结合控制台信息分析即可!

发布订阅模式

在这里插入图片描述

1、编写生产者的代码

package com.xuexiangban.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,学相伴!!!";
            String exchangeName = "fanout-exchange";
            String routingKey = "";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routingkey
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());

            // 这里交换机没有用代码进行操作绑定队列,是因为我们在web可视化界面上手动绑定了

            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.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、编写消费者的代码

package com.xuexiangban.rabbitmq.routing;
import com.rabbitmq.client.*;
import java.io.IOException;

public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();// 以线程名字作为队列的名字
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                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();
    }
}

3、先运行生产者生产消息,再运行消费者消费消息。结合RabbitMQ的管理界面分析即可!

路由模式

在这里插入图片描述

1、编写生产者的代码

package com.xuexiangban.rabbitmq.direct;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,学相伴!!!";
            String exchangeName = "direct-exchange";
            String routingKey = "email";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routingkey
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());

            // 这里交换机没有用代码进行操作绑定队列,是因为我们在web可视化界面上手动绑定了

            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.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、编写消费者的代码

package com.xuexiangban.rabbitmq.direct;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                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();
    }
}

3、先运行生产者生产消息,再运行消费者消费消息。结合RabbitMQ的管理界面分析即可!

主题模式

在这里插入图片描述

一定要注意 # 和 * 的匹配规则,可以百度一下搜索:RabbitMQ主题模式通配符的匹配规则

1、编写生产者的代码

package com.xuexiangban.rabbitmq.topics;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,学相伴!!!";
            String exchangeName = "topic-exchange";
            String routingKey = "com.order.user.wwe";
            // 7: 发送消息给中间件rabbitmq-server
            // @params1: 交换机exchange
            // @params2: 队列名称/routingkey
            // @params3: 属性配置
            // @params4: 发送消息的内容
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());

            // 这里交换机没有用代码进行操作绑定队列,是因为我们在web可视化界面上手动绑定了

            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.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、编写消费者的代码

package com.xuexiangban.rabbitmq.topics;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                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();
        new Thread(runnable, "queue4").start();
    }
}

3、先运行生产者生产消息,再运行消费者消费消息。结合RabbitMQ的管理界面分析即可!

纯代码完整地创建和声明的例子

1、编写生产者的代码

package com.xuexiangban.rabbitmq.all;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

public class Producer {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 6: 准备发送消息的内容
            String message = "你好,学相伴!!!";
            String exchangeName = "direct-order-exchange";
            String exchangeType = "direct";
            // 7: 发送消息给中间件rabbitmq-server
            // 声明交换机
            channel.exchangeDeclare(exchangeName,exchangeType,true);
            // 声明队列
            channel.queueDeclare("queue5",true,false,false,null);
            channel.queueDeclare("queue6",true,false,false,null);
            channel.queueDeclare("queue7",true,false,false,null);
            // 交换机绑定队列
            channel.queueBind("queue5",exchangeName,"order");
            channel.queueBind("queue6",exchangeName,"order");
            channel.queueBind("queue7",exchangeName,"course");

            channel.basicPublish(exchangeName, "order", null, message.getBytes());

            // 这里交换机没有用代码进行操作绑定队列,是因为我们在web可视化界面上手动绑定了

            System.out.println("消息发送成功!");
        } catch (Exception ex) {
            ex.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、编写消费者的代码

package com.xuexiangban.rabbitmq.all;

import com.rabbitmq.client.*;

import java.io.IOException;

public class Consumer {
    private static Runnable runnable = () -> {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        // 2: 设置连接属性
        connectionFactory.setHost("47.106.9.114");
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/");
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        //获取队列的名称
        final String queueName = Thread.currentThread().getName();
        Connection connection = null;
        Channel channel = null;
        try {
            // 3: 从连接工厂中获取连接
            connection = connectionFactory.newConnection("生产者");
            // 4: 从连接中获取通道channel
            channel = connection.createChannel();
            // 5: 申明队列queue存储消息
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            // 这里如果queue已经被创建过一次了,可以不需要定义
            //channel.queueDeclare("queue1", false, false, false, null);
            // 6: 定义接受消息的回调
            Channel finalChannel = channel;
            finalChannel.basicConsume(queueName, true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                }
            });
            System.out.println(queueName + ":开始接受消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常...");
        } finally {
            // 7: 释放连接关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    };
    public static void main(String[] args) {
        // 启动三个线程去执行
        new Thread(runnable, "queue5").start();
        new Thread(runnable, "queue6").start();
        new Thread(runnable, "queue7").start();
    }
}

3、先运行生产者生产消息,再运行消费者消费消息。结合RabbitMQ的管理界面分析即可!

AMQP

什么是AMQP?

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

AMQP生产者的流转过程

在这里插入图片描述

AMQP消费者的流转过程

在这里插入图片描述

RabbitMQ的核心

组成部分

在这里插入图片描述

核心概念:

  • Server:又称Broker,接受客户端的连接,实现AMQP实体服务
  • 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(消息队列),保存消息并将它们转发给消费者

整体架构

在这里插入图片描述

运行流程

在这里插入图片描述

支持消息的模式

  • 简单模式,对应交换机的类型为:direct

    在这里插入图片描述

  • 工作模式,对应交换机的类型为:direct

    在这里插入图片描述

  • 发布订阅模式,对应交换机的类型为:fanout

    在这里插入图片描述

  • 路由模式,对应交换机的类型为:direct

    在这里插入图片描述

  • 主题Topic模式,对应交换机的类型为:topic

    在这里插入图片描述

  • 参数模式,对应交换机的类型为:headers

    在这里插入图片描述

RabbitMQ的使用场景

串行—>异步—>RabbitMQ

  • 串行执行

    在这里插入图片描述

  • 异步执行

    在这里插入图片描述

    存在问题:

    1、耦合度高

    2、需要自己写线程池自己维护成本太高

    3、出现了消息可能会丢失,需要你自己做消息补偿

    4、如何保证消息的可靠性你自己写

    5、如果服务器承载不了,你需要自己去写高可用

  • 使用中间件:RabbitMQ

    在这里插入图片描述

    好处:
    1、完全解耦,用MQ建立桥接

    2、有独立的线程池和运行模型

    3、出现了丢失消息的情况,MQ有持久化功能

    4、如何保证消息的可靠性,死信队列和消息转移等

高内聚、低耦合

在这里插入图片描述

其它更多的应用场景

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

SpringBoot整合RabbitMQ

发布订阅模式

1、新建一个SpringBoot项目

在这里插入图片描述

在这里插入图片描述

2、把application.properties的后缀改成yml,然后编写application.yml文件

# 服务端口
server:
  port: 8080

# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 47.106.9.114
    port: 5672

3、编写一个配置类

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMqConfiguration {

    // 1、声明注册fanout类型的交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanout_order_exchange",true,false);
    }
    // 2、声明队列
    @Bean
    public Queue smsQueue(){
        return new Queue("sms.fanout.queue",true);
    }
    @Bean
    public Queue duanxinQueue(){
        return new Queue("duanxin.fanout.queue",true);
    }
    @Bean
    public Queue emailQueue(){
        return new Queue("email.fanout.queue",true);
    }
    // 3、交换机绑定队列
    @Bean
    public Binding smsBinding(){
        return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
    }
    @Bean
    public Binding duanxinBinding(){
        return BindingBuilder.bind(duanxinQueue()).to(fanoutExchange());
    }
    @Bean
    public Binding emailBinding(){
        return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
    }


}

4、编写生成订单的服务

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;

@Component
public class OrderService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void makeOrder(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }


}

5、测试类运行测试

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer;

import com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {

    @Resource
    private OrderService orderService;

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

}

接下来编写消费者

6、新建一个SpringBoot的Module

在这里插入图片描述

在这里插入图片描述

7、把application.properties的后缀改成yml,然后编写application.yml文件

# 服务端口
server:
  port: 8081

# 配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 47.106.9.114
    port: 5672

8、分别编写三个消费者

package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.fanout;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(queues = {"email.fanout.queue"})
public class FanoutEmailConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("email fanout--接收到的信息是:-->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.fanout;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(queues = {"sms.fanout.queue"})
public class FanoutSMSConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("sms fanout--接收到的信息是:-->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.fanout;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(queues = {"duanxin.fanout.queue"})
public class FanoutDuanxinConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("duanxin fanout--接收到的信息是:-->" + message);
    }
}

9、结合控制台和RabbitMQ的管理界面分析情况即可!

路由模式

1、在生产者的项目那里编写一个配置文件DirectRabbitMqConfiguration

在这里插入图片描述

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;

import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DirectRabbitMqConfiguration {

    // 1、声明注册direct类型的交换机
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange("direct_order_exchange",true,false);
    }
    // 2、声明队列
    @Bean
    public Queue directsmsQueue(){
        return new Queue("sms.direct.queue",true);
    }
    @Bean
    public Queue directduanxinQueue(){
        return new Queue("duanxin.direct.queue",true);
    }
    @Bean
    public Queue directemailQueue(){
        return new Queue("email.direct.queue",true);
    }
    // 3、交换机绑定队列
    @Bean
    public Binding directsmsBinding(){
        return BindingBuilder.bind(directsmsQueue()).to(directExchange()).with("sms");
    }
    @Bean
    public Binding directduanxinBinding(){
        return BindingBuilder.bind(directduanxinQueue()).to(directExchange()).with("duanxin");
    }
    @Bean
    public Binding directemailBinding(){
        return BindingBuilder.bind(directemailQueue()).to(directExchange()).with("email");
    }


}

2、在生产者的项目那里编写OrderService

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;

@Component
public class OrderService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void makeOrder(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }

    public void makeOrderDirect(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "direct_order_exchange";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,"email",orderId);
        rabbitTemplate.convertAndSend(exchangeName,"duanxin",orderId);
    }


}

3、在生产者的项目那里编写测试,并且运行发送消息

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer;

import com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {

    @Resource
    private OrderService orderService;

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

    @Test
    void test() {
        orderService.makeOrderDirect("1","1",12);
    }


}

4、在消费者的项目那里编写三个消费者

package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(queues = {"duanxin.direct.queue"})
public class DirectDuanxinConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("duanxin direct--接收到的信息是:-->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(queues = {"email.direct.queue"})
public class DirectEmailConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("email direct--接收到的信息是:-->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.direct;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(queues = {"sms.direct.queue"})
public class DirectSMSConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("sms direct--接收到的信息是:-->" + message);
    }
}

5、直接运行消费者项目即可!结合控制台和RabbitMQ的管理界面分析结果即可!

主题模式

1、在消费者的项目那里编写三个消费者**(这里用注解完成队列和交换机的声明,以及绑定,一步到位。但是建议大家还是通过编写配置类文件完成队列和交换机的声明和绑定)**

package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "duanxin.topic.queue",durable = "true",autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange",type = ExchangeTypes.TOPIC),
        key = "#.duanxin.#"
))// 这里用注解完成队列和交换机的声明,以及绑定,一步到位
public class TopicDuanxinConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("duanxin topic--接收到的信息是:-->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "email.topic.queue",durable = "true",autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange",type = ExchangeTypes.TOPIC),
        key = "*.email.#"
))
public class TopicEmailConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("email topic--接收到的信息是:-->" + message);
    }
}
package com.xuexiangban.rabbitmq.springbootorderrabbitmqconsumer.service.topic;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;

@Service
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "sms.topic.queue",durable = "true",autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange",type = ExchangeTypes.TOPIC),
        key = "com.#"
))
public class TopicSMSConsumer {
    @RabbitHandler
    public void reviceMessage(String message){
        System.out.println("sms topic--接收到的信息是:-->" + message);
    }
}

2、直接运行消费者项目

3、在生产者的那个项目那里编写OrderService

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;

@Component
public class OrderService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void makeOrder(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }

    public void makeOrderDirect(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "direct_order_exchange";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,"email",orderId);
        rabbitTemplate.convertAndSend(exchangeName,"duanxin",orderId);
    }

    public void makeOrderTopic(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "topic_order_exchange";
        // #.duanxin.#
        // *.email.#
        // com.#
        String routingKey = "wwe.email.duanxin.superstar";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }




}

4、在生产者的那个项目那里编写测试类,直接运行即可!

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer;

import com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {

    @Resource
    private OrderService orderService;

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

    @Test
    void test() {
        orderService.makeOrderDirect("1","1",12);
    }

    @Test
    void testOrderTopic() {
        orderService.makeOrderTopic("1","1",12);
    }


}

5、结合控制台和RabbitMQ的管理界面分析即可!

过期时间TTL

过期时间TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了这个时间之后消息将自动被删除。

RabbitMQ可以对消息和队列设置TTL,目前有两种方法可以设置:

  • 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间(队列过期后,队列的消息会进入死信队列)
  • 第二种方法是对消息进行单独设置,每条消息TTL可以不同(单独的消息过期,则不会进入死信队列)

如果上述两种方法同时使用,则消息的过期时间以两者之间TTL较小的那个数值为准。

消息在队列的生存时间一旦超过设置的TTL值,就称为dead message被投递到死信队列(如果不投递到死信队列,那就是直接删除这个信息,但是一般不直接删除),消费者将无法再收到该消息。

整个队列的过期

1、在生产者的那个项目那里编写TTLRabbitMqConfiguration配置类

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class TTLRabbitMqConfiguration {

    // 1、声明注册direct类型的交换机
    @Bean
    public DirectExchange ttldirectExchange(){
        return new DirectExchange("ttl_direct_exchange",true,false);
    }
    // 2、声明队列
    @Bean
    public Queue directttlQueue(){
        // 设置过期时间
        Map<String,Object> args = new HashMap<>();
        args.put("x-message-ttl",5000);
        return new Queue("ttl.direct.queue",true,false,false,args);
    }

    // 3、交换机绑定队列
    @Bean
    public Binding directttlBinding(){
        return BindingBuilder.bind(directttlQueue()).to(ttldirectExchange()).with("ttl");
    }

}

2、在生产者的那个项目那里编写OrderService

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;

@Component
public class OrderService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void makeOrder(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }

    public void makeOrderDirect(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "direct_order_exchange";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,"email",orderId);
        rabbitTemplate.convertAndSend(exchangeName,"duanxin",orderId);
    }

    public void makeOrderTopic(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "topic_order_exchange";
        // #.duanxin.#
        // *.email.#
        // com.#
        String routingKey = "wwe.email.duanxin.superstar";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }

    public void makeOrderTTL(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttl";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }




}

2、在生产者的那个项目那里编写测试类,直接运行测试即可!

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer;

import com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {

    @Resource
    private OrderService orderService;

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

    @Test
    void test() {
        orderService.makeOrderDirect("1","1",12);
    }

    @Test
    void testOrderTopic() {
        orderService.makeOrderTopic("1","1",12);
    }

    @Test
    void testTTL() {
        orderService.makeOrderTTL("1","1",12);
    }


}

3、结合RabbitMQ的管理界面分析即可!

单条消息的过期

1、在生产者的那个项目那里编写TTLRabbitMqConfiguration配置类

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class TTLRabbitMqConfiguration {

    // 1、声明注册direct类型的交换机
    @Bean
    public DirectExchange ttldirectExchange(){
        return new DirectExchange("ttl_direct_exchange",true,false);
    }
    // 2、声明队列
    @Bean
    public Queue directttlQueue(){
        // 设置过期时间
        Map<String,Object> args = new HashMap<>();
        args.put("x-message-ttl",5000);
        return new Queue("ttl.direct.queue",true,false,false,args);
    }

    @Bean
    public Queue directttlMessageQueue(){
        // 设置过期时间
        return new Queue("ttl.message.direct.queue",true);
    }

    // 3、交换机绑定队列
    @Bean
    public Binding directttlBinding(){
        return BindingBuilder.bind(directttlQueue()).to(ttldirectExchange()).with("ttl");
    }
    @Bean
    public Binding directttlMessageBinding(){
        return BindingBuilder.bind(directttlMessageQueue()).to(ttldirectExchange()).with("ttlmessage");
    }

}

2、在生产者的那个项目那里编写OrderService

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service;

import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.UUID;

@Component
public class OrderService {

    @Resource
    private RabbitTemplate rabbitTemplate;

    public void makeOrder(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }

    public void makeOrderDirect(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "direct_order_exchange";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,"email",orderId);
        rabbitTemplate.convertAndSend(exchangeName,"duanxin",orderId);
    }

    public void makeOrderTopic(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "topic_order_exchange";
        // #.duanxin.#
        // *.email.#
        // com.#
        String routingKey = "wwe.email.duanxin.superstar";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }

    public void makeOrderTTL(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttl";
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }

    public void makeOrderTTLMessage(String userid,String productid,int num){
        // 1、根据商品id查询库存是否充足
        // 2、保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:" + orderId);
        // 3、通过MQ完成消息的分发
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttlmessage";
        // 给消息设置过期时间
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("5000");
                // 也可以设置编码
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };
        // 参数1:交换机,参数2:路由key或者队列名字,参数3:消息内容
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId,messagePostProcessor);
    }

}

2、在生产者的那个项目那里编写测试类,直接运行测试即可!

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer;

import com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {

    @Resource
    private OrderService orderService;

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

    @Test
    void test() {
        orderService.makeOrderDirect("1","1",12);
    }

    @Test
    void testOrderTopic() {
        orderService.makeOrderTopic("1","1",12);
    }

    @Test
    void testTTL() {
        orderService.makeOrderTTL("1","1",12);
    }

    @Test
    void testTTLMessage() {
        orderService.makeOrderTTLMessage("1","1",12);
    }

}

3、结合RabbitMQ的管理界面分析即可!

死信队列

DLX,全称为Dead-Letter-Exchange,可以称之为死信交换机,也有人称之为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是DLX ,绑定DLX的队列就称之为死信队列

消息变成死信,可能是由于以下的原因:

  • 消息被拒绝
  • 消息过期
  • 队列达到最大长度

DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,RabbitMQ就会自动地将这个消息重新发布到设置的DLX上去,即被路由到另一个队列(死信队列)

要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange 指定交换机即可

在这里插入图片描述

在这里插入图片描述

消息过期的死信队列测试

1、在生产者的那个项目那里编写配置文件

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class DeadRabbitMqConfiguration {

    // 1、声明注册direct类型的交换机
    @Bean
    public DirectExchange deadDirectExchange(){
        return new DirectExchange("dead_direct_exchange",true,false);
    }
    // 2、声明队列
    @Bean
    public Queue deadQueue(){
        // 设置过期时间
        return new Queue("dead.direct.queue",true);
    }


    // 3、交换机绑定队列
    @Bean
    public Binding deadBinds(){
        return BindingBuilder.bind(deadQueue()).to(deadDirectExchange()).with("dead");
    }

}

2、在生产者的那个项目那里编写配置文件

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class TTLRabbitMqConfiguration {

    // 1、声明注册direct类型的交换机
    @Bean
    public DirectExchange ttldirectExchange(){
        return new DirectExchange("ttl_direct_exchange",true,false);
    }
    // 2、声明队列
    @Bean
    public Queue directttlQueue(){
        // 设置过期时间
        Map<String,Object> args = new HashMap<>();
        args.put("x-message-ttl",5000);
        args.put("x-dead-letter-exchange","dead_direct_exchange");
        args.put("x-dead-letter-routing-key","dead");
        return new Queue("ttl.direct.queue",true,false,false,args);
    }

    @Bean
    public Queue directttlMessageQueue(){
        // 设置过期时间
        return new Queue("ttl.message.direct.queue",true);
    }

    // 3、交换机绑定队列
    @Bean
    public Binding directttlBinding(){
        return BindingBuilder.bind(directttlQueue()).to(ttldirectExchange()).with("ttl");
    }
    @Bean
    public Binding directttlMessageBinding(){
        return BindingBuilder.bind(directttlMessageQueue()).to(ttldirectExchange()).with("ttlmessage");
    }

}

3、启动测试类即可

package com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer;

import com.xuexiangban.rabbitmq.springbootorderrabbitmqproducer.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {

    @Resource
    private OrderService orderService;

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

    @Test
    void test() {
        orderService.makeOrderDirect("1","1",12);
    }

    @Test
    void testOrderTopic() {
        orderService.makeOrderTopic("1","1",12);
    }

    @Test
    void testTTL() {// 启动这个方法测试
        orderService.makeOrderTTL("1","1",12);
    }

    @Test
    void testTTLMessage() {
        orderService.makeOrderTTLMessage("1","1",12);
    }


}

内存磁盘的监控

RabbitMQ的内存警告

当内存使用超过配置的阈值或者磁盘空间剩余空间对于配置的阈值时,RabbitMQ会暂时阻塞客户端的连接,并且停止接收从客户端发来的消息,以此避免服务器的崩溃,客户端与服务端的心态检测机制也会失效。

如下图:

在这里插入图片描述

RabbitMQ的内存控制

参考帮助文档:https://www.rabbitmq.com/configure.html

当出现警告的时候,可以通过配置去修改和调整

  • 通过命令的方式修改和调整:
rabbitmqctl set_vm_memory_high_watermark <fraction>

rabbitmqctl set_vm_memory_high_watermark absolute 50MB

fraction/value为内存阈值。默认情况是:总内存的40%。代表的含义是:当RabbitMQ的内存超过40%时,就会产生警告并且阻塞所有生产者的连接。**通过此命令修改阈值在Broker重启以后将会失效,**通过修改配置文件方式设置的阈值则不会随着重启而消失,但修改了配置文件一样要重启broker才会生效。

  • 通过配置文件(rabbitmq.conf)方式修改和调整:
# 默认情况下是0.4
# vm_memory_high_watermark.relative = 0.4
# 使用relative相对值进行设置fraction,建议取值在04~0.7之间,不建议超过0.7.
vm_memory_high_watermark.relative = 0.6

# 使用absolute的绝对值的方式,但是是KB,MB,GB对应的命令如下
vm_memory_high_watermark.absolute = 2GB

RabbitMQ的内存换页

在某个Broker节点及内存阻塞生产者之前,它会尝试将队列中的消息换页到磁盘以释放内存空间,持久化和非持久化的消息都会写入磁盘中,其中持久化的消息本身就在磁盘中有一个副本,所以在转移的过程中持久化的消息会先从内存中清除掉

默认情况下,内存达到RabbitMQ内存阈值时的50%,就会进行换页处理,也就是说,在默认情况下RabbitMQ内存的阈值是总内存的40%,并且当RabbitMQ的内存达到RabbitMQ内存阈值一半时,会进行换页动作。即当RabbitMQ的内存超过 总内存*0.4*0.5=总内存*0.2 时,会进行换页动作

比如:有1000MB内存,当RabbitMQ的内存使用率达到了400MB,已经达到了极限,但是因为存在内存换页,这个时候会在达到极限400MB之前,会把内存中的200MB进行转移到磁盘中,保证RabbitMQ的内存可用

可以通过配置文件,设置 vm_memory_high_watermark_paging_ratio 来进行调整

vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7 # 设置小于1的值

# 为什么设置小于1,因为如果你设置为1,意味着内存已经达到了极限,你再去换页,这时候换页的意义不是很大了

RabbitMQ的磁盘预警

当RabbitMQ磁盘的剩余空间低于确定的阈值时,RabbitMQ同样会阻塞生产者,这样可以避免因非持久化的消息持续换页而耗尽磁盘空间导致服务器崩溃

默认情况下:磁盘预警为50MB的时候会进行预警。表示当前磁盘空间第50MB的时候会阻塞生产者,并且停止内存消息换页到磁盘中

这个阈值可以减小,但是不能完全的消除因磁盘耗尽而导致崩溃的可能性。比如在两次磁盘空间的检查空隙内,第一次检查是:60MB ,第二检查可能就是1MB,就会出现警告

通过命令方式修改如下:

rabbitmqctl set_disk_free_limit memory_limit <fraction>

rabbitmqctl set_disk_free_limit <disk_limit>

# fraction:是相对阈值,建议范围在:1.0~2.0之间。(相对于内存)
# disk_limit:固定单位 KB MB GB

通过配置文件配置如下:

disk_free_limit.relative = 3.0

disk_free_limit.absolute = 50mb

RabbitMQ的集群

RabbitMQ这款消息队列中间件产品本身是基于Erlang语言编写的,Erlang语言天生具备分布式特性(通过同步Erlang集群各节点的magic cookie来实现)。因此,RabbitMQ天然支持Clustering。这使得RabbitMQ本身不需要像ActiveMQ、Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞吐量能力的目的。在实际使用过程中多采取多机多实例部署方式,为了便于同学们练习搭建,有时候你不得不在一台机器上去搭建一个RabbitMQ集群,本章主要针对单机多实例这种方式来进行开展

集群搭建

环境准备:成功安装RabbitMQ,并且RabbitMQ可以运行起来,不报错

1、启动第一个节点:rabbit-1(主节点)

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# sudo RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server start &
...............
...............
...............
...............
...............
...............
  Starting broker... completed with 3 plugins.# 出现这个代表启动完成!

2、启动第二个节点:rabbit-2(从节点)

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# sudo RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=rabbit-2 rabbitmq-server start &
...............
...............
...............
...............
...............
...............
  Starting broker... completed with 3 plugins.# 出现这个代表启动完成!

3、查看验证一下是否运行成功

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# ps aux|grep rabbitmq
root     15372  0.0  0.2 241296  4644 pts/0    S    23:27   0:00 sudo RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server start
root     15373  0.0  0.0 144420  1580 pts/0    S    23:27   0:00 /sbin/runuser -u rabbitmq -- /usr/lib/rabbitmq/bin/rabbitmq-server start
rabbitmq 15381  0.0  0.0 113284  1528 pts/0    S    23:27   0:00 /bin/sh /usr/lib/rabbitmq/bin/rabbitmq-server start
rabbitmq 15388  1.7  4.1 1821668 77676 pts/0   Sl   23:27   0:06 /usr/lib64/erlang/erts-11.1.8/bin/beam.smp -W w -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -P 1048576 -t 5000000 -stbt db -zdbbl 128000 -sbwt none -sbwtdcpu none -sbwtdio none -B i -- -root /usr/lib64/erlang -progname erl -- -home /var/lib/rabbitmq -- -pa  -noshell -noinput -s rabbit boot -boot start_sasl -lager crash_log false -lager handlers [] start
rabbitmq 15395  0.0  0.0   4356   548 ?        Ss   23:27   0:00 erl_child_setup 65535
rabbitmq 15419  0.0  0.0  48912   520 ?        S    23:27   0:00 /usr/lib64/erlang/erts-11.1.8/bin/epmd -daemon
rabbitmq 15440  0.0  0.0  11592   460 ?        Ss   23:27   0:00 inet_gethost 4
rabbitmq 15441  0.0  0.0  13716   692 ?        S    23:27   0:00 inet_gethost 4
root     15894  0.0  0.2 241296  4644 pts/0    S    23:32   0:00 sudo RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS=-rabbitmq_management listener [{port,15673}] RABBITMQ_NODENAME=rabbit-2 rabbitmq-server start
root     15895  0.0  0.0 144420  1580 pts/0    S    23:32   0:00 /sbin/runuser -u rabbitmq -- /usr/lib/rabbitmq/bin/rabbitmq-server start
rabbitmq 15903  0.0  0.0 113284  1528 pts/0    S    23:32   0:00 /bin/sh /usr/lib/rabbitmq/bin/rabbitmq-server start
rabbitmq 15910  9.3  4.0 1820704 76664 pts/0   Sl   23:32   0:06 /usr/lib64/erlang/erts-11.1.8/bin/beam.smp -W w -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -P 1048576 -t 5000000 -stbt db -zdbbl 128000 -sbwt none -sbwtdcpu none -sbwtdio none -B i -- -root /usr/lib64/erlang -progname erl -- -home /var/lib/rabbitmq -- -pa  -noshell -noinput -s rabbit boot -boot start_sasl -rabbitmq_management listener [{port,15673}] -lager crash_log false -lager handlers [] start
rabbitmq 15917  0.0  0.0   4356   540 ?        Ss   23:32   0:00 erl_child_setup 65535
rabbitmq 15962  0.0  0.0  11592   460 ?        Ss   23:32   0:00 inet_gethost 4
rabbitmq 15963  0.0  0.0  13716   692 ?        S    23:32   0:00 inet_gethost 4
root     16071  0.0  0.0 112812   980 pts/0    R+   23:33   0:00 grep --color=auto rabbitmq
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 

4、把rabbit-1作为主节点,分别执行如下3行命令

# 停止应用
sudo rabbitmqctl -n rabbit-1 stop_app

# 目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群)
sudo rabbitmqctl -n rabbit-1 reset

# 启动应用
sudo rabbitmqctl -n rabbit-1 start_app

5、把rabbit-2作为从节点,分别执行如下4行命令

# 停止应用
sudo rabbitmqctl -n rabbit-2 stop_app

# 目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群)
sudo rabbitmqctl -n rabbit-2 reset

# 将rabbit2节点加入到rabbit1(主节点)集群当中【Server-node是服务器的主机名】,这里的主机名是:iZwz9cj1ytrolpgw50tiksZ
# sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@'Server-node'
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@iZwz9cj1ytrolpgw50tiksZ

# 启动应用
sudo rabbitmqctl -n rabbit-2 start_app

6、验证一下集群是否生效

[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# sudo rabbitmqctl cluster_status -n rabbit-1
Cluster status of node rabbit-1@iZwz9cj1ytrolpgw50tiksZ ...
Basics

Cluster name: rabbit-1@iZwz9cj1ytrolpgw50tiksZ

Disk Nodes

rabbit-1@iZwz9cj1ytrolpgw50tiksZ
rabbit-2@iZwz9cj1ytrolpgw50tiksZ

Running Nodes

rabbit-1@iZwz9cj1ytrolpgw50tiksZ
rabbit-2@iZwz9cj1ytrolpgw50tiksZ

Versions

rabbit-1@iZwz9cj1ytrolpgw50tiksZ: RabbitMQ 3.8.14 on Erlang 23.2.7
rabbit-2@iZwz9cj1ytrolpgw50tiksZ: RabbitMQ 3.8.14 on Erlang 23.2.7

Maintenance status

Node: rabbit-1@iZwz9cj1ytrolpgw50tiksZ, status: not under maintenance
Node: rabbit-2@iZwz9cj1ytrolpgw50tiksZ, status: not under maintenance

Alarms

(none)

Network Partitions

(none)

Listeners

Node: rabbit-1@iZwz9cj1ytrolpgw50tiksZ, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit-1@iZwz9cj1ytrolpgw50tiksZ, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit-1@iZwz9cj1ytrolpgw50tiksZ, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit-2@iZwz9cj1ytrolpgw50tiksZ, interface: [::], port: 15673, protocol: http, purpose: HTTP API
Node: rabbit-2@iZwz9cj1ytrolpgw50tiksZ, interface: [::], port: 25673, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit-2@iZwz9cj1ytrolpgw50tiksZ, interface: [::], port: 5673, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0

Feature flags

Flag: drop_unroutable_metric, state: enabled
Flag: empty_basic_get_metric, state: enabled
Flag: implicit_default_bindings, state: enabled
Flag: maintenance_mode_status, state: enabled
Flag: quorum_queue, state: enabled
Flag: user_limits, state: enabled
Flag: virtual_host_metadata, state: enabled
[root@iZwz9cj1ytrolpgw50tiksZ rabbitmq]# 

Web监控集群

1、先要保证打开Web端的客户端插件

rabbitmq-plugins enable rabbitmq_management

在这里插入图片描述

2、注意在访问的时候:web界面的管理需要给15672 node-1 和15673的 node-2 设置用户名和密码。分别执行如下6行代码!

# rabbit-1
rabbitmqctl -n rabbit-1 add_user admin admin

rabbitmqctl -n rabbit-1 set_user_tags admin administrator

rabbitmqctl -n rabbit-1 set_permissions -p / admin ".*" ".*" ".*"

# rabbit-2
rabbitmqctl -n rabbit-2 add_user admin admin

rabbitmqctl -n rabbit-2 set_user_tags admin administrator

rabbitmqctl -n rabbit-2 set_permissions -p / admin ".*" ".*" ".*"

3、查看一下效果

在这里插入图片描述

小结

上面我们学习的是使用单机部署集群的方式。

如果采用多机部署方式,需读取其中一个节点的cookie的值,并复制到其他节点的cookie(节点之间通过cookie确定相互是否可通信)。cookie的值一般存放在 /var/lib/rabbitmq/.erlang.cookie 文件里

例如:

有两个节点,分别为rabbit-1、rabbit-2

1、先配置各节点的hosts文件( vim /etc/hosts)

ip1:rabbit-1
ip2:rabbit-2

# 将rabbit-2节点加入到rabbit-1(主节点)集群当中
# 【多机部署集群的情况下这里的Server-node需要填写rabbit-1节点的ip地址,而不是主机名】
# sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@'Server-node'

其它步骤雷同单机部署方式

2、保持rabbit-1、rabbit-2的cookie的值一致

3、逐个启动各节点

RabbitMQ的分布式事务

概述

分布式事务指事务的操作位于不同的节点上,需要保证事务的 AICD 特性

分布式事务的方式

在分布式系统中,要实现分布式事务,无外乎那几种解决方案

  • 两阶段提交(2PC)
  • 补偿事务(TCC)
  • 本地消息表(异步确保)
  • MQ事务消息

两阶段提交(2PC)

两阶段提交(2PC)需要数据库产商的支持,java组件有atomikos等。

两阶段提交(Two-phase Commit,2PC),通过引入协调者(Coordinator)来协调参与者的行为,并最终决定这些参与者是否要真正执行事务。

准备阶段:

协调者询问参与者事务是否执行成功,参与者发回事务执行结果

在这里插入图片描述

提交阶段:

如果事务在每个参与者上都执行成功,事务协调者发送通知让参与者提交事务。否则,事务协调者发送通知让参与者回滚事务。

需要注意的是,在准备阶段,参与者执行了事务,但是还未提交。只有在提交阶段接收到协调者发来的通知后,才进行提交或者回滚。

在这里插入图片描述

两阶段提交(2PC)存在的问题:

  • 同步阻塞。所有事务参与者在等待其它参与者响应的时候都处于同步阻塞状态,无法进行其它操作
  • 单点问题。协调者在 2PC 中起到非常大的作用,发生故障将会造成很大影响。特别是在阶段二发生故障,所有参与者会处于一直等待的状态,无法完成其它操作
  • 数据不一致。在阶段二,如果协调者只发送了部分 Commit 消息,此时网络发生异常,那么只有部分参与者接收到 Commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致
  • 太过保守。任意一个节点失败就会导致整个事务失败,没有完善的容错机制

补偿事务(TCC)

TCC其实就是采用补偿机制。其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作

它分为三个阶段

  • Try阶段。主要是对业务系统做检测及资源预留
  • Confirm阶段。主要是对业务系统做确认提交,Try阶段执行成功并开始执行Confirm阶段时,默认Confirm阶段是不会出错的。即只要Try成功,Confirm一定成功
  • Cancel阶段。主要是在业务执行错误的时候,需要在回滚的状态下执行业务取消,预留资源释放

举个例子:

假入 Bob 要向 Smith 转账,思路大概是:我们有一个本地方法,里面依次调用

1、首先在Try阶段,要先调用远程接口把 Smith 和 Bob 的钱给冻结起来

2、在Confirm阶段,执行远程调用的转账的操作,转账成功进行解冻

3、如果第2步执行成功,那么转账成功,如果第2步执行失败,则调用远程冻结接口对应的解冻方法(Cancel)

补偿事务(TCC)的优点:

  • 跟2PC比起来,实现以及流程相对简单了一些,但数据的一致性比2PC要差一些

补偿事务(TCC)的缺点:

  • 在第2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,用TCC不太好定义及处理

本地消息表(异步确保)

本地消息表与业务数据表处于同一个数据库中,这样就能利用本地事务来保证对这两个表的操作满足事务特性,并且使用了消息队列来保证最终一致性

具体流程如下:

  • 在分布式事务操作的一方完成写业务数据的操作之后,向本地消息表发送一个消息,本地事务能保证这个消息一定会被写入本地消息表中
  • 之后将本地消息表中的消息转发到 Kafka 等消息队列中,如果转发成功则将消息从本地消息表中删除,否则继续重新转发
  • 在分布式事务操作的另一方从消息队列中读取一个消息,并执行消息中的操作

在这里插入图片描述

本地消息表(异步确保)的优点:

  • 一种非常经典的实现,避免了分布式事务,实现了最终一致性

本地消息表(异步确保)的缺点:

  • 消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理

MQ事务消息

有一些第三方的MQ是支持事务消息的,比如RocketMQ,它们支持事务消息的方式也是类似于采用两阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 Kafka 不支持

以阿里的RabbitMQ中间件为例,其思路大致为:

  • 第一阶段Prepared消息,会拿到消息的地址。第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态
  • 也就是说在业务方法内要向消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败,RabbitMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RabbitMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败

在这里插入图片描述

MQ事务消息的优点:

  • 实现了最终一致性,不需要依赖本地数据库事务

MQ事务消息的缺点:

  • 实现难度大,主流MQ不支持,RocketMQ事务消息部分代码也未开源

小结:

通过上面的内容,我们总结并对比了几种分布式事务方案的优缺点,分布式事务本身是一个技术难题,暂时没有一种完美的方案应对所有场景。具体还是要根据业务场景去抉择。阿里的RocketMQ实现的分布式事务,现在也有除了很多分布式事务的协调器,比如LCN等,大家可以多去尝试

结合案例说明分析

在这里插入图片描述

基于MQ的分布式事务整体设计思路:

在这里插入图片描述

保证消息可靠生产:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

保证消息可靠消费:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

基于MQ的分布式事务解决方案的优点:

  • 通用性强
  • 拓展方便
  • 耦合度低,方案也比较成熟

基于MQ的分布式事务解决方案的缺点:

  • 基于消息中间件,只适合异步场景
  • 消息会延迟处理,需要业务上能够容忍

建议:

  • 尽量去避免分布式事务,能不用分布式架构就不用
  • 尽量将非核心业务做成异步
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值