RabbitMQ工作模式
RabbitMQ提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
简单模式
模型图如下:
在上图的模型中,有以下概念:
P:生产者,也就是要发送消息的程序
C:消费者:消息的接收者,会一直等待消息到来
queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息
代码案例如下:
生产者实现代码如下:
package com.doudou.rabbitmq.helloword;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
public class Producer {
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtil.getConnection();
// 创建通信“通道”,相当于TCP中的虚拟链接
Channel channel = connection.createChannel();
// 创建队列,申请并创建一个队列,如果队列不存在,则创建这个队列
// param1:队列名称ID
// parm2:是否持久化,false对应不持久化数据,MQ停掉数据就会丢失
// pram3:是否队列私有化,false代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用
// param4:是否自动删除,false代表连接停掉后不自动删除这个队列
// param5:其他额外的参数,null
channel.queueDeclare("helloworld", false, false, false, null);
// 待发消息
String message = "message";
// param1:exchange 交换机,暂时用不到,在后面进行发布订阅时才会用到
// param2:队列名称
// param3:额外的设置属性
// param4:要传递的消息字节数组
channel.basicPublish("", "helloworld", null, message.getBytes(StandardCharsets.UTF_8));
// 关闭连接
channel.close();
connection.close();
System.out.println("====发送成功");
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
代码执行:
管理面可以查看到生产者产生了一条未消费的消息在队列中:
消费者实现代码如下:
package com.doudou.rabbitmq.helloword;
import java.io.IOException;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
public class Consumer {
public static void main(String[] args) {
try {
Connection connection = RabbitMQUtil.getConnection();
Channel consumerChannel = connection.createChannel();
// param1:队列ID
// param2:false代表MQ服务停止,队列删除,true则相反,MQ服务停止,持久化队列数据
// param3:false代表队列不针对消费者,任意消费者均可消费,true代表第一个消费者消费了消息,别的消费者则不能消费该消息了
// param4:true代表MQServer停止服务,队列删除,false代表MQServer停止服务,队列保留
// param5:额外信息
consumerChannel.queueDeclare("helloworld", false, false, false, null);
// 从MQ服务器中获取数据
// param1:队列ID
// param2:是否自动确认收到消息,false代表手动编程来确认消息(MQ推荐做法)
// param3:传入DefaultConsumer的实现类
consumerChannel.basicConsume("helloworld", false, new Receiver(consumerChannel));
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 手动编程来实现消息确认
*/
class Receiver extends DefaultConsumer {
private final Channel channel;
/**
* 重写构造函数,Channel通道对象需要从外部传入,在handleDelivery要用到
*
* @param channel
* channel
*/
public Receiver(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
throws IOException {
String message = new String(body);
System.out.println("消费者接收到的消息" + message);
System.out.println("消息的Tag" + envelope.getDeliveryTag());
// param1:签收的ID
// param2:false代表该消费者只确认当前的消息,true代表该消费者签收所有未签收的消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
代码执行:
可以发现消费者消费后,队列中已经没有未消费的消息了:
RabbitMQUtil工具类代码如下:
package com.doudou.rabbitmq.utils;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RabbitMQUtil {
/**
* 设置连接工厂
*/
private static ConnectionFactory connectionFactory = new ConnectionFactory();
static {
// 设置主机信息
connectionFactory.setHost("192.168.216.129");
// 5672是RabbitMQ默认端口号
connectionFactory.setPort(5672);
connectionFactory.setUsername("admin");
connectionFactory.setPassword("admin");
// RabbitMQ管理面手动创建的virtualHost,意义等同于Mysql中的数据库的地位
connectionFactory.setVirtualHost("virtualHost");
}
public static Connection getConnection() {
// 获取连接
Connection connection = null;
try {
connection = connectionFactory.newConnection();
return connection;
} catch (IOException | TimeoutException e) {
throw new RuntimeException(e);
}
}
}
所需要的maven依赖如下:
<!-- 发端和收端(即客户端)所需依赖 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.3.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
Work queues工作队列模式
模型图如下:
Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
代码编写:
Work Queues 与入门程序的简单模式的代码几乎是一样的。可以完全复制,并多复制一个消费者进行多个消费者同时对消费消息的测试。
订单系统代码:
package com.doudou.rabbitmq.workqueue;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 订单系统
*
* @author doudou
*/
public class OrderSystem {
public static void main(String[] args) {
Connection connection = null;
Channel channel = null;
try {
connection = RabbitMQUtil.getConnection();
channel = connection.createChannel();
channel.queueDeclare(RabbitMQConstant.WORK_QUEUE, false, false, false, null);
for (int i = 0; i < 100; i++) {
SMS note = new SMS("中国铁路", "12306", "第" + i + "条短信");
channel.basicPublish("", RabbitMQConstant.WORK_QUEUE, null, note.getContent().getBytes());
}
System.out.println("发送成功===========");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (channel != null) {
try {
channel.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
短信实体类:
package com.doudou.rabbitmq.workqueue;
public class SMS {
private String name;
private String mobile;
private String content;
public SMS(String name, String mobile, String content) {
this.name = name;
this.mobile = mobile;
this.content = content;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
消费者代码,此处定义了3个消费者,代码类似,仅贴其中一个消费者代码:
package com.doudou.rabbitmq.workqueue;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @description: 短信服务1
* @author: doudou
* @createDate: 2021/3/21 15:57
*/
public class SMSSender1 {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtil.getConnection();
final Channel channel = connection.createChannel();;
channel.queueDeclare(RabbitMQConstant.WORK_QUEUE, false, false, false, null);
// 如果不写basicQos(1),则自动MQ会将所有请求平均发送给所有消费者
// basicQos,MQ不再对消费者一次发送多个请求,而是消费者处理完一个消息后(确认后),在从队列中获取一个新的
// 处理完一个取一个
channel.basicQos(1);
channel.basicConsume(RabbitMQConstant.WORK_QUEUE, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
channel.basicAck(envelope.getDeliveryTag(), false);
System.out.println("SMSSender1 发送" + new String(body));
}
});
}
}
代码执行结果如下:
Pub/Sub 订阅模式(Exchange类型为Fanout)
模型图如下:
在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:
P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
C:消费者,消息的接收者,会一直等待消息到来
Queue:消息队列,接收消息、缓存消息
Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与 Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!
发布/订阅模式使用场景:
例如:
中国气象局提供“天气预报”送入交换机,网易、新浪、百度、搜狐等门户接入通过队列绑定到该交换机,自动获取气象局推送的气象数据。
创建交换机:
生产者代码:
package com.doudou.rabbitmq.pubsub;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @description: WeatherForeCast
* @author: doudou
* @createDate: 2021/3/21 17:30
*/
public class WeatherForeCast {
public static void main(String[] args) throws IOException, TimeoutException {
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
String weather = "多云";
// 交换机必须手动去服务器管理面上创建,否则会出错
channel.basicPublish(RabbitMQConstant.EXCHANGE_WEATHER, "", null, weather.getBytes(StandardCharsets.UTF_8));
channel.close();
connection.close();
}
}
消费者代码:
package com.doudou.rabbitmq.pubsub;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/21 17:33
*/
public class BaiDu {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
// param1:队列名
// param2:是否持久化。true持久化,队列会保存磁盘。服务器重启时可以保证不丢失相关信息。
// param3:是否队列私有化,false代表所有消费者都可以访问,true代表只有第一次拥有它的消费者才能一直使用
// param4:服务停掉,是否自动删除队列
// param5:拓展信息
channel.queueDeclare(RabbitMQConstant.QUEUE_BAIDU, false, false, false, null);
// 交换机与队列绑定
// param1:队列名 param2:交换机名 param3:路由key
channel.queueBind(RabbitMQConstant.QUEUE_BAIDU, RabbitMQConstant.EXCHANGE_WEATHER, "");
// 指该消费者在接收到队列里的消息但没有返回确认结果之前,队列不会将新的消息分发给该消费者。队列中没有被消费的消息不会被删除,还是存在于队列中。
channel.basicQos(1);
channel.basicConsume(RabbitMQConstant.QUEUE_BAIDU, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("百度收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
package com.doudou.rabbitmq.pubsub;
import java.io.IOException;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
/**
* @description: SiNa
* @author: doudou
* @createDate: 2021/3/21 17:33
*/
public class SiNa {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
// param1: 队列名
// param2: 队列是否持久化于磁盘
// param3: 队列是否对消费者私有化
// param4: 队列是否自动删除
// param5: 拓展信息
channel.queueDeclare(RabbitMQConstant.QUEUE_SINA, false, false, false, null);
// 交换机与队列绑定
// param1:队列名 param2:交换机名 param3:路由key
channel.queueBind(RabbitMQConstant.QUEUE_SINA, RabbitMQConstant.EXCHANGE_WEATHER, "");
// 指该消费者在接收到队列里的消息但没有返回确认结果之前,队列不会将新的消息分发给该消费者。队列中没有被消费的消息不会被删除,还是存在于队列中。
channel.basicQos(1);
channel.basicConsume(RabbitMQConstant.QUEUE_SINA, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("新浪收到气象信息:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
执行结果:
Pub/Sub订阅模式小结
- 交换机需要与队列进行绑定,绑定之后;一个消息可以被多个消费者都收到。
- 发布订阅模式与工作队列模式的区别:
2.1 工作队列模式不用定义交换机,而发布/订阅模式需要定义交换机
2.2 发布/订阅模式的生产方是面向交换机发送消息,工作队列模式的生产方是面向队列发送消息(底层使用默认交换机)
2.3 发布/订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑 定到默认的交换机
Routing路由模式(Exchange类型Direct)
模式说明:
-
队列与交换机的绑定,不能是任意绑定了,而是要指定一个 RoutingKey(路由key)
-
消息的发送方在向 Exchange 发送消息时,也必须指定消息的 RoutingKey
-
Exchange 不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列的Routingkey 与消息的 Routing key 完全一致,才会接收到消息
模型图如下:
P:生产者,向 Exchange 发送消息,发送消息时,会指定一个routing key
X:Exchange(交换机),接收生产者的消息,然后把消息递交给与 routing key 完全匹配的队列
C1:消费者,其所在队列指定了需要 routing key 为 error 的消息
C2:消费者,其所在队列指定了需要 routing key 为 info、error、warning 的消息
管理面创建交换机:
生产者代码:
package com.doudou.rabbitmq.routing;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/21 23:03
*/
public class WeatherForeCast {
public static void main(String[] args) throws IOException, TimeoutException {
LinkedHashMap<String, String> area = new LinkedHashMap<>();
area.put("china.hunan.changsha.20201127", "中国湖南长沙20201127天气数据");
area.put("china.hubei.wuhan.20201127", "中国湖北武汉20201127天气数据");
area.put("china.hunan.zhuzhou.20201127", "中国湖南株洲20201128天气数据");
area.put("us.cal.lsj.20201127", "美国加州洛杉矶20201127天气数据");
area.put("china.hebei.shijiazhuang.20201128", "中国河北石家庄20201128天气数据");
area.put("china.hubei.wuhan.20201128", "中国湖北武汉20201128天气数据");
area.put("china.henan.zhengzhou.20201128", "中国河南郑州20201128天气数据");
area.put("us.cal.lsj.20201128", "美国加州洛杉矶20201128天气数据");
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
for (Map.Entry<String, String> entry : area.entrySet()) {
channel.basicPublish(RabbitMQConstant.EXCHANGE_WEATHER_ROUT, entry.getKey(), null,
entry.getValue().getBytes(StandardCharsets.UTF_8));
}
channel.close();
connection.close();
}
}
消费者代码:
package com.doudou.rabbitmq.routing;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/21 23:13
*/
public class BaiDu {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RabbitMQConstant.QUEUE_BAIDU_ROUT, false, false, false, null);
channel.queueBind(RabbitMQConstant.QUEUE_BAIDU_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"china.hunan.changsha.20201127");
channel.queueBind(RabbitMQConstant.QUEUE_BAIDU_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"china.hubei.wuhan.20201127");
channel.queueBind(RabbitMQConstant.QUEUE_BAIDU_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"china.hunan.zhuzhou.20201127");
channel.queueBind(RabbitMQConstant.QUEUE_BAIDU_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"us.cal.lsj.20201127");
channel.basicQos(1);
channel.basicConsume(RabbitMQConstant.QUEUE_BAIDU_ROUT, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("百度:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), true);
}
});
}
}
package com.doudou.rabbitmq.routing;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/21 23:13
*/
public class Sina {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RabbitMQConstant.QUEUE_SINA_ROUT, false, false, false, null);
channel.queueBind(RabbitMQConstant.QUEUE_SINA_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"china.hebei.shijiazhuang.20201128");
channel.queueBind(RabbitMQConstant.QUEUE_SINA_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"china.hubei.wuhan.20201128");
channel.queueBind(RabbitMQConstant.QUEUE_SINA_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"china.henan.zhengzhou.20201128");
channel.queueBind(RabbitMQConstant.QUEUE_SINA_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_ROUT,
"us.cal.lsj.20201128");
channel.basicQos(1);
channel.basicConsume(RabbitMQConstant.QUEUE_SINA_ROUT, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
System.out.println("新浪:" + new String(body));
channel.basicAck(envelope.getDeliveryTag(), true);
}
});
}
}
代码执行结果如下:
Routing路由模式小结
Routing 模式要求队列在绑定交换机时要指定 routing key,消息会转发到符合 routing key 的队列。
Topics 通配符模式(Exchange类型Topic)
模式说明:
- Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型Exchange 可以让队列在绑定 Routing key 的时候使用通配符!
- Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
- 通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert
模型图如下:
图解:
- 红色 Queue:绑定的是 usa.# ,因此凡是以 usa. 开头的 routing key 都会被匹配到
- 黄色 Queue:绑定的是 #.news ,因此凡是以 .news 结尾的 routing key 都会被匹配
创建交换机:
生产者代码:
package com.doudou.rabbitmq.topic;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/22 22:45
*/
public class WeatherForeCast {
public static void main(String[] args) throws IOException, TimeoutException {
LinkedHashMap<String, String> area = new LinkedHashMap<String, String>();
area.put("china.hunan.changsha.20201127", "中国湖南长沙20201127天气数据");
area.put("china.hubei.wuhan.20201127", "中国湖北武汉20201127天气数据");
area.put("china.hunan.zhuzhou.20201127", "中国湖南株洲20201128天气数据");
area.put("us.cal.lsj.20201127", "美国加州洛杉矶20201127天气数据");
area.put("china.hebei.shijiazhuang.20201128", "中国河北石家庄20201128天气数据");
area.put("china.hubei.wuhan.20201128", "中国湖北武汉20201128天气数据");
area.put("china.henan.zhengzhou.20201128", "中国河南郑州20201128天气数据");
area.put("us.cal.lsj.20201128", "美国加州洛杉矶20201128天气数据");
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
for (Map.Entry<String, String> entry : area.entrySet()) {
channel.basicPublish(RabbitMQConstant.EXCHANGE_WEATHER_TOPIC, entry.getKey(), null,
entry.getValue().getBytes());
}
channel.close();
connection.close();
}
}
消费者代码:
package com.doudou.rabbitmq.topic;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/22 22:55
*/
public class BaiDu {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RabbitMQConstant.QUEUE_BAIDU_TOPIC, false, false, false, null);
channel.queueBind(RabbitMQConstant.QUEUE_BAIDU_ROUT, RabbitMQConstant.EXCHANGE_WEATHER_TOPIC, "china.#");
channel.basicQos(1);
channel.basicConsume(RabbitMQConstant.QUEUE_BAIDU_ROUT, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
channel.basicAck(envelope.getDeliveryTag(), true);
System.out.println("百度:" + new String(body));
}
});
}
}
package com.doudou.rabbitmq.topic;
import com.doudou.rabbitmq.constant.RabbitMQConstant;
import com.doudou.rabbitmq.utils.RabbitMQUtil;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/22 23:03
*/
public class Sina {
public static void main(String[] args) throws IOException {
Connection connection = RabbitMQUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(RabbitMQConstant.QUEUE_SINA_TOPIC, false, false, false, null);
channel.queueBind(RabbitMQConstant.QUEUE_SINA_TOPIC, RabbitMQConstant.EXCHANGE_WEATHER_TOPIC, "us.cal.lsj.*");
channel.basicQos(1);
channel.basicConsume(RabbitMQConstant.QUEUE_SINA_TOPIC, false, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
channel.basicAck(envelope.getDeliveryTag(), true);
System.out.println("新浪:" + new String(body));
}
});
}
}
代码执行结果:
Topic通配符模式小结
Topic主题模式可以实现Pub/Sub发布与订阅模式和Routing路由模式的功能,只是Topic在配置routing key的时候可以使用通配符,显得更加灵活。
RabbitMQ的工作模式总结
工作模式 | 总结 |
---|---|
简单模式 HelloWorld | 一个生产者、一个消费者,不需要设置交换机(使用默认的交换机) |
工作队列模式 Work Queue | 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机) |
发布订阅模式 Publish/subscribe | 需要设置类型为fanout的交换机,并且交换机和队列进行绑定, 当发送消息到交换机后,交换机会将消息发送到绑定的队列 |
路由模式 Routing | 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key, 当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列 |
通配符模式 Topic | 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key, 当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列 |
Spring整合RabbitMQ
步骤
生产者
- 创建生产者工程
- 添加依赖
- 配置整合
- 编写代码发送消息
消费者
- 创建消费者工程
- 添加依赖
- 配置整合
- 编写消息监听器
添加相关依赖
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.7.RELEASE</version>
</dependency>
</dependencies>
RabbitMQ Server 配置文件
rabbitmq.host=192.168.216.129
rabbitmq.port=5672
rabbitmq.username=admin
rabbitmq.password=admin
rabbitmq.virtual-host=virtualHost
简单模式
生产者配置:
<!--加载配置文件-->
<context:property-placeholder location="classpath:rabbitmq.properties"/>
<!-- 定义rabbitmq connectionFactory -->
<rabbit:connection-factory id="connectionFactory" host="${rabbitmq.host}"
port="${rabbitmq.port}"
username="${rabbitmq.username}"
password="${rabbitmq.password}"
virtual-host="${rabbitmq.virtual-host}"/>
<!--定义管理交换机、队列-->
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue id="spring_queue" name="spring_queue" auto-declare="true"/>
<!--定义rabbitTemplate对象操作可以在代码中方便发送消息-->
<rabbit:template id="rabbitTemplate" connection-factory="connectionFactory"/>
生产者代码:
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/24 22:04
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring-rabbitmq-producer.xml")
public class ProducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testHelloWorld() {
rabbitTemplate.convertAndSend("spring_queue", "test helloworld spring ");
}
}
消费者配置:
<bean id="springQueueListener" class="com.doudou.rabbitmq.listener.SpringQueueListener"/>
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="springQueueListener" queue-names="spring_queue"/>
</rabbit:listener-container>
消费者统一运行入口:
package com.doudou.rabbitmq;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/24 22:53
*/
public class Main {
public static void main(String[] args) {
// 初始化IOC容器
new ClassPathXmlApplicationContext("classpath:spring-rabbitmq-consumer.xml");
}
}
消费者代码:
package com.doudou.rabbitmq.listener;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageListener;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/24 22:56
*/
public class SpringQueueListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println(new String(message.getBody()));
}
}
执行结果:
PubSub 发布订阅模式
生产者配置:
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="baidu_spring_fanout_queue" name="baidu_spring_fanout_queue" auto-declare="true"/>
<rabbit:queue id="sina_spring_fanout_queue" name="sina_spring_fanout_queue" auto-declare="true"/>
<!--定义广播类型交换机;并绑定上述两个队列-->
<rabbit:fanout-exchange id="spring_fanout_exchange" name="spring_fanout_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding queue="baidu_spring_fanout_queue"/>
<rabbit:binding queue="sina_spring_fanout_queue"/>
</rabbit:bindings>
</rabbit:fanout-exchange>
生产者代码:
@Test
public void testPubSub() {
rabbitTemplate.convertAndSend("spring_fanout_exchange", "", "天气晴朗");
}
消费者配置:
<bean id="baiDuFanoutListener" class="com.doudou.rabbitmq.listener.pubsub.BaiDuFanoutListener"/>
<bean id="sinaFanoutListener" class="com.doudou.rabbitmq.listener.pubsub.SinaFanoutListener"/>
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="baiDuFanoutListener" queue-names="baidu_spring_fanout_queue"/>
<rabbit:listener ref="sinaFanoutListener" queue-names="sina_spring_fanout_queue"/>
</rabbit:listener-container>
消费者代码:
public class BaiDuFanoutListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println("百度天气: " + new String(message.getBody()));
}
}
public class SinaFanoutListener implements MessageListener {
@Override
public void onMessage(Message message) {
System.out.println("新浪天气: " + new String(message.getBody()));
}
}
执行结果:
Routing 路由模式
生产者配置:
<!-- 定义队列-->
<rabbit:queue id="spring_direct_queue" name="spring_direct_queue" auto-declare="true"/>
<!--
定义 Routing 路由模式 交互机
-->
<rabbit:direct-exchange name="spring_direct_exchange">
<rabbit:bindings>
<!--direct 类型的交换机绑定队列 key :路由key queue:队列名称-->
<rabbit:binding queue="spring_direct_queue" key="health"></rabbit:binding>
</rabbit:bindings>
</rabbit:direct-exchange>
生产者代码:
@Test
public void testDirect() {
rabbitTemplate.convertAndSend("spring_direct_exchange", "health", "I am health !");
}
消费者配置:
<bean id="routingListener" class="com.doudou.rabbitmq.listener.routing.RoutingListener"/>
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="routingListener" queue-names="spring_direct_queue"/>
</rabbit:listener-container>
消费者代码同简单模式,不再写。
运行结果:
Topic 通配符模式
生产者配置:
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~通配符;*匹配一个单词,#匹配多个单词 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_doudou" name="spring_topic_queue_doudou" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_ai" name="spring_topic_queue_ai" auto-declare="true"/>
<!--定义广播交换机中的持久化队列,不存在则自动创建-->
<rabbit:queue id="spring_topic_queue_datou" name="spring_topic_queue_datou" auto-declare="true"/>
<!--
声明 topic 类型的交换机
-->
<rabbit:topic-exchange id="spring_topic_exchange" name="spring_topic_exchange" auto-declare="true">
<rabbit:bindings>
<rabbit:binding pattern="doudou.*" queue="spring_topic_queue_doudou"/>
<rabbit:binding pattern="ai.#" queue="spring_topic_queue_ai"/>
<rabbit:binding pattern="datou.#" queue="spring_topic_queue_datou"/>
</rabbit:bindings>
</rabbit:topic-exchange>
生产者代码:
@Test
public void testTopic() {
rabbitTemplate.convertAndSend("spring_topic_exchange", "doudou.*", "doudou.doudou");
rabbitTemplate.convertAndSend("spring_topic_exchange", "ai.#", "ai.doudou.ai");
rabbitTemplate.convertAndSend("spring_topic_exchange","doudou.#","doudou.ai.datou");
}
消费者配置:
<bean id="topicListenerDoudou" class="com.doudou.rabbitmq.listener.topic.TopicListenerDoudou"/>
<bean id="topicListenerAi" class="com.doudou.rabbitmq.listener.topic.TopicListenerAi"/>
<bean id="topicListenerDatou" class="com.doudou.rabbitmq.listener.topic.TopicListenerDatou"/>
<rabbit:listener-container connection-factory="connectionFactory" auto-declare="true">
<rabbit:listener ref="topicListenerDoudou" queue-names="spring_topic_queue_doudou"/>
<rabbit:listener ref="topicListenerAi" queue-names="spring_topic_queue_ai"/>
<rabbit:listener ref="topicListenerDatou" queue-names="spring_topic_queue_datou"/>
</rabbit:listener-container>
消费者代码同上,不再写。
运行结果:
Spring整合RabbitMQ小结
- 使用 Spring 整合 RabbitMQ 将组件全部使用配置方式实现,简化编码
- Spring 提供 RabbitTemplate 简化发送消息 API
- 使用监听机制简化消费者编码
SpringBoot整合RabbitMQ
步骤
生产者:
- 创建生产者SpringBoot工程
- 引入start,依赖坐标
- 编写yml配置,基本信息配置
- 定义交换机,队列以及绑定关系的配置类
- 注入RabbitTemplate,调用方法,完成消息发送
消费者:
- 创建消费者SpringBoot工程
- 引入start,依赖坐标
- 编写yml配置,基本信息配置
- 定义监听类,使用@RabbitListener注解完成队列监听。
生产者代码:
package com.doudou.rabbitmq.config;
import com.rabbitmq.client.AMQP;
import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/28 17:03
*/
@Configuration
public class RabbitMqConfig {
public static final String QUEUE_SPRINGBOOT = "springboot_queue";
public static final String WORK_QUEUE_SPRINGBOOT = "springboot_work_queue";
public static final String FANOUT_EXCHANGE_SPRINGBOOT = "springboot_fanout_exchange";
public static final String FANOUT_QUEUE_SPRINGBOOT = "springboot_fanout_queue";
public static final String DIRECT_EXCHANGE_SPRINGBOOT = "springboot_direct_exchange";
public static final String DIRECT_QUEUE_SPRINGBOOT = "springboot_direct_queue";
public static final String TOPIC_EXCHANGE_SPRINGBOOT = "springboot_topic_exchange";
public static final String TOPIC_QUEUE_SPRINGBOOT = "springboot_topic_queue";
@Bean("springbootQueue")
public Queue springbootQueue() {
return QueueBuilder.durable(QUEUE_SPRINGBOOT).build();
}
@Bean("springbootworkqueue")
public Queue springbootWorkQueue() {
return QueueBuilder.durable(WORK_QUEUE_SPRINGBOOT).build();
}
@Bean("springbootfanoutexchange")
public Exchange springbootFanoutExchange() {
return ExchangeBuilder.fanoutExchange(FANOUT_EXCHANGE_SPRINGBOOT).durable(true).build();
}
@Bean("springbootfanoutqueue")
public Queue springbootFanoutQueue() {
return QueueBuilder.durable(FANOUT_QUEUE_SPRINGBOOT).build();
}
@Bean
public Binding bindFanoutQueueExchange(@Qualifier("springbootfanoutqueue") Queue queue, @Qualifier("springbootfanoutexchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("").noargs();
}
@Bean("springbootdirectexchange")
public Exchange springbootDirectExchange() {
return ExchangeBuilder.fanoutExchange(DIRECT_EXCHANGE_SPRINGBOOT).durable(true).build();
}
@Bean("springbootdirectqueue")
public Queue springbootDirectQueue() {
return QueueBuilder.durable(DIRECT_QUEUE_SPRINGBOOT).build();
}
@Bean
public Binding bindDirectQueueExchange(@Qualifier("springbootdirectqueue") Queue queue, @Qualifier("springbootdirectexchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("doudouaidatou").noargs();
}
@Bean("springboottopicexchange")
public Exchange springbootTopicExchange() {
return ExchangeBuilder.fanoutExchange(TOPIC_EXCHANGE_SPRINGBOOT).durable(true).build();
}
@Bean("springboottopicqueue")
public Queue springbootTopicQueue() {
return QueueBuilder.durable(TOPIC_QUEUE_SPRINGBOOT).build();
}
@Bean
public Binding bindTopicQueueExchange(@Qualifier("springboottopicqueue") Queue queue, @Qualifier("springboottopicexchange") Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("doudou.*").noargs();
}
}
测试代码:
package com.doudou.test;
import com.doudou.rabbitmq.ProducerApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/28 17:22
*/
@SpringBootTest
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {ProducerApplication.class})
public class ProducerTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void springbootSend() {
rabbitTemplate.convertAndSend("springboot_queue", "test springboot simple mode");
rabbitTemplate.convertAndSend("springboot_work_queue", "test springboot work mode");
rabbitTemplate.convertAndSend("springboot_fanout_exchange", "", "test springboot pubsub mode");
rabbitTemplate.convertAndSend("springboot_direct_exchange", "doudouaidatou", "test springboot routing mode");
rabbitTemplate.convertAndSend("springboot_topic_exchange", "doudou.*", "test springboot topic mode");
}
}
消费者代码:
package com.doudou.consumerspringboot;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @description:
* @author: doudou
* @createDate: 2021/3/28 17:36
*/
@Component
public class RabbitMQListener {
@RabbitListener(queues = "springboot_queue")
public void listenerQueue(Message message) {
System.out.println(new String(message.getBody()));
}
@RabbitListener(queues = "springboot_work_queue")
public void listenerWorkQueue(Message message) {
System.out.println(new String(message.getBody()));
}
@RabbitListener(queues = "springboot_fanout_queue")
public void listenerPubSubQueue(Message message) {
System.out.println(new String(message.getBody()));
}
@RabbitListener(queues = "springboot_direct_queue")
public void listenerDirectQueue(Message message) {
System.out.println(new String(message.getBody()));
}
@RabbitListener(queues = "springboot_topic_queue")
public void listenerTopicQueue(Message message) {
System.out.println(new String(message.getBody()));
}
}
生产者所需依赖:
<!--2. rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
消费者所需依赖:
<!--RabbitMQ 启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
RabbitMQServer配置信息:
# 配置RabbitMQ的基本信息 ip 端口 username password..
spring:
rabbitmq:
host: 192.168.216.129 # ip
port: 5672
username: admin
password: admin
virtual-host: virtualHost
代码运行结果:
SpringBoot 整合 RabbitMQ小结
- SpringBoot提供了快速整合RabbitMQ的方式
- 基本信息再yml中配置,队列交互机以及绑定关系在配置类中使用Bean的方式配置
- 生产端直接注入RabbitTemplate完成消息发送
- 消费端直接使用@RabbitListener完成消息接收
高阶知识转另外的博客,敬请期待