rabbitmq消息失败的补偿

	**     当消息在客户端消费失败时,我们会将异常的消息加入到一个消息重试对象中,同时设置最大
	重试次数,并将消息重新推送到 MQ 消息中间件里,当重试次数超过最大值时,会将异常的消息存
	储到 MongoDB数据库中,方便后续查询异常的信息。**
//  消息重试实体:
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class MessageRetryDTO implements Serializable {
 
    private static final long serialVersionUID = 1L;
 
    /**
     * 原始消息body
     */
    private String bodyMsg;
 
    /**
     * 消息来源ID
     */
    private String sourceId;
 
    /**
     * 消息来源描述
     */
    private String sourceDesc;
 
    /**
     * 交换器
     */
    private String exchangeName;
 
    /**
     * 路由键
     */
    private String routingKey;
 
    /**
     * 队列
     */
    private String queueName;
 
    /**
     * 状态,1:初始化,2:成功,3:失败
     */
    private Integer status = 1;
 
    /**
     * 最大重试次数
     */
    private Integer maxTryCount = 3;
 
    /**
     * 当前重试次数
     */
    private Integer currentRetryCount = 0;
 
    /**
     * 重试时间间隔(毫秒)
     */
    private Long retryIntervalTime = 0L;
 
    /**
     * 任务失败信息
     */
    private String errorMsg;
 
    /**
     * 创建时间
     */
    private Date createTime;
 
    @Override
    public String toString() {
        return "MessageRetryDTO{" +
                "bodyMsg='" + bodyMsg + '\'' +
                ", sourceId='" + sourceId + '\'' +
                ", sourceDesc='" + sourceDesc + '\'' +
                ", exchangeName='" + exchangeName + '\'' +
                ", routingKey='" + routingKey + '\'' +
                ", queueName='" + queueName + '\'' +
                ", status=" + status +
                ", maxTryCount=" + maxTryCount +
                ", currentRetryCount=" + currentRetryCount +
                ", retryIntervalTime=" + retryIntervalTime +
                ", errorMsg='" + errorMsg + '\'' +
                ", createTime=" + createTime +
                '}';
    }
 
    /**
     * 检查重试次数是否超过最大值
     *
     * @return
     */
    public boolean checkRetryCount() {
        retryCountCalculate();
        //检查重试次数是否超过最大值
        if (this.currentRetryCount < this.maxTryCount) {
            return true;
        }
        return false;
    }
 
    /**
     * 重新计算重试次数
     */
    private void retryCountCalculate() {
        this.currentRetryCount = this.currentRetryCount + 1;
    }
 
}
//服务重试抽象类:
public abstract class CommonMessageRetryService {
 
    private static final Logger log = LoggerFactory.getLogger(CommonMessageRetryService.class);
 
    @Autowired
    private RabbitTemplate rabbitTemplate;
 
    @Autowired
    private MongoTemplate mongoTemplate;
 
 
    /**
     * 初始化消息
     *
     * @param message
     */
    public void initMessage(Message message) {
        log.info("{} 收到消息: {},业务数据:{}", this.getClass().getName(), message.toString(), new String(message.getBody()));
        try {
            //封装消息
            MessageRetryDTO messageRetryDto = buildMessageRetryInfo(message);
            if (log.isInfoEnabled()) {
                log.info("反序列化消息:{}", messageRetryDto.toString());
            }
            prepareAction(messageRetryDto);
        } catch (Exception e) {
            log.warn("处理消息异常,错误信息:", e);
        }
    }
 
    /**
     * 准备执行
     *
     * @param retryDto
     */
    protected void prepareAction(MessageRetryDTO retryDto) {
        try {
            execute(retryDto);
            doSuccessCallBack(retryDto);
        } catch (Exception e) {
            log.error("当前任务执行异常,业务数据:" + retryDto.toString(), e);
            //执行失败,计算是否还需要继续重试
            if (retryDto.checkRetryCount()) {
                if (log.isInfoEnabled()) {
                    log.info("重试消息:{}", retryDto.toString());
                }
                retrySend(retryDto);
            } else {
                if (log.isWarnEnabled()) {
                    log.warn("当前任务重试次数已经到达最大次数,业务数据:" + retryDto.toString(), e);
                }
                doFailCallBack(retryDto.setErrorMsg(e.getMessage()));
            }
        }
    }
 
    /**
     * 任务执行成功,回调服务(根据需要进行重写)
     *
     * @param messageRetryDto
     */
    private void doSuccessCallBack(MessageRetryDTO messageRetryDto) {
        try {
            successCallback(messageRetryDto);
        } catch (Exception e) {
            log.warn("执行成功回调异常,队列描述:{},错误原因:{}", messageRetryDto.getSourceDesc(), e.getMessage());
        }
    }
 
    /**
     * 任务执行失败,回调服务(根据需要进行重写)
     *
     * @param messageRetryDto
     */
    private void doFailCallBack(MessageRetryDTO messageRetryDto) {
        try {
            saveMessageRetryInfo(messageRetryDto.setErrorMsg(messageRetryDto.getErrorMsg()));
            failCallback(messageRetryDto);
        } catch (Exception e) {
            log.warn("执行失败回调异常,队列描述:{},错误原因:{}", messageRetryDto.getSourceDesc(), e.getMessage());
        }
    }
 
    /**
     * 执行任务
     *
     * @param messageRetryDto
     */
    protected abstract void execute(MessageRetryDTO messageRetryDto);
 
    /**
     * 成功回调
     *
     * @param messageRetryDto
     */
    protected abstract void successCallback(MessageRetryDTO messageRetryDto);
 
    /**
     * 失败回调
     *
     * @param messageRetryDto
     */
    protected abstract void failCallback(MessageRetryDTO messageRetryDto);
 
    /**
     * 构建消息补偿实体
     * @param message
     * @return
     */
    private MessageRetryDTO buildMessageRetryInfo(Message message){
        //如果头部包含补偿消息实体,直接返回
        Map<String, Object> messageHeaders = message.getMessageProperties().getHeaders();
        if(messageHeaders.containsKey("message_retry_info")){
            Object retryMsg = messageHeaders.get("message_retry_info");
            if(Objects.nonNull(retryMsg)){
                return JSONObject.parseObject(String.valueOf(retryMsg), MessageRetryDTO.class);
            }
        }
        //自动将业务消息加入补偿实体
        MessageRetryDTO messageRetryDto = new MessageRetryDTO();
        messageRetryDto.setBodyMsg(new String(message.getBody(), StandardCharsets.UTF_8));
        messageRetryDto.setExchangeName(message.getMessageProperties().getReceivedExchange());
        messageRetryDto.setRoutingKey(message.getMessageProperties().getReceivedRoutingKey());
        messageRetryDto.setQueueName(message.getMessageProperties().getConsumerQueue());
        messageRetryDto.setCreateTime(new Date());
        return messageRetryDto;
    }
 
    /**
     * 异常消息重新入库
     * @param retryDto
     */
    private void retrySend(MessageRetryDTO retryDto){
        //将补偿消息实体放入头部,原始消息内容保持不变
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setContentType(MessageProperties.CONTENT_TYPE_JSON);
        messageProperties.setHeader("message_retry_info", JSONObject.toJSON(retryDto));
        Message message = new Message(retryDto.getBodyMsg().getBytes(), messageProperties);
        rabbitTemplate.convertAndSend(retryDto.getExchangeName(), retryDto.getRoutingKey(), message);
    }
 
 
 
    /**
     * 将异常消息存储到mongodb中
     * @param retryDto
     */
    private void saveMessageRetryInfo(MessageRetryDTO retryDto){
        try {
            mongoTemplate.save(retryDto, "message_retry_info");
        } catch (Exception e){
            log.error("将异常消息存储到mongodb失败,消息数据:" + retryDto.toString(), e);
        }
    }
}
//服务监听类:
@Component
public class OrderServiceListener extends CommonMessageRetryService {
 
    private static final Logger log = LoggerFactory.getLogger(OrderServiceListener.class);
 
    /**
     * 监听订单系统下单成功消息
     * @param message
     */
    @RabbitListener(queues = "mq.order.add")
    public void consume(Message message) {
        log.info("收到订单下单成功消息: {}", message.toString());
        super.initMessage(message);
    }
 
 
    @Override
    protected void execute(MessageRetryDTO messageRetryDto) {
        //调用扣减库存服务,将业务异常抛出来
    }
 
    @Override
    protected void successCallback(MessageRetryDTO messageRetryDto) {
        //业务处理成功,回调
    }
 
    @Override
    protected void failCallback(MessageRetryDTO messageRetryDto) {
        //业务处理失败,回调
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@淡 定

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

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

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

打赏作者

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

抵扣说明:

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

余额充值