1、MQ基本概念
MQ:全称 Message Queue 消息队列 ,是在消息传输过程中保存消息的容器。多用于分布式系统之间进行通信。
A系统 B系统 干的事不一样,但是组合起来是一个大的系统,这样的系统称为分布式系统,A,B称为分布式系统的子系统
要完成数据通信,有两种方式
第一种
第二种:(RabbitMQ属于第二种)间接通信
A:生产者
B:消费者
MQ:消费者
1.1、小结
-
MQ消息对列,存储消息的中间件
-
分布式系统通信的两种方式:直接远程调用 和 借助第三方完成间接通信
-
发送方称为生产者,接收方称为消费者
1.2、MQ的优势和劣势
优势:
-
应用解耦:提高了系统的容错性和可维护性
-
异步提速:提升用户体验和系统吞吐量
-
削峰填谷:提高系统稳定性
应用解耦
系统的耦合性越高,容错性就越低,可维护性就越低。 eg:库存系统出现了问题,会导致订单系统也失败
使用MQ使得应用间解耦,提升容错性和可维护性
异步提速
提升了用户体验和系统吞吐量(单位时间内处理请求的数目)
削峰填谷
访问人多,会使系统宕机,用户体验感差
使用MQ来削掉峰值
填谷
劣势:
-
系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦MQ宕机,就会对业务造成影响如何保证MQ的高可用?
-
系统复杂度提高
MQ的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过MQ进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
-
一致性问题
A系统处理完业务,通过MQ给B、C、D三个系统发消息数据,如果B系统、C系统处理成功,D系统处理失败。如何保证消息数据处理的一致性?
小结:
既然MQ有优势也有劣势,那么使用MQ需要满足什么条件呢?
-
生产者不需要从消费者处获得反馈。引入消息队列之前的直接调用,其接口的返回值应该为空,这才让明明下层的动作还没做,上层却当成动作做完了继续往后走,即所谓异步成为了可能。
-
容许短暂的不一致性。
-
确实是用了有效果。即解耦、提速、削峰这些方面的收益,超过加入MQ,管理MQ这些成本。
1.3、RabbitMQ 简介
基于AMQP协议.
AMQP,即 Advanced Message Queuing Protocol (高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP规范发布。类比HTTP。
2007年,Rabbit 技术公司基于AMQP标准开发的RabbitMQ1.0发布。RabbitMQ采用Erlang语言开发。Erlang 语言由Ericson设计,专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。
RabbitMQ 基础架构如图:
-
Broker:接收和分布消息的应用,RabbitMQ Server 就是 Message Server
-
Virtual host:虚拟机,理解为mysql中的数据库一样,逻辑分区
-
Connection:publisher / consumer 和 broker 之间的 TCP连接
-
Channel:如果每一次访问RabbitMQ都建立一个Connection,在消息量大的时候建立TCP Connection的开销将是巨大的,效率也较低。Channel 是在connection内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的channel进行通讯,AMQP method包含了channel id帮助客户端和message broker识别channel,所以channel之间是完全隔离的。Channel作为轻量级的Connection极大减少了操作系统建立TCP connection 的开销
-
Exchange:交换机,根据分发规则,匹配查询表中的 routing key,分发消息到queue中去,
-
Queue:消息最终被送到这里等待consumer取走
-
Binding: exchange和queue之间的虚拟连接,binding 中可以包含routing keyBinding 信息被保存到exchange 中的查询表中,用于message 的分发依据
RabbitMQ提供了6种工作模式:
简单模式、work queues、Publish/Subscribe发布与订阅模式、Routing路由模式、Topics主题模式、RPC远程调用模式(远程调用,不太算MQ;暂不作介绍)。
工作模式:生产消息和消费消息的一种工作方式
1.4、JMS
JMS:即Java消息服务(JavaMessage Service) 应用程序接口,是一个Java平台中关于面向消息中间件的API
JMS是JavaEE规范中的一种,类比JDBC
很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ官方没有提供JMS的实现包,但是开源社区
2、RabbitMQ的快速入门
2.1、hello world 模式
需求:使用简单模式完成消息传递
P:生产者 C:消费者 红色:消息队列,类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者向其中取出消息
步骤:
-
创建工程,(生产者,消费者)
-
分别添加依赖
-
编写生产者发送消息
-
编写消费者接收消息
生产者
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_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("ip地址"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue (简单模式,不需要交换机)
/*
参数:
1. queue:队列名称
2.durable:是否持久化,当mq重启之后,还在
3.exclusive:
* 是否独占,只能有一个消费者监听队列
* 当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.arguments:参数。
* */
//如果没有一个名字叫hello_world的队列,则会创建该队类,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
//6.发送消息
/*
1.exchange:交换机的名称,简单模式下交换机会使用默认的 ""
2.routingkey:路由名称 使用默认的交换机,名称要和队列的名称一样
3.props:配置信息
4.body:发送的消息数据
* */
String body = "hello rabbitmq~~~";
channel.basicPublish("","hello_world",null,body.getBytes());
//7.释放资源
// channel.close();
// connection.close();
}
}
不执行,没有队列
消费者
ready从3变为0,消费了
public class Consumer_HelloWorld {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("localhost); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue (简单模式,不需要交换机)
/*
参数:
1. queue:队列名称
2.durable:是否持久化,当mq重启之后,还在
3.exclusive:
* 是否独占,只能有一个消费者监听队列
* 当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.arguments:参数。
* */
//如果没有一个名字叫hello_world的队列,则会创建该队类,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
//接收消息
/*
1.queue:队列名称
2.autoAck:是否自动确认
3.collback:回调对象
*/
Consumer consumer = new DefaultConsumer(channel){
//回调方法,收到消息后会自动执行该方法
// consumerTag:标识
// envelope: 获取一些信息,交换机,路由key...
//properties:配置信息
// body:数据
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag:" + consumerTag); //amq.ctag-7v5Ku6wJtt8UVr0z7_joOw
System.out.println("Exchange:" + envelope.getExchange()); //
System.out.println("RoutingKey:" + envelope.getRoutingKey()); //hello_world
System.out.println("properties:" + properties); //#contentHeader<basic>(content-type=null, content-encoding=null,...
System.out.println("body:" + new String(body)); //rabbitmq~~~
}
};
channel.basicConsume("hello_world",true,consumer);
//不要关闭资源
}
}
3、RabbitMQ的工作模式
3.1、Work queues
1.模式说明
-
Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
-
应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
有10条消息
package com.bo.producer; 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_WorkQueues {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("localhost"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue (简单模式,不需要交换机)
/*
参数:
1. queue:队列名称
2.durable:是否持久化,当mq重启之后,还在
3.exclusive:
* 是否独占,只能有一个消费者监听队列
* 当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.arguments:参数。
* */
//如果没有一个名字叫hello_world的队列,则会创建该队类,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
//6.发送消息
/*
1.exchange:交换机的名称,简单模式下交换机会使用默认的 ""
2.routingkey:路由名称 使用默认的交换机,名称要和队列的名称一样
3.props:配置信息
4.body:发送的消息数据
* */
for (int i = 1; i <= 10 ; i++) {
String body = i + "hello work_queues~~~";
channel.basicPublish("","work_queues",null,body.getBytes());
}
//7.释放资源
channel.close();
connection.close();
}
}
有两个消费者
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_WorkQueue1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("localhost"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
//5.创建队列Queue (简单模式,不需要交换机)
/*
参数:
1. queue:队列名称
2.durable:是否持久化,当mq重启之后,还在
3.exclusive:
* 是否独占,只能有一个消费者监听队列
* 当Connection关闭时,是否删除队列
4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
5.arguments:参数。
* */
//如果没有一个名字叫hello_world的队列,则会创建该队类,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
//接收消息
/*
1.queue:队列名称
2.autoAck:是否自动确认
3.collback:回调对象
*/
Consumer consumer = new DefaultConsumer(channel){
//回调方法,收到消息后会自动执行该方法
// consumerTag:标识
// envelope: 获取一些信息,交换机,路由key...
//properties:配置信息
// body:数据
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:" + consumerTag); //amq.ctag-7v5Ku6wJtt8UVr0z7_joOw
// System.out.println("Exchange:" + envelope.getExchange()); //
// System.out.println("RoutingKey:" + envelope.getRoutingKey()); //hello_world
// System.out.println("properties:" + properties); //#contentHeader<basic>(content-type=null, content-encoding=null,...
System.out.println("body:" + new String(body)); //
}
};
channel.basicConsume("work_queues",true,consumer);
//不要关闭资源
}
}
消费者1拿5条
消费者2拿5条
小结:
1.在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。
2.Work Queues对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要有一个节点成功发送即可。
3.2、Pub/Sub 订阅模式
1.模式说明
由路由分发给不同的队列,消费者监听队列,来获取消息,
P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
X:交换机,一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
-
Fanout:广播,将消息交给所有绑定到交换机的队列 扇形(广播)
-
Direct:定向,把消息交给符合指定routing key 的队列
-
Topic:通配符,把消息交给符合routing pattern(路由模式)的队列
-
headers:通过参数匹配,用的少,不做详解
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
生产者
import com.rabbitmq.client.BuiltinExchangeType;
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_PubSub {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("localhost"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
/*
1.exchange 交换机名称
2.type 类型
3.durable 是否持久化
4.autoDelete:自动删除
5.internal:内部使用。一般为false
6.arguments:参数
*/
String exchangeName = "test_fanout";
//5.创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
//6.创建队列
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7.绑定队列和交换机
/*
queue : 队列名称
exchange:交换机名称
routingkey:路由键,绑定规则
如果交换机类型为fanout,routingkey设置为""
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
//8.发送消息
String body = "日志信息:zs调用了findAll方法...日志级别:info...";
channel.basicPublish(exchangeName,"",null,body.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
执行完毕后会有两个队列
以及一个交换机
以下为交换机与队列之间的绑定方式
消费者
public class Consumer_PubSub1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("8.130.118.75"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
//接收消息
/*
1.queue:队列名称
2.autoAck:是否自动确认
3.collback:回调对象
*/
Consumer consumer = new DefaultConsumer(channel){
//回调方法,收到消息后会自动执行该方法
// consumerTag:标识
// envelope: 获取一些信息,交换机,路由key...
//properties:配置信息
// body:数据
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:" + consumerTag); //amq.ctag-7v5Ku6wJtt8UVr0z7_joOw
// System.out.println("Exchange:" + envelope.getExchange()); //
// System.out.println("RoutingKey:" + envelope.getRoutingKey()); //hello_world
// System.out.println("properties:" + properties); //#contentHeader<basic>(content-type=null, content-encoding=null,...
System.out.println("body:" + new String(body)); //
System.out.println("将日志信息打印到控制台....");
}
};
channel.basicConsume(queue1Name,true,consumer);
//不要关闭资源
}
}
package com.bo.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_PubSub2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("8.130.118.75"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("gongbo"); //用户名,默认 guest
factory.setPassword("gongbo"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_fanout_queue1";
String queue2Name = "test_fanout_queue2";
//接收消息
/*
1.queue:队列名称
2.autoAck:是否自动确认
3.collback:回调对象
*/
Consumer consumer = new DefaultConsumer(channel){
//回调方法,收到消息后会自动执行该方法
// consumerTag:标识
// envelope: 获取一些信息,交换机,路由key...
//properties:配置信息
// body:数据
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:" + consumerTag); //amq.ctag-7v5Ku6wJtt8UVr0z7_joOw
// System.out.println("Exchange:" + envelope.getExchange()); //
// System.out.println("RoutingKey:" + envelope.getRoutingKey()); //hello_world
// System.out.println("properties:" + properties); //#contentHeader<basic>(content-type=null, content-encoding=null,...
System.out.println("body:" + new String(body)); //
System.out.println("将日志信息保存数据库....");
}
};
channel.basicConsume(queue2Name,true,consumer);
//不要关闭资源
}
}
执行完毕后,为空
WorkQueues:很多消费者监听同一个队列,只能用一个消费者收到
Pub/Sub 订阅模式:很多消费者,每个消费者监听自己的队列,每一个人都可以收到这个消息
3.3、Routing路由模式
小需求:info级别的日志信息打印到控制台,error级别的日志信息,打印到数据库
模式说明:
-
队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey (路由key)
-
消息的发送方在向Exchange 发送消息时,也必须指定消息的 RoutingKey
-
Exchange不在把消息交给每一个绑定的队列,而是根据消息的RoutingKey进行判断,只有队列的RoutingKey与消息的RoutingKey完全一致,才会接收到消息
生产者
/**
* 发送消息
*/
public class Producer_Routing {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("8.130.118.75"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
/*
1.exchange 交换机名称
2.type 类型
3.durable 是否持久化
4.autoDelete:自动删除
5.internal:内部使用。一般为false
6.arguments:参数
*/
String exchangeName = "test_direct";
//5.创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
//6.创建队列
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7.绑定队列和交换机
/*
queue : 队列名称
exchange:交换机名称
routingkey:路由键,绑定规则
如果交换机类型为fanout,routingkey设置为""
*/
//队列1的绑定 error
channel.queueBind(queue1Name,exchangeName,"error");
//队列二的绑定 error info warning
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
//8.发送消息
String body = "日志信息:zs调用了delete方法...出错误了... 日志级别:error...";
channel.basicPublish(exchangeName,"error",null,body.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者
public class Consumer_Routing1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("8.130.118.75"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_direct_queue1";
String queue2Name = "test_direct_queue2";
//接收消息
/*
1.queue:队列名称
2.autoAck:是否自动确认
3.collback:回调对象
*/
Consumer consumer = new DefaultConsumer(channel){
//回调方法,收到消息后会自动执行该方法
// consumerTag:标识
// envelope: 获取一些信息,交换机,路由key...
//properties:配置信息
// body:数据
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:" + consumerTag); //amq.ctag-7v5Ku6wJtt8UVr0z7_joOw
// System.out.println("Exchange:" + envelope.getExchange()); //
// System.out.println("RoutingKey:" + envelope.getRoutingKey()); //hello_world
// System.out.println("properties:" + properties); //#contentHeader<basic>(content-type=null, content-encoding=null,...
System.out.println("body:" + new String(body)); //
System.out.println("将日志信息打印到控制台....");
}
};
channel.basicConsume(queue2Name,true,consumer);
//不要关闭资源
}
}
绑定方式
总结
-
P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key
-
X: Exchange (交换机),接收生产者的消息,然后把消息递交给与routing key完全匹配的队列
-
C1:消费者,其所在队列指定了需要routing key为error 的消息
-
C2:消费者,其所在队列指定了需要routing key为 info、error、warning 的消息
Routing模式要求队列再绑定交换机时要指定 routingkey,消息会转发到符合routing key的队列
3.4、Topics通配符模式
模式说明:
routingkey变为正则表达式
#代表0个或者多个 * 代表1个单词
生产者
/**
* 发送消息
*/
public class Producer_Topics {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("8.130.118.75"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
/*
1.exchange 交换机名称
2.type 类型
3.durable 是否持久化
4.autoDelete:自动删除
5.internal:内部使用。一般为false
6.arguments:参数
*/
String exchangeName = "test_topic";
//5.创建交换机
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//6.创建队列
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//7.绑定队列和交换机
/*
queue : 队列名称
exchange:交换机名称
routingkey:路由键,绑定规则
如果交换机类型为fanout,routingkey设置为""
*/
//routing key 系统的名称.日志的级别
// 需求,所有error级别的日志存入数据库,所有order系统的日志存入数据库
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.*");
channel.queueBind(queue2Name,exchangeName,"*.*");
//8.发送消息
String body = "日志信息:zs调用了findAll方法...... 日志级别:info...";
channel.basicPublish(exchangeName,"order.info",null,body.getBytes());
//9.释放资源
channel.close();
connection.close();
}
}
消费者
public class Consumer_Topic1 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("8.130.118.75"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
//接收消息
/*
1.queue:队列名称
2.autoAck:是否自动确认
3.collback:回调对象
*/
Consumer consumer = new DefaultConsumer(channel){
//回调方法,收到消息后会自动执行该方法
// consumerTag:标识
// envelope: 获取一些信息,交换机,路由key...
//properties:配置信息
// body:数据
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:" + consumerTag); //amq.ctag-7v5Ku6wJtt8UVr0z7_joOw
// System.out.println("Exchange:" + envelope.getExchange()); //
// System.out.println("RoutingKey:" + envelope.getRoutingKey()); //hello_world
// System.out.println("properties:" + properties); //#contentHeader<basic>(content-type=null, content-encoding=null,...
System.out.println("body:" + new String(body)); //
System.out.println("将日志信息存到数据库....");
}
};
channel.basicConsume(queue1Name,true,consumer);
//不要关闭资源
}
}
package com.bo.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class Consumer_Topic2 {
public static void main(String[] args) throws IOException, TimeoutException {
//1.创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2.设置参数
factory.setHost("8.130.118.75"); //ip,默认值:localhost
factory.setPort(5672); //端口:默认值5672
factory.setVirtualHost("/itcast"); //虚拟机 默认值/
factory.setUsername("guest"); //用户名,默认 guest
factory.setPassword("guest"); //密码 默认值:guest
//3.创建连接 Connection
Connection connection = factory.newConnection();
//4.创建Channel
Channel channel = connection.createChannel();
String queue1Name = "test_topic_queue1";
String queue2Name = "test_topic_queue2";
//接收消息
/*
1.queue:队列名称
2.autoAck:是否自动确认
3.collback:回调对象
*/
Consumer consumer = new DefaultConsumer(channel){
//回调方法,收到消息后会自动执行该方法
// consumerTag:标识
// envelope: 获取一些信息,交换机,路由key...
//properties:配置信息
// body:数据
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
// System.out.println("consumerTag:" + consumerTag); //amq.ctag-7v5Ku6wJtt8UVr0z7_joOw
// System.out.println("Exchange:" + envelope.getExchange()); //
// System.out.println("RoutingKey:" + envelope.getRoutingKey()); //hello_world
// System.out.println("properties:" + properties); //#contentHeader<basic>(content-type=null, content-encoding=null,...
System.out.println("body:" + new String(body)); //
System.out.println("将日志信息打印到控制台....");
}
};
channel.basicConsume(queue2Name,true,consumer);
//不要关闭资源
}
}
小结:Topic主题模式可以实现 Pub/Sub发布与订阅模式和Routing路由模式的功能,只是Topic在配置routing key的时候可以使用通配符,显得更加灵活。
3.5、总结
-
helloworld模式:只有生产者消费者与队列,没有交换机
-
workqueues模式:比起helloworld,多了一个消费端,多个消费端共同消费同一个队列中的消息。
-
Pub/Sub模式:有交换机,有Routingkey,如果交换机类型为fanout,routingkey设置为""
-
Routing路由模式:可以对消息的key进行绑定,使其进入不同的队列
-
Topics:对消息的key进行通配符绑定
4、SpringBoot整合Rabbitmq
4.1、整合
步骤
-
创建生产者SpringBoot工程
-
引入依赖坐标
<!--父工程依赖--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent> <dependencies> <!--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> </dependencies>
-
编写xml配置,基本信息配置
# 配置rabbitmq的基本信息 ip、端口、username、pwd... spring: rabbitmq: host: 8.130.118.75 username: guest password: guest virtual-host: / port: 5672
-
定义交换机,队列以及绑定关系的配置类
package com.bo.rabbitmq.config; import org.springframework.amqp.core.*; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class RabbitMQConfig { public static final String EXCHANGE_NAME = "boot-topic-exchange"; public static final String QUEUE_NAME = "boot-queue-exchange"; //1.交换机 @Bean("bootExchange") public Exchange bootExchange(){ return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build(); } //2.队列 @Bean("bootQueue") public Queue bootQueue(){ return QueueBuilder.durable(QUEUE_NAME).build(); } //3.队列与交换机的绑定关系 Binding //要知道队列、交换机、Routing Key @Bean //不需要被注入,不用写名字 public Binding bindQueueExchange(@Qualifier("bootQueue") Queue queue, @Qualifier("bootExchange")Exchange exchange){ return BindingBuilder.bind(queue).to(exchange).with("boot.#").noargs(); } }
-
注入RabbitTemplate,调用方法,完成消息发送
//1.注入RabbitTemplate @Autowired private RabbitTemplate rabbitTemplate; @Test public void testSend(){ rabbitTemplate.convertAndSend(RabbitMQConfig.EXCHANGE_NAME,"boot.haha","boot mq hello~~~"); }
-
需要一个启动类
@SpringBootApplication public class ProducerApplication { public static void main(String[] args) { SpringApplication.run(ProducerApplication.class); } }
消费者:
-
创建消费者SpringBoot工程
-
引入start,依赖坐标
<!--父工程依赖--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.4.RELEASE</version> </parent> <dependencies> <!--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> </dependencies>
-
编写yml配置,基本信息配置
# 配置rabbitmq的基本信息 ip、端口、username、pwd... spring: rabbitmq: host: 8.130.118.75 username: guest password: guest virtual-host: / port: 5672
-
定义监听类,使用@RabbitListener注解完成队列监听
package com.bo; import com.sun.org.apache.xpath.internal.operations.String; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class RabbitMQlistener { @RabbitListener(queues = "boot-queue-exchange") public void ListenerQueue(Message message){ // System.out.println(message); System.out.println(new String(message.getBody())); } }
-
启动类
package com.bo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class ConsumerSpringbootApplication { public static void main(String[] args) { SpringApplication.run(ConsumerSpringbootApplication.class,args); } }
小结
-
SpringBoot提供了快速整合RabbitMQ的方式
-
基本信息再yml中配置,队列交互机以及绑定关系在配置类中使用Bean的方式配置
-
生产端直接注入RabbitTemplate完成消息发送
-
消费端直接使用@RabbitListener完成消息接收