基本概念
1、Producer(消息的生产者):向消息队列发布消息的客户端应用程序。
1)Message(消息):消息由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(消息优先权)、delivery-mode(是否持久性存储)等。
2)Channel(信道):多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,复用TCP连接的通道。
3)Routing Key(路由键):消息头的一个属性,用于标记消息的路由规则,决定了交换机的转发路径。最大长度255 字节。
2、Broker:RabbitMQ Server,服务器实体。
1)Binding(绑定):用于建立Exchange和Queue之间的关联。一个绑定就是基于Binding Key将Exchange和Queue连接起来的路由规则,所以可以将交换器理解成一个由Binding构成的路由表。
2)exchange(交换路由器):提供生产者和队列之间的匹配,接收生产者发送的消息,并根据路由规则将这些消息转发到消息队列柱。交换用于转发消息。它不会存储消息。如果没有绑定到exchange的队列,它将直接丢弃生产者发送的消息。交出交换机有四种消息调度策略(将在下一节中介绍),即扇出、直接、主题和头。
3)Binding Key(绑定键):Exchange与Queue的绑定关系,用于匹配Routing Key。最大长度255 字节。
4)Queue(消息队列):存储消息的一种数据结构,用来保存消息,直到消息发送给消费者。
它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将消息取走。需要注意,当多个消费者订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者进行处理,而不是每个消费者都收到所有的消息并处理,每一条消息只能被一个订阅者接收。
3、Consumer(消息的消费者):从消息队列取得消息的客户端应用程序。
Exchange模式
1、Direct(路由模式) ,即直接交换机:精确匹配:当消息的Routing Key 与 Exchange和Queue之间的Binding Key完全匹配,如果匹配成功,将消息分发到该Queue。只有当Routing Key和Binding Key完全匹配的时候,消息队列才可以获取消息。
2、Fanout(订阅模式|广播模式),即扇形交换机:交换器会把所有发送到该交换器的消息路由到所有与该交换器绑定的消息队列中。订阅模式与Binding Key和Routing Key无关,交换器将接受到的消息分发给有绑定关系的所有消息队列队列(不论Binding Key和Routing Key是什么)。类似于子网⼴播,子网内的每台主机都获得了一份复制的消息。Fanout交换机转发消息是最快的。
3、Topic (通配符模式) ,即主题交换机:按照正则表达式模糊匹配:用消息的Routing Key与Exchange和Queue之间的Binding Key进行模糊匹配,如果匹配成功,将消息分发到该Queue。#匹配0个或多个单词,“*”匹配不多不少一个单词。
4、Headers(键值对模式) ,即头交换机:Headers不依赖于Routing Key与Binding Key的匹配规则来转发消息,交换器的路由规则是通过消息头的Headers属性来进⾏匹配转发的,类似HTTP请求的Headers。
因此它并不太实用,而且几乎再也用不到了。
参考: https://blog.csdn.net/m0_68949064/article/details/124082131
消息传递流程
1.客户端连接到消息队列服务器,打开一个 Channel;
2.客户端声明一个 ExChange,并设置相关属性;
3.客户端声明一个 Queue,并设置相关属性;
4.客户端使用 Routing Key,在 ExChange 和 Queue 之间建立好绑定关系;
5.客户端投递消息到 ExChange;
6.ExChange 接收到消息后,就根据消息的 key 和已经设置的 binding,进行消息路由,将消息投递到一个或多个队列里.
代码
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置文件
rabbitmq:
host: xxx # ip地址
port: 5672
username: xxx # 连接账号
password: xxx # 连接密码
template:
retry:
enabled: true # 开启失败重试
initial-interval: 10000ms # 第一次重试的间隔时长
max-interval: 300000ms # 最长重试间隔,超过这个间隔将不再重试
multiplier: 2 # 下次重试间隔的倍数,此处是2即下次重试间隔是上次的2倍
exchange: topic.exchange # 缺省的交换机名称,此处配置后,发送消息如果不指定交换机就会使用这个
publisher-confirm-type: correlated # 生产者确认机制,确保消息会正确发送,如果发送失败会有错误回执,从而触发重试
publisher-returns: true
listener:
type: simple
simple:
acknowledge-mode: manual
prefetch: 1 # 限制每次发送一条数据。
concurrency: 3 # 同一个队列启动几个消费者
max-concurrency: 3 # 启动消费者最大数量
# 重试策略相关配置
retry:
enabled: true # 是否支持重试
max-attempts: 5
stateless: false
multiplier: 1.0 # 时间策略乘数因子
initial-interval: 1000ms
max-interval: 10000ms
default-requeue-rejected: true
确认模式
AcknowledgeMode.NONE:不确认
AcknowledgeMode.AUTO:自动确认
AcknowledgeMode.MANUAL:手动确认
配置类
Fanout模式
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitmqConfig {
// 交换机
public static final String dExchange = "dExchange";
/**
* 绑定交换机
*/
@Bean(dExchange)
public FanoutExchange dExchange() {
return new FanoutExchange(dExchange);
}
}
Topic 模式
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
@Slf4j
@Configuration
public class RabbitMqConfig {
public final static String TEST1_QUEUE = "test1-queue";
public final static String TEST2_QUEUE = "test2-queue";
public final static String EXCHANGE_NAME = "test.topic.exchange";
public final static String TOPIC_TEST1_ROUTINGKEY = "topic.test1.*";
public final static String TOPIC_TEST2_ROUTINGKEY = "topic.test2.*";
/**
* 声明交换机
*/
@Bean(EXCHANGE_NAME)
public Exchange exchange(){
//durable(true) 持久化,mq重启之后交换机还在
return ExchangeBuilder.topicExchange(EXCHANGE_NAME).durable(true).build();
}
/**
* 声明队列
* durable="true" 持久化 rabbitmq重启的时候不需要创建新的队列
* auto-delete 表示消息队列没有在使用时将被自动删除 默认是false
* exclusive 表示该消息队列是否只在当前connection生效,默认是false
*/
@Bean(TEST1_QUEUE)
public Queue esQueue() {
return new Queue(TEST1_QUEUE);
}
/**
* 声明队列
*/
@Bean(TEST2_QUEUE)
public Queue gitalkQueue() {
return new Queue(TEST2_QUEUE);
}
/**
* TEST1_QUEUE队列绑定交换机,指定routingKey
*/
@Bean
public Binding bindingEs(@Qualifier(TEST1_QUEUE) Queue queue,
@Qualifier(EXCHANGE_NAME) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(TOPIC_TEST1_ROUTINGKEY).noargs();
}
/**
* TEST2_QUEUE队列绑定交换机,指定routingKey
*/
@Bean
public Binding bindingGitalk(@Qualifier(TEST2_QUEUE) Queue queue,
@Qualifier(EXCHANGE_NAME) Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(TOPIC_TEST2_ROUTINGKEY).noargs();
}
}
消费者类
/**
* 接收消息
*/
@Component
public class OrderReceiver {
/**
* 交换机、队列不存在的话,以下注解可以自动创建交换机和队列
*
*/
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "order-queue", durable = "true",arguments = {@Argument(name = "x-max-length", value = "500", type = "java.lang.Integer")}),
exchange = @Exchange(value = "order-exchange", durable = "true", type = "topic"),
key = ""
))
public void onOrderMessage(@Payload Message<?> msg,
@Headers Map<String, Object> headers,
Channel channel) throws Exception {
System.out.println("--------------收到消息,开始消费------------");
byte[] payload = (byte[]) msg.getPayload();
Map<String, Object> map = (Map<String, Object>) JSON.parse(new String(payload));
Long deliveryTag = (Long) headers.get(AmqpHeaders.DELIVERY_TAG);
// ACK
channel.basicAck(deliveryTag, false);
}
}
arguments参数注释
1、x-max-length:
消息条数限制,该参数是非负整数值。限制加入queue中消息的条数。先进先出原则,超过10条后面的消息会顶替前面的消息。
2、x-max-length-bytes
消息容量限制,该参数是非负整数值。该参数和x-max-length目的一样限制队列的容量,但是这个是靠队列大小(bytes)来达到限制。
3、x-message-ttl
消息存活时间,该参数是非负整数值.创建queue时设置该参数可指定消息在该queue中待多久,可根据x-dead-letter-routing-key和x-dead-letter-exchange生成可延迟的死信队列。
4、x-max-priority
消息优先级,创建queue时arguments可以使用x-max-priority参数声明优先级队列 。该参数应该是一个整数,表示队列应该支持的最大优先级。建议使用1到10之间。目前使用更多的优先级将消耗更多的资源(Erlang进程)。
设置该参数同时设置死信队列时或造成已过期的低优先级消息会在未过期的高优先级消息后面执行。该参数会造成额外的CPU消耗。
5、x-expires
存活时间,创建queue时参数arguments设置了x-expires参数,该queue会在x-expires到期后queue消息,亲身测试直接消失(哪怕里面有未消费的消息)。
6、x-dead-letter-exchange和x-dead-letter-routing-key
创建queue时参数arguments设置了x-dead-letter-routing-key和x-dead-letter-exchange,会在x-message-ttl时间到期后把消息放到x-dead-letter-routing-key和x-dead-letter-exchange指定的队列中达到延迟队列的目的。
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import com.rabbitmq.client.Channel;
@Slf4j
@Component
public class RabbitMqListener {
public final static String TEST1_QUEUE = "test1-queue";
public final static String TEST2_QUEUE = "test2-queue";
@RabbitListener(queues = TEST1_QUEUE)
public void test1Consumer(Message message, Channel channel) {
try {
//手动确认消息已经被消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("Test1消费消息:" + message.toString() + "。成功!");
} catch (Exception e) {
e.printStackTrace();
log.info("Test1消费消息:" + message.toString() + "。失败!");
}
}
@RabbitListener(queues = TEST2_QUEUE)
public void test2Consumer(Message message, Channel channel) {
try {
//手动确认消息已经被消费
channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
log.info("Test2消费消息:" + message.toString() + "。成功!");
} catch (Exception e) {
e.printStackTrace();
log.info("Test2消费消息:" + message.toString() + "。失败!");
}
}
}
手动确认
- 成功
消费者成功处理后,调用channel.basicAck(message.getMessageProperties().getDeliveryTag(), false)方法对消息进行确认。
void basicAck(long deliveryTag, boolean multiple) throws IOException;
deliveryTag:该消息的index
multiple:是否批量. true:将一次性ack所有小于deliveryTag的消息。
- 失败
channel.basicNack 与 channel.basicReject 的区别在于basicNack可以批量拒绝多条消息,而basicReject一次只能拒绝一条消息。
void basicNack(long deliveryTag, boolean multiple, boolean requeue) throws IOException;
deliveryTag:该消息的index。
multiple:是否批量. true:将一次性拒绝所有小于deliveryTag的消息。
requeue:被拒绝的是否重新入队列。void basicReject(long deliveryTag, boolean requeue) throws IOException;
deliveryTag:该消息的index。
requeue:被拒绝的是否重新入队列。
生产者类
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 消息队列发送
*
*/
@Slf4j
@Component
public class RabbitMqSender implements RabbitTemplate.ReturnCallback, RabbitTemplate.ConfirmCallback {
private RabbitTemplate rabbitTemplate;
@Autowired
public RabbitMqSender(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
this.rabbitTemplate.setConfirmCallback(this);
this.rabbitTemplate.setReturnCallback(this);
this.rabbitTemplate.setUsePublisherConnection(true);
}
/**
* 这里返回消息是否到达exchange
*
* @param correlationData 1
* @param ack 2
* @param cause 3
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
//消息未到达exchange
log.info("消息 confirm: cause" + cause + "||||correlationData=" + correlationData + "|||||打印线程名称:" + Thread.currentThread().getName() + "||||" + Thread.currentThread().getId());
if (!ack) {
log.error("消息未到达exchange,confirm: " + cause);
}
}
/**
* 如果找得到exchange但是找不到消息队列,这里返回
*
* @param message 1
* @param replyCode 2
* @param replyText 3
* @param exchange 4
* @param routingKey 5
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
log.error("找不到消息队列,return--message:" + message.toString() + ",replyCode:" + replyCode + ",replyText:" + replyText + ",exchange:" + exchange + ",routingKey:" + routingKey);
}
/**
* 发送到消息队列
*
* @param exchange 消息路由
* @param routeKey key
* @param obj 消息
*/
public void sendRabbitmqMessage(String exchange, String routeKey, Object obj) {
this.rabbitTemplate.convertAndSend(exchange, routeKey, obj);
}
/**
* 发送到消息队列
*
* @param exchange 消息路由
* @param routeKey key
* @param obj 消息
*/
public void sendRabbitmqMessageCorrelationData(String exchange, String routeKey, Object obj, String correlationDataKey) {
this.rabbitTemplate.convertAndSend(exchange, routeKey, obj, new CorrelationData(correlationDataKey));
}
参考:
https://blog.csdn.net/qq_43659871/article/details/135003166
https://blog.csdn.net/kdzandlbj/article/details/136265586