六、RabbitMQ 入门案例
上一篇文章《AMQP协议》
【注】实现方式是:代码 + web图形界面
6.1 Simple 模式
实现步骤
- jdk1.8
- 构建一个maven工程
- 导入 rabbitmq 的 maven 依赖
- 启动 rabbitmq-server 服务
- 定义生产者
- 定义消费者
- 观察消息在 rabbitmq-server 服务中的过程
代码实现
1)创建一个maven工程
2)导入依赖
<!--RabbitMQ-->
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
</dependencies>
3)启动 rabbitmq-server 服务
4)定义生产者
package com.vinjcent.rabbitmq.simple;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3.通过连接获取通道Channel
channel = connection.createChannel();
// 4.通过创建交换机、声明队列、绑定关系,路由key,发送消息和接收消息
String queueName = "queue1";
/*
* @param1 队列的名字
* @param2 是否需要持久化durable,所谓的持久化消息就是是否存盘
* @param3 排他性,是否独占队列
* @param4 是否自动删除,随着最后一个消费者消息完毕,消息以后是否把队列自动删除
* @param5 携带一些附加参数
*
*/
channel.queueDeclare(queueName, false,false,false,null);
// 5.准备消息内容
String message = "hello world";
// 6.发送消息给队列queue
channel.basicPublish("", queueName,null,message.getBytes());
} catch (Exception e) {
// 7.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 8.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
5)定义消费者
package com.vinjcent.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/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3.通过连接获取通道Channel
channel = connection.createChannel();
// 4.接收queue1队列的消息
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("收到的消息是" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 5.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 6.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
运行生产者
【注】如果没有达到下图的效果,请继续往下看
消息生产失败问题解决
用户权限问题
配置好后重新运行Producer即可
运行消费者
接受完消息之后,会发现有两个正在连接着broker
通过web界面实现
界面模拟生产者投递消息
-
创建一个queue2队列
-
选择默认交换机进行消息生产
- 查看queue2队列中的消息
- 接收队列queue2的消息
小结
-
持久化队列会存盘;非持久化队列也会存盘,但是会随着重启服务丢失
-
如果没有声明交换机,那么该交换机是一个默认的交换机
-
发送消息一定是交换机进行发送,而不是队列去发送,交换机接收消息后会推送到队列,所有的消费者会监听和订阅队列,交换机就会推送至消费者
6.2 Fanout 模式
方式一:通过界面实现
根据队列绑定交换机
根据交换机绑定队列
通过上面的例子说明,队列与交换机的绑定在可视化界面中有两种绑定
- 通过队列绑定已存在的交换机
- 通过交换机绑定已存在的队列
进入queue3
队列查看绑定信息
进入交换机查看绑定的队列
在fanout-exchange交换机中发布消息
在queue2队列中获取消息
方式二:通过代码实现
将队列与交换机进行绑定
运行生产者
package com.vinjcent.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/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接属性
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 3.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 4.通过连接获取通道Channel
channel = connection.createChannel();
// 5.准备消息内容
String message = "hello world";
// 6.准备交换机
String exchangeName = "fanout-exchange";
// 7.定义路由key
String routeKey = "";
// 8.指定交换机类型
String type = "fanout";
// 9.发送消息给中间件rabbitmq-server
/*
* @param1 交换机
* @param2 队列、路由key
* @param3 属性配置
* @param4 消息内容
* 可以存在没有交换机的队列码?不可能,虽然没有指定交换机,但是一定会存在一个默认的交换机
*/
channel.basicPublish(exchangeName, routeKey,null,message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
// 10.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 11.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
运行消费者
package com.vinjcent.rabbitmq.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) {
// 使用多线程实时监听
new Thread(runnable,"queue1").start();
new Thread(runnable,"queue2").start();
new Thread(runnable,"queue3").start();
}
private static Runnable runnable = new Runnable() {
@Override
public void run() {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接属性
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
// 获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3.从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4.通过连接获取通道Channel
channel = connection.createChannel();
// 5.定义接收消息的回调
channel.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("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 6.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 7.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
};
}
删除fanout交换机绑定关系,以及清空队列中的消息
6.3 Direct 模式
方式一:通过界面实现
方式二:通过代码实现
运行生产者
运行消费者
package com.vinjcent.rabbitmq.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) {
// 使用多线程实时监听
new Thread(runnable,"queue1").start();
new Thread(runnable,"queue2").start();
new Thread(runnable,"queue3").start();
}
private static Runnable runnable = new Runnable() {
@Override
public void run() {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接属性
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
// 获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3.从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4.通过连接获取通道Channel
channel = connection.createChannel();
// 5.定义接收消息的回调
channel.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("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 6.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 7.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
};
}
小结:使用 Direct 模式有一定的性能损耗,因为需要经过路由key过滤
【注】fanout模式下指定路由key是没有效果的,默认交换机是 Direct 模式
6.4 Topic 模式理解
方式一:通过界面实现
方式二:通过代码实现
运行生产者
运行消费者
package com.vinjcent.rabbitmq.routing;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
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();
}
private static Runnable runnable = new Runnable() {
@Override
public void run() {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接属性
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
// 获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3.从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4.通过连接获取通道Channel
channel = connection.createChannel();
// 5.定义接收消息的回调
channel.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"));
}
}, s -> System.out.println("接收消息失败!"));
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 6.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 7.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
};
}
小结:
#
:代表匹配多个词,必须要填写*
:代表匹配一个词,可以不需填写
6.5 Headers 模式
带有响应头参数发送消息
6.6 完整的声明创建方式
- 观察web界面的队列和交换机
查看队列
查看交换机
- 编写创建队列和交换机代码的生产者,并运行
package com.vinjcent.rabbitmq.all;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
public class Producer {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接属性
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 3.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 4.通过连接获取通道Channel
channel = connection.createChannel();
// 5.准备消息内容
String message = "hello world";
// 6.准备交换机
String exchangeName = "direct_message_exchange";
// 7.指定交换机类型
String exchangeType = "direct";
// 声明交换机所谓的持久化就是指,交换机不会随着服务器重启造成丢失,如果true代表不丢失,false重启就会丢失
channel.exchangeDeclare(exchangeName, exchangeType,true);
// 8.声明队列
channel.queueDeclare("queue5", true,false,false,null);
channel.queueDeclare("queue6", true,false,false,null);
channel.queueDeclare("queue7", true,false,false,null);
// 9.绑定队列与交换机的关系
channel.queueBind("queue5",exchangeName,"order");
channel.queueBind("queue6",exchangeName,"stock");
channel.queueBind("queue7",exchangeName,"order");
// 10.发送消息给中间件rabbitmq-server
/*
* @param1 交换机
* @param2 队列、路由key
* @param3 属性配置
* @param4 消息内容
* 可以存在没有交换机的队列码?不可能,虽然没有指定交换机,但是一定会存在一个默认的交换机
*/
channel.basicPublish(exchangeName, "order",null,message.getBytes());
System.out.println("消息发送成功!");
} catch (Exception e) {
// 11.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (Exception ex) {
ex.printStackTrace();
}
}
// 12.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
- 查看web界面
- 测试消费者接收
package com.vinjcent.rabbitmq.all;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer {
public static void main(String[] args) {
// 使用多线程实时监听
new Thread(runnable,"queue5").start();
new Thread(runnable,"queue6").start();
new Thread(runnable,"queue7").start();
}
private static Runnable runnable = new Runnable() {
@Override
public void run() {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
// 2.设置连接属性
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
// 获取队列的名称
final String queueName = Thread.currentThread().getName();
Connection connection = null;
Channel channel = null;
try {
// 3.从连接工厂中获取连接
connection = connectionFactory.newConnection("生产者");
// 4.通过连接获取通道Channel
channel = connection.createChannel();
// 5.定义接收消息的回调
channel.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("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 6.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 7.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
};
}
小结
如果队列没有指定的交换机,会选择默认交换机,默认交换机是 direct 模式;当消费者接收队列里的消息时,如果队列不存在,则会抛出异常
6.7 Work 轮询模式
当有多个消费者时,我们的消息会被哪个消费者接收呢?我们又该如何均衡消费者消费信息的多少呢?
主要有两种模式
- 轮询模式:一个消费者一条消息,按均分配
- 公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配
Work 模式轮询模式(Round-Robin)
特点:该模式接收消息是当有多个消费者接入时,消费分配模式是一个消费者分配一条消息,直至消息消费完成
生产者
package com.vinjcent.rabbitmq.work.polling;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2:创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3:通过连接获取通道Channel
channel = connection.createChannel();
// 4:通过创建交换机、声明队列、绑定关系,路由key,发送消息和接收消息
String queueName = "queue1";
for (int i = 0; i < 20; i++) {
// 5:准备消息内容
String message = "hello world" + i;
// 6:发送消息给队列queue
/*
* @param1 交换机
* @param2 队列、路由key
* @param3 消息的状态控制
* @param4 消息内容
* 可以存在没有交换机的队列码?不可能,虽然没有指定交换机,但是一定会存在一个默认的交换机
*/
channel.basicPublish("", queueName,null,message.getBytes());
}
} catch (Exception e) {
// 7:关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 8:关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
两个消费者 Work1 和 Work2
// ===============================Work1===============================
package com.vinjcent.rabbitmq.work.polling;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Work1 {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3.通过连接获取通道Channel
channel = connection.createChannel();
// 4.通过创建交换机、声明队列、绑定关系,路由key,发送消息和接收消息
// 6.接收queue1队列的消息
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("Work1-收到的消息是" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 7.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 8.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
// ===============================Work2===============================
package com.vinjcent.rabbitmq.work.polling;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Work2 {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3.通过连接获取通道Channel
channel = connection.createChannel();
// 4.通过创建交换机、声明队列、绑定关系,路由key,发送消息和接收消息
// 6.接收queue1队列的消息
channel.basicConsume("queue1", true, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("Work2-收到的消息是" + new String(delivery.getBody(), "UTF-8"));
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 7.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 8.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
- 先启动 Work1 和 Work2 实时监听消息队列
- 再启动 producer
可以看到在生产20条消息的时候,消费者实时监听模式是根据轮询分发的策略进行接收消息的
6.8 Work 公平分发
公平分发
这里的公平分发指的是,根据消费者消费能力为前提,去接收消息队列里的消息
生产者
package com.vinjcent.rabbitmq.work.weigh;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1:创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2:创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3:通过连接获取通道Channel
channel = connection.createChannel();
// 4:通过创建交换机、声明队列、绑定关系,路由key,发送消息和接收消息
String queueName = "queue1";
for (int i = 0; i < 20; i++) {
// 5:准备消息内容
String message = "hello world" + i;
// 6:发送消息给队列queue
/*
* @param1 交换机
* @param2 队列、路由key
* @param3 消息的状态控制
* @param4 消息内容
* 可以存在没有交换机的队列码?不可能,虽然没有指定交换机,但是一定会存在一个默认的交换机
*/
channel.basicPublish("", queueName,null,message.getBytes());
}
} catch (Exception e) {
// 7:关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 8:关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
两个消费者 Work1 和 Work2(这里修改了两者的睡眠时间)
// ===============================Work1===============================
package com.vinjcent.rabbitmq.work.weigh;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Work1 {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3.通过连接获取通道Channel
channel = connection.createChannel();
// 4.定义接收消息的回调
Channel backChannel = channel;
// 6.接收queue1队列的消息
// 指标定义,代表分发消息的单位,尽量不能太高(<20)
backChannel.basicQos(1);
// 公平分发必须手动改成false
backChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("Work1-收到的消息是" + new String(delivery.getBody(), "UTF-8"));
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 单条消费
backChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 7.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 8.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
// ===============================Work2===============================
package com.vinjcent.rabbitmq.work.weigh;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Work2 {
public static void main(String[] args) {
// 所有的中间件技术都是基于tcp/ip协议基础之上构建新型的协议规范,只不过rabbitmq遵循的是amqp
// ip port
// 1.创建连接工程
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.159.100");
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
// 2.创建连接Connections
connection = connectionFactory.newConnection("生产者");
// 3.通过连接获取通道Channel
channel = connection.createChannel();
// 4.定义接收消息的回调
Channel backChannel = channel;
// 6.接收queue1队列的消息
// 指标定义,代表分发消息的单位,尽量不能太高(<20)
backChannel.basicQos(1);
// 公平分发必须手动改成false
backChannel.basicConsume("queue1", false, new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
System.out.println("Work2-收到的消息是" + new String(delivery.getBody(), "UTF-8"));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 单条消费
backChannel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
System.out.println("接收消息失败!");
}
});
System.out.println("开始接收消息");
System.in.read();
} catch (Exception e) {
// 7.关闭连接
if (channel != null && channel.isOpen()){
try {
channel.close();
} catch (IOException ioException) {
ioException.printStackTrace();
} catch (TimeoutException timeoutException) {
timeoutException.printStackTrace();
}
}
// 8.关闭通道
if (connection != null && connection.isOpen()){
try {
connection.close();
} catch (IOException ioException) {
ioException.printStackTrace();
}
}
}
}
}
- 先启动 work1 和 work2 实时监听消息队列
- 再启动 producer
解释
由于设置为根据消费者的接收响应时间,将 work1 消费者设置 2s 接收一次消息,work2 消费者设置 1s 接收一次消息,根据不同消费者接收响应时间,生产者在生产消息之后,推送到队列 queue1 中,根据不同的处理消息时间,能者多劳
6.9 RabbitMQ 使用场景
解耦、削峰、异步
1)同步异步的问题(串行)
代码
public void createOrderFanout(){
// 1.保存订单
orderService.saveOrder();
// 2.账户查询
accountService.getAccount();
// 3.发送email服务
emailService.sendEmailMessage();
// 4.发送app服务
appService.sendAppMessage();
}
2)并行方式,异步线程池
并行方式:将订单信息写入数据库成功后的同时,发送注册短信,以上上个任务完成后,返回客户端。与串行的差别在于:并行的方式可以提高处理的时间
代码
public void createOrderFanout(){
// 1.保存订单
orderService.saveOrder();
relationMessage();
}
public void relationMessage(){
// 异步
threadpool.submit(new Callable<Objcet> {
public Object call(){
// 2.账户查询
accountService.getAccount();
}
});
// 异步
threadpool.submit(new Callable<Objcet> {
public Object call(){
// 3.发送email服务
emailService.sendEmailMessage();
}
});
// 异步
threadpool.submit(new Callable<Objcet> {
public Object call(){
// 4.发送app服务
appService.sendAppMessage();
}
});
}
存在问题
- 耦合度高
- 需要自己写线程池维护成本太高
- 出现了消息可能会丢失,需要自己做消息补偿
- 需要保证消息的可靠性
- 需要保证服务器的高可用
异步消息队列方式
优点
- 完全解耦,用MQ建立桥接
- 有独立的线程池和运行模式
- 出现了消息可能会丢失,MQ有持久化功能
- 死信队列和消息转移等,能够解决可靠性
- HA镜像模型提供高可用
- 服务效率整体提高
高内聚,低耦合
下一篇文章《SpringBoot案例》