工作流程
生产者通过connection向服务器(broker)发送消息,由服务器内部的交换机路由到对应的队列,消费者通过connection订阅队列来获取消息

-
connection连接:是客户端和RabbitMQ服务器之间的一个TCP连接,负责传输客户端和服务器之间的所有数据和控制信息
-
channel通道/信道:是connection之上的一个抽象层,一个连接可以有多个chennel,生产者(消费者)向服务器发送(接收)消息实际上就是通过channel来发送(接收)消息。作用是让少量TCP连接,可以通过多路复用来并发处理多个请求

-
virtual host虚拟主机:为消息队列提供的一种逻辑上的隔离机制,一个服务器中可以有多个虚拟主机,每个虚拟主机都有自己的交换机和队列。当不同用户使用同一个RabbitMQ服务器时,每个用户可以创建自己的虚拟主机(类似于MySql的database)
-
queue 队列:RabbitMQ内部的对象,用于存储消息。一个消费者可以订阅多个队列,一个队列也可被多个消费者订阅(多对多)
-
exchange 交换机:接收生产者的消息,并按照一定规则路由到一个或多个queue中
RabbitMQ交换机的四种类型
- Fanout:广播,将消息发送给所有绑定到交换机的队列(发布/订阅模式)
- direct:定向,将消息发送给符合指定routingKey的队列(Routing模式)
- topic:通配符,将消息发送给符合routing pattern的队列(Topics模式)
- headers:根据发送的消息内容中的headers属性进行匹配
RoutingKey:路由键,生产者将消息发给交换机时,指定一个字符串(一系列由.分隔的单词),用来告诉交换机这条消息路由到哪里
BindingKey:交换机与队列在绑定时会指定一个BindingKey,如果消息的RoutingKey与BindingKey匹配,则将消息路由到对应的队列

java
//建立连接
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost(HOST);
connectionFactory.setPort(PORT);
connectionFactory.setUsername(USERNAME);
connectionFactory.setPassword(PASSWORD);
connectionFactory.setVirtualHost(VHOST);
Connection connection= connectionFactory.newConnection();
//开启信道
Channel channel=connection.createChannel();
//声明交换机
//声明队列
//绑定交换机
//发送消息
channel.basicPublish(交换机名称,路由键,消息属性,消息体);
//消费消息
DefaultConsumer consumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//消息处理逻辑
}
};
channel.basicConsume(队列名称,是否自动确认,consumer);
//断开连接
工作模式介绍
简单模式
一个生产者,一个消费者,消息只能被消费一次,也称为点对点模式

声明队列,如果队列已经存在则直接返回:
String queue(队列名称)
boolean durable(是否持久化)将数据保存到磁盘,RabbitMQ重启后,消息不丢失
boolean exclusive(是否排他)仅对首次声明它的连接可见,该连接断开时,自动删除该队列
boolean autoDelete(是否自动删除)所有消费者都断开连接时,自动删除
Map<String, Object> arguments(其他结构化参数)

工作队列
一个生产者,对应多个消费者,消息分配给不同的消费者,消息不会重复消费

发布/订阅
一个生产者,多个消费者。交换机将消息复制多份,每个消费者接收相同的消息。适用于实时通知或者广播消息

声明交换机:
String exchange(交换机名称)
String type(交换机类型)BuiltinExchangeType下的枚举类型
boolean durable(是否持久化)
boolean autoDelete(是否自动删除)如果最后一个与其绑定的队列解除绑定时,这个交换机是否自动删除
boolean internal(是否为内部交换机)客户端不能直接将消息发送给内部交换机,一般用于交换机之间的路由
Map<String, Object> arguments(其他结构化参数)

绑定交换机:
String queue: 要绑定的队列名称
String exchange: 要绑定的交换机名称
String routingKey: 路由键,当消息携带的路由键与这个参数匹配,才转发到指定队列

路由模式
根据routingKey,将数据筛选后发送到指定的消息队列
通配符模式
在路由模式的基础上,增加通配符的功能,更加灵活
* :任意一个字符
#:任意多个字符
RPC模式
- 客户端发送消息到一个指定的队列,并在消息属性中设置replyTo字段,用于指定回调队列,来接收服务器的响应;设置correlationID属性,用于给这段消息设置标识
- 服务器从队列中接收到请求,处理之后,将其与correlationID字段一起发送到指定的回调队列
- 客户端从回调队列中接收处理后的消息,根据correlationID判断是否是要接收的消息

客户端:
java
//建立连接,开启信道
//消息唯一标识
String correlationId= String.valueOf(UUID.randomUUID());
//声明队列,一个请求队列,一个响应队列
//发送消息
AMQP.BasicProperties basicProperties=new AMQP.BasicProperties()
.builder()
.correlationId(correlationId)//设置消息的唯一标识
.replyTo(RPC_RESPONSE)//指定消息处理后发送的队列
.build();
channel.basicPublish("",RPC_REQUEST,basicProperties,body);
//接收消息
//使用阻塞队列,来存储响应信息
final ArrayBlockingQueue<String> response=new ArrayBlockingQueue<>(1);
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
if(correlationId.equals(properties.getCorrelationId())){
//如果消息标识正确,则放入阻塞队列
response.offer(new String(body));
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
};
channel.basicConsume(RPC_RESPONSE,false,defaultConsumer);
//如果阻塞队列中没有消息,则一直阻塞
String result=response.take();
//关闭连接
服务器:
java
//建立连接,开启信道
//同一时间,MQ只向消费者发送一条消息,这条消息处理完,才发送另一条
channel.basicQos(1);
//处理消息
DefaultConsumer defaultConsumer=new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//从请求队列中接收并处理消息
//将处理后的消息,发送到响应队列
AMQP.BasicProperties basicProperties=new AMQP.BasicProperties().builder().correlationId(properties.getCorrelationId()).build();
channel.basicPublish("",RPC_RESPONSE,basicProperties,body);
//向服务器发送应答
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume(RPC_REQUEST,false,defaultConsumer);
//关闭连接
发布确认模式
发布的每一个消息都会有一个唯一的ID,当RabbitMQ服务器成功接收消息,回向生产者发送一个ACK应答(携带消息的唯一ID),解决的是生产者向服务器发送消息时,出现的消息丢失问题
三种确认方式
-
单独确认模式
java//建立连接,开启信道 //设置信道为Confirm模式 channel.confirmSelect(); //声明交换机 //声明队列 //发送消息 for(int i=0;i<MESSAGE_COUNT;i++){ channel.basicPublish("",PUBLISHER_CONFIRM_QUEUE1,null,body); //消息确认 channel.waitForConfirmsOrDie(5000); } -
批量确认
java//建立连接,开启信道 //设置信道为Confirm模式 channel.confirmSelect(); //声明交换机,队列 //发送消息,并进行确认 int batchSize=100; int counts=0; for(int i=0;i<MESSAGE_COUNT;i++){ channel.basicPublish("",PUBLISHER_CONFIRM_QUEUE2,null,body); counts++; if(counts==batchSize){ //消息数量达到batchSize时进行批量确认 channel.waitForConfirmsOrDie(5000); counts=0; } } //解决发送消息数与批量确认数无法整除时,造成的没有确认问题 if(counts>0){ channel.waitForConfirmsOrDie(5000); } -
异步确认
- 生产者注册确认监听器,发送消息后无需等待服务器返回确认信息,继续发送后续消息
- 服务器返回确认信息时,客户端自动调用监听器的回调方法,在回调方法中处理消息的确认结果
java//建立连接,开启信道 //设置信道为Confirm模式 channel.confirmSelect(); //声明交换机,队列 //集合中存储着未确认的消息ID SortedSet<Long> confirm= Collections.synchronizedSortedSet(new TreeSet<>()); //注册监听器 channel.addConfirmListener(new ConfirmListener() { //消息被成功确认 @Override public void handleAck(long deliveryTag, boolean multiple) throws IOException { if(multiple){ confirm.headSet(deliveryTag+1).clear(); }else{ confirm.remove(deliveryTag+1); } } //消息未被成功确认 @Override public void handleNack(long deliveryTag, boolean multiple) throws IOException { if(multiple){ confirm.headSet(deliveryTag+1).clear(); }else{ confirm.remove(deliveryTag+1); } //消息重发 } }); //发送消息,并进行确认 for(int i=0;i<MESSAGE_COUNT;i++){ long id=channel.getNextPublishSeqNo(); channel.basicPublish("",PUBLISHER_CONFIRM_QUEUE3,null,body); confirm.add(id); } while(!confirm.isEmpty()){ Thread.sleep(10); }
Spring整合RabbitMQ
-
引入依赖

-
建立连接,创建信道
ymlspring: application: name: rabbitmq-spring-boot rabbitmq: host: port: username: password: virtual-host: -
声明队列
java@Bean public Queue directQueue(){ return QueueBuilder.durable(name).build(); } -
声明交换机,direct/fanout/topic方法类似
java@Bean public TopicExchange topicExchange(){ return ExchangeBuilder.topicExchange(name).durable(true).build(); } -
声明绑定关系,因为BeanName不一定为参数名,所以使用
@Qualifier注解指定名称java@Bean public Binding topicQueueBinding3(@Qualifier("beanName")TopicExchange topicExchange , @Qualifier("beanName") Queue queue){ return BindingBuilder.bind(queue).to(topicExchange).with(bindingKey); } -
发送消息,rabbitTemplate对象使用
@Autowired注入javarabbitTemplate.convertAndSend(EXCHANGE_NAME,routingKey,body); -
接收消息
@RabbitListener(queues = QUEUE_NAME) public void queueListener2(Message message,Channel channel){ //具体操作 }Message/Channel对象的内部构造:
