RabbitMQ的概念
1.MQ消息中间件
MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。它是应用程序和应用程序之间的通信方法。
2.为什么要使用MQ
在项目中,可将一些无需即时返回且耗时的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高了系统的吞吐量。
MQ总结为三个好处:
(1) 应用解耦
以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内容被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中间用户感受不到物流系统的故障,提升系统的可用性。
(2)异步提速
上面要完成下单需要花费的时间: 20 + 300 + 300 + 300 = 920ms 用户点击完下单按钮后,需要等待920ms才能得到下单响应,太慢!使用MQ可以解决上述问题
用户点击完下单按钮后,只需等待25ms就能得到下单响应 (20 + 5 =25ms)。提升用户体验和系统吞吐量(单位时间内处理请求的数目)。
(3)削峰填谷
举个例子,如果订单系统最多能处理一千次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两千次下单操作系统是处理不了的,只能限制订单超过一千后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。简单来说: 就是在访问量剧增的情况下,但是应用仍然不能停,比如“双十一”下单的人多,但是淘宝这个应用仍然要运行,所以就可以使用消息中间件采用队列的形式减少突然访问的压力
使用了 MQ 之后,限制消费消息的速度为1000,这样一来,高峰期产生的数据势必会被积压在 MQ 中,高峰就被“削”掉了,但是因为消息积压,在高峰期过后的一段时间内,消费消息的速度还是会维持在1000,直到消费完积压的消息,这就叫做“填谷”。使用MQ后,可以提高系统稳定性。
使用MQ以后,可以提高系统的稳定性
MQ的缺点
- 系统可用性降低
系统引入的外部依赖越多,系统稳定性越差。一旦 MQ 宕机,就会对业务造成影响。如何保证MQ的高可用?
- 系统复杂度提高
MQ 的加入大大增加了系统的复杂度,以前系统间是同步的远程调用,现在是通过 MQ 进行异步调用。如何保证消息没有被重复消费?怎么处理消息丢失情况?那么保证消息传递的顺序性?
- 一致性问题
A 系统处理完业务,通过 MQ 给B、C、D三个系统发消息数据,如果 B 系统、C 系统处理成功,D 系统处理失败。如何保证消息数据处理的一致性?
3.常见的MQ组件
目前业界有很多的 MQ 产品,例如 RabbitMQ、RocketMQ、ActiveMQ、Kafka、ZeroMQ、MetaMq等,也有直接使用 Redis 充当消息队列的案例,而这些消息队列产品,各有侧重,在实际选型时,需要结合自身需求及 MQ 产品特征
4.什么是RabbitMQ
2007 年发布,是一个在 AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。
RabbitMQ是一个由erlang开发的AMQP(Advanced MessageQueue 高级消息队列协议 )的开源实现,由于erlang 语言的高并发特性,性能较好,本质是个队列,FIFO 先入先出,里面存放的内容是message
RabbitMQ是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑RabbitMQ是一个快递站,一个快递员帮你传递快件。RabbitMQ与快递站的主要区别在于,它不处理快件而是接收,存储和转发消息数据。
5.RabbitMQ的原理--必须记住
名词解释:
Broker:接收和分发消息的应用,RabbitMQ Server就是Message Broker
Connection:publisher/consumer 和 broker 之间的 TCP 连接
Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和messagebroker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCPconnection 的开销.
Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) andfanout(multicast)
Queue:消息最终被送到这里等待 consumer 取走
Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据
Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建exchange/queue 等
RabbitMQ中的6种工作模式
RabbitMQ 提供了 6 种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing 路由模式、Topics 主题模式、RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。
官网对应模式介绍:www.rabbitmq.com/getstarted.…
1.simple (简单模式)
在上图的模型中,有以下概念:
P:生产者,也就是要发送消息的程序
C:消费者:消息的接收者,会一直等待消息到来
queue:消息队列,图中红色部分。类似一个邮箱,可以缓存消息;
生产者向其中投递消息,消费者从其中取出消息
引入依赖
<dependencies>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.14.2</version>
</dependency>
</dependencies>
生产者代码
public class HelloProducer {
public static void main(String[] args) throws Exception {
//创建工厂类并设置连接信息
final ConnectionFactory factory = new ConnectionFactory();
//rabbitMQ服务的IP地址,默认是localhost
factory.setHost("192.168.94.131");
//设置rabbitmq的端口号AMQP端口
factory.setPort(5672);
//rabbitmq的管理员账号密码,默认是guest
factory.setUsername("cjj");
factory.setPassword("cjj");
//设置虚拟主机
factory.setVirtualHost("/cjj");
//获取连接对象
final Connection connection = factory.newConnection();
//获取channel对象
final Channel channel = connection.createChannel();
/**
*String queue, 队列的名称,如果该改名称不存在,则创建,如果存在,则不创建
* boolean durable, 该对象是否持久化,如果不持久化,当rabbitmq重启后,队列就会消失
* boolean exclusive, 该队列是否被一个消费者独占
* boolean autoDelete, 当没有消费者时,该队列是否被自动删除
* Map<String, Object> arguments 额外的参数设置
*/
channel.queueDeclare("hello-producer",true,false,false,null);
/**
* String exchange, 交换机的名称,简单模式没有交换机,使用""表示采用默认的交换机
* String routingKey, 路由标识 如果是简单模式,起名为队列的名称
* BasicProperties props, 消息的属性设置 设置为null
* byte[] body 消息的内容
*/
String msg="cjj";
channel.basicPublish("","hello-producer",null,msg.getBytes());
channel.close();
connection.close();
}
}
消费者代码
public class HelloConsumer {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
channel.queueDeclare("hello-producer",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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);
System.out.println("交换机名称:"+envelope.getExchange());
System.out.println("路由key标志:"+envelope.getRoutingKey());
System.out.println("消息的属性对象:"+properties);
System.out.println("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("hello-producer",true,consumer);
//这里不能关闭connection和channel
}
}
2.Work queues(工作模式)
Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。
应用场景:对于任务过重或任务较多情况使用工作模式可以提高任务处理的速度。
生产者代码
public class WorkProducer {
public static void main(String[] args) throws Exception {
//创建工厂类并设置连接信息
final ConnectionFactory factory = new ConnectionFactory();
//rabbitMQ服务的IP地址,默认是localhost
factory.setHost("192.168.94.131");
//设置rabbitmq的端口号AMQP端口
factory.setPort(5672);
//rabbitmq的管理员账号密码,默认是guest
factory.setUsername("cjj");
factory.setPassword("cjj");
//设置虚拟主机
factory.setVirtualHost("/cjj");
//获取连接对象
final Connection connection = factory.newConnection();
//获取channel对象
final Channel channel = connection.createChannel();
/**
*String queue, 队列的名称,如果该改名称不存在,则创建,如果存在,则不创建
* boolean durable, 该对象是否持久化,如果不持久化,当rabbitmq重启后,队列就会消失
* boolean exclusive, 该队列是否被一个消费者独占
* boolean autoDelete, 当没有消费者时,该队列是否被自动删除
* Map<String, Object> arguments 额外的参数设置
*/
channel.queueDeclare("work-queues",true,false,false,null);
/**
* String exchange, 交换机的名称,简单模式没有交换机,使用""表示采用默认的交换机
* String routingKey, 路由标识 如果是简单模式,起名为队列的名称
* BasicProperties props, 消息的属性设置 设置为null
* byte[] body 消息的内容
*/
for (int i = 0; i < 100; i++) {
String msg="work模式"+i;
channel.basicPublish("","work-queues",null,msg.getBytes());
}
channel.close();
connection.close();
}
}
消费者一号代码
public class WorkConsumer {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("work-queues",true,consumer);
//这里不能关闭connection和channel
}
}
消费者二号代码
public class WorkConsumer02 {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("work-queues",true,consumer);
//这里不能关闭connection和channel
}
}
总结: 在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是分配的关系。
Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要有一个节点成功发送即可。
3.Publish/Subscribe(发布订阅模式)
在订阅模型中,多了一个 Exchange 角色,而且过程略有变化:
P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)
C:消费者,消息的接收者,会一直等待消息到来
Queue:消息队列,接收消息、缓存消息
Exchange:交换机(X)。一方面,接收生产者发送的消息。另一方面,知道如何处理消息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于Exchange的类型。Exchange有常见以下3种类型:
Fanout:广播,将消息交给所有绑定到交换机的队列
Direct:定向,把消息交给符合指定routing key 的队列
Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列.
Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange 绑定,或者没有符合路由规则的队列,那么消息会丢失!
生产者代码
public class PublishProducer {
public static void main(String[] args) throws Exception {
//创建工厂类并设置连接信息
final ConnectionFactory factory = new ConnectionFactory();
//rabbitMQ服务的IP地址,默认是localhost
factory.setHost("192.168.94.131");
//设置rabbitmq的端口号AMQP端口
factory.setPort(5672);
//rabbitmq的管理员账号密码,默认是guest
factory.setUsername("cjj");
factory.setPassword("cjj");
//设置虚拟主机
factory.setVirtualHost("/cjj");
//获取连接对象
final Connection connection = factory.newConnection();
//获取channel对象
final Channel channel = connection.createChannel();
//创建交换机
/**
* String exchange, 交换机的名称,如果不存在则创建,存在则不创建
* BuiltinExchangeType type, 交换机的类型
* boolean durable 是否持久化
*/
channel.exchangeDeclare("publish_exchange", BuiltinExchangeType.FANOUT,true);
//创建队列
channel.queueDeclare("publish-queues001",true,false,false,null);
channel.queueDeclare("publish-queues002",true,false,false,null);
/**
* 队列和交换机的绑定
*String queue, 绑定的队列名
* String exchange, 交换机
* String routingKey 路由key 发布订阅则模式,没有routingkey则写为""
*/
channel.queueBind("publish-queues001","publish_exchange","");
channel.queueBind("publish-queues002","publish_exchange","");
String msg="订阅则模式启动,001和002队列可以收到消息";
channel.basicPublish("publish_exchange","",null,msg.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
消费者一号代码
public class PublishConsumer01 {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("publish-queues001",true,consumer);
//这里不能关闭connection和channel
}
}
消费者二号
public class PublishConsumer02 {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("publish-queues002",true,consumer);
//这里不能关闭connection和channel
}
}
4.Routing(路由模式)
队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
消息的发送方在向 Exchange 发送消息时,也必须指定消息的RoutingKey
Exchange 不再把消息交给每一个绑定的队列,而是根据消息的Routing Key 进行判断,只有队列的Routingkey 有消息的 Routingkey 完全一致,才会接收到消息
P:生产者,向 Exchange 发送消息,发送消息时,会指定一个routing key
X:Exchange(交换机),接收生产者的消息,然后把消息递交给与 routing key 完全匹配的队列
C1:消费者,其所在队列指定了需要 routing key 为 error 的消息
C2:消费者,其所在队列指定了需要 routing key 为 info、error、warning 的消息
简单来说,路由模式的传输队列是根据路由key来决定给哪个队列发送消息 交换机的类型是
BuiltinExchangeType.DIRECT
生产者代码
public class RoutingProducer {
public static void main(String[] args) throws Exception {
//创建工厂类并设置连接信息
final ConnectionFactory factory = new ConnectionFactory();
//rabbitMQ服务的IP地址,默认是localhost
factory.setHost("192.168.94.131");
//设置rabbitmq的端口号AMQP端口
factory.setPort(5672);
//rabbitmq的管理员账号密码,默认是guest
factory.setUsername("cjj");
factory.setPassword("cjj");
//设置虚拟主机
factory.setVirtualHost("/cjj");
//获取连接对象
final Connection connection = factory.newConnection();
//获取channel对象
final Channel channel = connection.createChannel();
//创建交换机
/**
* String exchange, 交换机的名称,如果不存在则创建,存在则不创建
* BuiltinExchangeType type, 交换机的类型
* boolean durable 是否持久化
*/
channel.exchangeDeclare("routing_exchange", BuiltinExchangeType.DIRECT,true);
//创建队列
channel.queueDeclare("routing-queues001",true,false,false,null);
channel.queueDeclare("routing-queues002",true,false,false,null);
/**
* 队列和交换机的绑定
*String queue, 绑定的队列名
* String exchange, 交换机
* String routingKey 路由key 路由模式不是任意绑定,而是需要路由key
*/
channel.queueBind("routing-queues001","routing_exchange","error");
channel.queueBind("routing-queues002","routing_exchange","error");
channel.queueBind("routing-queues002","routing_exchange","info");
channel.queueBind("routing-queues002","routing_exchange","warning");
String msg="路由模式启动,001和002队列可以收到消息";
channel.basicPublish("routing_exchange","error",null,msg.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
消费者一号代码
public class RoutingConsumer01 {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("routing-queues001",true,consumer);
//这里不能关闭connection和channel
}
}
消费者二号代码
public class RoutingConsumer02 {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("routing-queues002",true,consumer);
//这里不能关闭connection和channel
}
}
小结:
Routing 模式要求队列在绑定交换机时要指定 routing key,消息会转发到符合routing key 的队列。
5.Topics(主题模式)
Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型的Exchange 可以让队列在绑定Routing key 的时候使用通配符!
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,
例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.*只能匹配 item.insert
生产者代码
public class TopicsProducer {
public static void main(String[] args) throws Exception {
//创建工厂类并设置连接信息
final ConnectionFactory factory = new ConnectionFactory();
//rabbitMQ服务的IP地址,默认是localhost
factory.setHost("192.168.94.131");
//设置rabbitmq的端口号AMQP端口
factory.setPort(5672);
//rabbitmq的管理员账号密码,默认是guest
factory.setUsername("cjj");
factory.setPassword("cjj");
//设置虚拟主机
factory.setVirtualHost("/cjj");
//获取连接对象
final Connection connection = factory.newConnection();
//获取channel对象
final Channel channel = connection.createChannel();
//创建交换机
/**
* String exchange, 交换机的名称,如果不存在则创建,存在则不创建
* BuiltinExchangeType type, 交换机的类型
* boolean durable 是否持久化
*/
channel.exchangeDeclare("topics_exchange", BuiltinExchangeType.TOPIC,true);
//创建队列
channel.queueDeclare("topics-queues001",true,false,false,null);
channel.queueDeclare("topics-queues002",true,false,false,null);
/**
* 队列和交换机的绑定
*String queue, 绑定的队列名
* String exchange, 交换机
* String routingKey 路由key 路由模式不是任意绑定,而是需要路由key
*/
channel.queueBind("topics-queues001","topics_exchange","*.one.*");
channel.queueBind("topics-queues002","topics_exchange","*.*.two");
channel.queueBind("topics-queues002","topics_exchange","three.#");
String msg="主题模式启动,001和002队列可以收到消息";
channel.basicPublish("topics_exchange","three.one.fore",null,msg.getBytes());
//关闭资源
channel.close();
connection.close();
}
}
消费者一号代码
public class TopicsConsumer01 {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("topics-queues001",true,consumer);
//这里不能关闭connection和channel
}
}
消费者二号代码
public class TopicsConsumer02 {
public static void main(String[] args) throws Exception {
final ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.94.131");
factory.setPort(5672);
factory.setUsername("cjj");
factory.setPassword("cjj");
factory.setVirtualHost("/cjj");
final Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
// 这里的队列可有可无,如果没有则会创建,有的话就不创建,因为这个在生产端已经声明,所以这里就不需要写了
// channel.queueDeclare("work-queues",true,false,false,null);
//接收消息队列中的消息
final DefaultConsumer 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("接收的内容:"+new String(body));
}
};
/**
* String queue, 队列名
* boolean autoAck, 是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认信息
* Consumer callback 回调,当rabbitmq队列中存在消息,则触发该回调
*/
channel.basicConsume("topics-queues002",true,consumer);
//这里不能关闭connection和channel
}
}
Topic 主题模式可以实现 Pub/Sub 发布与订阅模式和 Routing 路由模式的功能,只是 Topic 在配置routing key 的时候可以使用通配符,显得更加灵活。