消息队列,一般我们会简称它为MQ(Message Queue)翻译过来就是消息队列
消息(Message)是指在应用间传送的数据,Queue队列是一种先进先出的数据结构。
消息队列可以看成一个存放数据的容器,我们将传输的数据存放在队列当中。
消息发布者只管把数据发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取数据而不管是谁发布的。
我们通过模拟一些场景来告诉大家,消息队列有什么好处
参考知乎java3y
现在我有一个系统A,系统A里面有一个UserId的属性, 系统A的一个方法是发送消息。
然后,系统A需要调用系统·B和系统C的接口去获取一些信息
然后,现在有系统B和系统C都需要这个findUser()
去做相关的操作
写成伪代码可能是这样的:
public class SystemA {
// 系统B和系统C的依赖
SystemB systemB = new SystemB();
SystemC systemC = new SystemC();
// 系统A独有的数据userId
private String userId = "Java3y";
public void sendMessage() {
// 系统B和系统C都需要拿着系统A的userId去获取一些信息
systemB.getSomeInfo1(userId);
systemC.getSomeInfo2(userId);
}
}
ok,一切平安无事度过了几个天。
某一天,系统B的负责人告诉系统A的负责人,现在系统B的getSomeInfo1(String userId)
这个接口不再使用了,让系统A别去调它了。
于是,系统A的负责人说"好的,那我就不调用你了。",于是就把调用系统B接口的代码给删掉了:
public void doSomething() {
// 系统A不再调用系统B的接口了
//systemB.getSomeInfo1(userId);
systemC.SystemCNeed2do(userId);
}
又过了几天,系统D的负责人接了个需求,也需要用到系统A的userId,于是就跑去跟系统A的负责人说:“老哥,我要用到你的userId,你调一下我的接口吧”
于是系统A说:“没问题的,这就搞”
然后,系统A的代码如下:
public class SystemA {
// 已经不再需要系统B的依赖了
// SystemB systemB = new SystemB();
// 系统C和系统D的依赖
SystemC systemC = new SystemC();
SystemD systemD = new SystemD();
// 系统A独有的数据
private String userId = "Java3y";
public void doSomething() {
// 已经不再需要系统B的依赖了
//systemB.getSomeInfo1(userId);
// 系统C和系统D都需要拿着系统A的userId去操作其他的事
systemC.getSomeInfo2(userId);
systemD.getSomeInfo3(userId);
}
}
那这样下去每有一个人需用用A的userId, 我们就需要改一次系统A的代码,而且不仅如此,如果在调用系统C的时候,系统C挂了,系统A还得想办法处理。如果调用系统D时,由于网络延迟,请求超时了,那系统A是反馈fail
还是重试?
然后有人跟系统A的负责人说将系统A的userId写到消息队列中,这样系统A就不用经常改动了。
系统A将userId写到消息队列中,系统C和系统D从消息队列中拿数据。这样有什么好处?
这样一来,系统A与系统B、C、D都解耦了。
我们再来看看下面这种情况:系统A还是直接调用系统B、C、D
代码如下:
public class SystemA {
SystemB systemB = new SystemB();
SystemC systemC = new SystemC();
SystemD systemD = new SystemD();
// 系统A独有的数据
private String userId ;
public void doOrder() {
// 下订单
userId = this.order();
// 如果下单成功,则安排其他系统做一些事
systemB.SystemBNeed2do(userId);
systemC.SystemCNeed2do(userId);
systemD.SystemDNeed2do(userId);
}
}
假设系统A运算出userId具体的值需要50ms,调用系统B的接口需要300ms,调用系统C的接口需要300ms,调用系统D的接口需要300ms。那么这次请求就需要50+300+300+300=950ms
并且我们得知,系统A做的是主要的业务,而系统B、C、D是非主要的业务。比如系统A处理的是订单下单,而系统B是订单下单成功了,那发送一条短信告诉具体的用户此订单已成功,而系统C和系统D也是处理一些小事而已。
那么此时,为了提高用户体验和吞吐量,其实可以异步地调用系统B、C、D的接口。所以,我们可以弄成是这样的:
系统A执行完了以后,将userId写到消息队列中,然后就直接返回了(至于其他的操作,则异步处理)。
我们再来一个场景,现在我们每个月要搞一次大促,大促期间的并发可能会很高的,比如每秒3000个请求。假设我们现在有两台机器处理请求,并且每台机器只能每次处理1000个请求。
那多出来的1000个请求,可能就把我们整个系统给搞崩了…所以,有一种办法,我们可以写到消息队列中:
系统B和系统C根据自己的能够处理的请求数去消息队列中拿数据,这样即便有每秒有8000个请求,那只是把请求放在消息队列中,去拿消息队列的消息由系统自己去控制,这样就不会把整个系统给搞崩。
系统可用性降低: 系统可用性在某种程度上降低,为什么这样说呢?在加入MQ之前,你不用考虑消息丢失或者说MQ挂掉等等的情况,但是,引入MQ之后你就需要去考虑了!
系统复杂性提高: 加入MQ之后,你需要保证消息没有被重复消费、处理消息丢失的情况、保证消息传递的顺序性等等问题!、
我们将数据写到消息队列上,系统B和C还没来得及取消息队列的数据,就挂掉了。如果没有做任何的措施,我们的数据就丢了。
一致性问题: 我上面讲了消息队列可以实现异步,消息队列带来的异步确实可以提高系统响应速度。但是,万一消息的真正消费者并没有正确消费消息怎么办?这样就会导致数据不一致的情况了!
除了这些,我们在使用的时候还得考虑各种的问题:
我们自己来处理这些逻辑显得有些复杂,但我们可以选用市面上常见的几种MQ技术kafka、activeMq、rabbitMq、rocketMq
让我们先看下他们的优缺点
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。 |
topic数量对吞吐量的影响 | topic数量对吞吐量的影响 | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降,这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | ||
时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 |
可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 高,基于主架构实现高可用性 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 | |
功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用,偶尔会有较低概率丢失消息 | erlang语言开发,性能极其好,延时很低;吞吐量到万级,MQ功能比较完备,而且开源提供的管理界面非常棒,用起来很好用,社区相对比较活跃,几乎每个月都发布几个版本分,在国内一些互联网公司近几年用rabbitmq也比较多一些,但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本。而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景。而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码。还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | 号称大数据的杀手锏,谈到大数据领域内的消息传输,则绕不开Kafka,这款为大数据而生的消息中间件,以其百万级TPS的吞吐量名声大噪,迅速成为大数据领域的宠儿,在数据采集、传输、存储的过程中发挥着举足轻重的作用 |
参考博客https://www.cnblogs.com/ithushuai/p/12443460.html
RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,RabbitMQ是使用Erlang语言来编写的,并且RabbitMQ是基于AMQP协议的。
Erlang语言最初在于交换机领域的架构模式,这样使得RabbitMQ在Broker之间进行数据交互的性能是非常优秀的
Erlang的优点:Erlang有着和原生Socket一样的延迟
AMQP定义:是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
所有 MQ 产品从模型抽象上来说都是一样的过程:
消费者(consumer)订阅某个队列。生产者(producer)创建消息,然后发布到队列(queue)中,最后将消息发送到监听的消费者。
上面只是最简单抽象的描述,具体到 RabbitMQ 则有更详细的概念需要解释。上面介绍过 RabbitMQ 是 AMQP 协议的一个开源实现,所以其内部实际上也是 AMQP 中的基本概念:
Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Binding
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。
Queue
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
Connection
网络连接,比如一个TCP连接。
Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。
Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
Broker
表示消息队列服务器实体。
从以上可以看出rabbitMQ工作原理大致就是producer把一条消息发送给exchange。rabbitMQ根据routingKey负责将消息从exchange发送到对应绑定的queue中去,这是由rabbitMQ负责做的,而consumer只需从queue获取消息即可。
大致流程如下:
一般来说安装 RabbitMQ 之前要安装 Erlang ,可以去Erlang官网下载。接着去RabbitMQ官网下载安装包,之后解压缩即可。根据操作系统不同官网提供了相应的安装说明:Windows、Debian / Ubuntu、RPM-based Linux、Mac
在这里我们在自己的服务器里安装rabbitMQ
用docker安装rabbitMQ
# 拉取rabbitmq镜像
docker pull rabbitmq
# 启动rabbitmq容器
docker run -d --name myRabbitmq -p 5672:5672 -p 15672:15672 -v `pwd`/data:/var/lib/rabbitmq 7471fb821b97
说明:
7471fb821b97 为 IMAGE ID
RabbitMQ默认禁用了管理界面,需要通过命令重新开启管理界面,
#进入容器
docker exec -it rabbitmq bash
#开启管理界面
rabbitmq-plugins enable rabbitmq_management
记得打开安全组
访问15672端口出现下面界面代表RabbitMQ安装成功
默认账号密码都为guest
#1.服务启动相关
systemctl start|restart|stop | status rabbitmq-server
#2.管理命令行用来在不使用web管理界面情况下命令操作RabbitMQ
rabbitmqctl help可以查看更多命令
#3.插件管理命令行
rabbitmq-plugins enable|list|disable
这是最简单的消息模型,如下图:
生产者将消息发送到队列,消费者从队列中获取消息,队列是存储消息的缓冲区。
再演示代码之前,我们先创建一个工程rabbitmq-demo,并编写一个工具类,用于提供与mq服务创建连接以及关闭连接
package com.example.rabbitmqDemo2;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Assert;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* @author lixiangxiang
* @description
* @date 2021/9/13 15:05
*/
public class RabbitmqUtil {
private static ConnectionFactory connectionFactory;
static {
connectionFactory = new ConnectionFactory();
//设置主机
connectionFactory.setHost("159.75.91.15");
//设置端口号
connectionFactory.setPort(5672);
connectionFactory.setUsername("ems");
connectionFactory.setPassword("xgx0417");
connectionFactory.setVirtualHost("/ems");
}
public static Connection getConnection(){
Connection connection = null;
try {
// 获取连接对象
connection = connectionFactory.newConnection();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
return connection;
}
//关闭通道关闭连接工具
public static void closeConnectionAndChannel(Channel channel,Connection connection) {
try {
assert channel != null;
channel.close();
assert connection != null;
connection.close();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
生产者发送消息
接下来是生产者发送消息,其过程包括:
与mq服务建立连接
建立通道
声明队列(有相同队列则不创建,没有则创建)
发送消息
代码如下:
public class Provider {
private static final String QUEUE_NAME = "basic_queue";
public static void main(String[] args) throws Exception {
//消息发送端与mq服务创建连接
Connection connection = ConnectionUtil.getConnection();
//建立通道
Channel channel = connection.createChannel();
// 绑定对应消息队列
//参数1 队列名称
//参数2 用来定义队列特性是否需要持久化, true 持久化队列
//参数3 是否独占队列,
//参数4 是否在消费完成后自动删除队列
//参数5 额外参数
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
String message = "hello world";
//发布消息
// 参数1交换机名称,参数2队列名称 参数3 传递消息额外设置 参数4:消息的具体内容
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
//发送消息
System.out.println("生产者已发送:" + message);
//关闭连接
RabbitmqUtil.closeConnectionAndChannel(channel,connection);
}
}
消费者接受消息
消费者在接收消息的过程需要经历如下几个步骤:
与mqfuwu建立连接
建立通道
声明队列
接收消息
代码如下:
public class Consumer {
private static final String QUEUE_NAME = "basic_queue";
public static void main(String[] args) throws Exception {
//消息消费者与mq服务建立连接
Connection connection = RabbitmqUtil.getConnection();
//建立通道
Channel channel = connection.createChannel();
//声明队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//参数1:队列名称
//参数2:开始消息的自动确认机制
//参数3:消费时的回调接口
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel){
// 最后一个参数:消息队列中取出的消息
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者接受到消息"+new String(body));
}
}););
}
}
注意:队列需要提前声明,如果未声明就使用队列,则会报错。如果不清楚生产者和消费者谁先声明,为了保证不报错,生产者和消费者都声明队列,队列的创建会保证幂等性,也就是说生产者和消费者都声明同一个队列,则只会创建一个队列
在基本消息模型中,一个生产者对应一个消费者,而实际生产过程中,往往消息生产会发送很多条消息,如果消费者只有一个的话效率就会很低,因此rabbitmq有另外一种消息模型,这种模型下,一个生产发送消息到队列,允许有多个消费者接收消息,但是一条消息只会被一个消费者获取。
角色:
生产者发送消息
与基本消息模型基本一致,这里测试循环发布20条消息:
public class Send {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 循环发布任务
for (int i = 1; i <= 20; i++) {
// 消息内容
String message = "task .. " + i;
channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
System.out.println("生产者发送消息:" + message);
Thread.sleep(500);
}
RabbitmqUtil.closeConnectionAndChannel(channel,connection);
}
}
消费者1
public class Consumer1 {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body);
System.out.println("消费者1接收到消息:" + msg);
try {
Thread.sleep(50);//模拟消费耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
消费者2
public class Consumer2 {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String msg = new String(body);
System.out.println("消费者2接收到消息:" + msg);
try {
Thread.sleep(50);//模拟消费耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
channel.basicConsume(QUEUE_NAME, true, consumer);
}
}
此时有两个消费者监听同一个队列,当两个消费者都工作时,生成者发送消息,就会按照负载均衡算法分配给不同消费者(默认平均分配),如下图:
默认的确认机制
//参数2:开启消息的自动确认机制 如果为true则为自动确认,自动确认机制不会考虑消息是否消费完,只管把消息给消费者就直接确认了,而且如果消费者消费消息的过程中宕机了,那么将永久丢失该条消息
channel.basicConsume(QUEUE_NAME, true, consumer);
消息队列默认使用的是平均分配策略,并且一次性将消息发给消费者,如果消费者在消费消息中途宕机,剩余消息将会丢失。
改进
public class Consumer1 {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
channel.basicQos(1);//每次只能消费一个消息
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String msg = new String(body);
System.out.println("消费者1接收到消息:" + msg);
try {
Thread.sleep(1000);//模拟消费耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
//参数1:手动确认消息标识 参数2:false 每次确认一个
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//关闭自动确认
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
public class Consumer2 {
private static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws Exception {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
channel.basicQos(1);//每次只能消费一个消息
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
DefaultConsumer consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties,byte[] body) throws IOException {
String msg = new String(body);
System.out.println("消费者2接收到消息:" + msg);
//参数1:手动确认消息标识 参数2:false 每次确认一个
channel.basicAck(envelope.getDeliveryTag(), false);
}
};
//关闭自动确认
channel.basicConsume(QUEUE_NAME, false, consumer);
}
}
结果可以看出消费者2执行的快,消费了更多消息
在之前的模型中,一条消息只能被一个消费者获取,而在订阅模式中,可以实现一条消息被多个消费者获取。在这种模型下,消息传递过程中比之前多了一个exchange交换机,生产者不是直接发送消息到队列,而是先发送给交换机,经由交换机分配到不同的队列,而每个消费者都有自己的队列:
解读:
1、1个生产者,多个消费者
2、每一个消费者都有自己的一个队列
3、生产者没有将消息直接发送到队列,而是发送到了交换机
4、每个队列都要绑定到交换机
5、生产者发送的消息,经过交换机到达队列,实现一个消息被多个消费者获取的目的
X(exchange)交换机的类型有以下几种:
Fanout:广播,交换机将消息发送到所有与之绑定的队列中去
Direct:定向,交换机按照指定的Routing Key发送到匹配的队列中去
Topics:通配符,与Direct大致相同,不同在于Routing Key可以根据通配符进行匹配
注意:在发布订阅模型中,生产者只负责发消息到交换机,至于消息该怎么发,以及发送到哪个队列,生产者都不负责。一般由消费者创建队列,并且绑定到交换机
在广播模式下,消息发送的流程如下:
生产者发送消息
public class Send {
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws Exception {
Connection connection = RabbitmqUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange,指定类型为fanout
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
String message = "hello world";
channel.basicPublish(EXCHANGE_NAME, "", null, message.getBytes());
System.out.println("生产者发送消息:" + message);
RabbitmqUtil.closeConnectionAndChannel(channel,connection);
}
}
消费者
public class Consumer1 {
private static final String QUEUE_NAME = "fanout_queue_1";
private static final String EXCHANGE_NAME = "fanout_exchange";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//消费者声明自己的队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
// 声明exchange,指定类型为direct
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
//消费者将队列与交换机进行绑定
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String msg = new String(body);
System.out.println("消费者1获取到消息:" + msg);
}
});
}
}
其他消费者只需修改QUEUE_NAME即可
注意:exchange与队列一样都需要提前声明,如果未声明就使用交换机,则会报错。如果不清楚生产者和消费者谁先声明,为了保证不报错,生产者和消费者都声明交换机,同样的,交换机的创建也会保证幂等性。
在fanout模式中,一条消息会被所有订阅的对列消费,但是,在某些场景下,我们希望不同的消息被不同的对列消费。这时就要用到Direct类型的Exchange
在Direct模型下:
图解:
如果发送路由key为error的消息,则C1 和 C2 都能接收到
但如果发送路由key为info的消息,只有C2能接收到
生产者发送消息
public class Send {
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
// 声明exchange,指定类型为direct
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
String message = "新增一个订单";
//生产者发送消息时,设置消息的Routing Key:"insert"
channel.basicPublish(EXCHANGE_NAME, "insert", null, message.getBytes());
System.out.println("生产者发送消息:" + message);
channel.close();
connection.close();
}
}
消费者
public class Consumer1 {
private static final String QUEUE_NAME = "direct_queue_1";
private static final String EXCHANGE_NAME = "direct_exchange";
public static void main(String[] args) throws Exception {
Connection connection = ConnectionUtil.getConnection();
Channel channel = connection.createChannel();
//消费者声明自己的队列
channel.queueDeclare(QUEUE_NAME, false, false, false, null);
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//消费者将队列与交换机进行绑定,并且设置Routing Key:"insert"
channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "insert");
channel.basicConsume(QUEUE_NAME, true, new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String msg = new String(body);
System.out.println("消费者1获取到消息:" + msg);
}
});
}
}
其他消费者需要修改队列名QUEUE_NAME和Routing Key,上述生产者发送的消息,消费者1是可以获取到的
Topic类型的Exchange与Direct相比,都是可以根据RoutingKey把消息路由到不同的队列。只不过Topic类型Exchange可以让队列在绑定Routing key 的时候使用通配符
Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert
通配符规则:
#:匹配一个或多个词
*:匹配不多不少恰好1个词
举例:
audit.#:能够匹配audit.irs.corporate 或者 audit.irs
audit.*:只能匹配audit.irs
Topics生产者代码与Direct大致相同,只不过子声明交换机时,将类型设为BuiltinExchangeType.TOPIC(topic),
消费者代码也与Direct大致相同,也是在声明交换机时设置类型为topic,代码不再演示