文章目录
1监听生产者消息回调方法中状态的简单使用
1.1 自定义配置类
用的上文的direct来演示
package fastwave.cloud.demo.fastwavebizpublisher.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.SerializerMessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
@Configuration
public class TemplateConfig {
@Primary
@Bean
public RabbitTemplate createRabbitTemplate(ConnectionFactory connectionFactory){
RabbitTemplate rabbitTemplate = new RabbitTemplate();
rabbitTemplate.setConnectionFactory(connectionFactory);
//设置开启Mandatory,才能触发回调函数,无论消息推送结果怎么样都强制调用回调函数
rabbitTemplate.setMandatory(true);
rabbitTemplate.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
System.out.println("ConfirmCallback: "+"相关数据:"+correlationData);
System.out.println("ConfirmCallback: "+"确认情况:"+ack);
System.out.println("ConfirmCallback: "+"原因:"+cause);
}
});
rabbitTemplate.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
System.out.println("ReturnCallback: "+"消息:"+message);
System.out.println("ReturnCallback: "+"回应码:"+replyCode);
System.out.println("ReturnCallback: "+"回应信息:"+replyText);
System.out.println("ReturnCallback: "+"交换机:"+exchange);
System.out.println("ReturnCallback: "+"路由键:"+routingKey);
}
});
return rabbitTemplate;
}
}
1.2 yml.xml添加配置
spring:
application:
name: biz-publisher
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
# publisher-confirms: true
publisher-returns: true
publisher-confirm-type: correlated
1.3测试
controller中编写一个方法:
1交换机未定义
2 交换机正确,routekey 未定义
都会进ConfirmCallback方法
@GetMapping("/noExchange")
public String noExchange(@RequestParam Map<String, Object> params)
{
try
{
String msg = params.get("msg").toString();
String uuid = UUID.randomUUID().toString();
template.convertAndSend("EmailExchange", "EmailRouting2", msg, new CorrelationData(uuid));
return "OK";
}
catch (Exception e)
{
return "网络中断,请稍后重试";
}
}
1交换机未定义
template.convertAndSend("EmailExchange", "EmailRouting2", msg, new CorrelationData(uuid));
2 routekey 未定义
template.convertAndSend("EmailExchange", "EmailRouting2", msg, new CorrelationData(uuid));
都会进入到ConfirmCallback方法都中
2主流程讲解
2.1 生产端:确认100%投递

2.1.1 消息的可靠性投递
2.1.1.1数据库
CREATE TABLE `msg_log` (
`msg_id` varchar(255) NOT NULL DEFAULT '' COMMENT '消息唯一标识',
`msg` text COMMENT '消息体, json格式化',
`exchange` varchar(255) NOT NULL DEFAULT '' COMMENT '交换机',
`routing_key` varchar(255) NOT NULL DEFAULT '' COMMENT '路由键',
`status` int(11) NOT NULL DEFAULT '0' COMMENT '状态: 0投递中 1投递成功 2投递失败 3已消费',
`try_count` int(11) NOT NULL DEFAULT '0' COMMENT '重试次数',
`next_try_time` datetime DEFAULT NULL COMMENT '下一次重试时间',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`msg_id`),
UNIQUE KEY `unq_msg_id` (`msg_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息投递日志';
2.1.1.2 在发送消息之前,填一条记录到数据库
@Resource(name = "TemplateReliable")
private RabbitTemplate templateReliable;
@GetMapping("/reliable")
public String reliable(@RequestParam Map<String, Object> params)
{
try
{
String msg = JSON.toJSONString(params);
String uuid = UUID.randomUUID().toString();
Date curTime = new Date();
MsgLogDO msgLogDO = new MsgLogDO();
msgLogDO.setMsgId(uuid);
msgLogDO.setMsg(msg);
msgLogDO.setExchange("EmailExchange");
msgLogDO.setRoutingKey("EmailRouting");
msgLogDO.setStatus(-1);
msgLogDO.setTryCount(0);
msgLogDO.setCreateTime(curTime);
msgLogDO.setUpdateTime(curTime);
msgLogDO.setCreateTime(curTime);
msgLogService.save(msgLogDO);
templateReliable.convertAndSend("EmailExchange", "EmailRouting", msg, new CorrelationData(uuid));
return "已成功发送到broker";
}
catch (Exception e)
{
logger.error(e.getMessage());
return "出现异常,请稍后重试";
}
}
2.1.1.3 回调方法
package fastwave.cloud.demo.fastwavebizpublisher.config;
import fastwave.cloud.demo.fastwavebizpublisher.domain.MsgLogDO;
import fastwave.cloud.demo.fastwavebizpublisher.services.MsgLogService;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.SerializerMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Date;
import java.util.UUID;
@Configuration
public class TemplateReliableConfig {
@Autowired
MsgLogService msgLogService;
// @Scope("prototype")
@Bean(name = "TemplateReliable")
public RabbitTemplate rabbitTemplate(ConnectionFactory factory) {
RabbitTemplate template = new RabbitTemplate(factory);
template.setMandatory(true);
template.setMessageConverter(new SerializerMessageConverter());
template.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> {
if(ack){//如果找到交换机,就更新数据库状态
MsgLogDO msgLogDO = new MsgLogDO();
msgLogDO.setMsgId(correlationData.getId());
msgLogDO.setStatus(1);
msgLogService.update(msgLogDO);
}
});
//如果找到类交换机,但是没有找到路由,就新增一条记录 4 ,人工手动操作,重新发送(一般是代码问题,)
template.setReturnCallback(new RabbitTemplate.ReturnCallback() {
@Override
public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) {
Date curTime = new Date();
MsgLogDO msgLogDO = new MsgLogDO();
msgLogDO.setMsgId(UUID.randomUUID().toString());
msgLogDO.setMsg(message.toString());
msgLogDO.setExchange(exchange);
msgLogDO.setRoutingKey(routingKey);
msgLogDO.setStatus(4);
msgLogDO.setTryCount(0);
msgLogDO.setCreateTime(curTime);
msgLogDO.setUpdateTime(curTime);
msgLogDO.setCreateTime(curTime);
msgLogService.save(msgLogDO);
}
});
return template;
}
}
2.1.2补偿机制------消息的可靠性投递
2.1.2.1可能出现的问题,使得补偿流程就可以起作用
消息没有找到交换机,没有进入ack,没有更新数据库
或者broker接受到消息,但是由于网络原因没,没有进入ack,没有更新数据库(存在幂等性问题: broker接受到,但是以为没有接收到,导致补偿流程又执行一遍,就会传递两次,消费端就可能接收到两条操作)
2.1.2.2 补偿流程的代码

定时去扫描数据库,查询状态为-1 且重试次数小于3的数据
------》找到后,更新状态,并且重新发送
如果中间出现啦问题,定时器下一个十秒仍会扫到这条记录,然后在进行操作。
重试次数超过3次,就需要人工进行操作啦。
package fastwave.cloud.demo.fastwavebizpublisher.job;
import fastwave.cloud.demo.fastwavebizpublisher.domain.MsgLogDO;
import fastwave.cloud.demo.fastwavebizpublisher.services.MsgLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
@EnableScheduling
public class MQMessageJob {
@Autowired
private MsgLogService msgLogService;
@Resource(name = "TemplateReliable")
private RabbitTemplate reliableTemplate;
private static Logger logger = LoggerFactory.getLogger(MQMessageJob.class);
//定时扫描记录表,将发送状态为-1且未超重发次数的消息再次发送,超过重发次数,必要时人工干预,生产环境中可以单独部署定时任务
@Scheduled(cron ="10/10 * * * * ?" )
public void scanNoConfirmMsg(){
Map<String, Object> searchParams = new HashMap<String, Object>();
searchParams.put("status", -1);
searchParams.put("tryCount", 3);
try {
List<MsgLogDO> list = msgLogService.list(searchParams);
for(MsgLogDO item : list)
{
item.setTryCount(item.getTryCount() + 1);
msgLogService.update(item);
Message message = MessageBuilder.withBody(item.getMsg().getBytes()).setMessageId(item.getMsgId()).build();
reliableTemplate.convertAndSend(item.getExchange(), item.getRoutingKey(), message, new CorrelationData(item.getMsgId()));
}
}
catch(Exception e)
{
logger.error("扫描作业出错,信息:" + e.getMessage());
}
}
}
2.2消费端:异常处理
2.2.1消息失败重试
解决:
在配置类中配置:不让她无限的尝试,有可能是网络的原因,重试几次就可以啦

enabled:false 不开启死信队列
虽然解决啦不循环调用的性能问题,但是重试3次以后,消息就直接丢弃啦,消费者接受不到。
-----》死信队列解决,重试3次仍然不行就放进死信队列
2.2.2死信队列
和正常队列一样,就是把长时间无法消费的放进消费队列
2.2.2.1配置类
package fastwave.cloud.demo.fastwavebizsubscriber.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class EmailConfig {
@Bean
DirectExchange EmailExchange()
{
return new DirectExchange("EmailExchange");
}
@Bean
Queue EmailQueue()
{
// return new Queue("EmailQueue");
return QueueBuilder.durable("EmailQueue")
.withArgument("x-dead-letter-exchange", "EmailDlExchange")
.withArgument("x-dead-letter-routing-key", "EmailDlRouting")
.build();
}
@Bean
Binding BindEmail()
{
return BindingBuilder.bind(EmailQueue()).to(EmailExchange()).with("EmailRouting");
}
@Bean
DirectExchange EmailDlExchange()
{
return new DirectExchange("EmailDlExchange");
}
@Bean
Queue EmailDlQueue()
{
return new Queue("EmailDlQueue",true);
}
@Bean
Binding BindDlEmail()
{
return BindingBuilder.bind(EmailDlQueue()).to(EmailDlExchange()).with("EmailDlRouting");
}
}
2.2.2.2yml.xml添加配置

enabled:true开启死信队列
2.2.3用redis解决重复消费
不管发送多少次,她携带的ID是一样的,即消费者会收到多条ID相同的消息
解决:
生产者发送消息是修改代码:
Message message = MessageBuilder.withBody(msg.getBytes()).setMessageId(uuid).build();
templateReliable.convertAndSend("EmailExchange", "EmailRouting", message, new CorrelationData(uuid));
消费者监听修改代码:
@RabbitListener(queues = "EmailQueue")
public void receiver(Message message) throws Exception {
String messageId = message.getMessageProperties().getMessageId();
if(messageId != null && !cacheUtil.exists(messageId))
{
String msg = new String(message.getBody());
Map<String, Object> map = JSONObject.parseObject(msg, Map.class);
System.out.println("开始开送邮件:" + msg);
cacheUtil.set(messageId, true);
}else
{
System.out.println("已经消费过了");
}
throw new Exception("消费消息出现异常");
}