一、 引用文章

RabbitMQ狂神说笔记(B站狂神说笔记、KuangStudy、学相伴飞哥)
RabbitMQ狂神说笔记(B站狂神说笔记、KuangStudy、学相伴飞哥)百度云盘地址,提取码:0702

二、 入门及安装

1. 狂神说bilibili地址

https://www.bilibili.com/video/BV1dX4y1V73G?p=27

2. 概述

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

3. 下载

1. 打开rabbit 官网查看版本号

https://www.rabbitmq.com/which-erlang.html

2. 点开首页查看自己要安装的版本号

路径:https://www.rabbitmq.com/

3. More Updates-> 查看更多更新

路径:https://www.rabbitmq.com/news.html

4. 选择自己要安装的版本号,点击Package Cloud

路径:https://www.rabbitmq.com/news.html

5. 跳转到rabbit仓库

路径: https://packagecloud.io/rabbitmq

6. 查看自己的系统

命令:lsb_release -a
我当前的系统是 Description: CentOS Linux release 7.9.2009 (Core) CentOS 7

[root@iZbp1av1izm1qqcdfa0nndZ rabbitmq]# lsb_release -a
LSB Version:    :core-4.1-amd64:core-4.1-noarch
Distributor ID: CentOS
Description:    CentOS Linux release 7.9.2009 (Core)
Release:        7.9.2009
Codename:       Core

7. 点进去rabbitmq-server


路径:https://packagecloud.io/rabbitmq/rabbitmq-server

8. 选择自己的系统版本的包

路径:https://packagecloud.io/rabbitmq/rabbitmq-server?page=7

9. 点击rabbitmq-server-3.10.0-1.el7.noarch.rpm

路径:https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.10.0-1.el7.noarch.rpm
发现有多种获取方式

10. 使用wget 方式下载

在服务器创建文件夹,用来放下载的包

[root@iZbp1av1izm1qqcdfa0nndZ /]# mkdir /usr/rabbitmq/

复制下载命令

wget --content-disposition https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.10.0-1.el7.noarch.rpm/download.rpm

复制命令到服务器,完成下载

[root@iZbp1av1izm1qqcdfa0nndZ rabbitmq]# wget --content-disposition https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.10.0-1.el7.noarch.rpm/download.rpm

如下图完成下载

查看下载文件


[root@iZbp1av1izm1qqcdfa0nndZ rabbitmq]# lsrabbitmq-server-3.10.0-1.el7.noarch.rpm

11. erlang 下载和rabbit一样

我们回到rabbitmq仓库页面
路径:https://packagecloud.io/rabbitmq

12. 查看下载的rabbitmq对应的erlang的版本

对应版本查看路径:https://www.rabbitmq.com/which-erlang.html
刚才下载的是rabbitmq-server-3.10.0-1.el7.noarch.rpm看到最小对应的erlang版本是23.3,最大对应的erlang版本是25.0

13 .点开erlang进入erlang版本库

erlang版本库:https://packagecloud.io/rabbitmq/erlang
在第一页找到contos7系统有 erlang-23.3.4.11-1.el7.x86_64.rpm版本包可以用。

14 .点开erlang-23.3.4.11-1.el7.x86_64.rpm

路径:https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-23.3.4.11-1.el7.x86_64.rpm

15 使用wget 方式下载

打开存放包的路径

[root@iZbp1av1izm1qqcdfa0nndZ /]# cd /usr/rabbitmq/

复制下载命令

wget --content-disposition https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-23.3.4.11-1.el7.x86_64.rpm/download.rpm

复制命令到服务器,完成下载

[root@iZbp1av1izm1qqcdfa0nndZ rabbitmq]# wget --content-disposition https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-23.3.4.11-1.el7.x86_64.rpm/download.rpm

查看下载的包


[root@iZbp1av1izm1qqcdfa0nndZ rabbitmq]# ls
erlang-23.3.4.11-1.el7.x86_64.rpm  rabbitmq-server-3.10.0-1.el7.noarch.rpm

4. 传统安装RabbitMQ

1.安装 erlang

//安装命令yum localinstall erlang-23.3.4.11-1.el7.x86_64.rpm// 查看版本号erl -v

2. 安装socat

yum install -y socat

3. 导入rabbitmq Key

rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc

4.安装 rabbitmq

 yum localinstall rabbitmq-server-3.10.0-1.el7.noarch.rpm

5. 启动rabbitmq

systemctl start rabbitmq-server

6. 设置开机自动启动

systemctl enable rabbitmq-server

7. RabbitMQ Web界面管理

//安装界面管理rabbitmq-plugins enable rabbitmq_management// 重启服务systemctl restart rabbitmq-server

8. 访问界面管理

访问地址:http://localhost:15672/ 或者http://121.196.153.197:15672/

注意:一定要记住,在对应服务器(阿里云,腾讯云等)的安全组中开放15672端口
rabbitmq有一个默认账号和密码是: guest
默认情况只能在 localhost本机下访问(见下图)。
当前使用的是阿里云服务器,报错,不能登录,所以需要新增一个远程登录的用户


新增用户admin

rabbitmqctl add_user admin admin

设置用户分配操作权限

rabbitmqctl set_user_tags admin administrator

用户级别:

 1. administrator:可以登录控制台、查看所有信息、可以对 rabbitmq进行管理2. monitoring:监控者 登录控制台,查看所有信息  3. policymaker:策略制定者 登录控制台,指定策略4. managment 普通管理员 登录控制台

为用户添加资源权限,可以执行也可以不执行,当前已经是超级管理员

rabbitmqctl set_permissions -p / admin ".*"".*"".*"

再次重新登录图形化界面http://121.196.153.197:15672/
用户名和密码都为:admin


登录成功

关闭防火墙服务

systemctl disable firewalld
Removed symlink /etc/systemd/system/multi-user.target.wants/firewalld.service.
Removed symlink /etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service.
systemctl stop firewalld

9. rabbitmqctl 常用命令

1.rabbitmqctl add_user 账号 密码                                     -- 新增用户
2.rabbitmqctl set_user_tags 账号 administrator                        -- 为用户设置权限
3.rabbitmqctl change_password Username Newpassword                  -- 修改密码
4.rabbitmqctl delete_user Username                                  -- 删除用户
5.rabbitmqctl list_users                                            -- 查看用户清单
6.rabbitmqctl.bat_set_permissions -p / 用户名 ".*" ".*" ".*"         -- 为用户设置administrator角色
7.rabbitmqctl.bat_set_permissions -p / root ".*" ".*" ".*"        -- 为root设置administrator角色

5.linux环境下卸载rabbitmq

卸载前先停止rabbitmq服务

/usr/lib/rabbitmq/bin/rabbitmqctl stop

查看rabbitmq安装的相关列表

yum list | grep rabbitmq

卸载rabbitmq已安装的相关内容

yum -y remove rabbitmq-server.noarch

查看erlang安装的相关列表

yum list | grep erlang

卸载erlang已安装的相关内容

yum -y remove erlang-*
rm -rf /usr/lib64/erlang

删除有关的所有文件

find / -name rabbit*
rm -rf 依次删除find出的文件rm -rf /usr/lib64/erlang
rm -rf /var/lib/rabbitmq
rm -rf /usr/local/erlang
rm -rf /usr/local/rabbitmq

6. RabbitMQ之Docker安装

1. Dokcer安装RabbitMQ

  1. yum 包更新到最新
yum update
  1. 安装软件包,yum-util提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
yum install -y yum-utils device-mapper-persistent-data lvm2
  1. 设置yum源为阿里云
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
  1. 安装docker
yum install docker-ce-y
  1. 安装后查看docker版本
docker-v
  1. 安装加速镜像

从阿里云获取镜像加速器:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{"registry-mirrors": ["https://spukdfwp.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docke

2. docker的相关命令

https://blog.csdn.net/qq_45392321/article/details/124218816

3 .安装rabbitmq

  1. 路径:https://www.rabbitmq.com/download.html

  2. 点击上图中标红线的 community Docker image,跳转到如下地址:https://registry.hub.docker.com/_/rabbitmq/

当前可以看到安装镜像的时候可以设置用户名,密码,ip。就不用安装完进入容器内部设置


3. 官网给的安装案例

$ docker run -d --hostname my-rabbit --name some-rabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password rabbitmq:3-management
  1. 命令讲解
docker run -id --hostname my-rabbit --name=myrabbit -p 15672:15672 rabbitmq:3-management--hostname:指定容器主机名称
--name:指定容器名称
-p:将mq端口号映射到本地
-e 设置
  1. 修改命令创建并安装
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:3-management
  1. 报错:listen tcp4 0.0.0.0:xx端口: bind: address already in use的端口占用问题

停掉手动安装的rabbimq

systemctl stop rabbitmq-server

如果还有端口被占用,查看下方博客https://blog.csdn.net/shenxinde/article/details/123720168

  1. 阿里云开放上方命令 设置的端口号
    -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883
  2. 安装成功
[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker images
REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
rabbitmq     3-management   6c3c2a225947   7 months ago   253MB
[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED         STATUS         PORTS                                                                                                                                                                                                                                                                               NAMES
1de1f1e10cb0   rabbitmq:3-management   "docker-entrypoint.s…"   6 minutes ago   Up 6 minutes   4369/tcp, 0.0.0.0:1883->1883/tcp, :::1883->1883/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp, 0.0.0.0:25672->25672/tcp, :::25672->25672/tcp, 0.0.0.0:61613->61613/tcp, :::61613->61613/tcp, 15691-15692/tcp   myrabbit
[root@iZbp1av1izm1qqcdfa0nndZ ~]#
  1. 停掉手动安装的rabbimq
systemctl stop rabbitmq-server
  1. 启动docker的rabbitmq容器
## 查看容器
[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED         STATUS         PORTS                                                                                                                                                                                                                                                                               NAMES
1de1f1e10cb0   rabbitmq:3-management   "docker-entrypoint.s…"   9 minutes ago   Up 9 minutes   4369/tcp, 0.0.0.0:1883->1883/tcp, :::1883->1883/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp, 0.0.0.0:25672->25672/tcp, :::25672->25672/tcp, 0.0.0.0:61613->61613/tcp, :::61613->61613/tcp, 15691-15692/tcp   myrabbit
## 启动容器 docker start 容器id(CONTAINER ID)
[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker start 1de1f1e10cb0
1de1f1e10cb0
[root@iZbp1av1izm1qqcdfa0nndZ ~]#

7. RabbitMQ的角色分类

1. 角色说明


2. 访问权限说明

路径:http://localhost/#/users
添加一个用户,可以选择设置不同的权限

三、入门案例

1. RabbitMQ入门案例 - Simple 简单模式

https://www.bilibili.com/video/BV1dX4y1V73G?p=44

实现步骤

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

1 构建一个maven工程

2. pom导入依赖

<dependencies><!--导入rabbitmq依赖--><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>5.10.0</version></dependency></dependencies>

3. 第一种模型:简单模式

在上图的模型中,有以下概念:

  1. 生产者–>p,也就是要发送消息的程序
  2. 消费者–>c:消息的接受者,会一直等待消息到来。
  3. 消息队列–>图中红色部分:类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。

4. 代码的实现

路径说明:

1. 生产者

路径:src/main/java/com/xxx/rabbitmq/simple/Producer.java
代码:

package com.xxx.rabbitmq.simple;import com.rabbitmq.client.*;/*** @description:  简单模式Simple* 生产者*/
public class Producer {public static void main(String[] args) {// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp// ip port// 1: 创建连接工程ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setUsername("admin");//rabbitmq登录的账号connectionFactory.setPassword("admin");//rabbitmq登录的密码connectionFactory.setVirtualHost("/");//springboot ---rabbitmqConnection connection = null;Channel channel = null;try {// 2: 创建连接Connection Rabbitmq为什么是基于channel去处理而不是链接? 长连接----信道channelconnection = connectionFactory.newConnection("生产者");// 3: 通过连接获取通道Channelchannel = connection.createChannel();// 4: 通过通创建交换机,声明队列,绑定关系,路由key,发送消息,和接收消息String queueName = "queue1";/** @params1 队列的名称* @params2 是否要持久化durable=false 所谓持久化消息是否存盘,如果false 非持久化 true是持久化? 非持久化会存盘吗? 会存盘,但是会随从重启服务会丢失。* @params3 排他性,是否是独占独立* @params4 是否自动删除,随着最后一个消费者消息完毕消息以后是否把队列自动删除* @params5 携带附属参数*/channel.queueDeclare(queueName, true, false, false, null);// 5: 准备消息内容String message = "Hello SSSS!!!";// 6: 发送消息给队列queue// @params1: 交换机  @params2 队列、路由key @params 消息的状态控制  @params4 消息主题// 面试题:可以存在没有交换机的队列吗?不可能,虽然没有指定交换机但是一定会存在一个默认的交换机。channel.basicPublish("", queueName, null, message.getBytes());System.out.println("消息发送成功!!!");} catch (Exception ex) {ex.printStackTrace();} finally {// 7: 关闭通道if (channel != null && channel.isOpen()) {try {channel.close();} catch (Exception ex) {ex.printStackTrace();}}// 8: 关闭连接if (connection != null && connection.isOpen()) {try {connection.close();} catch (Exception ex) {ex.printStackTrace();}}}}
}

2. 消费者

路径:src/main/java/com/xxx/rabbitmq/simple/Consumer.java
代码:

package com.xxx.rabbitmq.simple;import com.rabbitmq.client.*;import java.io.IOException;/*** 消费者*/
public class Consumer {//快捷键mainpublic static void main(String[] args) {// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp// ip port// 1: 创建连接工程ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");connectionFactory.setVirtualHost("/");Connection connection = null;Channel channel = null;try {// 2: 创建连接Connectionconnection = connectionFactory.newConnection("消费者");// 3: 通过连接获取通道Channelchannel = connection.createChannel();// 4: 通过通创建交换机,声明队列,绑定关系,路由key,发送消息,和接收消息// true = ack 正常的逻辑是没问题 死循环 rabbit 重发策略// false = nack 消息这在消费消息的时候可能会异常和故障final  Channel channel2 = channel;channel2.basicConsume("queue1", false, new DeliverCallback() {public void handle(String consumerTag, Delivery message) throws IOException {try {System.out.println("收到消息是" + new String(message.getBody(), "UTF-8"));channel2.basicAck(message.getEnvelope().getDeliveryTag(),false);}catch (Exception ex){ex.printStackTrace();// 三次确认 -- reject + sixin}}}, new CancelCallback() {public void handle(String consumerTag) throws IOException {System.out.println("接受失败了...");}});System.out.println("开始接受消息");System.in.read();} catch (Exception ex) {ex.printStackTrace();} finally {// 7: 关闭通道if (channel != null && channel.isOpen()) {try {channel.close();} catch (Exception ex) {ex.printStackTrace();}}// 8: 关闭连接if (connection != null && connection.isOpen()) {try {connection.close();} catch (Exception ex) {ex.printStackTrace();}}}}
}

5 . 查看RabbitMQ Management

路径:http://121.196.153.197:15672/
发现有一条消息,数量为1

点击queue1

进入后,点击Get Message,发现我们发送的消息已存在

2. AMQP

1 什么是AMQP

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

2 AMQP生产者流转过程

3 AMQP消费者流转过程

3. RabbitMQ的核心组成部分

1 RabbitMQ的核心组成部分

核心概念

Server:又称Broker,接受客户端的连接,实现AMQP实体服务,安装rabbitmg-server 。

Connection :连接,应用程序与Broker的网络连接 TCP/IP/三次握手和四次挥手 。

Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各 Channel,每个Channel代表一个会话任务。

Message:消息,服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。

Virtual Host: 虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若千个Exhange和Queueu,同一个虚拟主机里面不能有相同名字的Exchange 。

Exchange :交换机,接受消息,根据路由键发送消息到绑定的队列。(不具备消息存储的能力)。

Bindings:Exchange和Queue之间的虚拟连接,binding中可以保护多个routing key。

Routing key : 是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。

Queue:队列,也成为Message Queue,消息队列,保存消息并将它们转发给消费者。

2 RabbitMQ整体架构是什么样子的?

3 RabbitMQ的运行流程

4 RabbitMQ支持的消息模型

官网参考:https://www.rabbitmq.com/getstarted.html
模式说明:

官网说明:

简单模式 -->Simple -->类型:无
工作模式 -->Work -->类型:无
发布订阅模式 -->Publish/Subscribe–>类型:fanout
路由模式 --> Routing–>类型:direct
主题 模式 --> Topic–>类型:topic
参数模式 --> RPC–>类型:headers(用的人很少)


4. RabbitMQ入门案例 - fanout <发布与订阅>模式

1 RabbitMQ的模式之发布订阅模式

发布订阅模式的具体实现

web操作查看视频
类型:fanout
特点:Fanout - 发布与订阅模式,是一种广播机制,它是没有路由 key的模式

2. 可视化界面绑定交换机和队列

路径:http://121.196.153.197:15672/#/exchanges

1. 添加交换机Exchange

  1. 输入交换机名称,选择模式,点击添加
    路径:http://121.196.153.197:15672/#/exchanges
  2. 查看添加成功的交换机信息
    路径:http://121.196.153.197:15672/#/exchanges

2. 添加队列Queues

  1. 输入队列名称,点击添加
    路径:http://121.196.153.197:15672/#/queues

  2. 添加成功查看

3. 队列Queues标签页面绑定交换机

  1. 点击队列名称queue1如下图
  2. 查看队列queue1是否有绑定交换机,如下图发现没有

    3 输入交换机名称,进行绑定,如下图绑定成功

4. 交换机Exchange标签页面绑定队列

  1. 点击要绑定的交换机名称,如下图

    2.进入交换机详情页面,打开Bindings, 输入queue2的名称进行绑定
  2. 绑定成功如下图
  3. 点击绑定成功的queue3
  4. 发现自动跳转到queues的详情页面。发现交换机和队列已绑定
    路径:http://121.196.153.197:15672/#/queues/%2F/queue3

5. 交换机发送消息

  1. 点击要发送消息的交换机
    路径:http://121.196.153.197:15672/#/exchanges

  2. 进入详情,在Publish message标签输入发送的消息,点击发送

  3. 发送成功提示

  4. 点击队列标签,发现被绑定当前交换机的队列,所有消息的总数追加了1条

  5. 点击queue1的详情页,查看消息

3. 代码的实现

1. 生产者

路径:src/main/java/com/xxx/rabbitmq/fanout/Producer.java
代码:

package com.xxx.rabbitmq.fanout;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;/*** RabbitMQ入门案例 - fanout <发布与订阅>模式* 类型:fanout* 特点:Fanout - 发布与订阅模式,是一种广播机制,它是没有路由 key的模式* 生产者*/
public class Producer {public static void main(String[] args) {//1. 创建链接工厂ConnectionFactory connectionFactory = new ConnectionFactory();connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 2: 创建连接Connectionconnection = connectionFactory.newConnection();// 4: 从连接中获取通道channelchannel = connection.createChannel();// 5: 准备发送消息的内容String message = " fanout <发布与订阅>模式";// 6:准备交换机String exchangeName = "fanout-exchanges";// 7.定义路由keyString routeKey = "";// 8: 指定交换机的类型String type = "fanout";//channel.queueBind();// 9: 发送消息给中间件rabbitmq-server// @params1: 交换机exchange// @params2: 队列名称// @params3: 属性配置// @params4: 发送消息的内容channel.basicPublish(exchangeName, routeKey, null, message.getBytes());System.out.println("消息发送成功!");} catch (Exception e) {e.printStackTrace();System.out.println("发送消息出现异常...");} finally {// 9: 释放连接关闭通道if (channel != null && channel.isOpen()) {try {channel.close();} catch (Exception ex) {ex.printStackTrace();}}//10if (connection != null) {try {connection.close();} catch (Exception ex) {ex.printStackTrace();}}}}
}

2. 消费者

路径:src/main/java/com/xxx/rabbitmq/fanout/Consumer.java
代码:

package com.xxx.rabbitmq.fanout;import com.rabbitmq.client.*;import java.io.IOException;/*** RabbitMQ入门案例 - fanout <发布与订阅>模式* 类型:fanout* 特点:Fanout - 发布与订阅模式,是一种广播机制,它是没有路由 key的模式* 消费者*/
public class Consumer {private static Runnable runnable = new Runnable() {public void run() {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");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: 从连接中获取通道channelchannel = 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() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));}}, new CancelCallback() {@Overridepublic 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();//new Thread(runnable, "queue5").start();}
}

5. RabbitMQ入门案例 - Direct <路由模式>模式

1 RabbitMQ的模式之路由模式

路由模式的具体实现

类型:direct
特点:Routing key的匹配模式。
理解:类似于交换机绑定队列的时候,给队列一个标签,发送消息的时候,通过标签进行筛选过滤,发送消息

2 可视化界面绑定交换机和队列

1. 添加交换机

  1. 输入交换机名称,选择模式,点击添加
  2. 添加成功查看

2. 绑定队列

  1. 点击direct-exchanges名称的交换机,进入详情页

  2. 队列1[queue1]进行路由模式的交换机绑定队列

  3. 绑定成功查看

  4. queue2/queue3 进行交换机绑定,绑定成功如下图

3. 发送消息

  1. 首先查看当前的队列条数
  2. 给路由key 为email的队列发送消息
  3. 发送成功
  4. 查看当前queue1 和queue3的条数,比原来都新增了一条

3. 代码的实现

1. 生产者

路径:src/main/java/com/xxx/rabbitmq/direct/Producer.java
代码:

package com.xxx.rabbitmq.direct;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;/*** RabbitMQ入门案例 - Direct <路由模式>模式* 类型:direct* 特点:Routing key的匹配模式。* 理解:类似于交换机绑定队列的时候,给队列一个标签,发送消息的时候,通过标签进行筛选过滤,发送消息* 生产者*/
public class Producer {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("生产者");// 4: 从连接中获取通道channelchannel = connection.createChannel();// 5: 准备发送消息的内容String message = "hello direct-exchanges!!!";// 6:准备交换机String exchangeName = "direct-exchanges";// 7: 定义路由keyString routeKey = "email";// 8: 指定交换机的类型String type = "direct";// 9: 发送消息给中间件rabbitmq-server// @params1: 交换机exchange// @params2: 队列名称// @params3: 属性配置// @params4: 发送消息的内容channel.basicPublish(exchangeName, routeKey, null, message.getBytes());System.out.println("消息发送成功!");} catch (Exception ex) {ex.printStackTrace();System.out.println("发送消息出现异常...");} finally {// 10: 释放连接关闭通道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. 消费者

路径:src/main/java/com/xxx/rabbitmq/direct/Consumer.java
代码:

package com.xxx.rabbitmq.direct;import com.rabbitmq.client.*;import java.io.IOException;/*** RabbitMQ入门案例 - Direct <路由模式>模式* 类型:direct* 特点:Routing key的匹配模式。* 理解:类似于交换机绑定队列的时候,给队列一个标签,发送消息的时候,通过标签进行筛选过滤,发送消息* 消费者*/
public class Consumer {private static Runnable runnable = new Runnable() {public void run() {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");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: 从连接中获取通道channelchannel = 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() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));}}, new CancelCallback() {@Overridepublic 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();//  new Thread(runnable, "queue5").start();}}

6. RabbitMQ入门案例 - Topic <主题 模式>模式

1 RabbitMQ的模式之主题模式

主题 模式的具体实现

类型:topic
特点:模糊的Routing key的匹配模式。
理解:

2 可视化界面绑定交换机和队列

1. 添加交换机

  1. 输入添加信息
  2. 查看添加完成

2. 绑定队列

  1. 点击交换机topic-exchanges,进入详情页绑定

  1. 绑定成功页面

queue1 :com.#
queue2:*.course.*
queue3:#.order.#
queue4 :#.user.*
网友理解:*代表一个目录名的单词长度可以为0~n。而#代表的是多级目录0~n
个人理解:* 代表有且只能有一个,必须有一个。# 代表0~n,可以有0个或者无数个

3. 发送消息

  1. 查看当前队列条数
  2. 发送数据,发送的是com.course.xxx 所以可以匹配上queue1和queue2
  3. 查看数据,发现queue1 和queue2个自增一条数据。

3. 代码的实现

1. 生产者

路径:src/main/java/com/xxx/rabbitmq/topics/Producer.java
代码:

package com.xxx.rabbitmq.topics;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;/*** RabbitMQ入门案例 - Topic <主题 模式>模式* 类型:topic* 特点:模糊的Routing key的匹配模式。* 理解:* 生产者*/
public class Producer {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("生产者");// 4: 从连接中获取通道channelchannel = connection.createChannel();// 5: 准备发送消息的内容String message = "hello ,topic-exchanges";// 6:准备交换机String exchangeName = "topic-exchanges";// 7: 定义路由keyString routeKey = "com.order.xxx.xxx";// 8: 指定交换机的类型String type = "topic";// 9: 发送消息给中间件rabbitmq-server// @params1: 交换机exchange// @params2: 队列名称// @params3: 属性配置// @params4: 发送消息的内容//  #.course.* queue3// *.order.# queue2 ta// com.order.course.xxx collecionchannel.basicPublish(exchangeName, routeKey, null, message.getBytes());System.out.println("消息发送成功!");} catch (Exception ex) {ex.printStackTrace();System.out.println("发送消息出现异常...");} finally {// 10: 释放连接关闭通道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. 消费者

路径:src/main/java/com/xxx/rabbitmq/topics/Consumer.java
代码:

package com.xxx.rabbitmq.topics;import com.rabbitmq.client.*;import java.io.IOException;/*** RabbitMQ入门案例 - Topic <主题 模式>模式* 类型:topic* 特点:模糊的Routing key的匹配模式。* 理解:* 消费者*/
public class Consumer {private static Runnable runnable = new Runnable() {public void run() {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");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: 从连接中获取通道channelchannel = 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() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));}}, new CancelCallback() {@Overridepublic 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();//  new Thread(runnable, "queue5").start();}}

7. RabbitMQ入门案例 - Headers<参数模式>模式

1 RabbitMQ的模式之参数模式

参数 模式的具体实现

类型:Headers
特点:参数匹配模式
理解:

2 可视化界面绑定交换机和队列

1. 添加交换机

  1. 输入名称,选择模式
  2. 查看添加的交换机

2. 绑定队列

  1. 点击上图列表中添加的headers-exchanges队列名称,进入详情,输入绑定信息
  2. 查看绑定的队列

3. 发送消息

  1. 查看消息数量
  2. 发送消息
  3. 查看消息数量,queue1数量增加了1

3. 代码的实现

1. 生产者

路径:src/main/java/com/xxx/rabbitmq/headers/Producer.java
代码:

package com.xxx.rabbitmq.headers;import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;import java.util.HashMap;
import java.util.Map;/*** RabbitMQ入门案例 - Headers<参数模式>模式* 类型:Headers* 特点:参数匹配模式* 理解:* 生产者*/
public class Producer {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("生产者");// 4: 从连接中获取通道channelchannel = connection.createChannel();// 5: 准备发送消息的内容String message = "hello ,headers-exchanges";// 6:准备交换机String exchangeName = "headers-exchanges";// 7: 定义路由keyString routeKey = "";// 8: 指定交换机的类型String type = "headers";//9: 消息头Map<String,Object> headers=new HashMap<>();headers.put("x","1");//headers.put("type","pdf");//10:添加到headersAMQP.BasicProperties basicProperties = new AMQP.BasicProperties.Builder().headers(headers).build();// 11: 发送消息给中间件rabbitmq-server// @params1: 交换机exchange// @params2: 队列名称/routingkey// @params3: 属性配置// @params4: 发送消息的内容channel.basicPublish(exchangeName, routeKey, basicProperties , message.getBytes());System.out.println("消息发送成功!");} catch (Exception ex) {ex.printStackTrace();System.out.println("发送消息出现异常...");} finally {// 12: 释放连接关闭通道if (channel != null && channel.isOpen()) {try {channel.close();} catch (Exception ex) {ex.printStackTrace();}}//13if (connection != null) {try {connection.close();} catch (Exception ex) {ex.printStackTrace();}}}}
}

2. 消费者

路径:src/main/java/com/xxx/rabbitmq/headers/Consumer.java
代码:

package com.xxx.rabbitmq.headers;import com.rabbitmq.client.*;import java.io.IOException;/*** RabbitMQ入门案例 - Headers<参数模式>模式* 类型:Headers* 特点:参数匹配模式* 理解:* 消费者*/
public class Consumer {private static Runnable runnable = new Runnable() {public void run() {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");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: 从连接中获取通道channelchannel = 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() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));}}, new CancelCallback() {@Overridepublic 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();//  new Thread(runnable, "queue5").start();}}

8. RabbitMQ入门案例 - Work<工作模式>模式

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

主要有两种模式:

轮询模式的分发:一个消费者一条,按均分配
公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配

1 Work模式轮询模式(Round-Robin)

轮询模式的具体实现

类型:无
特点:该模式接受消息是当有多个消费者接入时,消息的分配模式是一个消费者消费一条,直至消息消费完成

1. 生产者代码实现

路径:src/main/java/com/xxx/rabbitmq/work/lunxun/Producer.java

package com.xxx.rabbitmq.work.lunxun;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;/*** Work模式轮询模式(Round-Robin)* 生产者*/
public class Producer {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("生产者");// 4: 从连接中获取通道channelchannel = 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代码实现

路径:src/main/java/com/xxx/rabbitmq/work/lunxun/work1.java

package com.xxx.rabbitmq.work.lunxun;import com.rabbitmq.client.*;import java.io.IOException;public class work1 {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("消费者-Work1");// 4: 从连接中获取通道channelchannel = 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() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {try{System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));Thread.sleep(100);}catch(Exception ex){ex.printStackTrace();}}}, new CancelCallback() {@Overridepublic 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代码实现

路径:src/main/java/com/xxx/rabbitmq/work/lunxun/work2.java

package com.xxx.rabbitmq.work.lunxun;import com.rabbitmq.client.*;import java.io.IOException;public class work2 {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("消费者-Work2");// 4: 从连接中获取通道channelchannel = 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() {@Overridepublic 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() {@Overridepublic 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. 点击 public static void main(String[] args) { 前面的倒三角按钮,弹出如下框,点击第一个运行work1
  2. 点击 public static void main(String[] args) { 前面的倒三角按钮,弹出如下框,点击第一个运行work2
  3. 运行成功如下图
  4. 点击生产者 public static void main(String[] args) { 前面的倒三角按钮,发送消息
  5. 查看work1的接收数据
  6. 查看work2的接收消息
  7. 总结

场景说明:work1 使用了 Thread.sleep(100); work2使用了 Thread.sleep(200);模拟服务器延时情况。
说明:无论服务器效能怎么样,轮询模式都是一个消费者一条,按均分配

2 Work模式公平分发模式

1. 生产者代码实现

路径:src/main/java/com/xxx/rabbitmq/work/fair/Producer.java

package com.xxx.rabbitmq.work.fair;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;/*** Work模式公平分发模式* 生产者*/
public class Producer {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("生产者");// 4: 从连接中获取通道channelchannel = 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代码实现

路径:src/main/java/com/xxx/rabbitmq/work/fair/work1.java

package com.xxx.rabbitmq.work.fair;import com.rabbitmq.client.*;import java.io.IOException;public class work1 {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("消费者-Work1");// 4: 从连接中获取通道channelchannel = 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;// @todo  同一时刻服务器只会发送一条消息给消费者//RabbitMQ中的概念,channel.basicQos(1)指该消费者在接收到队列里的消息但没有返回确认结果之前,队列不会将新的消息分发给该消费者。队列中没有被消费的消息不会被删除,还是存在于队列中。//channel.basicQos(1);和channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);是配套使用,只有在channel.basicQos被使用的时候channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false)才起到作用。finalChannel.basicQos(1);// @todo 公平分发一定要改成手动应答,把第二个参数是否自动应答<autoAsk> 从true改成 false。finalChannel.basicConsume("queue1", false, new DeliverCallback() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {try{System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));Thread.sleep(1000);// @todo 改成手动应答:单条消费 返回确认消息finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);}catch(Exception ex){ex.printStackTrace();}}}, new CancelCallback() {@Overridepublic 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代码实现

路径:src/main/java/com/xxx/rabbitmq/work/fair/work2.java

package com.xxx.rabbitmq.work.fair;import com.rabbitmq.client.*;import java.io.IOException;public class work2 {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("消费者-Work2");// 4: 从连接中获取通道channelchannel = 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;// @todo 同一时刻服务器只会发送一条消息给消费者//RabbitMQ中的概念,channel.basicQos(1)指该消费者在接收到队列里的消息但没有返回确认结果之前,队列不会将新的消息分发给该消费者。队列中没有被消费的消息不会被删除,还是存在于队列中。//channel.basicQos(1);和channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);是配套使用,只有在channel.basicQos被使用的时候channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false)才起到作用。finalChannel.basicQos(1);// @todo 公平分发一定要改成手动应答,把第二个参数是否自动应答<autoAsk> 从true改成 false。finalChannel.basicConsume("queue1", false, new DeliverCallback() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {try{System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));Thread.sleep(2000);// @todo 改成手动应答:单条消费 返回确认状态finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);}catch(Exception ex){ex.printStackTrace();}}}, new CancelCallback() {@Overridepublic 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. 点击work1 以及work2 的 public static void main(String[] args) { 前面的倒三角运行work1和work2
  2. 运行生产者,看到运行成功
  3. 查看work1 的运行结果
  4. 查看work2的运行结果
  5. 总结

work1 阻塞 Thread.sleep(1000); work2阻塞Thread.sleep(2000);
work2 服务器响应久一点,所以work1服务器接受到消息比较多
能者多劳

9. RabbitMQ入门案例 - 完整的声明方式创建

1 生产者 代码实现【交换机和队列】的声明和绑定

路径:src/main/java/com/xxx/rabbitmq/all/Producer.java
代码:

package com.xxx.rabbitmq.all;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;/*** RabbitMQ入门案例 - 完整的声明方式创建* 代码实现创建交换机和队列,并绑定关系* 生产者*/
public class Producer {public static void main(String[] args) {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");connectionFactory.setPort(5672);connectionFactory.setVirtualHost("/");connectionFactory.setUsername("admin");connectionFactory.setPassword("admin");Connection connection = null;Channel channel = null;try {// 3: 从连接工厂中获取连接connection = connectionFactory.newConnection("生产者");// 4: 从连接中获取通道channelchannel = connection.createChannel();// 5: 准备发送消息的内容String message = "你好,交换机";// 6:准备交换机。取名规范 :  类型_业务模块_交换机String exchangeName = "direct_order_exchange";// 7: 定义路由keyString routeKeyOrder = "order";String routeKeyCourse = "course";// 8: 指定交换机的类型String exchangeType = "direct";// 9: 声明交换机/注册交换机// @params1: 交换机名称// @params2: 交换机类型// @params3: 是否持久化 所谓的持久化就是值:交换机不会随着服务器的重启造成丢失,如果是true代表不丢失,false重启丢失channel.exchangeDeclare(exchangeName, exchangeType, true);// 10: 声明队列/注册队列// @params1: 队列名称// @params2: 是否持久化// @params3: 是不是排他性,是否是独占独立// @params4: 是不是自动删除 随着最后一个消费者消息完毕消息以后是否把队列自动删除// @params5: 是不是有参数 参数携带可能会引发headers模式channel.queueDeclare("queue5", true, false, false, null);channel.queueDeclare("queue6", true, false, false, null);channel.queueDeclare("queue7", true, false, false, null);// 11: 绑定队列// @params1: 队列名称// @params2: 交换机名称// @params3: routeKeychannel.queueBind("queue5", exchangeName, routeKeyOrder);channel.queueBind("queue6", exchangeName, routeKeyOrder);channel.queueBind("queue7", exchangeName, routeKeyCourse);// 12: 发送消息给中间件rabbitmq-server// @params1: 交换机exchange// @params2: 队列名称// @params3: 属性配置// @params4: 发送消息的内容channel.basicPublish(exchangeName, routeKeyOrder, null, message.getBytes());System.out.println("消息发送成功!");} catch (Exception ex) {ex.printStackTrace();System.out.println("发送消息出现异常...");} finally {// 13: 释放连接关闭通道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 消费者,获取消息

路径:src/main/java/com/xxx/rabbitmq/all/Consumer.java
代码:

package com.xxx.rabbitmq.all;import com.rabbitmq.client.*;import java.io.IOException;/*** RabbitMQ入门案例 - 完整的声明方式创建* 消费者*/
public class Consumer {private static Runnable runnable = new Runnable() {public void run() {// 1: 创建连接工厂ConnectionFactory connectionFactory = new ConnectionFactory();// 2: 设置连接属性connectionFactory.setHost("121.196.153.197");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: 从连接中获取通道channelchannel = 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() {@Overridepublic void handle(String s, Delivery delivery) throws IOException {System.out.println(queueName + ":收到消息是:" + new String(delivery.getBody(), "UTF-8"));}}, new CancelCallback() {@Overridepublic 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();new Thread(runnable, "queue5").start();new Thread(runnable, "queue6").start();new Thread(runnable, "queue7").start();}}

10. RabbitMQ使用场景

1 解耦、削峰、异步

1. 同步异步的问题(串行)

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

代码

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

2. 并行方式 异步线程池

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

代码

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

存在问题

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

3. 异步消息队列的方式

好处:

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

2 高内聚,低耦合

好处:

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

3. 流量的削峰

四、RabbitMQ-Springboot案例

哔哩哔哩路径:https://www.bilibili.com/video/BV1dX4y1V73G?p=44

1. Fanout <发布订阅>模式

1. 生产者

1. 新建项目

  1. 输入名称springboot-order-rabbitmq-producer
  2. 选择依赖,点击创建
  3. pom依赖
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-test</artifactId><scope>test</scope></dependency></dependencies>

2. 修改配置文件

  1. src/main/resources/application.properties改为src/main/resources/application.yml 并编写配置
# 服务器
server:port: 8080
# rabbitmq配置
spring:rabbitmq:username: adminpassword: adminvirtual-host: /host: 121.196.153.197    #127.0.0.1port: 5672

3. 生产者服务层

路径:src/main/java/com/xxx/rabbitmq/service/OrderService.java

package com.xxx.rabbitmq.service;import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.UUID;/*** Fanout发布订阅模式*/
@Service
public class OrderService {@Autowiredprivate 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来完成消息的分发//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容String exchangeName = "fanout_order_exchange";String routingKey = "";rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);}}

4. 队列和交换机配置文件

路径:src/main/java/com/xxx/rabbitmq/config/RabbitMqConfiguration.java
代码:

package com.xxx.rabbitmq.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;/*** Fanout发布订阅模式*/
@Configuration
public class RabbitMqConfiguration {// 1 :声明注册fanout模式的交换机@Beanpublic FanoutExchange fanoutExchange(){//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new FanoutExchange("fanout_order_exchange",true,false);}// 2 :声明队列sms.fanout.queue  email.fanout.queue  note.fanout.queue@Bean//sms短信public Queue smsQueue(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("sms.fanout.queue",true);}@Bean//邮件public Queue emailQueue(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("email.fanout.queue",true);}@Bean//短信public Queue noteQueue(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("note.fanout.queue",true);}// 3 :完成绑定关系(把队列和交换机完成绑定关系)@Bean//sms短信public Binding smsBingDing(){return BindingBuilder.bind(smsQueue())   //绑定队列.to(fanoutExchange());   //队列绑定到哪个交换器}@Bean//邮件public Binding emailBingDing(){return BindingBuilder.bind(emailQueue())   //绑定队列.to(fanoutExchange());   //队列绑定到哪个交换器}@Bean//短信public Binding noteBingDing(){return BindingBuilder.bind(noteQueue())   //绑定队列.to(fanoutExchange());   //队列绑定到哪个交换器}
}

5. 测试

路径:src/test/java/com/xxx/rabbitmq/SpringbootOrderRabbitmqProducerApplicationTests.java
代码:

package com.xxx.rabbitmq;import com.xxx.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {@Autowiredprivate OrderService orderService;@Testvoid contextLoads() {orderService.makeOrder("1","1",12);}}

2. 消费者

1. 新建module

  1. 项目->右键->new->module
  2. 输入名称springboot-order-rabbitmq-consumber,选择和父类统计目录

  1. 选择依赖,创建
  2. 查看创建完成的项目,父类和子类(消费者)目录同级

  1. pom.xml
  <dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.amqp</groupId><artifactId>spring-rabbit-test</artifactId><scope>test</scope></dependency></dependencies>

2. 修改配置文件

  1. src/main/resources/application.properties改为src/main/resources/application.yml 并编写配置
# 服务器
server:port: 8081
# rabbitmq配置
spring:rabbitmq:username: adminpassword: adminvirtual-host: /host: 121.196.153.197    #127.0.0.1port: 5672

3. 创建FanoutEmailConsumer消费者接收

路径:src/main/java/com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/fanout/FanoutEmailConsumer.java

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.fanout;import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;/*** Fanout发布订阅模式* 邮件*/
@Service
@RabbitListener(queues = {"email.fanout.queue"})
public class FanoutEmailConsumer {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("email->fanout 接收到了的订单信息是:" + msg);}
}

4. 创建FanoutNoteConsumer消费者接收

路径:src/main/java/com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/fanout/FanoutNoteConsumer.java

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.fanout;import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;/*** Fanout发布订阅模式* 短信*/
@Service
@RabbitListener(queues = {"note.fanout.queue"})
public class FanoutNoteConsumer {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("note->fanout 接收到了的订单信息是:" + msg);}
}

5. 创建FanoutSMSConsumer消费者接收

路径:src/main/java/com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/fanout/FanoutSMSConsumer.java

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.fanout;import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;/*** Fanout发布订阅模式* sms短信*/
@Service
@RabbitListener(queues = {"sms.fanout.queue"})
public class FanoutSMSConsumer {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("sms->fanout 接收到了的订单信息是:" + msg);}
}

6. 运行测试

  1. 点击消费者端的启动服务

  2. 点击生产者的测试发送代码

  3. 查看当前消费者,已经接收到信息

2. Direct <路由>模式

1. 生产者

1. 生产者服务层

路径:src/main/java/com/xxx/rabbitmq/service/OrderService.java
代码:

package com.xxx.rabbitmq.service;import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.UUID;/*** 模式服务层*/
@Service
public class OrderService {@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 模拟用户下单 fanout 订阅模式* @param userid* @param productId* @param num*/public void makeOrder(String userid, String productId, int num) {//1.根据商品id查询库存是否足够//2.保存订单String orderId = UUID.randomUUID().toString();System.out.println("订单生产成功:" + orderId);//3.通过MQ来完成消息的分发//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容String exchangeName = "fanout_order_exchange";String routingKey = "";rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);}/*** 模拟用户下单 direct 路由模式* @param userid* @param productId* @param num*/public void makeOrderDirect(String userid, String productId, int num) {//1.根据商品id查询库存是否足够//2.保存订单String orderId = UUID.randomUUID().toString();System.out.println("订单生产成功:" + orderId);//3.通过MQ来完成消息的分发//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容String exchangeName = "direct_order_exchange";String routingKeySMS = "sms";String routingKeyEmail = "email";rabbitTemplate.convertAndSend(exchangeName, routingKeySMS, orderId);rabbitTemplate.convertAndSend(exchangeName, routingKeyEmail, orderId);}
}

2. 队列和交换机配置文件

注意:被注册的bean 不能同名。已经有的bean方法,不能重新注册。否则报错,启动不起来
注意:当前交换机和队列在生产者端,如果先启动消费者端,会报错说队列和交换机不存在。如果想消费者先启动可以把当前配置文件及目录复制到消费者端
注意:配置文件定义在消费者端比较好,因为消费者端直接和队列打交道

路径:com/xxx/rabbitmq/config/DirectRabbitMqConfiguration.java
代码:

package com.xxx.rabbitmq.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** Direct 路由模式*/
@Configuration
public class DirectRabbitMqConfiguration {// 1 :声明注册fanout模式的交换机@Beanpublic DirectExchange directExchange(){//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new DirectExchange("direct_order_exchange",true,false);}// 2 :声明队列sms.fanout.queue  email.fanout.queue  note.fanout.queue@Bean//sms短信public Queue smsQueueDirect(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("sms.direct.queue",true);}@Bean//邮件public Queue emailQueueDirect(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("email.direct.queue",true);}@Bean//短信public Queue noteQueueDirect(){//参数介绍//1.队列名 2.是否持久化 3.是否独占 4.自动删除 5.其他参数return new Queue("note.direct.queue",true);}// 3 :完成绑定关系(把队列和交换机完成绑定关系)@Bean//sms短信public Binding smsBingDingDirect(){return BindingBuilder.bind(smsQueueDirect())   //绑定队列.to(directExchange()) //队列绑定到哪个交换器.with("sms");  //设置路由key}@Bean//邮件public Binding emailBingDingDirect(){return BindingBuilder.bind(emailQueueDirect())   //绑定队列.to(directExchange())//队列绑定到哪个交换器.with("email");//设置路由key}@Bean//短信public Binding noteBingDingDirect(){return BindingBuilder.bind(noteQueueDirect())   //绑定队列.to(directExchange()) //队列绑定到哪个交换器.with("note");//设置路由key}
}

3. 测试

路径:com/xxx/rabbitmq/SpringbootOrderRabbitmqProducerApplicationTests.java
代码:

package com.xxx.rabbitmq;import com.xxx.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {/*** fanout 发布订阅模式测试*/@Autowiredprivate OrderService orderService;@Testvoid contextLoads() {orderService.makeOrder("1","1",12);}/*** direct 路由模式测试*/@Testvoid contextLoadsDirect() {orderService.makeOrderDirect("2","1",2);}}

2. 消费者

1. 创建DirectEmailConsumer消费者

路径:com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/direct/DirectEmailConsumer.java
代码

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.direct;import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;/*** direct 路由模式* 邮件*/
@Service
@RabbitListener(queues = {"email.direct.queue"})
public class DirectEmailConsumer {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("email->direct 接收到了的订单信息是:" + msg);}
}

2. 创建DirectNoteConsumer消费者

路径:com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/direct/DirectNoteConsumer.java
代码

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.direct;import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;/*** direct 路由模式* 短信*/
@Service
@RabbitListener(queues = {"note.direct.queue"})
public class DirectNoteConsumer {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("note->direct 接收到了的订单信息是:" + msg);}
}

3. 创建DirectSMSConsumer消费者

路径:com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/direct/DirectSMSConsumer.java
代码

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.direct;import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;/*** direct 路由模式* sms短信*/
@Service
@RabbitListener(queues = {"sms.direct.queue"})
public class DirectSMSConsumer {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("sms->direct 接收到了的订单信息是:" + msg);}
}

4. 运行测试

  1. 运行生产者的测试发送发法,看下图已经成功
  2. 启动服务端的服务,如下图消息已经进来。
    因为发送的路由key是sms 和 email 如下代码,
   String routingKeySMS = "sms";String routingKeyEmail = "email";rabbitTemplate.convertAndSend(exchangeName, routingKeySMS, orderId);rabbitTemplate.convertAndSend(exchangeName, routingKeyEmail, orderId);

所以接收到的也是只有email和sms,如下图

3. Topic<主题>模式

  1. 路由和发布订阅模式都是在生产者端使用配置文件配置的交换机和队列
  2. 当前 主题模式在消费者端使用注解完成交换机和队列的配置
  3. 推荐使用配置文件配置,方便管理,建议配置在消费者端,因为和队列直接打交道

1. 生产者

1. 生产者服务层

路径:src/main/java/com/xxx/rabbitmq/service/OrderService.java

package com.xxx.rabbitmq.service;import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.UUID;/*** 模式服务层*/
@Service
public class OrderService {@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 模拟用户下单 topic 主题模式 模糊匹配** @param userid* @param productId* @param num*/public void makeOrderTopic(String userid, String productId, int num) {//1.根据商品id查询库存是否足够//2.保存订单String orderId = UUID.randomUUID().toString();System.out.println("订单生产成功:" + orderId);//3.通过MQ来完成消息的分发//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容String exchangeName = "topic_order_exchange";/**  #代表: 0-n  *代表:有且只有一个* *.email.#   email*  com.#      sms 短信*  #.note.#   短信*/// 满足sms 短信 和  短信String routingKey = "com.note"; //sms 短信 和 note短信能收到//String routingKey = "com.email.note.test";//都能收到//String routingKey = "com";//只有sms 短信 能收到rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);}
}

2. 测试

路径:src/test/java/com/xxx/rabbitmq/SpringbootOrderRabbitmqProducerApplicationTests.java

package com.xxx.rabbitmq;import com.xxx.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {/*** topic 主题模式测试*/@Testvoid contextLoadsTopic() {orderService.makeOrderTopic("2","1",2);}
}

2. 消费者

1. 创建TopicEmailConsumer消费者

路径:src/main/java/com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/topic/TopicEmailConsumer.java

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.topic;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;/*** topic  主题模式* 邮件*/
@Service
//@RabbitListener(queues = {"email.topic.queue"})
@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 {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("email->topic 接收到了的订单信息是:" + msg);}
}

2. 创建TopicNoteConsumer消费者

路径:src/main/java/com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/topic/TopicNoteConsumer.java

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.topic;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;/*** topic  主题模式* 短信*/
@Service
//@RabbitListener(queues = {"note.topic.queue"})
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "note.topic.queue",durable = "true",autoDelete = "false"),exchange = @Exchange(value = "topic_order_exchange",type = ExchangeTypes.TOPIC),key = "#.note.#"
))
public class TopicNoteConsumer {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("note->topic 接收到了的订单信息是:" + msg);}
}

3. 创建TopicSMSConsumer消费者

路径:src/main/java/com/xxx/rabbitmq/springbootorderrabbitmqconsumber/service/topic/TopicSMSConsumer.java

package com.xxx.rabbitmq.springbootorderrabbitmqconsumber.service.topic;import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;/*** topic  主题模式* sms短信*/
@Service
//@RabbitListener(queues = {"sms.topic.queue"})
@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 {@RabbitHandlerpublic void receiveMessage(String msg) {System.out.println("sms->topic 接收到了的订单信息是:" + msg);}
}

4. 运行测试

  1. 启动消费者端,因为配置类以注解的方式写在了消费者端
  2. 启动生产者测试方法
  3. 查看消费者端,sms,note 收到信息

五、RabbitMQ高级

https://www.bilibili.com/video/BV1dX4y1V73G?p=44

1. 过期时间TTL

1. 概述

TTl表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。
RabbitMQ可以对消息和队列设置 TTL,目前有两种方法可以设置

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

如果上述两种方法同时使用,则消息的过期时间以两者 TTL较小的那个数值为准
消息在队列的生存时间一旦超过设置的 TTL值,就称为 dead
message被投递到死信队列,消费者将无法再收到该消息

2. 队列 设置TTL

1. 生产者服务层

路径:src/main/java/com/xxx/rabbitmq/service/OrderService.java

package com.xxx.rabbitmq.service;import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.util.UUID;/*** 模式服务层*/
@Service
public class OrderService {@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 模拟用户下单 TTL 队列过期时间** @param userid* @param productId* @param num*/public void makeOrderTTL(String userid, String productId, int num) {//1.根据商品id查询库存是否足够//2.保存订单String orderId = UUID.randomUUID().toString();System.out.println("订单生产成功:" + orderId);//3.通过MQ来完成消息的分发//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容String exchangeName = "ttl_direct_exchange";String routingKey = "ttl";rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);}}

2. 队列和交换机配置文件

路径:com/xxx/rabbitmq/config/TTLRabbitMqConfiguration.java

package com.xxx.rabbitmq.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;/*** TTL 队列过期时间*/
@Configuration
public class TTLRabbitMqConfiguration {// 1 :声明注册fanout模式的交换机@Beanpublic DirectExchange TtlDirectExchange() {//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new DirectExchange("ttl_direct_exchange", true, false);}//2 :声明队列->设置队列过期时间@Beanpublic Queue TtlDirectQueue() {//设置过期时间Map<String, Object> args = new HashMap<>();args.put("x-message-ttl", 5000);//5秒 这里一定是int类型return new Queue("ttl.direct.queue", true, false, false, args);}// 3 :完成绑定关系(把队列和交换机完成绑定关系)//设置绑定队列过期时间@Beanpublic Binding ttlBingDingDirect() {return BindingBuilder.bind(TtlDirectQueue())   //绑定队列.to(TtlDirectExchange()) //队列绑定到哪个交换器.with("ttl");  //设置路由key}}

3. 测试运行

路径:src/test/java/com/xxx/rabbitmq/SpringbootOrderRabbitmqProducerApplicationTests.java

package com.xxx.rabbitmq;import com.xxx.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {/*** ttl 过期时间*/@Testvoid contextLoadsTtl() {orderService.makeOrderTTL("2","1",2);}
}

4. 查看可视化界面

路径:http://121.196.153.197:15672/#/queues

  1. 发现新建的队列已存在。是ttl的
  2. 点击进去ttl.direct.queue,查看详情,可以看出里面有显示设置过期时间为5秒

3. 消息设置TTL

1. 生产者服务层

路径:src/main/java/com/xxx/rabbitmq/service/OrderService.java

package com.xxx.rabbitmq.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.Service;import java.util.UUID;/*** 模式服务层*/
@Service
public class OrderService {@Autowiredprivate RabbitTemplate rabbitTemplate;/*** 模拟用户下单 TTL 消息过期时间** @param userid* @param productId* @param num*/public void makeOrderTTLMessage(String userid, String productId, int num) {//1.根据商品id查询库存是否足够//2.保存订单String orderId = UUID.randomUUID().toString();System.out.println("订单生产成功:" + orderId);//3.通过MQ来完成消息的分发//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容String exchangeName = "ttlMsg_direct_exchange";String routingKey = "ttl_message";//给消息设置过期时间MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {@Overridepublic Message postProcessMessage(Message message) throws AmqpException {//这里就是字符串message.getMessageProperties().setExpiration("5000");message.getMessageProperties().setContentEncoding("UTF-8");return message;}};rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId,messagePostProcessor);}
}

2. 队列和交换机配置文件

路径:com/xxx/rabbitmq/config/TTLMsgRabbitMqConfiguration.java

package com.xxx.rabbitmq.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;/*** TTL 消息过期时间*/
@Configuration
public class TTLMsgRabbitMqConfiguration {// 1 :声明注册fanout模式的交换机@Beanpublic DirectExchange TtlDirectMessageExchange() {//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new DirectExchange("ttlMsg_direct_exchange", true, false);}//2 :声明队列->设置消息过期时间@Beanpublic Queue TtlDirectMessageQueue() {//设置过期时间return new Queue("ttl.message.direct.queue", true);}// 3 :完成绑定关系(把队列和交换机完成绑定关系)//设置绑定消息过期时间@Beanpublic Binding ttlBingDingDirectMessage() {return BindingBuilder.bind(TtlDirectMessageQueue()).to(TtlDirectMessageExchange()).with("ttl_message");}
}

3. 测试运行

路径:src/test/java/com/xxx/rabbitmq/SpringbootOrderRabbitmqProducerApplicationTests.java

package com.xxx.rabbitmq;import com.xxx.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {/*** ttl 消息过期时间*/@Testvoid contextLoadsTtlMessage() {orderService.makeOrderTTLMessage("2","1",2);}
}

4. 查看可视化界面

队列设置了过期时间有个ttl的标签。而消息过期并队列中并没有ttl的标签。

队列过期:放入死信队列
消息过期:不会放入死信队列

2. 死信队列

1. 概述

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

  1. 消息被拒绝
  2. 消息过期
  3. 队列达到最大长度

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

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

2. 死信队列设置

1. 添加死信队列配置文件

路径:src/main/java/com/xxx/rabbitmq/config/DeadRabbitMqConfiguration.java

package com.xxx.rabbitmq.config;import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** 死信队列*/
@Configuration
public class DeadRabbitMqConfiguration {//1.声明注册direct模式的交换机@Beanpublic DirectExchange deadDirectExchange() {return new DirectExchange("dead_direct_exchange", true, false);}//2.声明队列@Beanpublic Queue deadDirectQueue() {return new Queue("dead.direct.queue", true);}@Bean//3. 绑定public Binding deadDirectBingDing() {return BindingBuilder.bind(deadDirectQueue())   //绑定队列.to(deadDirectExchange()) //队列绑定到哪个交换器.with("dead");//设置路由key}}

2. 消息过期进入死信队列

  1. 修改队列过期的配置文件TTLRabbitMqConfiguration
    路径:src/main/java/com/xxx/rabbitmq/config/TTLRabbitMqConfiguration.java
package com.xxx.rabbitmq.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;/*** TTL 队列过期时间*/
@Configuration
public class TTLRabbitMqConfiguration {// 1 :声明注册fanout模式的交换机@Beanpublic DirectExchange TtlDirectExchange() {//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new DirectExchange("ttl_direct_exchange", true, false);}//2 :声明队列->设置队列过期时间@Beanpublic Queue TtlDirectQueue() {//设置过期时间Map<String, Object> args = new HashMap<>();args.put("x-message-ttl", 5000);//5秒 这里一定是int类型args.put("x-dead-letter-exchange", "dead_direct_exchange"); //死信队列名称设置args.put("x-dead-letter-routing-key", "dead");//设置死信队列的routeKey ,fanout<广播模式>不需要配置,目前是direct<路由模式>需要配置return new Queue("ttl.direct.queue", true, false, false, args);}// 3 :完成绑定关系(把队列和交换机完成绑定关系)//设置绑定队列过期时间@Beanpublic Binding ttlBingDingDirect() {return BindingBuilder.bind(TtlDirectQueue())   //绑定队列.to(TtlDirectExchange()) //队列绑定到哪个交换器.with("ttl");  //设置路由key}}
  1. 重新运行队列过期时间测试
    路径:src/test/java/com/xxx/rabbitmq/SpringbootOrderRabbitmqProducerApplicationTests.java
    代码:
package com.xxx.rabbitmq;import com.xxx.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {/*** ttl 队列过期时间*/@Testvoid contextLoadsTtl() {orderService.makeOrderTTL("2","1",2);}

3 . 查看可视化界面
死信队列有一条数据,过期时间的队列已没有数据

3. 报错解决

报错:channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED -

原因:队列和交换机已经创建,但是本地重新修改运行,导致报错。队列修改重新提交不会覆盖线上的队列,只会报错。
解决:如果是线上,不要轻易删除,可以重新命名。本地测试的话可以删除队列和交换机

4. 队列达到最大长度进入死信队列

  1. 修改队列过期配置文件,设置队列最大长度
    路径:src/main/java/com/xxx/rabbitmq/config/TTLRabbitMqConfiguration.java
package com.xxx.rabbitmq.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;/*** TTL 队列过期时间*/
@Configuration
public class TTLRabbitMqConfiguration {// 1 :声明注册fanout模式的交换机@Beanpublic DirectExchange TtlDirectExchange() {//参数介绍//1.交换器名 2.是否持久化 3.自动删除 4.其他参数return new DirectExchange("ttl_direct_exchange", true, false);}//2 :声明队列->设置队列过期时间@Beanpublic Queue TtlDirectQueue() {//设置过期时间Map<String, Object> args = new HashMap<>();args.put("x-message-ttl", 5000);//5秒 这里一定是int类型args.put("x-max-length", 5);//队列最大长度是5args.put("x-dead-letter-exchange", "dead_direct_exchange"); //死信队列名称设置args.put("x-dead-letter-routing-key", "dead");//设置死信队列的routeKey ,fanout<广播模式>不需要配置,目前是direct<路由模式>需要配置return new Queue("ttl.direct.queue", true, false, false, args);}// 3 :完成绑定关系(把队列和交换机完成绑定关系)//设置绑定队列过期时间@Beanpublic Binding ttlBingDingDirect() {return BindingBuilder.bind(TtlDirectQueue())   //绑定队列.to(TtlDirectExchange()) //队列绑定到哪个交换器.with("ttl");  //设置路由key}}
  1. 重新运行队列过期时间测试
    路径:src/test/java/com/xxx/rabbitmq/SpringbootOrderRabbitmqProducerApplicationTests.java
    代码:
package com.xxx.rabbitmq;import com.xxx.rabbitmq.service.OrderService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;@SpringBootTest
class SpringbootOrderRabbitmqProducerApplicationTests {/*** ttl 队列过期时间*/@Testvoid contextLoadsTtl() {//测试进入队列为11条for (int i = 0; i < 11; i++) {orderService.makeOrderTTL("2", "1", 2);}}
  1. 查看可视化
    设置队列最大长度为5 args.put("x-max-length", 5);//队列最大长度是5,发送请求11次,当前队列存在五条,死信队列存在6条


又因为设置 args.put("x-message-ttl", 5000);//5秒 这里一定是int类型 过期时间为5秒,5秒后所有数据进入死信队列

3. 内存磁盘的监控

1. RabbitMQ内存警告

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

当出现blocking或blocked话说明到达了阈值和以及高负荷运行了

2. RabbitMQ的内存控制

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

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

1. 命令的方式

两者选其一就可以,没必要两个都设置

# 1.内存阀值设置[建议0.4到0.7之间]rabbitmqctl set_vm_memory_high_watermark  0.4# 2.设置具体内存值:单位为:KB、MB、GB,根据需求所用rabbitmqctl set_vm_memory_high_watermark absolute 50MB

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

如下说明:

查看当前服务器全部内存:dmidecode -t memory | grep Size: | grep -v "No Module Installed"
查看当前服务器可使用内存:free -m

 // 查询全部内存 2048 MB /1024 = 2GB
[root@iZbp1av1izm1qqcdfa0nndZ ~]# dmidecode -t memory | grep Size: | grep -v "No Module Installed"Size: 2048 MB// 查询去掉系统等其他占用内存后的内存   -m 代表单位是 mb
[root@iZbp1av1izm1qqcdfa0nndZ ~]# free -mtotal        used        free      shared  buff/cache   available
Mem:           1837         298         128          29        1410        1326
Swap:             0           0           0

服务器可使用内存是 1837 mb默认情况是:0.4/2GB,代表的含义是:当RabbitMQ的内存超过40%会报错
计算RabbitMQ可以使用的内存: 1837 *0.4 = 734.8
字节单位换算器:https://www.elecfans.com/tools/zijiehuansuan.html
如下图 :

  1. 127MiB :RabbitMQ当前已经使用了127MiB MB内存
  2. 735 MiB high watermark:RabbitMQ最高可以使用735 MiB内存

分析

进入rabbitmq: docker exec -it 42068f6b803b /bin/bash
设置rabbitmq绝对值是50mb : rabbitmqctl set_vm_memory_high_watermark absolute 50MB

[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker ps
CONTAINER ID   IMAGE                   COMMAND                  CREATED      STATUS      PORTS                                                                                                                                                                                                                                                                               NAMES
42068f6b803b   rabbitmq:3-management   "docker-entrypoint.s…"   4 days ago   Up 4 days   4369/tcp, 0.0.0.0:1883->1883/tcp, :::1883->1883/tcp, 5671/tcp, 0.0.0.0:5672->5672/tcp, :::5672->5672/tcp, 15671/tcp, 0.0.0.0:15672->15672/tcp, :::15672->15672/tcp, 0.0.0.0:25672->25672/tcp, :::25672->25672/tcp, 0.0.0.0:61613->61613/tcp, :::61613->61613/tcp, 15691-15692/tcp   myrabbit
[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker exec -it 42068f6b803b /bin/bash
root@42068f6b803b:/# rabbitmqctl set_vm_memory_high_watermark absolute 50MB
Setting memory threshold on rabbit@42068f6b803b to 50MB bytes ...
root@42068f6b803b:/#

发现rabbitmq已经挂起

已使用内存128mb,但RabbitMQ最大使用内存是50MB,图中是48mb是因为有部分损耗,实际设置值是50mb,内存爆红,所有连接处于阻塞状态。


重新设置相对值为0.4 rabbitmqctl set_vm_memory_high_watermark 0.4

root@42068f6b803b:/# rabbitmqctl set_vm_memory_high_watermark  0.4
Setting memory threshold on rabbit@42068f6b803b to 0.4 ...

发现已经回复正常

2. 配置文件配置

rabbitMq有三个配置文件:分别为主配置文件(rabbitmq.conf),Erlang术语格式配置文件(advanced.config)、环境变量配置文件(rabbitmq-env.conf)。
位置:/etc/rabbitmq/rabbitmq.conf
说明:配置文件可能不存在,可以自己创建

# 设置阀值:建议取值在0.4~0.7之间,不建议超过0.7
vm_memory_high_watermark.relative=0.4# 设置具体值:单位为:KB、MB、GB
vm_memory_high_watermark.absolute=2GB注意:两种方式二选一即可

其他配置

#设置rabbimq的监听端口,默认为[5672]
listeners.tcp.local = 127.0.0.1:5672
#客户端与服务端心跳间隔,用来检测通信的对端是否存活,rabbitmq使用心跳机制来保持连接,设置为0则关闭心跳,默认是600秒,600S发一次心跳包
heartbeat = 60
#包大小,若包小则低延迟,若包则高吞吐,默认131072=128K
frame_max = 131072
#连接客户端数量
channel_max = 128
#内存告警值设置(相对值)
vm_memory_high_watermark.relative = 0.4
#内存阈值,该值为默认为0.5,该值为vm_memory_high_watermark的20%时,将把内存数据写到磁盘。如机器内存16G,当RABBITMQ占用内存1.28G(160.40.2)时把内存数据放到磁盘
vm_memory_high_watermark_paging_ratio = 0.5
#磁盘可用空间设置(绝对值)
disk_free_limit.absolute = 50000
#日志是否在控制台输出
log.console = false
#控制台输出的日志级别
log.console.level = info
log.exchange = false
log.exchange.level = info
#rabbitmq管理页面端口
management.tcp.port = 18085
#rabbitmq管理页面IP地址
management.tcp.ip = 0.0.0.0
#开启guest用户的远程链接
loopback_users.guest = none

3. RabbitMQ的磁盘预警

在磁盘使用空间一栏中,存在2个参数,分别是:33GiB、48MiB low watermark

  1. 33GiB :当前剩余可用磁盘空间
  2. 48MiB low watermark:剩余可用磁盘空间低于48mb将触发磁盘预警
    注意: 与内存空间不同的是,磁盘预警的触发机制是:低于下限模式<48MiB>,而内存预警是,高于上限模式<735MiB>
1. 磁盘触发预警机制

RabbitMQ 默认的磁盘下限配置是50 MB,图中显示48mb,是因为有损耗,实际是50默认

  1. 当磁盘剩余空间低于确定的阀值(默认50MB)时,RabbitMQ同样会阻塞生产者,目的是避免填充整个磁盘,导致Rabbit中断,可避免因非持久化的消息持续换页而耗尽磁盘空间导致服务器奔溃。

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

2. 命令方式设置

阀值和具体值都是针对 " 48MB low watermark" 进行设置,同样有两种设置类型:阀值设置、具体值设置

# 1.磁盘阀值设置[建议1.0到2.0之间]
sudo rabbitmqctl set_disk_free_limit memory_limit 1.0# 2.设置具体内存值:单位为:KB、MB、GB,根据需求所用
sudo rabbitmqctl set_disk_free_limit 20GB注意:以上两种方式二选一即可
3. 配置文件方式设置

位置:/etc/rabbitmq/rabbitmq.conf

# 设置阀值:建议取值在1.0~2.0之间
disk_free_limit.relative=2.0# 设置具体值:单位为:KB、MB、GB
disk_free_limit.absolute=2GB注意:两种方式二选一即可

4. 内存换页

磁盘空间远远大于内存空间,因此需要进行资源的置换,不可能等到内存空间触及达到0.4阀值的时候,再把消息写入到磁盘中去
在某个 Broker 节点及内存阻塞生产者之前,它会尝试将队列中的消息,换页到磁盘以释放内存空间,持久化和非持久化的消息都会写入磁盘,其中持久化的消息本身就在磁盘中有一个副本,所以在转移的过程中持久化的消息会先从内存中清除掉。

结论:为了避免内存空间爆满 和 消息的持久化,会将内存中的消息定时写入到磁盘中去。

1. 触发换页机制

默认情况下,内存达到50% 的阈值时就会换页处理,
也就是说,在默认情况下该内存的阈值是 0.4 的情况下,当内存超过 0.2 时,就会进行换页动作。

比如:
电脑1000MB 内存,内存阀值为0.4,配置的换页阀值是0.5,
rabbit 最大能够使用的内存空间是1000*0.4=400MB,由于配置的换页内存为 0.5,
因此使用率在达到极限400MB之前,会把内存中的 200MB 进行转移到磁盘中,从而达到稳健的运行。

2. 配置换页值
vm_memory_high_watermark.ralative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(设置值小于1)

思考:为什么设置小于1的值,如果设置为1,内部都已经达到极限了,再去换页意义不是很大了

3. 集群

1. 概述

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

主要参考官方文档:https://www.rabbitmq.com/clustering.html

2. 搭建集群

1. 查看安装环境

配置的前提是你的 rabbitmq可以运行起来,
比如: ps aux|grep rebbitmq 你能看到相关进程,
又比如:运行 rabbitmqctl status 你可以看到类似如下信息而不报错:
当前环境是docker 安装的,查看镜像存在即可
安装参考<二、入门及安装>下的 6. RabbitMQ之Docker安装


[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker images
REPOSITORY   TAG            IMAGE ID       CREATED        SIZE
rabbitmq     3-management   6c3c2a225947   7 months ago   253MB
[root@iZbp1av1izm1qqcdfa0nndZ ~]#

移除当前环境,重启docker

[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker rmi -f $(docker images  -aq)
Untagged: rabbitmq:3-management
Untagged: rabbitmq@sha256:4c4b66ad5ec40b2c27943b9804d307bf31c17c8537cd0cd107236200a9cd2814
Deleted: sha256:6c3c2a225947fba15a76015eb596fd1e768b0fbec7829008e57d54d35cee039c
[root@iZbp1av1izm1qqcdfa0nndZ ~]# docker rm -f $(docker ps  -aq)
[root@iZbp1av1izm1qqcdfa0nndZ ~]# systemctl restart docker

2. 单机多实例搭建

1. 创建 rabbit01

端口号配置 :-p 15672:15672 -p 5672:5672 -p 25672:25672


docker run -itd  --name rabbit01 --hostname myrabbit01 -v /home/software/rabbitmqcluster/rabbitmq01:/var/lib/rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin  -e RABBITMQ_ERLANG_COOKIE='rabbitmqCookie' -p 15672:15672 -p 5672:5672 -p 25672:25672  rabbitmq:3-management

报错:端口号占用

Error response from daemon: driver failed programming external
connectivity on endpoint rabbit01 (96cdb2022eab94b1d8a3f1af5124ef4b695cca1d523bfb4b5b8738436bf22ff2):
Error starting userland proxy: listen tcp4 0.0.0.0:25672: bind:
address already in use Error: failed to start containers: 89426c3184cc
查看占用的端口号pid,并杀死

[root@iZbp1av1izm1qqcdfa0nndZ ~]# netstat -anp |grep 25672
tcp        0      0 0.0.0.0:25672           0.0.0.0:*               LISTEN      389/beam.smp
[root@iZbp1av1izm1qqcdfa0nndZ ~]# kill -9 389 2665 2873
2. 创建 rabbit02

端口号配置 : -p 15673:15672 -p 5673:5672 -p 25673:25672
容器互联(单方向的互联): --link rabbit01:myrabbit01


docker run -itd  --name rabbit02 --hostname myrabbit02 -v /home/software/rabbitmqcluster/rabbitmq01:/var/lib/rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -e RABBITMQ_ERLANG_COOKIE='rabbitmqCookie' --link rabbit01:myrabbit01 -p 15673:15672 -p  5673:5672 -p 25673:25672  rabbitmq:3-management
3. 创建 rabbit03

端口号配置 : -p 15674:15672 -p 5674:5672 -p 25674:25672
容器互联(单方向的互联): --link rabbit01:myrabbit01 --link rabbit02:myrabbit02

docker run -itd  --name rabbit03 --hostname myrabbit03 -v /home/software/rabbitmqcluster/rabbitmq01:/var/lib/rabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -e RABBITMQ_ERLANG_COOKIE='rabbitmqCookie' --link rabbit01:myrabbit01  --link rabbit02:myrabbit02 -p 15674:15672 -p  5674:5672 -p 25674:25672 rabbitmq:3-management
4. 开放端口号

开放所需要的端口号
阿里云安全组配置:https://account.aliyun.com/login/login.htm

5. 访问

启动容器成功后,读者可以访问
分别通过浏览器访问:ip(自己的IP地址)ip:15672;ip:15673;ip:15674都可以访问

3.容器节点加入集群

1. rabbit01

进入容器

docker exec -it rabbit01 /bin/bash

操作rabbitmq,执行如下命令:

# 停止应用rabbitmqctl stop_app# 重置:目的清除节点上的历史数据,如果不清除无法将节点加入集群rabbitmqctl reset
# 启动应用rabbitmqctl start_app# 退出
exit
2. rabbit02

进入容器

docker exec -it rabbit02 /bin/bash

操作rabbitmq,执行如下命令:

# 停止应用rabbitmqctl stop_app# 重置:目的清除节点上的历史数据,如果不清除无法将节点加入集群rabbitmqctl reset# 将当前节点加入到rabbit01主节点,myrabbit01是 rabbit01的主机名rabbitmqctl join_cluster --ram rabbit@myrabbit01# 启动应用rabbitmqctl start_app# 退出容器exit
3. rabbit03

进入容器

docker exec -it rabbit03 /bin/bash

操作rabbitmq,执行如下命令:

# 停止应用rabbitmqctl stop_app# 重置:目的清除节点上的历史数据,如果不清除无法将节点加入集群rabbitmqctl reset# 将当前节点加入到rabbit01主节点,myrabbit01是 rabbit01的主机名rabbitmqctl join_cluster --ram rabbit@myrabbit01# 启动应用rabbitmqctl start_app# 退出容器exit
4. 可视化界面

4. 分布式事务

RabbitMQ狂神说笔记(RabbitMQ B站狂神说笔记、KuangStudy、学相伴飞哥)相关推荐

  1. 学相伴飞哥RabbitMQ笔记

    什么是中间件 我国企业从20世纪80年代开始就逐渐进行信息化建设,由于方法和体系的不成熟,以及企业业务和市场需求的不断变化,一个企业可能同时运行着多个不同的业务系统,这些系统可能基于不同的操作系统.不 ...

  2. 学相伴飞哥RabbitMQ笔记以及个人总结

    一.什么是中间件? 1.简介 我国企业从20世纪80年代开始就逐渐进行信息化建设,由于方法和体系的不成熟,以及企业业务和市场需求的不断变化,一个企业可能同时运行着多个不同的业务系统,这些系统可能基于不 ...

  3. 前端三板斧: HTML+CSS+JS笔记 摘自b站狂神说

    参考b站狂神说的前端视频,做的笔记 视频地址:https://www.bilibili.com/video/BV1x4411V75C HTML 5 什么是HTML? hyper text markup ...

  4. 【学相伴】狂神说 RabbitMQ笔记(简单使用RabbitMQ)

    目录 什么是rabbitMQ 使用docker安装RabbitMQ,如果没有使用过docker的可以看这篇文章https://blog.csdn.net/qq_44716544/article/det ...

  5. Redis学习笔记(B站狂神说)(自己总结方便复习)

    Redis学习笔记B站狂神说 redis: 非关系型数据库 一.NoSQL概述 1.为什么要用Nosql 1.单机Mysql的年代 思考一下,这种情况下:整个网站的瓶颈是什么? 1.数据量如果太大,一 ...

  6. B站狂神说JavaWeb学习笔记

    JavaWeb学习笔记(根据b站狂神说java编写) 1.基本概念 1.1 前言 静态Web: 提供给所有人看数据不会发生变化! HTML,CSS 动态Web: 有数据交互,登录账号密码,网站访问人数 ...

  7. B站---【狂神说Java】JavaWeb入门到实战---笔记

    该笔记大部分搬运B站遇见狂神说的javaWeb,顺便把图文合并记录,便于回顾 视频地址:https://www.bilibili.com/video/BV12J411M7Sj 记得三连 文章目录 1. ...

  8. B站狂神 JavaWeb 1-6

    Java Web 学习(上) 本博客基于B站狂神老师的JavaWeb课程,地址:https://space.bilibili.com/95256449,记录自己的代码和老师的笔记,同时也会有自己关于这 ...

  9. 【狂神说Java】JavaWeb入门到实战---笔记

    该笔记大部分搬运B站遇见狂神说的javaWeb,便于回顾 文章目录 1.基本概念 1.1.前言 1.2.web应用程序web应用程序: 1.3.静态web 1.4. 动态web 2.web服务器 2. ...

最新文章

  1. python中execute函数_Python 中的内置函数与模块
  2. verilog一些小知识点注意事项集合
  3. java 命令直接执行(运行)jar 包的几种方法【笔记自用】
  4. Hadoop安装过程
  5. HarmonyOS之常用布局TableLayout的使用
  6. javascript事件监听与事件委托
  7. POI读取word文件,(支持HSSF和XSSF两种方式)
  8. python lambda表达式及用法_Python:lambda表达式和yield关键字理解与使用讲解
  9. 热门开源后端软件Parse Server中存在严重的 RCE ,CVSS评分10分
  10. Struts2-Value Stack浅析
  11. 使用C#生成word文件
  12. 模拟I2C怎么用--教你使用GPIO口模拟I2C总线协议
  13. 单点登录原理及其实现方案
  14. SSM+广西壮族文化宣传网站 毕业设计-附源码230932
  15. 博客园主题美化(仅兼容 Markdown)
  16. 折半查找的实现 swustoj
  17. P4147 玉蟾宫 题解
  18. python程序设计搜题软件_智慧职教云课堂APPPython程序设计(常州工业职业技术学院)期末考试搜题公众号答案...
  19. 未来云计算虚拟化技术的发展趋势
  20. ZYNQ Linux 移植:包含petalinux移植和手动移植debian9

热门文章

  1. [倚天屠龙记] vim 查找与替换(简单查找)
  2. 制作报价单,制作价目表的软件和app
  3. 一文速学-时间序列分析算法之移动平均模型(MA)详解+Python实例代码
  4. 台式电脑CPU散热器怎么选?
  5. 浪潮超融合服务器虚拟机管理,浪潮联合VMware发布新一代超融合一体机
  6. el-table动态合并单元格
  7. nginx的重试机制
  8. php 循环电泳函数,【分享】单细胞凝胶电泳(彗星实验)分析软件CASP及教程【已搜索无重复】...
  9. 动态规划-钢条切割问题
  10. 学习日记——ESP8266读写外部Flash(2020.5.26)