课程笔记:消息队列-RabbitMQ篇章- 专栏 -KuangStudy
一、安装
1、安装MQ
1、普通安装
[root@localhost ~]# docker search rabbitMq # 搜索 MQ 版本
[root@localhost ~]# docker pull rabbitmq # 下载 MQ
[root@localhost ~]# docker run -d --hostname my-rabbit --name rabbit -p 15672:15672 -p 5673:5672 rabbitmq # 运行 MQ
[root@localhost ~]# docker ps -a # 查看 MQ 容器ID
[root@localhost ~]# docker exec -it 容器id /bin/bssh # 进入 MQ 内部
root@my-rabbit:/# rabbitmq-plugins enable rabbitmq_management # 安装 MQ 图形化界面
[root@localhost ~]# curl localhost:15672 # 测试是否安装成功
2、直接运行
# —hostname:指定容器主机名称
# —name:指定容器名称
# -p:将mq端口号映射到本地
# 或者运行时设置用户和密码
docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
# 查看日志
docker logs -f myrabbit
> more xxx.log 查看日记信息
> netstat -naop | grep 5672 查看端口是否被占用
> ps -ef | grep 5672 查看进程
> systemctl stop 服务
2、添加用户
需要在 MQ 内部进行操作:直接运行可以忽略这步
root@my-rabbit:/# rabbitmqctl add_user admin 123456 # 添加 admin 用户
root@my-rabbit:/# rabbitmqctl set_user_tags admin administrator # 授予操作权限
root@my-rabbit:/# rabbitmqctl set_permissions -p / admin ".*" ".*" ".*" # 授予资源权限
root@my-rabbit:/# rabbitmqctl change_password admin admin # 修改密码
root@my-rabbit:/# rabbitmqctl delete_user admin 删除用户
root@my-rabbit:/# rabbitmqctl list_users # 查看用户列表
二、入门案例
一、Simple 简单模式<Hello RabbitMQ>
1、引入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.10.0</version>
</dependency>
2、创建结构包
3、生产者代码
package org.example.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 所有中间件 都是遵循 tcp 协议 而 mq 是遵循 amqp
// 1:创建链接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
// ip port
connectionFactory.setHost("192.168.1.7");
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 发送消息 和 接收消息
String queueName = "queue1";
/*
* @params 01 队列的每次
* @params 02 是否要持久化 durable = false 消息是否存盘 true 不持久化 false 持久化 | 非持久化会存盘吗?
* @params 03 排他性 是否是独占队列
* @params 04 是否自动删除 随着最后一个消费者消费完是否把队列删除
* @params 05 携带附加参数
*/
channel.queueDeclare(queueName, false, false, false, null);
// 5:准备消息内容
String message = "hello weilekaixin!!!";
// 6:发送消息给队列 queue
// @params 01 交换机 @params 02 队列、路由 key @params 03 消息是否持久化 @params 04 消息的内容
// TODO : 面试题 : 可以存在没有交换机的队列吗? 不可能,虽然没有指定交换机但是一定会存在一个默认的交换机
channel.basicPublish("", queueName, null, message.getBytes());
System.out.println("消息发送成功");
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
} finally {
// 7:关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
// 8:关闭链接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
4、消费者代码
package org.example.rabbitmq.simple;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) {
// 所有中间件 都是遵循 tcp 协议 而 mq 是遵循 amqp
// 1:创建链接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
// ip port
connectionFactory.setHost("192.168.1.7");
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();
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到消息是" + new String(message.getBody()));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接受失败了...");
}
});
System.out.println("开始接受消息");
System.in.read();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
} finally {
// 7:关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
// 8:关闭链接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
5、测试
6、总结
二、fanout 发布订阅模式
1、生产者代码
package org.example.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 所有中间件 都是遵循 tcp 协议 而 mq 是遵循 amqp
// 1:创建链接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
// ip port
connectionFactory.setHost("192.168.1.7");
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 发送消息 和 接收消息
String queueName = "queue1";
/*
* @params 01 队列的每次
* @params 02 是否要持久化 durable = false 消息是否存盘 true 不持久化 false 持久化 | 非持久化会存盘吗?
* @params 03 排他性 是否是独占队列
* @params 04 是否自动删除 随着最后一个消费者消费完是否把队列删除
* @params 05 携带附加参数
*/
channel.queueDeclare(queueName, false, false, false, null);
// 5:准备消息内容
String message = "hello weilekaixin!!!";
// 6:准备交换机
String exchangeName = "fanout-exchange";
// 7:定义路由key
String routeKey = "";
// 8:指定交换机类型
String type = "fanout";
// 9:发送消息给队列 queue
// @params 01 交换机 @params 02 队列、路由 key @params 03 消息是否持久化 @params 04 消息的内容
// TODO : 面试题 : 可以存在没有交换机的队列吗? 不可能,虽然没有指定交换机但是一定会存在一个默认的交换机
channel.basicPublish(exchangeName, routeKey, null, message.getBytes());
System.out.println("消息发送成功");
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
} finally {
// 10:关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
// 11:关闭链接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
2、消费者代码
package org.example.rabbitmq.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
private static Runnable runnable = new Runnable() {
@Override
public void run() {
// 所有中间件 都是遵循 tcp 协议 而 mq 是遵循 amqp
// 1:创建链接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
// ip port
connectionFactory.setHost("192.168.1.7");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 2:创建连接 Connection
connection = connectionFactory.newConnection("生产者");
// 3:通过链接获取通道 Channel
channel = connection.createChannel();
channel.basicConsume(queueName, true, new DeliverCallback() {
@Override
public void handle(String s, Delivery message) throws IOException {
System.out.println("收到消息是" + new String(message.getBody()));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接受失败了...");
}
});
System.out.println(queueName + "开始接受消息");
System.in.read();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
} finally {
// 7:关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
// 8:关闭链接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
};
public static void main(String[] args) {
new Thread(runnable, "queue1").start();
new Thread(runnable, "queue2").start();
new Thread(runnable, "queue3").start();
}
}
3、绑定关系
4、测试
5、生产者发送消息
6、消费者接受消息
三、direct 路由模式
1、生产者代码
package org.example.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 所有中间件 都是遵循 tcp 协议 而 mq 是遵循 amqp
// 1:创建链接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
// ip port
connectionFactory.setHost("192.168.1.7");
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 发送消息 和 接收消息
String queueName = "queue1";
/*
* @params 01 队列的每次
* @params 02 是否要持久化 durable = false 消息是否存盘 true 不持久化 false 持久化 | 非持久化会存盘吗?
* @params 03 排他性 是否是独占队列
* @params 04 是否自动删除 随着最后一个消费者消费完是否把队列删除
* @params 05 携带附加参数
*/
channel.queueDeclare(queueName, false, false, false, null);
// 5:准备消息内容
String message = "hello weilekaixin!!!";
// 6:准备交换机
String exchangeName = "direct-exchange";
// 7:定义路由key
String routeKey = "email";
// 8:指定交换机类型
String type = "direct";
// 9:发送消息给队列 queue
// @params 01 交换机 @params 02 队列、路由 key @params 03 消息是否持久化 @params 04 消息的内容
// TODO : 面试题 : 可以存在没有交换机的队列吗? 不可能,虽然没有指定交换机但是一定会存在一个默认的交换机
channel.basicPublish(exchangeName, routeKey, null, message.getBytes());
System.out.println("消息发送成功");
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
} finally {
// 10:关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
// 11:关闭链接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
2、绑定关系
3、生产者发送消息
4、消费者匹配路由key接收消息
四、topic 主题模式
1、生产者代码
package org.example.rabbitmq.routing;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 所有中间件 都是遵循 tcp 协议 而 mq 是遵循 amqp
// 1:创建链接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
// ip port
connectionFactory.setHost("192.168.1.7");
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 发送消息 和 接收消息
String queueName = "queue1";
/*
* @params 01 队列的每次
* @params 02 是否要持久化 durable = false 消息是否存盘 true 不持久化 false 持久化 | 非持久化会存盘吗?
* @params 03 排他性 是否是独占队列
* @params 04 是否自动删除 随着最后一个消费者消费完是否把队列删除
* @params 05 携带附加参数
*/
channel.queueDeclare(queueName, false, false, false, null);
// 5:准备消息内容
String message = "hello weilekaixin!!!";
// 6:准备交换机
String exchangeName = "topic-exchange";
// 7:定义路由key
String routeKey = "com.order.test.xxx";
// 8:指定交换机类型
String type = "topic";
// 9:发送消息给队列 queue
// @params 01 交换机 @params 02 队列、路由 key @params 03 消息是否持久化 @params 04 消息的内容
// TODO : 面试题 : 可以存在没有交换机的队列吗? 不可能,虽然没有指定交换机但是一定会存在一个默认的交换机
channel.basicPublish(exchangeName, routeKey, null, message.getBytes());
System.out.println("消息发送成功");
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
} finally {
// 10:关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
// 11:关闭链接
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}
2、绑定关系
3、生产者发送消息
4、消费者接受消息
五、Work 工作模式
1、轮询分发
1、生产者代码
package org.example.rabbitmq.work.lunxun;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Producer {
public static void main(String[] args) {
// 1: 创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2: 设置连接属性
connectionFactory.setHost("192.168.1.7");
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 = "weilekaixin:" + i;
// 7: 发送消息给中间件rabbitmq-server
// @params1: 交换机exchange
// @params2: 队列名称/routingkey
// @params3: 属性配置
// @params4: 发送消息的内容
channel.basicPublish("", "queue1", null, msg.getBytes());
}
System.out.println("消息发送成功!");
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
2、消费者1
package org.example.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("192.168.1.7");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work1");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
// channel.queueDeclare("queue1", false, false, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work1-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
3、消费者2
package org.example.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("192.168.1.7");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work2");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, true, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
//channel.basicQos(1);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work2-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
4、测试
生产者发送消息
消费者1
消费者2
2、公平分发
必须手动应答
1、消费者1
package org.example.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("192.168.1.7");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work1");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
// channel.queueDeclare("queue1", false, false, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try {
System.out.println("Work1-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(2000);
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work1-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
2、消费者2
package org.example.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("192.168.1.7");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/");
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
Connection connection = null;
Channel channel = null;
try {
// 3: 从连接工厂中获取连接
connection = connectionFactory.newConnection("消费者-Work2");
// 4: 从连接中获取通道channel
channel = connection.createChannel();
// 5: 申明队列queue存储消息
/*
* 如果队列不存在,则会创建
* Rabbitmq不允许创建两个相同的队列名称,否则会报错。
*
* @params1: queue 队列的名称
* @params2: durable 队列是否持久化
* @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
* @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除消息。
* @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
* */
// 这里如果queue已经被创建过一次了,可以不需要定义
//channel.queueDeclare("queue1", false, true, false, null);
// 同一时刻,服务器只会推送一条消息给消费者
//channel.basicQos(1);
// 6: 定义接受消息的回调
Channel finalChannel = channel;
finalChannel.basicQos(1);
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
try{
System.out.println("Work2-收到消息是:" + new String(delivery.getBody(), "UTF-8"));
Thread.sleep(200);
finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}catch(Exception ex){
ex.printStackTrace();
}
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
System.out.println("Work2-开始接受消息");
System.in.read();
} catch (Exception ex) {
ex.printStackTrace();
System.out.println("发送消息出现异常...");
} finally {
// 7: 释放连接关闭通道
if (channel != null && channel.isOpen()) {
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
if (connection != null && connection.isOpen()) {
try {
connection.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
}
3、生产者发送消息
消费者1消费消息
4、消费者2消费消息
三、应用场景
linsten to me feifei
解耦、削峰、异步
01-1、同步异步的问题(串行)
串行代码
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
// 2: 发送短信服务
messageService.sendSMS("order");//1-2 s
// 3: 发送email服务
emailService.sendEmail("order");//1-2 s
// 4: 发送APP服务
appService.sendApp("order");
}
01-2、并行方式 异步线程池
异步代码
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
// 相关发送
relationMessage();
}
public void relationMessage(){
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 2: 发送短信服务
messageService.sendSMS("order");
}
})
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 3: 发送email服务
emailService.sendEmail("order");
}
})
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 4: 发送短信服务
appService.sendApp("order");
}
})
// 异步
theadpool.submit(new Callable<Object>{
public Object call(){
// 4: 发送短信服务
appService.sendApp("order");
}
})
}
01-2、异步消息队列的方式
public void makeOrder(){
// 1 :保存订单
orderService.saveOrder();
rabbitTemplate.convertSend("ex","2","消息内容");
}
四、SpringBoot 案例
1、初始化项目
1、创建SpringBoot项目
2、选择SpringWeb 和 Spring or RabbitMQ
2、配置yml
# 服务端口
server:
port: 8080
# 配置rabbitmq服务
spring:
rabbitmq:
username: admin
password: admin
virtual-host: /
host: 192.168.1.7
port: 5672
一、fanout 模式
1、配置config
package com.example.producer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMqConfiguration {
// 1.生命注册 fanout 模式交换机
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("fanout_order_exchange", true, false);
}
// 2.声明队列 sms.fanout.queue.email.fanout.queue.duanxin.fanout.queue
@Bean
public Queue smsQueue() {
return new Queue("sms.fanout.queue", true);
}
@Bean
public Queue duanxinQueue() {
return new Queue("duanxin.fanout.queue", true);
}
@Bean
public Queue emailQueue() {
return new Queue("email.fanout.queue", true);
}
@Bean
public Binding smsBinding() {
return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
}
@Bean
public Binding emailBinding() {
return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
}
@Bean
public Binding duanxinBinding() {
return BindingBuilder.bind(duanxinQueue()).to(fanoutExchange());
}
}
2、Order 接口
package com.example.producer.service;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import javax.annotation.Resource;
import java.util.UUID;
public class OrderService {
@Resource
private RabbitTemplate rabbitTemplate;
public void makeOrder(String userid, String productid, int num) {
// 1.根据商品ID查询库存是否充足
// 2.保存订单
String orderId = UUID.randomUUID().toString();
System.out.println("订单生成成功" + orderId);
// 3.通过MQ来完成消息分发
// 参数1:交换机 参数2:路由key/queue队列名 参数3:消息内容
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
}
3、生产者创建订单
4、创建消费者
5、消费者代码
package com.example.consumer.service.fanout;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = {"duanxin.fanout.queue"})
public class FanoutDuanxinConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("duanxin fanout --- 接收到了订单信息是:->" + message);
}
}
package com.example.consumer.service.fanout;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = {"email.fanout.queue"})
public class FanoutEmailConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("email fanout --- 接收到了订单信息是:->" + message);
}
}
package com.example.consumer.service.fanout;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = {"sms.fanout.queue"})
public class FanoutSMSConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("SMS fanout --- 接收到了订单信息是:->" + message);
}
}
6、消费者消费信息
二、Direct 模式
1、配置类代码
package com.example.producer.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DirectRabbitMqConfiguration {
// 1.生命注册 direct 模式交换机
@Bean
public DirectExchange directExchange() {
return new DirectExchange("direct_order_exchange", true, false);
}
// 2.声明队列 sms.direct.queue.email.direct.queue.duanxin.direct.queue
@Bean
public Queue directSmsQueue() {
return new Queue("sms.direct.queue", true);
}
@Bean
public Queue directDuanxinQueue() {
return new Queue("duanxin.direct.queue", true);
}
@Bean
public Queue directEmailQueue() {
return new Queue("email.direct.queue", true);
}
@Bean
public Binding directSmsBinding() {
return BindingBuilder.bind(directSmsQueue()).to(directExchange()).with("sms");
}
@Bean
public Binding directEmailBinding() {
return BindingBuilder.bind(directEmailQueue()).to(directExchange()).with("email");
}
@Bean
public Binding directDuanxinBinding() {
return BindingBuilder.bind(directDuanxinQueue()).to(directExchange()).with("duanxin");
}
}
2、接口
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";
rabbitTemplate.convertAndSend(exchangeName, "email", orderId);
rabbitTemplate.convertAndSend(exchangeName, "duanxin", orderId);
}
3、消费者代码
package com.example.consumer.service.direct;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = {"duanxin.direct.queue"})
public class DirectDuanxinConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("duanxin direct --- 接收到了订单信息是:->" + message);
}
}
package com.example.consumer.service.direct;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = {"email.direct.queue"})
public class DirectEmailConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("email direct --- 接收到了订单信息是:->" + message);
}
}
package com.example.consumer.service.direct;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(queues = {"sms.direct.queue"})
public class DirectSMSConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("SMS direct --- 接收到了订单信息是:->" + message);
}
}
4、测试
5、面试题
问:绑定关系配置在生产者好还是在消费者好
答:都可以 最好在消费者 因为消费要跟队列打交道
三、topic 模式
1、消费者代码
package com.example.consumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "duanxin.topic.queue", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "#.duanxin.#"
))
public class TopicDuanxinConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("duanxin topic --- 接收到了订单信息是:->" + message);
}
}
package com.example.consumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "email.topic.queue", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "*.email.#"
))
public class TopicEmailConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("email topic --- 接收到了订单信息是:->" + message);
}
}
package com.example.consumer.service.topic;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.stereotype.Service;
@Service
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "sms.topic.queue", durable = "true", autoDelete = "false"),
exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
key = "com.#"
))
public class TopicSMSConsumer {
@RabbitHandler
public void receiveMessage(String message) {
System.out.println("SMS topic --- 接收到了订单信息是:->" + message);
}
}
2、接口
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";
// #.duanxin.# duanxin
// *.email.# email
// com.# sms
String routingKey = "com.email.duanxin";
rabbitTemplate.convertAndSend(exchangeName, routingKey, orderId);
}
3、测试
五、RabbitMQ 高级
一、TTL 过期时间
1、消费者配置
package com.example.consumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class TTLRabbitMqConfiguration {
// 1.生命注册 direct 模式交换机
@Bean
public DirectExchange ttlExchange() {
return new DirectExchange("ttl_direct_exchange", true, false);
}
// 2.声明队列 sms.direct.queue.email.direct.queue.duanxin.direct.queue
@Bean
public Queue ttlQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000); // 这里一定是个 int 类型
return new Queue("ttl.direct.queue", true, false, false, args);
}
@Bean
public Binding ttlBindings() {
return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("sms");
}
}
2、 生产者接口
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);
}
3、测试
二、死信队列
- 消息被拒绝
- 消息过期
- 队列达到最大长度
1、TTL代码配置传参
@Bean
public Queue ttlQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-message-ttl", 5000); // 这里一定是个 int 类型
args.put("x-dead-letter-exchange", "dead_direct_exchange");
args.put("x-dead-letter-routing-key", "dead"); // fanout 不需要配
return new Queue("ttl.direct.queue", true, false, false, args);
}
2、死信队列配置
package com.example.consumer.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DeadRabbitMqConfiguration {
// 1.生命注册 direct 模式交换机
@Bean
public DirectExchange deadExchange() {
return new DirectExchange("dead_direct_exchange", true, false);
}
// 2.声明队列 sms.direct.queue.email.direct.queue.duanxin.direct.queue
@Bean
public Queue deadQueue() {
return new Queue("dead.direct.queue", true);
}
@Bean
public Binding deadBindings() {
return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("sms");
}
}
3、测试
4、总结
可靠消费机制 类似回收站