◆Routing Key:路由键,用来指示消息的路由转发,相当于快递的地址
◆Exchange: 交换机,相当于快递的分拨中心
◆Queue:消息队列,消息最终被送到这里等待consumer取走
◆Binding: exchange和queue之间的虚拟连接,用于message的分发依据
Exchange是 AMQP协议和RabbitMQ的核心组件。Exchange的功能是根据绑定关系和路由键为消息提供路由,将消息转发至相应的队列。Exchange有4种类型: Direct / Topic / Fanout / Headers ,其中Headers使用很少,以前三种为主。
Message中的Routing Key如果和Binding Key一致,Direct Exchange则将message发到对应的queue中。
每个发到Fanout Exchange的message都会分发到所有绑定的queue上去。扇形交换机(广播)
根据Routing Key及通配规则,Topic Exchange将消息分发到目标Queue中。
通配符".*"匹配一个,如:queue.*.go 匹配 queue.shuai.go
通配符".#"匹配多个,如:queue.# 匹配 queue.shuai.go和queue.shuai等
用于模拟rabbitmq的网站:RabbitMQ Simulator
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management
通过15672端口,可以访问rebbitmq的前台管理工具默认用户名和密码都是:guest
docker ps 查看运行的容器,看容器对应的ID
下载对应版本的延迟队列插件:
Release 3.9.0 · rabbitmq/rabbitmq-delayed-message-exchange · GitHub
上传到服务器的/home/soft/
文件夹下,拷贝到容器中
docker cp /home/soft/rabbitmq_delayed_message_exchange-3.9.0.ez 7f8eabb93cf8:/plugins
docker exec -it 7f8eabb93cf8 /bin/bash 进入rabbitmq容器
rabbitmq-plugins list 查看rabbitmq有哪些插件,以及运行状况
rabbitmq-plugins enable rabbitmq_delayed_message_exchange启用插件的命令
可以看到延迟队列已经启用了
◆想看什么就List什么
◆想清空什么就purge什么
◆想删除什么就Delete什么
◆一切问题记得使用--help
◆查看connection: rabbitmqctl list_connections
◆查看消费者: rabbitmqctl list_consumers
◆查看交换机: rabbitmqctl list_exchanges
◆查看队列: rabbitmqctl list_queues
◆删除队列: rabbitmqctl delete_queue
◆清空队列: rabbitmqctl purge_queue
◆新建用户: rabbitmqctl add_user
◆修改用户密码: rabbitmqctl change_password
◆删除用户: rabbitmqctl delete_user
◆查看用户: rabbitmqctl list_users
◆设置用户角色: rabbitmqctl rabbitmqctl set_user_tags
◆启动应用: rabbitmqctl start_app
◆关闭应用: rabbitmqctl stop_app,保留Erlang虚拟机(暂停)
◆关闭应用: rabbitmqctl stop,并关闭Erlang虚拟机
◆加入集群: rabbitmqctl join_cluster
◆离开集群: rabbitmqctl reset
◆设置镜像队列: rabbitmqctl sync_queue
◆取消镜像队列: rabbitmqctl cancel_sync_queue
◆交换机数量不能过多,一般来说同一个业务,或者同一类业务使用同一-个交换机
◆合理设置队列数量,- -般来说一个微服务监听一 个队列,或者一个微服务的一个业务监听-个队列
◆合理配置交换机类型,使用Topic模式时仔细设置绑定键
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
//业务逻辑
}
channel.exchangeDeclare(
"exchange.name", //交换机名字
BuiltinExchangeType.DIRECT, //交换机类型
true, //durable:是否要持久化
false, //autoDelete:交换机没有被使用时,是否删掉
null); //arguments:特殊属性
channel.queueDeclare(
"queue.name", //队列的名称
true, //durable:是否持久话
false, //exclusive:是否独占
false, //autoDelete:交换机没有被使用时,是否删掉
null); //arguments:特殊属性
channel.queueBind(
"queue.name",
"exchange.name",
"key.name");
channel.basicPublish("exchange.name", "key.name", null, messageToSend.getBytes());
DeliverCallback deliverCallback = (consumerTag, message) -> {
//业务逻辑
};
@Async
public void handleMessage() {
channel.basicConsume("queue.name", true, deliverCallback, consumerTag -> {
});
}
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
}
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.imooc.food.orderservicemanager.dao.OrderDetailDao;
import com.imooc.food.orderservicemanager.dto.OrderMessageDTO;
import com.imooc.food.orderservicemanager.enummeration.OrderStatus;
import com.imooc.food.orderservicemanager.po.OrderDetailPO;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.DeliverCallback;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
@Slf4j
@Service
public class OrderMessageService {
@Autowired
private OrderDetailDao orderDetailDao;
ObjectMapper objectMapper = new ObjectMapper();
//初始化消息通道
@Async
public void handleMessage() throws IOException, TimeoutException, InterruptedException {
log.info("start linstening message");
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
/*------------监听要申明交换机和队列,并声明绑定关系-------------*/
channel.exchangeDeclare(
"exchange.order.restaurant",
BuiltinExchangeType.DIRECT,
true,
false,
null);
channel.queueDeclare(
"queue.order",
true,
false,
false,
null);
channel.queueBind(
"queue.order",
"exchange.order.restaurant",
"key.order");
/*---------------------deliveryman---------------------*/
channel.exchangeDeclare(
"exchange.order.deliveryman",
BuiltinExchangeType.DIRECT,
true,
false,
null);
channel.queueBind(
"queue.order",
"exchange.order.deliveryman",
"key.order");
//监听queue.order队列,一旦收到消息,调用deliverCallback方法,处理消息。
channel.basicConsume("queue.order", true, deliverCallback, consumerTag -> {
});
while (true) {
Thread.sleep(100000);
}
}
}
//处理收到的消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
String messageBody = new String(message.getBody());
log.info("deliverCallback:messageBody:{}", messageBody);
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try {
OrderMessageDTO orderMessageDTO = objectMapper.readValue(messageBody,
OrderMessageDTO.class);
OrderDetailPO orderPO = orderDetailDao.selectOrder(orderMessageDTO.getOrderId());
switch (orderPO.getStatus()) {
case ORDER_CREATING:
if (orderMessageDTO.getConfirmed() && null != orderMessageDTO.getPrice()) {
orderPO.setStatus(OrderStatus.RESTAURANT_CONFIRMED);
orderPO.setPrice(orderMessageDTO.getPrice());
orderDetailDao.update(orderPO);
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.basicPublish("exchange.order.deliveryman",
"key.deliveryman",
null,
messageToSend.getBytes());
}
} else {
orderPO.setStatus(OrderStatus.FAILED);
orderDetailDao.update(orderPO);
}
break;
case RESTAURANT_CONFIRMED:
break;
case DELIVERYMAN_CONFIRMED:
break;
case SETTLEMENT_CONFIRMED:
break;
}
} catch (JsonProcessingException | TimeoutException e) {
e.printStackTrace();
}
};
}
@Configuration
@EnableAsync
public class AsyncTaskConfig implements AsyncConfigurer {
// ThredPoolTaskExcutor的处理流程
// 当池子大小小于corePoolSize,就新建线程,并处理请求
// 当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去workQueue中取任务
// 当workQueue放不下任务时,就新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize,
// 当池子的线程数大于corePoolSize时,多余的线程会等待keepAliveTime长时间,如果无请求可处理
@Override
@Bean
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor threadPool = new ThreadPoolTaskExecutor();
//设置核心线程数
threadPool.setCorePoolSize(10);
//设置最大线程数
threadPool.setMaxPoolSize(100);
//线程池所使用的缓冲队列
threadPool.setQueueCapacity(10);
//等待任务在关机时完成--表明等待所有线程执行完
threadPool.setWaitForTasksToCompleteOnShutdown(true);
// 等待时间 (默认为0,此时立即停止),并没等待xx秒后强制停止
threadPool.setAwaitTerminationSeconds(60);
// 线程名称前缀
threadPool.setThreadNamePrefix("Rabbit-Async-");
// 初始化线程
threadPool.initialize();
return threadPool;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
public class RabbitConfig {
@Autowired
OrderMessageService orderMessageService;
@Autowired
public void startListenMessage() throws IOException, TimeoutException, InterruptedException{
//调用接收消息的方法
orderMessageService.handleMessage();
}
}
◆需要使用RabbitMQ发送端确认机制,确认消息成功发送到RabbitMQ并被处理.
◆需要使用RabbitMQ消息返回机制,若没发现目标队列,中间件会通知发送方
◆需要使用RabbitMQ消费端确认机制,确认消息没有发生处理异常
◆需要使用RabbitMQ消费端限流机制,限制消息推送速度,保障接收端服务稳定
◆大量堆积的消息会给RabbitMQ产生很大的压力,需要使用RabbitMQ消息过期时间,防止消息大量积压
◆过期后会直接被丢弃,无法对系统运行异常发出警报,需要使用RabbitMQ死信队列,收集过期消息,以供分析
◆配置channel,开启确认模式: channel.confirmSelect()
◆每发送一条消息,调用channel.waitForConfirms()方法,等待确认
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.confirmSelect();
channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
log.info("message sent");
if(channel.waitForConfirms()){
log.info("confirm OK");
}else {
log.info("confirm Failed");
}
Thread.sleep(100000);
}
◆配置channel,开启确认模式: channel.confirmSelect()
◆发送多条消息后,调用channel.waitForConfirms()方法,等待确认
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
log.info("message sent");
}
if(channel.waitForConfirms()){
log.info("confirm OK");
}else {
log.info("confirm Failed");
}
Thread.sleep(100000);
}
◆配置channel,开启确认模式: channel.confirmSelect()
◆在channel. 上添加监听: addConfirmListener, 发送消息后,会回调此方法,通知是否发送成功
◆异步确认有可能是单条,也有可能是多条,取决于MQ
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("localhost");
try (Connection connection = connectionFactory.newConnection();
Channel channel = connection.createChannel()) {
String messageToSend = objectMapper.writeValueAsString(orderMessageDTO);
channel.confirmSelect();
channel.addConfirmListener(new ConfirmListener() {
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
log.info("Ack, deliveryTag: {}, multiple: {}", deliveryTag, multiple);
}
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
log.info("Nack, deliveryTag: {}, multiple: {}", deliveryTag, multiple);
}
});
for (int i = 0; i < 10; i++) {
channel.basicPublish("exchange.order.restaurant", "key.restaurant", null, messageToSend.getBytes());
log.info("message sent");
}
Thread.sleep(100000);
}
消息发送后,中间件会对消息进行路由 若没有发现目标队列,中间件会通知发送方
Return Listener 会被调用
在RabbitMQ基础配置中有一个关键配置项:Mandatory
Mandatory若为false,RabbitMQ将直接丢弃无法路由的消息。
Mandatory若为true,RabbitMQ才会处理无法路由的消息。
channel.basicPublish("exchange.order.restaurant",
"key.order",
true, //Mandatory属性
null,
messageToSend.getBytes());
//接收返回数据
channel.addReturnListener(new ReturnCallback() {
@Override
public void handle(Return returnMessage) {
log.info("Message Return: returnMessage{}", returnMessage);
//除了打印log,可以加别的业务操作
}
});
◆默认情况下,消费端接收消息时,消息会被自动确认(ACK)
◆消费端消息处理异常时,发送端与消息中间件无法得知消息处理情况
自动ACK:消费端收到消息后,会自动签收消息
手动ACK:消费端收到消息后,不会自动签收消息,需要我们在业务代码中显式签收消息
发送方开启手动签收
channel.basicConsume("queue.order",
false, //autoAck: false手动确认 true自动确认
deliverCallback,
consumerTag -> {
});
接收方签收
//手动签收
channel.basicAck(message.getMessageProperties().getDeliveryTag(),
false //multiple: false单条确定,true多条确定
);
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
◆若设置了重回队列,消息被NACK之后,会返回队列末尾,等待进一步被处理
◆一般不建议开启重回队列,因为第-次处理异常的消息,再次处理,基本上也是异常
//开启重回队列
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
◆prefetchCount: 针对一个消费端最多推送多少未确认消息
◆global: true:针对整个消费端限流false:针对当前channel
◆prefetchSize:0 (单个消息大小限制, 一般为0)
◆prefetchSize与global两项,RabbitMQ暂时未实现
channel.basicQos(2);
channel.basicConsume("queue.order",
false, //autoAck: false手动确认 true自动确认
deliverCallback,
consumerTag -> {
});
RabbitMQ的过期时间称为TTL (Time to Live),
生存时间 RabbitMQ的过期时间分为消息TTL和队列TTL
消息TTL设置了单条消息的过期时间
队列TTL设置了队列中所有消息的过期时间
TTL的设置主要考虑技术架构与业务
TTL应该明显长于服务的平均重启时间
建议TTL长于业务高峰期时间
AMQP.BasicProperties properties = new AMQP.BasicProperties.builder().expiration("15000").build();
channel.basicPublish("exchange.order.restaurant", "key.restaurant", properties, messageToSend.getBytes());
Map args = new HashMap();
args.put("x-message-ttl", 10000); //队列中消息的过期时间
args.put("x-max-length", 5); //队列最大长度
args.put("x-expire", 10000); //队列的过期时间,一般不建议使用
channel.queueDeclare(
"queue.restaurant",
true,
false,
false,
args);
死信队列就是普通的队列,过期的消息会经过死信交换机(普通交换机),路由到死信队列,再由专门监听死信队列的服务,处理这些超时消息。
消息被拒绝(reject/nack)并且requeue=false
消息过期(TTL到期)
队列达到最大长度
1.设置转发、接收死信的交换机和队列:
Exchange: dlx.exchange
Queue: dlx.queue
RoutingKey: #
2.在需要设置死信的队列加入参数:
x-dead-letter-exchange = dlx.exchange
创建方法:
ConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setPassword("guest");
connectionFactory.setUsername("guest");
//declareExchange:创建交换机 (常用)
//deleteExchange:删除交换机 (尽量不要在代码里删除操作)
//declareQueue:创建队列 (常用)
//deleteQueue:删除队列 (尽量不要在代码里删除操作)
//purgeQueue:清空队列 (尽量不要在代码里删除操作)
//declareBinding:新建绑定关系 (常用)
//removeBinding:删除绑定关系 (尽量不要在代码里删除操作)
//getQueueProperties:查询队列属性
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
//创建队列
Exchange exchange = new DirectExchange("exchange.order.restaurant");
rabbitAdmin.declareExchange(exchange);
//创建队列
Queue queue = new Queue("queue.order");
rabbitAdmin.declareQueue(queue);
//绑定
Binding binding = new Binding(
"queue.order",
Binding.DestinationType.QUEUE,
"exchange.order.restaurant",
"key.order",
null);
rabbitAdmin.declareBinding(binding);
创建方法:等同于上面的
@Bean
public Exchange exchange1() {
return new DirectExchange("exchange.order.restaurant");
}
@Bean
public Queue queue1() {
return new Queue("queue.order");
}
@Bean
public Binding binding1() {
return new Binding(
"queue.order",
Binding.DestinationType.QUEUE,
"exchange.order.restaurant",
"key.order",
null
);
}
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setPassword("guest");
connectionFactory.setUsername("guest");
connectionFactory.setPublisherConfirmType(
CachingConnectionFactory.ConfirmType.CORRELATED);//开启发送端消息确认,单点确认
connectionFactory.setPublisherReturns(true); //开启消息返回机制
connectionFactory.createConnection();//让spring调用,一般不需要配置
return connectionFactory;
}
@Bean
public RabbitAdmin rabbitAdmin(@Autowired ConnectionFactory connectionFactory) {
RabbitAdmin rabbitAdmin = new RabbitAdmin(connectionFactory);
rabbitAdmin.setAutoStartup(true);
return rabbitAdmin;
}
创建RabbitTemplate的Bean
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMandatory(true); //开启消息返回机制
//消息没有送达的处理
rabbitTemplate.setReturnsCallback(returnedMessage -> {
log.info("message:{}, replyCode:{}, replyText:{}, Exchange:{}, routingKey:{}",
returnedMessage.getMessage(), returnedMessage.getReplyCode(),
returnedMessage.getReplyText(), returnedMessage.getExchange(),
returnedMessage.getRoutingKey());
});
//消息送达的处理
rabbitTemplate.setConfirmCallback((correlationData, b, s) -> {
log.info("correlationData:{}, ack:{}, cause:{}",
correlationData, b, s);
});
return rabbitTemplate;
}
在代码中使用
MessagePostProcessor messagePostProcessor = message -> {
//设置消息的持久
message.getMessageProperties()
.setDeliveryMode(MessageDeliveryMode.PERSISTENT);
//设置消息延迟的时间,单位ms
message.getMessageProperties()
.setDelay(30*60*1000);
//设置消息过期的时间,单位ms
message.getMessageProperties()
.setExpiration("15000");
return message;
};
//需要返回的消息
CorrelationData correlationData = new CorrelationData();
correlationData.setId(orderId);
rabbitTemplate.convertAndSend(
RabbitMqConfig.DELAY_LAZY_EXCHANGE,
RabbitMqConfig.DELAY_LAZY_QUEUE,
order.getOrderId(),
messagePostProcessor
correlationData); //在这里加入需要返回的消息
//支持自己写
rabbitTemplate.execute(channel -> {
channel.
。。。。。。。。。。
})