目录
开始语
一位普通的程序员,慢慢在努力变强!
SpringBoot集成RabbitMQ之ACK确认机制(第三节)
SpringBoot集成RabbitMQ之死信队列、限流队列、延迟队列(第四节)
📝简述
在前四篇文章中,都是对RabbitMQ的模式了解和基本使用,并且在第四接中讲述到了一些使用场景,但是在真实的开发中,不足的是配置和日志分析问题。上面四讲都是一些基本的使用,需要使用到什么功能就直接使用,没有配置的一个说明。 接下来,这章节会对mq的配置进行统一封装,简化mq的使用,一起学习,探讨吧!
🖋️封装RabbitMQConfig配置类
import org.springframework.amqp.rabbit.annotation.RabbitListenerConfigurer;
import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistrar;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.converter.MappingJackson2MessageConverter;
import org.springframework.messaging.handler.annotation.support.DefaultMessageHandlerMethodFactory;
import org.springframework.messaging.handler.annotation.support.MessageHandlerMethodFactory;
/**
* MQ配置类
*
* @author tianyu.Ge
* @date 2023/2/7 9:10
*/
@Configuration
public class RabbitConfig implements RabbitListenerConfigurer {
/**
* ==================设置请求body数据结构为:json=====================
*/
@Bean
public Jackson2JsonMessageConverter producerJackson2MessageConverter() {
return new Jackson2JsonMessageConverter();
}
/**
* ==================响应接收json类型自动转换javabean=====================
*/
@Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory());
}
@Bean
MessageHandlerMethodFactory messageHandlerMethodFactory() {
DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
messageHandlerMethodFactory.setMessageConverter(consumerJackson2MessageConverter());
return messageHandlerMethodFactory;
}
@Bean
public MappingJackson2MessageConverter consumerJackson2MessageConverter() {
return new MappingJackson2MessageConverter();
}
}
🖋️封装Callback配置类
import cn.hutool.json.JSONUtil;
import com.net.entity.request.RabbitMQRequestEntity;
import com.net.utils.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 消息发送确认
* ConfirmCallback 只确认消息是否正确到达 Exchange 中
* ReturnCallback 消息没有正确到达队列时触发回调,如果正确到达队列不执行
* 1. 如果消息没有到exchange,则confirm回调,ack=false
* 2. 如果消息到达exchange,则confirm回调,ack=true
* 3. exchange到queue成功,则不回调return
* 4. exchange到queue失败,则回调return
*
* @author 猿仁
* @date 2023/2/11 20:50
*/
@Slf4j
@Component
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnCallback {
@Autowired
private RabbitConfig rabbitConfig;
/**
* 配置统一head...等
*
* @param
* @return org.springframework.amqp.core.MessagePostProcessor
* @author tianyu.Ge
* @date 2022/9/13 10:25
*/
public static final MessagePostProcessor messagePostProcessor =
new MessagePostProcessor() {
@Override
public Message postProcessMessage(Message message) throws AmqpException {
message.getMessageProperties().setHeader("content_type", MessageProperties.CONTENT_TYPE_JSON);
return message;
}
};
/**
* ==================设置请求body数据结构为:json=====================
*/
@Bean
public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) {
final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(rabbitConfig.producerJackson2MessageConverter());
rabbitTemplate.setConfirmCallback(this);
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setMandatory(true);
return rabbitTemplate;
}
/**
* ==================配置admin管理MQ相关信息=====================
*/
@Bean
public RabbitAdmin rabbitAdmin(final ConnectionFactory createConnectionFactory) {
return new RabbitAdmin(createConnectionFactory);
}
/**
* confirm机制只保证消息到达exchange,不保证消息可以路由到正确的queue,如果exchange错误,就会触发confirm机制
*
* @param correlationData
* @param ack
* @param cause
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
// 消息发送时间
Date date = new Date();
String format = DateUtil.format(date, DateUtil.YYYY_MM_DD_HH_MM_SS);
String id = correlationData.getId();
String jsonData = new String(correlationData.getReturnedMessage().getBody());
RabbitMQRequestEntity rabbitMQRequest = JSONUtil.toBean(JSONUtil.toJsonStr(jsonData), RabbitMQRequestEntity.class);
if (ack) {
log.info("[MQProducerAckConfig.confirm]消息发送成功通道id[{}]时间[{}] BODY={}", id, format, rabbitMQRequest);
} else {
log.error("[MQProducerAckConfig.confirm]消息发送失败通道id[{}]时间[{}] cause={} BODY={}", id, format, cause, rabbitMQRequest);
}
}
/**
* Return 消息机制用于处理一个不可路由的消息。在某些情况下,如果我们在发送消息的时候,当前的 exchange 不存在或者指定路由 key 路由不到,这个时候我们需要监听这种不可达的消息
* 就需要这种return机制
*
* @param message
* @param replyCode
* @param replyText
* @param exchange
* @param routingKey
*/
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
RabbitMQRequestEntity rabbitMQRequest = JSONUtil.toBean(JSONUtil.toJsonStr(new String(message.getBody())), RabbitMQRequestEntity.class);
String format = DateUtil.format(new Date(), DateUtil.YYYY_MM_DD_HH_MM_SS);
// 反序列化对象输出
log.info("[MQProducerAckConfig.returnedMessage]消息送达MQ异常_业务id[{}]时间[{}] \n 消息主体: {} \n 应答码: {} \n 描述: {} \n 消息使用的交换器: {} \n 消息使用的路由键: {}"
, rabbitMQRequest.getBusinessId()
, format
, rabbitMQRequest.getData()
, replyCode
, replyText
, exchange
, routingKey);
}
}
🖋️封装请求类Entity
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @author tianyu.Ge
* @description 消息内容
* @date 2022/9/8
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class RabbitMQRequestEntity<T> implements Serializable {
/**
* 非必填,后台可自动生成 applicationName_随机日期id
* 业务id,用于识别集体业务(也是消息应答重试的主要来源)
*/
private String businessId;
/**
* 业务数据
*/
private T data;
/**
* 发送者主题
*/
public String sendTopic;
/**
* 业务描述
*/
private String description;
/**
* 发送消息的_项目名称
*/
private String sendApplicationName;
}
🖋️创建User对象
import lombok.Data;
import java.io.Serializable;
@Data
public class User implements Serializable {
// redis存储id
private String id;
// 消息体
private String message;
}
🖋️创建消费者
import com.net.entity.User;
import com.net.entity.request.RabbitMQRequestEntity;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.messaging.handler.annotation.Headers;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* 第四种模型:路由模式
* 死信队列之:重试次数达到最大次数,死信开始消费
*/
@Component
@Slf4j
public class RouteDeadLetterCustomer {
public static final String QUEUE = "queue";
public static final String ACK = "ack";
public static final String EXCHANGE = "route_direct";
/**
* 死信队列使用:重试除数达到最大
*/
@RabbitListener(bindings = {@QueueBinding(value = @Queue(name = QUEUE),
exchange = @Exchange(value = EXCHANGE),
key = ACK)})
public void queue(RabbitMQRequestEntity<User> entity, Channel channel, Message message, @Headers Map<String, Object> headers) throws Exception {
try {
log.info("交换机模式【direct】,key【queue】,开始消费:{},Channel:{}, Message:{}, Headers :{}", entity, channel, message, headers);
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
} catch (Exception e) {
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false);
}
}
}
🖋️创建生产者
import cn.hutool.json.JSONUtil;
import com.net.config.MQProducerAckConfig;
import com.net.entity.User;
import com.net.entity.request.RabbitMQRequestEntity;
import com.net.listener.consumer.RouteDeadLetterCustomer;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.UUID;
@SpringBootTest
class SpringbootRabbitmqPracticeApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
User user = new User();
user.setId("123");
user.setMessage("name");
RabbitMQRequestEntity build = RabbitMQRequestEntity.builder()
.businessId(UUID.randomUUID().toString())
.data(user)
.description("测试一下配置功能")
.sendApplicationName("rabbitmq")
.sendTopic("rabbitmq_发送测试demo")
.build();
CorrelationData correlationData = new CorrelationData();
correlationData.setReturnedMessage(new Message(JSONUtil.toJsonStr(build).getBytes(), new MessageProperties()));
rabbitTemplate.convertAndSend(RouteDeadLetterCustomer.EXCHANGE, RouteDeadLetterCustomer.ACK, build, MQProducerAckConfig.messagePostProcessor, correlationData);
}
}
😎运行查看结果
温馨提示------------------------------------------
结束语
温馨提示:如有问题,可在下方留言,作者看到了会第一时间回复!
本章节完成了,各位正在努力的程序员们,如果你们觉得本文章对您有用的话,你学到了一些东西,希望猿友们点个赞+关注,支持一下猿仁!
持续更新中…