五、RabbitMQ消息投递案例

消息投递案例

在这里插入图片描述

这里以推送文档到es,做同步更新索引为例

增加配置

  rabbitmq:
    # 使confirmCallback回调生效
    publisher-confirm-type: correlated
    # 使returnsCallback回调生效
    publisher-returns: true
    # 如果配置为false,交换机没有匹配到队列就会丢弃掉消息,而不会触发returnCallback的回调
    template:
      mandatory: true

创建消息体

/**
 * <p>
 *  消息体
 * </p>
 *
 * @author strap
 */
@Data
@Accessors(chain = true)
@AllArgsConstructor
@NoArgsConstructor
public class DocMessage implements Serializable {

    private static final long serialVersionUID = 2136809013109440975L;

    private String messageId;

    /**
     * 文档主键
     */
    private Long documentId;

    /**
     * 文档存放的路径
     */
    private String filePath;

}

创建消息日志类

/**
 * <p>
 * 
 * </p>
 *
 * @author strap
 */
@TableName("broker_message_log")
@ApiModel(value = "BrokerMessageLog对象", description = "消息记录")
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class BrokerMessageLog extends Model<BrokerMessageLog> implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(type = IdType.INPUT)
    private String messageId;

    private String message;

    private Integer tryCount;

    private Integer state;

    private LocalDateTime nextRetry;

    private LocalDateTime createTime;

    private LocalDateTime updateTime;

}

创建历史消息记录类

成功处理完的记录,从消息记录表删除,添加到历史记录表

/**
 * <p>
 *  已经成功消费的消息转到此处存放,即state=1
 * </p>
 *
 * @author strap
 */
@TableName("broker_message_log_history")
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class BrokerMessageLogHistory extends BrokerMessageLog implements Serializable {

    private static final long serialVersionUID = 4922336781826093421L;

    @TableId(type = IdType.NONE)
    private String messageId;

}

生产者代码

/**
 * <p>生产者</p>
 *
 * @author strap
 */
@Component
@Log4j
public class MqProducer {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private BrokerMessageLogMapper brokerMessageLogMapper;

    public MqProducer() {
    }

    public void sendOrder(DocMessage docMessage) throws Exception {
        // 消息投递到交换机,不管是否成功投递到交换机都回调,成功投递到交换机则ack=true
        rabbitTemplate.setConfirmCallback(
                (correlationData, ack, cause) -> {
                    if (ack) {
                        log.info("消息已成功投递到了交换机,但不知道是否路由到队列");
                    } else {
                        log.info("消息未投递到交换机");
                        new BrokerMessageLog()
                                .setState(4)  //消息未投递到交换机
                                .setUpdateTime(LocalDateTimeUtil.now())
                                .update(
                                        Wrappers.<BrokerMessageLog>lambdaUpdate()
                                                .set(
                                                        BrokerMessageLog::getNextRetry, null
                                                ).eq(
                                                        BrokerMessageLog::getMessageId, correlationData.getId()
                                                )
                                );
                    }
                }
        );

        // 消息从交换机分配到队列时失败才回调,假设连交换机都找不到(即只会触发ConfirmCallback返回nack),ReturnsCallback不会触发
        rabbitTemplate.setReturnsCallback(
                returnedMessage -> {
                    log.info("消息投递到队列失败,如可能匹配不到路由键");
                    String messageId = returnedMessage.getMessage().getMessageProperties().getCorrelationId();
                    new BrokerMessageLog()
                            .setState(5)  //消息投递到队列失败
                            .setUpdateTime(LocalDateTimeUtil.now())
                            .update(
                                    Wrappers.<BrokerMessageLog>lambdaUpdate()
                                            .set(
                                                    BrokerMessageLog::getNextRetry, null
                                            ).eq(
                                                    BrokerMessageLog::getMessageId, messageId
                                            )
                            );
                }
        );
        CorrelationData correlationData = new CorrelationData(docMessage.getMessageId());
        rabbitTemplate.convertAndSend(MyDemoConstants.MQConstants.EXCHANGE_NAME, MyDemoConstants.MQConstants.ROUTING_KEY_NAME, docMessage, correlationData);
    }

}

消费者代码

/**
 * <p>消费者</p>
 *
 * @author strap
 */
@Component
@Log4j
public class MqConsumer {

    @Resource
    private DocMessageService docMessageService;

    public MqConsumer() {
    }

    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = MyDemoConstants.MQConstants.QUEUE_NAME, durable = "true",
                            arguments = {
                                    @Argument(name = MyDemoConstants.MQConstants.DEAD_EXCHANGE_LABEL, value = MyDemoConstants.MQConstants.DEAD_EXCHANGE),
                                    @Argument(name = MyDemoConstants.MQConstants.DEAD_ROUTING_KEY_LABEL, value = MyDemoConstants.MQConstants.DEAD_ROUTING_KEY)
                            }),
                    exchange = @Exchange(name = MyDemoConstants.MQConstants.EXCHANGE_NAME, type = "topic"),
                    key = MyDemoConstants.MQConstants.ROUTING_KEY_NAME
            )
    )
    @RabbitHandler
    public void consume(@Payload DocMessage docMessage, @Headers Map<String, Object> headers, Channel channel) throws Exception {
        log.info("消费者已拿到消息:" + docMessage);

        // 消息的下标index,批量确认消息:即确认所有下标小于该值的消息
        Long index = Convert.toLong(headers.get(AmqpHeaders.DELIVERY_TAG));
        // 是否是重复消费
        Boolean isRedeliver = Convert.toBool(headers.get(AmqpHeaders.REDELIVERED));
        if (docMessageService.runTask(docMessage)) {
            // 业务执行成功, 确认消息,但不批量确认
            channel.basicAck(index, false);
            log.info("messageId:" + docMessage.getMessageId() + ", 成功被消费");
        } else if (!isRedeliver) {
            // 不是重复消费,及业务处理未完成,不批量确认,告知MQ重发
            channel.basicNack(index, false, true);
            log.info("messageId:" + docMessage.getMessageId() + ", 被重新投递");
        } else {
            channel.basicNack(index, false, false);
            log.info("messageId:" + docMessage.getMessageId() + ", 进入死信队列");
        }

    }


    /**
     * 处理死信消息
     */
    @RabbitListener(
            bindings = @QueueBinding(
                    value = @Queue(value = MyDemoConstants.MQConstants.DEAD_QUEUE, durable = "true"),
                    exchange = @Exchange(name = MyDemoConstants.MQConstants.DEAD_EXCHANGE, type = "topic"),
                    key = MyDemoConstants.MQConstants.DEAD_ROUTING_KEY
            )
    )
    @RabbitHandler
    public void consumeDeadLetter(@Payload DocMessage docMessage, @Headers Map<String, Object> headers, Channel channel) throws Exception {
        // 消息的下标index,批量确认消息:即确认所有下标小于该值的消息
        Long index = Convert.toLong(headers.get(AmqpHeaders.DELIVERY_TAG));
        channel.basicAck(index, false);
        log.info("死信消息已经被处理:" + docMessage.getMessageId());
        new BrokerMessageLog()
                .setState(3)  //死信消息
                .setUpdateTime(LocalDateTimeUtil.now())
                .update(
                        Wrappers.<BrokerMessageLog>lambdaUpdate()
                                .set(
                                        BrokerMessageLog::getNextRetry, null
                                ).eq(
                                        BrokerMessageLog::getMessageId, docMessage.getMessageId()
                                )
                );
    }

}

业务处理类

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author strap
 */
@Service
@Log4j
public class DocMessageService {

    @Resource
    private MqProducer mqProducer;

    @Resource
    private BrokerMessageLogMapper brokerMessageLogMapper;

    @Resource
    private BrokerMessageLogHistoryMapper brokerMessageLogHistoryMapper;

    public void updateDoc(DocMessage docMessage) throws Exception {
        LocalDateTime now = LocalDateTimeUtil.now();
        boolean add = new BrokerMessageLog()
                .setMessageId(docMessage.getMessageId())
                .setMessage(JSONUtil.toJsonStr(docMessage))
                .setState(0)
                .setCreateTime(now)
                .setTryCount(1)
                .setUpdateTime(now)
                .setNextRetry(now.plus(3, ChronoUnit.MINUTES))// 3分钟后如果状态还是0就取出来重试
                .insert();
        if (add) {
            mqProducer.sendOrder(docMessage);
        }
    }

    // 模拟处理业务,处理成功返回true
    @Transactional(rollbackFor = Exception.class)
    public boolean runTask(DocMessage docMessage) throws Exception {
        BrokerMessageLog brokerMessageLog = brokerMessageLogMapper.selectById(docMessage.getMessageId());
        if (brokerMessageLog == null) {
            return true;
        }
        String filePath = docMessage.getFilePath();
        log.info("filePath:" + filePath);
        // 取路径读取文档获取请求es进行索引更新,这里模拟业务处理 TODO
        boolean success = filePath.length() > 3;
        if (success) {
            // 修改状态并存入历史消息
            BrokerMessageLogHistory logHistory = BeanUtil.copyProperties(brokerMessageLog, BrokerMessageLogHistory.class);
            logHistory.setNextRetry(null).setState(1).setUpdateTime(LocalDateTimeUtil.now());
            brokerMessageLogHistoryMapper.insert(logHistory);
            brokerMessageLog.deleteById();
        }
        return success;
    }

}

定时任务处理类

/**
 * <p></p>
 *
 * @author strap
 */
@Log4j
@ConditionalOnExpression("#{T(cn.hutool.core.util.StrUtil).isNotEmpty(\"${cron.job1}\")}")
@Component
public class MySpringQuartzJob {

    @Resource
    private BrokerMessageLogMapper brokerMessageLogMapper;

    @Resource
    private MqProducer mqProducer;

    public MySpringQuartzJob() {
    }

    @Scheduled(cron = "${cron.job1}")
    public void run() throws Exception {
        log.info("定时任务开始执行");

        Page<BrokerMessageLog> records = brokerMessageLogMapper.selectPage(
                new Page<>(),
                Wrappers.<BrokerMessageLog>lambdaQuery()
                        .eq(BrokerMessageLog::getState, 0)
                        .lt(BrokerMessageLog::getNextRetry, LocalDateTimeUtil.now())
        );
        for (BrokerMessageLog record : records.getRecords()) {
            if (record.getTryCount() > 2) {
                // 已尝试三次,不需要再重发
                new BrokerMessageLog()
                        .setState(2) // 重试多次仍然失败,不再重发
                        .update(
                                Wrappers.<BrokerMessageLog>lambdaUpdate()
                                        .set(
                                                BrokerMessageLog::getNextRetry, null
                                        ).eq(
                                                BrokerMessageLog::getMessageId, record.getMessageId()
                                        )
                        );
            } else {
                new BrokerMessageLog()
                        .setMessageId(record.getMessageId())
                        .setTryCount(record.getTryCount() + 1)
                        .setUpdateTime(LocalDateTimeUtil.now())
                        .updateById();
                mqProducer.sendOrder(JSONUtil.toBean(record.getMessage(), DocMessage.class));
            }
        }

    }

}

业务触发

    @GetMapping("/updateDoc")
    public void updateDoc() throws Exception {
        docMessageService.updateDoc(
                new DocMessage(
                        UUID.randomUUID().toString(true),
                        10251L,
                        "/article/20220322/xxx.doc"
                )
        );
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值