RabbitMQ狂神说笔记(B站狂神说笔记、KuangStudy、学相伴飞哥)
RabbitMQ狂神说笔记(B站狂神说笔记、KuangStudy、学相伴飞哥)百度云盘地址,提取码:0702
https://www.bilibili.com/video/BV1dX4y1V73G?p=27
RabbitMQ是一个开源的遵循 AMQP协议实现的基于 Erlang语言编写,支持多种客户端(语言),用于在分布式系统中存储消息,转发消息,具有高可用,高可扩性,易用性等特征
https://www.rabbitmq.com/which-erlang.html
路径:https://www.rabbitmq.com/news.html
路径:https://www.rabbitmq.com/news.html
路径: https://packagecloud.io/rabbitmq
命令: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
路径:https://packagecloud.io/rabbitmq/rabbitmq-server
路径:https://packagecloud.io/rabbitmq/rabbitmq-server?page=7
路径:https://packagecloud.io/rabbitmq/rabbitmq-server/packages/el/7/rabbitmq-server-3.10.0-1.el7.noarch.rpm
发现有多种获取方式
在服务器创建文件夹,用来放下载的包
[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]# ls
rabbitmq-server-3.10.0-1.el7.noarch.rpm
我们回到rabbitmq仓库页面
路径:https://packagecloud.io/rabbitmq
对应版本查看路径:https://www.rabbitmq.com/which-erlang.html
刚才下载的是rabbitmq-server-3.10.0-1.el7.noarch.rpm
,看到最小对应的erlang版本是23.3,最大对应的erlang版本是25.0
erlang版本库:https://packagecloud.io/rabbitmq/erlang
在第一页找到contos7系统有 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
打开存放包的路径
[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
//安装命令
yum localinstall erlang-23.3.4.11-1.el7.x86_64.rpm
// 查看版本号
erl -v
yum install -y socat
rpm --import https://www.rabbitmq.com/rabbitmq-release-signing-key.asc
yum localinstall rabbitmq-server-3.10.0-1.el7.noarch.rpm
systemctl start rabbitmq-server
systemctl enable rabbitmq-server
//安装界面管理
rabbitmq-plugins enable rabbitmq_management
// 重启服务
systemctl restart rabbitmq-server
访问地址:http://localhost:15672/
或者http://121.196.153.197:15672/
注意:一定要记住,在对应服务器(阿里云,腾讯云等)的
安全组中开放15672端口
rabbitmq有一个默认账号和密码
是:guest
。
默认情况只能在 localhost
本机下访问(见下图)。
当前使用的是阿里云服务器,报错,不能登录,
所以需要新增一个远程登录的用户
。
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
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角色
卸载前先停止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
yum update
yum install -y yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
yum install docker-ce-y
docker-v
从阿里云获取镜像加速器: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
https://blog.csdn.net/qq_45392321/article/details/124218816
点击上图中标红线的 community Docker image
,跳转到如下地址:https://registry.hub.docker.com/_/rabbitmq/
当前可以看到安装镜像的时候可以设置用户名,密码,ip。就不用安装完进入容器内部设置
$ docker run -d --hostname my-rabbit --name some-rabbit -e RABBITMQ_DEFAULT_USER=user -e RABBITMQ_DEFAULT_PASS=password rabbitmq:3-management
docker run -id --hostname my-rabbit --name=myrabbit -p 15672:15672 rabbitmq:3-management
--hostname:指定容器主机名称
--name:指定容器名称
-p:将mq端口号映射到本地
-e 设置
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
停掉手动安装的rabbimq
systemctl stop rabbitmq-server
如果还有端口被占用,查看下方博客https://blog.csdn.net/shenxinde/article/details/123720168
[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 ~]#
systemctl stop rabbitmq-server
## 查看容器
[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 ~]#
路径:http://localhost/#/users
添加一个用户,可以选择设置不同的权限
https://www.bilibili.com/video/BV1dX4y1V73G?p=44
实现步骤
- jdk1.8
- 构建一个 maven工程
- 导入 rabbitmq的 maven依赖
- 启动 rabbitmq-server服务
- 定义生产者
- 定义消费者
- 观察消息的在 rabbitmq-server服务中的进程
<dependencies>
<dependency>
<groupId>com.rabbitmqgroupId>
<artifactId>amqp-clientartifactId>
<version>5.10.0version>
dependency>
dependencies>
在上图的模型中,有以下概念:
- 生产者–>p,也就是要发送消息的程序
- 消费者–>c:消息的接受者,会一直等待消息到来。
- 消息队列–>图中红色部分:类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
路径: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 ---rabbitmq
Connection connection = null;
Channel channel = null;
try {
// 2: 创建连接Connection Rabbitmq为什么是基于channel去处理而不是链接? 长连接----信道channel
connection = connectionFactory.newConnection("生产者");
// 3: 通过连接获取通道Channel
channel = 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();
}
}
}
}
}
路径: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 {
//快捷键main
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");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2: 创建连接Connection
connection = connectionFactory.newConnection("消费者");
// 3: 通过连接获取通道Channel
channel = 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();
}
}
}
}
}
路径:http://121.196.153.197:15672/
发现有一条消息,数量为1
点击queue1
进入后,点击Get Message,发现我们发送的消息已存在
AMQP全称:Advanced Message Queuing Protocol(高级消息队列协议)。是应用层协议的一个开发标准,为面向消息的中间件设计
核心概念
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,消息队列,保存消息并将它们转发给消费者。
官网参考:https://www.rabbitmq.com/getstarted.html
模式说明:
官网说明:
简单模式 -->Simple -->类型:无
工作模式 -->Work -->类型:无
发布订阅模式 -->Publish/Subscribe–>类型:fanout
路由模式 --> Routing–>类型:direct
主题 模式 --> Topic–>类型:topic
参数模式 --> RPC–>类型:headers(用的人很少)
发布订阅模式的具体实现
web操作查看视频
类型:fanout
特点:Fanout - 发布与订阅模式,是一种广播机制,它是没有路由 key的模式
路径:http://121.196.153.197:15672/#/exchanges
http://121.196.153.197:15672/#/exchanges
http://121.196.153.197:15672/#/exchanges
http://121.196.153.197:15672/#/queues/%2F/queue3
路径: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: 创建连接Connection
connection = connectionFactory.newConnection();
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = " fanout <发布与订阅>模式";
// 6:准备交换机
String exchangeName = "fanout-exchanges";
// 7.定义路由key
String 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();
}
}
//10
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
路径: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: 从连接中获取通道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();
//new Thread(runnable, "queue5").start();
}
}
路由模式的具体实现
类型:direct
特点:Routing key的匹配模式。
理解:类似于交换机绑定队列的时候,给队列一个标签,发送消息的时候,通过标签进行筛选过滤,发送消息
路径: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: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = "hello direct-exchanges!!!";
// 6:准备交换机
String exchangeName = "direct-exchanges";
// 7: 定义路由key
String 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();
}
}
}
}
}
路径: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: 从连接中获取通道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();
// new Thread(runnable, "queue5").start();
}
}
主题 模式的具体实现
类型:topic
特点:模糊的Routing key的匹配模式。
理解:
queue1 :
com.#
queue2:*.course.*
queue3:#.order.#
queue4 :#.user.*
网友理解:*代表一个目录名的单词长度可以为0~n。而#代表的是多级目录0~n
个人理解:* 代表有且只能有一个,必须有一个。# 代表0~n,可以有0个或者无数个
路径: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: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = "hello ,topic-exchanges";
// 6:准备交换机
String exchangeName = "topic-exchanges";
// 7: 定义路由key
String 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 collecion
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();
}
}
}
}
}
路径: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: 从连接中获取通道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();
// new Thread(runnable, "queue5").start();
}
}
参数 模式的具体实现
类型:Headers
特点:参数匹配模式
理解:
路径: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: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = "hello ,headers-exchanges";
// 6:准备交换机
String exchangeName = "headers-exchanges";
// 7: 定义路由key
String routeKey = "";
// 8: 指定交换机的类型
String type = "headers";
//9: 消息头
Map<String,Object> headers=new HashMap<>();
headers.put("x","1");
//headers.put("type","pdf");
//10:添加到headers
AMQP.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();
}
}
//13
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
路径: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: 从连接中获取通道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();
// new Thread(runnable, "queue5").start();
}
}
当有多个消费者时,我们的消息会被哪个消费者消费呢,我们又该如何均衡消费者消费信息的多少呢?
主要有两种模式:
轮询模式的分发:一个消费者一条,按均分配
公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配
轮询模式的具体实现
类型:无
特点:该模式接受消息是当有多个消费者接入时,消息的分配模式是一个消费者消费一条,直至消息消费完成
路径: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: 从连接中获取通道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();
}
}
}
}
}
路径: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: 从连接中获取通道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(100);
}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();
}
}
}
}
}
路径: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: 从连接中获取通道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("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();
}
}
}
}
}
public static void main(String[] args) {
前面的倒三角按钮,弹出如下框,点击第一个运行work1 public static void main(String[] args) {
前面的倒三角按钮,弹出如下框,点击第一个运行work2 public static void main(String[] args) {
前面的倒三角按钮,发送消息场景说明:work1 使用了
Thread.sleep(100);
work2使用了Thread.sleep(200);
模拟服务器延时情况。
说明:无论服务器效能怎么样,轮询模式都是一个消费者一条,按均分配
路径: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: 从连接中获取通道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();
}
}
}
}
}
路径: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: 从连接中获取通道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;
// @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 公平分发一定要改成手动应答,把第二个参数是否自动应答 从true改成 false。
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(1000);
// @todo 改成手动应答:单条消费 返回确认消息
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();
}
}
}
}
}
路径: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: 从连接中获取通道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;
// @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 公平分发一定要改成手动应答,把第二个参数是否自动应答 从true改成 false。
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(2000);
// @todo 改成手动应答:单条消费 返回确认状态
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();
}
}
}
}
}
public static void main(String[] args) {
前面的倒三角运行work1和work2work1 阻塞
Thread.sleep(1000);
work2阻塞Thread.sleep(2000);
work2 服务器响应久一点,所以work1服务器接受到消息比较多
能者多劳
路径: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: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 准备发送消息的内容
String message = "你好,交换机";
// 6:准备交换机。取名规范 : 类型_业务模块_交换机
String exchangeName = "direct_order_exchange";
// 7: 定义路由key
String 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: routeKey
channel.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();
}
}
}
}
}
路径: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: 从连接中获取通道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();
new Thread(runnable, "queue5").start();
new Thread(runnable, "queue6").start();
new Thread(runnable, "queue7").start();
}
}
代码
public void makeOrder(){
//1.发送订单
orderService.saveOrder();
//2.发送短信服务
messageService.sendSMS("order");//1-2s
//3.发送email服务
emailService.sendEmail("order");//1-2s
//4.发送app服务
appService.sendApp("order");
}
并行方式:将订单信息写入数据库成功后,发送注册邮件的同时,发送注册短信。以上三个任务完成后,返回给客户端。与串行的差别是,并行的方式可以提高处理的时间
代码
public void makeOrder(){
//1.发送订单
orderService.saveOrder();
// 相关发送
relationMessage();
}
public void relationMessage(){
//异步
theadpool.submit(new Callable<Object>{
public Object call(){
//2.发送短信服务
messageService.sendSMS("order");//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");
}
})
}
存在问题
耦合度高
需要自己写线程池自己维护成本太高
出现了消息可能会丢失,需要你自己做消息补偿
如何保证消息的可靠性你自己写
如果服务器承载不了,你需要自己去写高可用
好处:
- 完全解耦,用 MQ建立桥接
- 有独立的线程池和运行模型
- 出现了消息可能会丢失,MQ有持久化功能
- 如何保证消息的可靠性,死信队列和消息转移等
- 如果服务器承载不了,你需要自己去写高可用,HA镜像模型高可用
- 按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20QPS。比串行提高了3倍,比并行提高了两倍
好处:
- 完全解耦,用 MQ建立桥接
- 有独立的线程池和运行模型
- 出现了消息可能会丢失,MQ有持久化功能
- 如何保证消息的可靠性,死信队列和消息转移等
- 如果服务器承载不了,你需要自己去写高可用,HA镜像模型高可用
- 按照以上约定,用户的响应时间相当于是订单信息写入数据库的时间,也就是50毫秒。注册邮件,发送短信写入消息队列后,直接返回,因此写入消息队列的速度很快,基本可以忽略,因此用户的响应时间可能是50毫秒。因此架构改变后,系统的吞吐量提高到每秒20QPS。比串行提高了3倍,比并行提高了两倍
哔哩哔哩路径:https://www.bilibili.com/video/BV1dX4y1V73G?p=44
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbit-testartifactId>
<scope>testscope>
dependency>
dependencies>
src/main/resources/application.properties
改为src/main/resources/application.yml
并编写配置# 服务器
server:
port: 8080
# rabbitmq配置
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 121.196.153.197 #127.0.0.1
port: 5672
路径: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 {
@Autowired
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来完成消息的分发
//参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
路径: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模式的交换机
@Bean
public 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()); //队列绑定到哪个交换器
}
}
路径: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 {
@Autowired
private OrderService orderService;
@Test
void contextLoads() {
orderService.makeOrder("1","1",12);
}
}
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.amqpgroupId>
<artifactId>spring-rabbit-testartifactId>
<scope>testscope>
dependency>
dependencies>
src/main/resources/application.properties
改为src/main/resources/application.yml
并编写配置# 服务器
server:
port: 8081
# rabbitmq配置
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 121.196.153.197 #127.0.0.1
port: 5672
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("email->fanout 接收到了的订单信息是:" + msg);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("note->fanout 接收到了的订单信息是:" + msg);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("sms->fanout 接收到了的订单信息是:" + msg);
}
}
路径: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 {
@Autowired
private 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);
}
}
注意:
被注册的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模式的交换机
@Bean
public 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
}
}
路径: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 发布订阅模式测试
*/
@Autowired
private OrderService orderService;
@Test
void contextLoads() {
orderService.makeOrder("1","1",12);
}
/**
* direct 路由模式测试
*/
@Test
void contextLoadsDirect() {
orderService.makeOrderDirect("2","1",2);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("email->direct 接收到了的订单信息是:" + msg);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("note->direct 接收到了的订单信息是:" + msg);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("sms->direct 接收到了的订单信息是:" + msg);
}
}
String routingKeySMS = "sms";
String routingKeyEmail = "email";
rabbitTemplate.convertAndSend(exchangeName, routingKeySMS, orderId);
rabbitTemplate.convertAndSend(exchangeName, routingKeyEmail, orderId);
- 路由和发布订阅模式都是在生产者端使用配置文件配置的交换机和队列
- 当前 主题模式在消费者端使用注解完成交换机和队列的配置
推荐使用配置文件配置
,方便管理,建议配置在消费者端
,因为和队列直接打交道
路径: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 {
@Autowired
private 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);
}
}
路径: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 主题模式测试
*/
@Test
void contextLoadsTopic() {
orderService.makeOrderTopic("2","1",2);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("email->topic 接收到了的订单信息是:" + msg);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("note->topic 接收到了的订单信息是:" + msg);
}
}
路径: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 {
@RabbitHandler
public void receiveMessage(String msg) {
System.out.println("sms->topic 接收到了的订单信息是:" + msg);
}
}
https://www.bilibili.com/video/BV1dX4y1V73G?p=44
TTl表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。
RabbitMQ可以对消息和队列设置 TTL,目前有两种方法可以设置
- 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间,过期后会写入死信队列
- 第二种方法是对消息进行单独设置,每条消息 TTL可以不同,过期后不会写入死信队列
如果上述两种方法同时使用,则
消息的过期时间以两者 TTL较小的那个数值为准
。
消息在队列的生存时间一旦超过设置的 TTL值,就称为 dead
message被投递到死信队列,消费者将无法再收到该消息
路径: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 {
@Autowired
private 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);
}
}
路径: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模式的交换机
@Bean
public DirectExchange TtlDirectExchange() {
//参数介绍
//1.交换器名 2.是否持久化 3.自动删除 4.其他参数
return new DirectExchange("ttl_direct_exchange", true, false);
}
//2 :声明队列->设置队列过期时间
@Bean
public 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 :完成绑定关系(把队列和交换机完成绑定关系)
//设置绑定队列过期时间
@Bean
public Binding ttlBingDingDirect() {
return BindingBuilder.bind(TtlDirectQueue()) //绑定队列
.to(TtlDirectExchange()) //队列绑定到哪个交换器
.with("ttl"); //设置路由key
}
}
路径: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 过期时间
*/
@Test
void contextLoadsTtl() {
orderService.makeOrderTTL("2","1",2);
}
}
路径:http://121.196.153.197:15672/#/queues
路径: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 {
@Autowired
private 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() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
//这里就是字符串
message.getMessageProperties().setExpiration("5000");
message.getMessageProperties().setContentEncoding("UTF-8");
return message;
}
};
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId,messagePostProcessor);
}
}
路径: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模式的交换机
@Bean
public DirectExchange TtlDirectMessageExchange() {
//参数介绍
//1.交换器名 2.是否持久化 3.自动删除 4.其他参数
return new DirectExchange("ttlMsg_direct_exchange", true, false);
}
//2 :声明队列->设置消息过期时间
@Bean
public Queue TtlDirectMessageQueue() {
//设置过期时间
return new Queue("ttl.message.direct.queue", true);
}
// 3 :完成绑定关系(把队列和交换机完成绑定关系)
//设置绑定消息过期时间
@Bean
public Binding ttlBingDingDirectMessage() {
return BindingBuilder.bind(TtlDirectMessageQueue())
.to(TtlDirectMessageExchange())
.with("ttl_message");
}
}
路径: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 消息过期时间
*/
@Test
void contextLoadsTtlMessage() {
orderService.makeOrderTTLMessage("2","1",2);
}
}
队列设置了过期时间有个ttl的标签。而消息过期并队列中并没有ttl的标签。
队列过期:放入死信队列
消息过期:不会放入死信队列
DLX,全称
Dead-Letter-Exchange
,可以称之为死信交换机,也有人称之为死信邮箱。
当消息再一个队列中变成死信之后,它能被重新发送到另一个交换机中,这个交换机就是 DLX,绑定 DLX的队列就称之为死信队列。
消息变成死信,可能是由于以下原因:
- 消息被拒绝
- 消息过期
- 队列达到最大长度
DLX也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性,当这个队列中存在死信时,Rabbitmq就会自动地将这个消息重新发布到设置的 DLX上去,进而被路由到另一个队列,即死信队列。
要想使用死信队列,只需要在定义队列的时候设置队列参数
x-dead-letter-exchange
指定交换机即可
路径: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模式的交换机
@Bean
public DirectExchange deadDirectExchange() {
return new DirectExchange("dead_direct_exchange", true, false);
}
//2.声明队列
@Bean
public Queue deadDirectQueue() {
return new Queue("dead.direct.queue", true);
}
@Bean
//3. 绑定
public Binding deadDirectBingDing() {
return BindingBuilder.bind(deadDirectQueue()) //绑定队列
.to(deadDirectExchange()) //队列绑定到哪个交换器
.with("dead");//设置路由key
}
}
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模式的交换机
@Bean
public DirectExchange TtlDirectExchange() {
//参数介绍
//1.交换器名 2.是否持久化 3.自动删除 4.其他参数
return new DirectExchange("ttl_direct_exchange", true, false);
}
//2 :声明队列->设置队列过期时间
@Bean
public 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 :完成绑定关系(把队列和交换机完成绑定关系)
//设置绑定队列过期时间
@Bean
public Binding ttlBingDingDirect() {
return BindingBuilder.bind(TtlDirectQueue()) //绑定队列
.to(TtlDirectExchange()) //队列绑定到哪个交换器
.with("ttl"); //设置路由key
}
}
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 队列过期时间
*/
@Test
void contextLoadsTtl() {
orderService.makeOrderTTL("2","1",2);
}
3 . 查看可视化界面
死信队列有一条数据,过期时间的队列已没有数据
报错:channel error; protocol method: #method
原因:队列和交换机已经创建,但是本地重新修改运行,导致报错。队列修改重新提交不会覆盖线上的队列,只会报错。
解决:如果是线上,不要轻易删除,可以重新命名。本地测试的话可以删除队列和交换机
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模式的交换机
@Bean
public DirectExchange TtlDirectExchange() {
//参数介绍
//1.交换器名 2.是否持久化 3.自动删除 4.其他参数
return new DirectExchange("ttl_direct_exchange", true, false);
}
//2 :声明队列->设置队列过期时间
@Bean
public Queue TtlDirectQueue() {
//设置过期时间
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000);//5秒 这里一定是int类型
args.put("x-max-length", 5);//队列最大长度是5
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 :完成绑定关系(把队列和交换机完成绑定关系)
//设置绑定队列过期时间
@Bean
public Binding ttlBingDingDirect() {
return BindingBuilder.bind(TtlDirectQueue()) //绑定队列
.to(TtlDirectExchange()) //队列绑定到哪个交换器
.with("ttl"); //设置路由key
}
}
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 队列过期时间
*/
@Test
void contextLoadsTtl() {
//测试进入队列为11条
for (int i = 0; i < 11; i++) {
orderService.makeOrderTTL("2", "1", 2);
}
}
args.put("x-max-length", 5);//队列最大长度是5
,发送请求11次,当前队列存在五条,死信队列存在6条
又因为设置 args.put("x-message-ttl", 5000);//5秒 这里一定是int类型
过期时间为5秒,5秒后所有数据进入死信队列
当内存使用超过配置的阈值或者磁盘空间剩余空间对于配置的阈值时,RabbitMQ会暂时阻塞客户端的连接,并且停止接收从客户端发来的消息,以此避免服务器的崩溃,客户端与服务端的心态检测机制也会失效。如下图:
当出现blocking或blocked话说明到达了阈值和以及高负荷运行了
参考帮助文档:https://www.rabbitmq.com/configure.html
当出现警告的时候,可以通过配置去修改和调整
两者选其一就可以,没必要两个都设置
# 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 -m
total 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
如下图 :
- 127MiB :RabbitMQ当前已经使用了127MiB MB内存
- 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 ...
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
在磁盘使用空间一栏中,存在2个参数,分别是:33GiB、48MiB low watermark
- 33GiB :当前剩余可用磁盘空间
- 48MiB low watermark:剩余可用磁盘空间低于48mb将触发磁盘预警
注意: 与内存空间不同的是,磁盘预警的触发机制是:低于下限模式<48MiB>,而内存预警是,高于上限模式<735MiB>
RabbitMQ 默认的磁盘下限配置是50 MB,图中显示48mb,是因为有损耗,实际是50默认
当磁盘剩余空间低于确定的阀值(默认50MB)时,RabbitMQ同样会阻塞生产者,目的是避免填充整个磁盘,导致Rabbit中断,可避免因非持久化的消息持续换页而耗尽磁盘空间导致服务器奔溃。
这个阀值可以减小,但不能完全消除因磁盘耗尽而导致奔溃的可能性,比如在两次磁盘空间检查空隙内,第一次检查时:60MB,第二次检查可能就是1MB,就会出现警告
阀值和具体值都是针对 " 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
注意:以上两种方式二选一即可
位置:/etc/rabbitmq/rabbitmq.conf
# 设置阀值:建议取值在1.0~2.0之间
disk_free_limit.relative=2.0
# 设置具体值:单位为:KB、MB、GB
disk_free_limit.absolute=2GB
注意:两种方式二选一即可
磁盘空间远远大于内存空间,因此需要进行资源的置换,不可能等到内存空间触及达到0.4阀值的时候,再把消息写入到磁盘中去
在某个 Broker 节点及内存阻塞生产者之前,它会尝试将队列中的消息,换页到磁盘以释放内存空间,持久化和非持久化的消息都会写入磁盘,其中持久化的消息本身就在磁盘中有一个副本,所以在转移的过程中持久化的消息会先从内存中清除掉。
结论:为了避免内存空间爆满 和 消息的持久化,会将内存中的消息定时写入到磁盘中去。
默认情况下,内存达到50% 的阈值时就会换页处理,
也就是说,在默认情况下该内存的阈值是 0.4 的情况下,当内存超过 0.2 时,就会进行换页动作。比如:
电脑1000MB 内存,内存阀值为0.4,配置的换页阀值是0.5,
rabbit 最大能够使用的内存空间是1000*0.4=400MB,由于配置的换页内存为 0.5,
因此使用率在达到极限400MB之前,会把内存中的 200MB 进行转移到磁盘中,从而达到稳健的运行。
vm_memory_high_watermark.ralative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(设置值小于1)
思考:为什么设置小于1的值,如果设置为1,内部都已经达到极限了,再去换页意义不是很大了
RabbitMO这款消息队列中间件产品本身是基于Erlana编写,Erlana语言天生具备分布式特性(通过同步Erlana集群各节点的maic cookie来实现)。
因此,RabbitMQ天然支持Clustering。
这使得RabbitMQ本身不需要像ActiveMQ. Kafka那样通过ZooKeeper分别来实现HA方案和保存集群的元数据。
集群是保证可靠性的一种方式,同时可以通过水平扩展以达到增加消息吞叶量能力的目的。
在实际使用过程中多采取多机多实例部署方式,为了便干同学们练习搭建,有时候你不得不在一台机器上去搭建一个rabbitmq集群,本章主要针对单机多实例这种方式来进行开展。
主要参考官方文档:https://www.rabbitmq.com/clustering.html
配置的前提是你的 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
端口号配置 :
-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
端口号配置 :
-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
端口号配置 :
-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
开放所需要的端口号
阿里云安全组配置:https://account.aliyun.com/login/login.htm
启动容器成功后,读者可以访问
分别通过浏览器访问:ip(自己的IP地址)ip:15672;ip:15673;ip:15674
都可以访问
进入容器
docker exec -it rabbit01 /bin/bash
操作rabbitmq,执行如下命令:
# 停止应用
rabbitmqctl stop_app
# 重置:目的清除节点上的历史数据,如果不清除无法将节点加入集群
rabbitmqctl reset
# 启动应用
rabbitmqctl start_app
# 退出
exit
进入容器
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
进入容器
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