6 消息可投递编码的实现(消息的百分之分百发送成功的方案)

1 图示:

在这里插入图片描述

2 数据库表

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='消息投递日志';

3 代码的编写

3.1消息发送前,在数据库中添加一条记录

package fastwave.cloud.demo.fastwavebizpublisher.controller;

import com.alibaba.fastjson.JSON;
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.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.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("amqp")
public class AmqpController {

    @Autowired
    RabbitTemplate template;

    @Autowired
    MsgLogService msgLogService;

    @Resource(name = "TemplateReliable")
    private RabbitTemplate templateReliable;

    private static Logger logger = LoggerFactory.getLogger(AmqpController.class);

    @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 "出现异常,请稍后重试";
        }
    }

    
}

3.2编写配置类

1 消息发送到exchange。无论有没有找到交换机。都会执行回调函数。setConfirmCallback。
如果找到,跟新数据库中的status为1 (ack为true )
如果没有找到不进行处理.(ack为false )

其实这么写有漏洞。找到交换机 ,不代表正确的路由到了队列当中。但是此时数据库中的status已经更改为1(投递成功了) 。

这种找到交换机但是没有路由到队列中的错误几率不大。可能是业务代码出现了问题,或者配置时,初始化路由,队列有问题。

为了防止可能是路由不到。可以由setReturnCallback兜底。往数据库中添加一条记录,status设置为4. 设置为4 就需要运维人员进行进行人为的处理数据。

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 org.springframework.context.annotation.Scope;

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);
            }
        });

        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;
    }
}

3.3 补偿机制代码的编写

3.3.1代码的使用场景

有些状况下,并没有发送成功,需要我们使用补偿机制重新在发送消息,来保证消息的百分之分百发送成功。

什么情况下会发送不成功那??

在发送消息之前的代码(即添加日志记录时发生错误):因为try catch ,可以根据日志查看错误原因。当添加日志记录成功 ,此时status为-1.

1 发送消息时没有连接到mq ,
也会try catch 捕获。
补偿机制就会起作用,因为状态仍然是-1.

2 消息发送到exchange。但是exchange有可能不存在,
无论有没有找到交换机。都会执行回调函数。setConfirmCallback。
如果找到,跟新数据库中的status为1 (ack为true )

如果没有找到不进行处理.(ack为false )补偿机制就会起作用因为状态仍然是-1.

3.3.2代码的编写

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.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);
                reliableTemplate.convertAndSend(item.getExchange(), item.getRoutingKey(), item.getMsg(), new CorrelationData(item.getMsgId()));

            }
        }
        catch(Exception e)
        {
            logger.error("扫描作业出错,信息:" + e.getMessage());
        }
    }
}

4 broker接受到消息,但是由于网络原因,并没有接受到回调(没有到回调的参数)。存在一个幂等性问题。broker收到了,但仍然会进行补偿机制。就会传递至少两次到broker。

发布了142 篇原创文章 · 获赞 3 · 访问量 5369

猜你喜欢

转载自blog.csdn.net/Insist___/article/details/105295063