(一)RabbitMQ的基础部分
消息中间件概述
什么是消息中间件
MQ全称为Message Queue,消息队列是应用程序和应用程序之间的通信方法。
-
为什么使用MQ
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
-
开发中消息队列通常有如下应用场景:
1、任务异步处理
将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间。
2、应用程序解耦合
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
3、削峰填谷
如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡死了。
消息被MQ保存起来了,然后系统就可以按照自己的消费能力来消费,比如每秒1000个数据,这样慢慢写入数据库,这样就不会卡死数据库了。
但是使用了MQ之后,限制消费消息的速度为1000,但是这样一来,高峰期产生的数据势必会被积压在MQ中,高峰就被“削”掉了。但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000QPS,直到消费完积压的消息,这就叫做“填谷”
AMQP 和 JMS
MQ是消息通信的模型;实现MQ的大致有两种主流方式:AMQP、JMS。
AMQP
AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议)。这是其和JMS的本质差别,AMQP不从API层进行限定,而是直接定义网络交换的数据格式。
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,并不受客户端/中间件不同产品,不同的开发语言等条件的限制。2006年,AMQP 规范发布。类比HTTP。
JMS
JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信。
AMQP 与 JMS 区别
- JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式
- JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。
- JMS规定了两种消息模式;而AMQP的消息模式更加丰富
消息队列产品
市场上常见的消息队列有如下:
- ActiveMQ:基于JMS
- ZeroMQ:基于C语言开发
- RabbitMQ:基于AMQP协议,erlang语言开发,稳定性好
- RocketMQ:基于JMS,阿里巴巴产品
- Kafka:类似MQ的产品;分布式消息系统,高吞吐量
RabbitMQ
RabbitMQ是由erlang语言开发,基于AMQP(Advanced Message Queue 高级消息队列协议)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。
RabbitMQ官方地址:http://www.rabbitmq.com/
RabbitMQ提供了6种模式(现在更新为7种了):简单模式,work模式,Publish/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式,RPC远程调用模式(远程调用,不太算MQ;暂不作介绍);
官网对应模式介绍:https://www.rabbitmq.com/getstarted.html
RabbitMQ的基础架构图:
RabbitMQ 中的相关概念:
Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等
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:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)
Queue:消息最终被送到这里等待 consumer 取走
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
RabbitMQ入门
准备工作:
创建maven工程
在
pom.xml文件中添加如下依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.6.0</version>
</dependency>
RabbitMQ几种常用的模式:
简单模式
模型图:
在上图的模型中,有以下概念:
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来。
- queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。
编写生产者:
public class HelloWorld_Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* queueDeclare(String queue, boolean durable, boolean exclusive,
* boolean autoDelete, Map<String, Object> arguments)
*参数:
* 1.queue:队列名称
* 2.durable:是否持久化,当mq重启之后,还在
* 3.exclusive:是否独占。只能有一个消费监听这个队列
* 当Connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
* 5.arguments:参数
*
*/
//如果没有一个名字叫做hello_word的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
/**
* basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
*
* 1.exchange:交换机的名字
* 2.routingKey:路由名称
* 3.props:配置信息
* 4.body:发送的消息数据
*/
String body="hello rabbitmq~";
//发送消息
channel.basicPublish("","hello_world",null,body.getBytes());
//释放资源
channel.close();
connection.close();
}
}
编写消费者:
public class HelloWorld_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* queueDeclare(String queue, boolean durable, boolean exclusive,
* boolean autoDelete, Map<String, Object> arguments)
*参数:
* 1.queue:队列名称
* 2.durable:是否持久化,当mq重启之后,还在
* 3.exclusive:是否独占。只能有一个消费监听这个队列
* 当Connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
* 5.arguments:参数
*
*/
//如果没有一个名字叫做hello_word的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("hello_world",true,false,false,null);
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.queue:队列的名称
* 2.autoAck:是否自动确认
* 3.callback:回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("consumerTag======"+consumerTag);
System.out.println("交换机的一些信息====="+envelope.getExchange());
System.out.println("路由key的一些消息====="+envelope.getRoutingKey());
System.out.println("properties===="+properties);
System.out.println("body========="+new String(body));
}
};
channel.basicConsume("hello_world",true,consumer);
//关闭资源? 不要关闭资源,保持监听即可
}
}
测试结果:
Work queues工作队列模式
模型图:
Work Queues
与入门程序的简单模式
相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
应用场景:对于 任务过重或任务较多情况使用工作队列可以提高任务处理的速度。
编写生产者:
public class WorkQueues_Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* queueDeclare(String queue, boolean durable, boolean exclusive,
* boolean autoDelete, Map<String, Object> arguments)
*参数:
* 1.queue:队列名称
* 2.durable:是否持久化,当mq重启之后,还在
* 3.exclusive:是否独占。只能有一个消费监听这个队列
* 当Connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
* 5.arguments:参数
*
*/
//如果没有一个名字叫做hello_word的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/**
* basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
*
* 1.exchange:交换机的名字
* 2.routingKey:路由名称
* 3.props:配置信息
* 4.body:发送的消息数据
*/
for (int i=1;i<=10;i++){
String body="序号"+i+"--work!!! rabbitmq~";
//发送消息
channel.basicPublish("","work_queues",null,body.getBytes());
}
//释放资源
channel.close();
connection.close();
}
}
编写消费者1:
public class WorkQueues1_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* queueDeclare(String queue, boolean durable, boolean exclusive,
* boolean autoDelete, Map<String, Object> arguments)
*参数:
* 1.queue:队列名称
* 2.durable:是否持久化,当mq重启之后,还在
* 3.exclusive:是否独占。只能有一个消费监听这个队列
* 当Connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
* 5.arguments:参数
*
*/
//如果没有一个名字叫做hello_word的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源? 不要关闭资源
}
}
编写消费者2:
public class WorkQueues2_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//声明(创建)队列
/**
* queueDeclare(String queue, boolean durable, boolean exclusive,
* boolean autoDelete, Map<String, Object> arguments)
*参数:
* 1.queue:队列名称
* 2.durable:是否持久化,当mq重启之后,还在
* 3.exclusive:是否独占。只能有一个消费监听这个队列
* 当Connection关闭时,是否删除队列
* 4.autoDelete:是否自动删除。当没有Consumer时,自动删除掉
* 5.arguments:参数
*
*/
//如果没有一个名字叫做hello_word的队列,则会创建该队列,如果有则不会创建
channel.queueDeclare("work_queues",true,false,false,null);
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
}
};
channel.basicConsume("work_queues",true,consumer);
//关闭资源? 不要关闭资源
}
}
测试结果:
消费者1:
消费者2:
小结:
我们通过测试结果可以发现:
在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系,但是他们是交替消费。
Pub/Sub 发布/订阅模式
示例图:
前面2个案例中,只有3个角色:
- P:生产者,也就是要发送消息的程序
- C:消费者:消息的接受者,会一直等待消息到来。
- queue:消息队列,图中红色部分
而在订阅模型中,多了一个exchange角色,而且过程略有变化:
- P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
- C:消费者,消息的接受者,会一直等待消息到来。
- Queue:消息队列,接收消息、缓存消息。
- Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
- Fanout:广播,将消息交给所有绑定到交换机的队列
- Direct:定向,把消息交给符合指定routing key 的队列
- Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息会丢失!
发布订阅模式:
1、每个消费者监听自己的队列。
2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收
到消息
编写生产者:
public class PubSub_Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//创建交换机
/**
*
* exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
* boolean autoDelete, boolean internal, Map<String, Object> arguments)
*
* 1.exchange:交换机的名称
* 2.type:交换机类型
* DIRECT("direct"), 定向
* FANOUT("fanout"), 扇形(广播),发送消息到每一个与之绑定的队列
* TOPIC("topic"), 通配符的方式
* HEADERS("headers"); 参数匹配
*
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用。一般为false
* 6.arguments:参数
*/
String exchangeName="test_fanout";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.FANOUT,true,false,false,null);
//创建队列
String queue1Name="test_fanout_queue1";
String queue2Name="test_fanout_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingKey:路由键,绑定规则
* 如果交换机类型为fanout,routingKey设置为"",代表之后
* 会将消息分发给每一个与之绑定的队列
*
*/
channel.queueBind(queue1Name,exchangeName,"");
channel.queueBind(queue2Name,exchangeName,"");
String body="日志信息: 李四调用了findAll方法----日志级别info----";
//发送消息
channel.basicPublish(exchangeName,"",null , body.getBytes());
//释放资源
channel.close();
connection.close();
}
}
编写消费者1:
public class PubSub1_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String queue1Name="test_fanout_queue1";
String queue2Name="test_fanout_queue2";
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
System.out.println("=====将日志信息打印到控制台=====");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源? 不要关闭资源
}
}
编写消费者2:
public class PubSub2_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String queue1Name="test_fanout_queue1";
String queue2Name="test_fanout_queue2";
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
System.out.println("=====将日志信息存入数据库=====");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源? 不要关闭资源
}
}
测试结果:
我们可以看到该交换机绑定了两个队列,且没有routing Key
消费者1:
消费者2:
Routing路由模式
模式说明
路由模式特点:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个
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 的消息
比较:
在编码上与 Publish/Subscribe发布与订阅模式
的区别是交换机的类型为:Direct,还有队列绑定交换机的时候需要指定routing key。
编写生产者:
public class Routing_Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//创建交换机
/**
*
* exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
* boolean autoDelete, boolean internal, Map<String, Object> arguments)
*
* 1.exchange:交换机的名称
* 2.type:交换机类型
* DIRECT("direct"), 定向
* FANOUT("fanout"), 扇形(广播),发送消息到每一个与之绑定的队列
* TOPIC("topic"), 通配符的方式
* HEADERS("headers"); 参数匹配
*
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用。一般为false
* 6.arguments:参数
*/
//路由模式的交换机的模式为: direct
String exchangeName="test_direct";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.DIRECT,true,false,false,null);
//创建队列
String queue1Name="test_direct_queue1";
String queue2Name="test_direct_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingKey:路由键,绑定规则
* 如果交换机类型为fanout,routingKey设置为"",代表之后
* 会将消息分发给每一个与之绑定的队列
*
*/
//队列1绑定error
channel.queueBind(queue1Name,exchangeName,"error");
//队列2绑定info、error、warning
channel.queueBind(queue2Name,exchangeName,"info");
channel.queueBind(queue2Name,exchangeName,"error");
channel.queueBind(queue2Name,exchangeName,"warning");
String body="日志信息: 李四调用了findAll方法----日志级别info----";
//发送消息
channel.basicPublish(exchangeName,"info",null , body.getBytes());
//释放资源
channel.close();
connection.close();
}
}
编写消费者1:
public class Routing1_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String queue1Name="test_direct_queue1";
String queue2Name="test_direct_queue2";
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
System.out.println("=====将日志信息存入数据库=====");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源? 不要关闭资源
}
}
编写消费者2:
public class Routing2_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String queue1Name="test_direct_queue1";
String queue2Name="test_direct_queue2";
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
System.out.println("=====将日志信息打印到控制台=====");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源? 不要关闭资源
}
}
测试:
路由模式下交换机切为direct
我们将队列1与交换机绑定时的routingKey设为error
我们将队列2与交换机绑定时的routingKey设有info、error、warning
开始测试:
我们把生产者发送消息时的routingKey设置为 info
结果:
消费者1:
消费者2:
我们把生产者发送消息时的routingKey设置为 error
结果:
消费者1:
消费者2:
小结:
Routing模式要求队列在绑定交换机时要指定routing key,消息会转发到符合routing key的队列。
Topics通配符模式
模式说明
Topic
类型与Direct
相比,都是可以根据RoutingKey
把消息路由到不同的队列。只不过Topic
类型Exchange
可以让队列在绑定Routing key
的时候使用通配符!
Routingkey
一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: savior.insert
通配符规则:
#
:匹配一个或多个词
*
:匹配不多不少恰好1个词
举例:
savior.#
:能够匹配savior.insert.abc
或者 savior.insert
savior.*
:只能匹配savior.insert
图解:
- 红色Queue:绑定的是
usa.#
,因此凡是以usa.
开头的routing key
都会被匹配到 - 黄色Queue:绑定的是
#.news
,因此凡是以.news
结尾的routing key
都会被匹配
编写生产者:
public class Topic_Producer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
//创建交换机
/**
*
* exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable,
* boolean autoDelete, boolean internal, Map<String, Object> arguments)
*
* 1.exchange:交换机的名称
* 2.type:交换机类型
* DIRECT("direct"), 定向
* FANOUT("fanout"), 扇形(广播),发送消息到每一个与之绑定的队列
* TOPIC("topic"), 通配符的方式
* HEADERS("headers"); 参数匹配
*
* 3.durable:是否持久化
* 4.autoDelete:自动删除
* 5.internal:内部使用。一般为false
* 6.arguments:参数
*/
//topic 通配符模式下交换机为 topic
String exchangeName="test_topic";
channel.exchangeDeclare(exchangeName, BuiltinExchangeType.TOPIC,true,false,false,null);
//创建队列
String queue1Name="test_topic_queue1";
String queue2Name="test_topic_queue2";
channel.queueDeclare(queue1Name,true,false,false,null);
channel.queueDeclare(queue2Name,true,false,false,null);
//绑定队列和交换机
/**
* queueBind(String queue, String exchange, String routingKey)
* 1.queue:队列名称
* 2.exchange:交换机名称
* 3.routingKey:路由键,绑定规则
* 如果交换机类型为fanout,routingKey设置为"",代表之后
* 会将消息分发给每一个与之绑定的队列
*
*/
//队列1 绑定的routingKey为 #.error 、order.*
channel.queueBind(queue1Name,exchangeName,"#.error");
channel.queueBind(queue1Name,exchangeName,"order.*");
//队列2 绑定的routingKey为 *.*
channel.queueBind(queue2Name,exchangeName,"*.*");
String body="日志信息: 李四调用了delete方法----日志级别error----";
//发送消息
channel.basicPublish(exchangeName,"order.error",null , body.getBytes());
//释放资源
channel.close();
connection.close();
}
}
编写消费者1:
public class Topic1_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String queue1Name="test_topic_queue1";
String queue2Name="test_topic_queue2";
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
System.out.println("=====将日志信息存入数据库=====");
}
};
channel.basicConsume(queue1Name,true,consumer);
//关闭资源? 不要关闭资源
}
}
编写消费者2:
public class Topic2_Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//主机地址;默认为 localhost
connectionFactory.setHost("192.168.237.128");
//连接端口;默认为 5672
connectionFactory.setPort(5672);
//虚拟主机名称;默认为 /
connectionFactory.setVirtualHost("/savior");
//连接用户名;默认为guest
connectionFactory.setUsername("root");
//连接密码;默认为guest
connectionFactory.setPassword("root");
//创建新连接
Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
String queue1Name="test_topic_queue1";
String queue2Name="test_topic_queue2";
/**
*
* basicConsume(String queue, boolean autoAck, Consumer callback)
*
* 1.队列的名称
* 2.是否自动确认
* 3.回调对象
*
*/
//接收消息
Consumer consumer=new DefaultConsumer(channel){
/**
* 回调方法,当收到消息之后,会自动执行该方法
*
* @param consumerTag: 标识
* @param envelope:获取一些信息,交换机,路由key
* @param properties:配置信息
* @param body:数据
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("body========="+new String(body));
System.out.println("=====将日志信息打印到控制台=====");
}
};
channel.basicConsume(queue2Name,true,consumer);
//关闭资源? 不要关闭资源
}
}
测试:
将发送消息时的将路由键routingKey设为 order.error
队列1 绑定的routingKey为 #.error 、order.*
队列2 绑定的routingKey为 .
消费者1:
消费者2:
小结:
Topic主题模式可以实现 Publish/Subscribe发布与订阅模式
和 Routing路由模式
的功能;只是Topic在配置routing key 的时候可以使用通配符,显得更加灵活。
模式总结:
RabbitMQ工作模式:
1、简单模式 HelloWorld
一个生产者、一个消费者,不需要设置交换机(使用默认的交换机)
2、工作队列模式 Work Queue
一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认的交换机)
3、发布订阅模式 Publish/subscribe
需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列
4、路由模式 Routing
需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列
5、通配符模式 Topic
需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列