rabbitMq高可用(避免消息丢失)

一、实现方案

        创建一张表记录消息发送的日志,消息发送时插入一条待发送的记录,如果消息发送成功则更新消息状态。发送失败时更新记录表的重发次数,达到重发次数则认定消息发送失败,使用定时任务扫描发出消息一段时间仍未待发送状态的消息,从而避免消息非人为编码原因导致中间件不可用。

二、创建表 和 对应实体,定义实体静态属性

CREATE TABLE `send_log` (
  `msgId` varchar(255) DEFAULT NULL,
  `serviceId` int(11) DEFAULT NULL  COMMENT '业务id',
  `status` int(11) DEFAULT '0' COMMENT '0发送中,1发送成功,2发送失败',
  `routeKey` varchar(255) DEFAULT NULL COMMENT '绑定交换机和队列的key',
  `exchange` varchar(255) DEFAULT NULL COMMENT '交换机名称',
  `count` int(11) DEFAULT NULL COMMENT '重试次数',
  `tryTime` date DEFAULT NULL COMMENT '第一次重试时间',
  `createTime` date DEFAULT NULL,
  `updateTime` date DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 private String msgId;
    private Integer serviceId;
    //0 消息投递中   1 投递成功   2投递失败
    private Integer status;
    private String routeKey;
    private String exchange;
    private Integer count;
    private Date tryTime;
    private Date createTime;
    private Date updateTime;
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_NAME = "xiaoxu.queue";
    public static final String EXCHANGE_NAME = "xiaoxu.exchange";
    public static final String ROUTING_KEY_NAME = "xiaoxu.routing.key";

三、自定义消息发送模板

package com.dong.boot.config;

@Configuration
public class SendLogConfig {
    private Logger log = LoggerFactory.getLogger(SendLogConfig.class);
    @Autowired
    CachingConnectionFactory cachingConnectionFactory;
    @Autowired
    SendLogService sendLogService;

    @Bean
    RabbitTemplate rabbitTemplate(){
        RabbitTemplate rabbitTemplate = new RabbitTemplate(cachingConnectionFactory);

        //消息发送成功 只能保证消息投递到broker
        rabbitTemplate.setConfirmCallback((data, ack, cause) -> { //data保存发送的唯一id ack表示消息有没有 发送成功
            if (ack){
                sendLogService.updateSendLogStatus(data.getId(),1);
                log.info("消息发送成功");
            }else{
                log.info("消息发送失败");
            }
        });

        //交换机往队列投递失败
       rabbitTemplate.setReturnsCallback((returnCallBack)->{
           log.info("消息发送失败");
       });
        return rabbitTemplate;
    }

    //创建队列
    @Bean
    Queue queue(){
        return new Queue(Constants.QUEUE_NAME,true);
    }
//创建交换机
    @Bean
    DirectExchange directExchange(){
        return new DirectExchange(Constants.EXCHANGE_NAME,true,false);
    }
//交换机和队列通过routingKey绑定
    @Bean
    Binding bindingBuilder(){
        return BindingBuilder.bind(queue()).to(directExchange()).with(Constants.ROUTING_KEY_NAME);
    }


}

四、配置开启消息确认和交换机投递失败回调

spring.rabbitmq.publisher-confirm-type=correlated
#之前版本的写法是 spring.rabbitmq.publisher-confirms=true 已废弃 
spring.rabbitmq.publisher-returns=true

五、消息发送时的逻辑

SendLog sendLog =new SendLog();
            //定时消息唯一id
            String msgId = UUID.randomUUID().toString().replace("-", "");
            sendLog.setMsgId(msgId);
            //对应业务id
            sendLog.setServiceId(service.getId());
            sendLog.setCount(Constants.MAX_TRY_COUNT);
            sendLog.setCreateTime(new Date());
            sendLog.setStatus(Constants.DELIVERING);
            // 配置在消息发送多长时间仍未发送状态才执行重发,如果消息发送时立刻进行扫描,就会立即重发,不合适
            sendLog.setTryTime(new Date(System.currentTimeMillis() + 1000 * 60 * MailConstants.MSG_TIMEOUT));
            sendLog.setExchange(Constants.MAIL_EXCHANGE_NAME);
            sendLog.setRouteKey(Constants.MAIL_ROUTING_KEY_NAME);
            sendLog.setUpdateTime(new Date());
            SendLogService.insert(SendLog);
            rabbitTemplate.convertAndSend(Constants.EXCHANGE_NAME,Constants.ROUTING_KEY_NAME,service,new CorrelationData(msgId));

六、定时任务扫描需要重发的记录

@Autowired
    SendLogService sendLogService;
    @Autowired
    RabbitTemplate rabbitTemplate;
    @Autowired
    Service service;

    @Scheduled(cron = "0/30 * * * *  ?" )
    public void mailTask(){
        //查询出需要处理的数据
        List<SendLog> sendLogList = sendLogService.getSendLogStatus();

        for (SendLog sendLog:sendLogList){
            //如果到达最大的配置次数仍发出失败,则不再重试
            if (sendLog.getCount() >= MailConstants.MAX_TRY_COUNT){
                mailSendLogService.updateSendLogStatus(sendLog.getMsgId(),2);
            }else {
                //重试发送 次数加一
                sendLogService.updateCount(sendLog.getMsgId(),new Date());
                Service service=service.getServiceById(sendLog.getServiceId());
                rabbitTemplate.convertAndSend(Constants.EXCHANGE_NAME,Constants.ROUTING_KEY_NAME,service,new CorrelationData(sendLog.getMsgId()));
            }
        }
    }

七、持久层实现

 Integer updateSendLogStatus(@Param("msgId") String msgId, @Param("status") Integer status);

    Integer insert(SendLog sendLog);

    List<MailSendLog> getSendLogsByStatus();

    Integer updateCount(@Param("msgId") String msgId, @Param("date") Date date);
<update id="updateSendLogStatus">
        update send_log set status = #{status} where msgId=#{msgId};
    </update>
    <insert id="insert" parameterType="com.dong.boot.model.SendLog">
        insert into send_log (msgId,serviceId,routeKey,exchange,tryTime,createTime) values (#{msgId},#{serviceId},#{routeKey},#{exchange},#{tryTime},#{createTime});
    </insert>

    <select id="getSendLogsByStatus" resultType="com.dong.boot.model.SendLog">
        select * from send_log where status=0 and tryTime &lt; now()
    </select>
    <update id="updateCount">
        update send_log set count=count+1,updateTime=#{date} where msgId=#{msgId};
    </update>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值