RabbitMQ生产端消息可靠性投递实现

常见的解决方案有两种:
1.消息落库,对消息状态进行打标
2.消息的延迟投递,做二次确认,回调检查

这里采用第一种方案
实现流程:

  1. 首先将将要发送的业务数据持久化到业务数据库中,消息状态的数据持久化到消息数据库中(初始状态为消息投递中)。
  2. 生产者发送消息到RabbitMQ队列中,RabbitMQ开启确认回调,生产者监听确认回调,如果监听到则更新消息数据库中的状态为消息投递成功
  3. 分布式定时任务获取所有投递中的消息,进行重发,如果某个任务的重发次数大于某个值分布式定时任务修改该消息状态为投递失败

1.创建消息状态数据库
对应实体类MaiLog
在这里插入图片描述

2.定义所需常量

public class MailConstants {
    //消息投递中
    public static final Integer DELIVERING = 0;
    //消息投递成功
    public static final Integer SUCCESS = 1;
    //消息投递失败
    public static final Integer FAILURE = 2;
    //最大重试次数
    public static final Integer MAX_TRY_COUNT = 3;
    //消息超时时间
    public static final Integer MSG_TIMEOUT = 1;
    //队列
    public static final String QUEUE = "mail.queue";
    //交换机
    public static final String EXCHANGE = "mail.exchange";
    //路由键
    public static final String ROUTING_KEY = "mail.routing.key";
}

3.在项目中发送消息的地方进行消息落库

            String msgId = UUID.randomUUID().toString();
            MailLog mailLog = new MailLog();
            mailLog.setMsgId(msgId);
            mailLog.setEId(employee.getId());
            mailLog.setStatus(0);
            mailLog.setRouteKey(MailConstants.ROUTING_KEY);
            mailLog.setExchange(MailConstants.EXCHANGE);
            mailLog.setCount(MailConstants.MAX_TRY_COUNT);
            //重试时间为当前时间加上消息超时时间
            mailLog.setTryTime(LocalDateTime.now().plusMinutes(MailConstants.MSG_TIMEOUT));
            mailLog.setCreateTime(LocalDateTime.now());
            mailLog.setUpdateTime(LocalDateTime.now());
            mailLogMapper.insert(mailLog);
            //发送消息
            rabbitTemplate.convertAndSend(MailConstants.EXCHANGE,MailConstants.ROUTING_KEY,emp,new CorrelationData(msgId));

4.开启消息回调

@Configuration
public class RabbitMQConfig {

    private static final Logger LOGGER = LoggerFactory.getLogger(RabbitMQConfig.class);
    @Autowired
    private CachingConnectionFactory cachingConnectionFactory;
    @Autowired
    private MailLogService mailLogService;

    @Bean
    public RabbitTemplate rabbitTemplate(){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);
        /**
         * 消息确认回调
         * data消息唯一表示,消息Id
         * ack确认结果
         * cause:失败原因
         */
        rabbitTemplate.setConfirmCallback((data,ack,cause)->{
            String msgId = data.getId();
            if (ack){
                LOGGER.info("=======>消息发送成功",msgId);
                mailLogService.update(new UpdateWrapper<MailLog>().set("status",1).eq("msg_id",msgId));
            }else {
                LOGGER.error("=======>消息发送失败",msgId);
            }
        });
        /**
         * 消息失败回调
         * msg:消息主题
         * repCode:响应码
         * repText:响应描述
         * exchange:交换机
         * routingkey:路由键
         */
        rabbitTemplate.setReturnCallback((msg,repCode,repText,exchange,routingkey)->{
            LOGGER.error("==========>消息发送到队列时失败",msg.getBody());
        });
        return rabbitTemplate;
    }



    @Bean
    public Queue queue(){
        return new Queue(MailConstants.QUEUE);
    }
    @Bean
    public DirectExchange directExchange(){
        return new DirectExchange(MailConstants.EXCHANGE);
    }

    /**
     * 队列和交换机绑定
     * @return
     */
    @Bean
    public Binding binding(){
        return BindingBuilder.bind(queue()).to(directExchange()).with(MailConstants.ROUTING_KEY);
    }
}

yml

    #消息确认回调
    publisher-confirm-type: correlated
    #消息失败回调
    publisher-returns: true

5.定时任务

@Component
public class MailTask {
    @Autowired
    private MailLogService mailLogService;
    @Autowired
    private EmployeeService employeeService;
    @Autowired
    private RabbitTemplate rabbitTemplate;

//每10秒执行一次
    @Scheduled(cron = "0/10 * * * * ?")
    public void mailTask(){
        //在发送中且发送超时的
        List<MailLog> list = mailLogService.list(new QueryWrapper<MailLog>().eq("status", 0).lt("try_time", LocalDateTime.now()));
        for (MailLog mailLog : list) {
        //重试次数超过三次,更新状态为投递失败
            if (mailLog.getCount()>=3){
                mailLogService.update(new UpdateWrapper<MailLog>().set("status",2).eq("msg_id",mailLog.getMsgId()));
            }
            mailLogService.update(new UpdateWrapper<MailLog>().set("count",mailLog.getCount()+1)
                    .set("update_time",LocalDateTime.now())
                    .set("try_time",LocalDateTime.now().plusMinutes(MailConstants.MSG_TIMEOUT))
                    .eq("msg_id",mailLog.getMsgId()));
            Employee employee = employeeService.getEmployee(mailLog.getEId()).get(0);
            //消息重发
            rabbitTemplate.convertAndSend(MailConstants.EXCHANGE,MailConstants.ROUTING_KEY,employee,new CorrelationData(mailLog.getMsgId()));
        }
    }
}

6.一次发送成功测试
在这里插入图片描述
7.模拟发送失败,重试一次测试
这里报发送时间交换机改为错误的从而模拟第一次发送失败
在这里插入图片描述

在这里插入图片描述
8.把定时任务中的发送也改错,模拟三次重试失败
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LeBron永鑫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值