rabbitmq异步处理的bug

1 篇文章 0 订阅

上周在完成利用rabbitmq异步发送邮件的需求中,意外发现了一个很微妙的bug,在此记录一下。本篇文章中的具体业务实现,读者可以忽略,主要看问题产生的缘由以及解决方法

公司需求,JavaMail发送邮件进行异步处理,邮件在发送后的一分钟之内可以撤回。

我的方案:前台提交发送邮件请求,后台定时任务扫描,比对发送邮件的创建时间,如果已经超过一分钟,则生产MQ消息,消息消费进行实际的邮件发送操作。

生产者(定时任务)和消费者(发送邮件)相关代码如下。

/**
 * 生产者:定时任务
 * @param outbox
 */
@XxlJob(value = "mailSentTask")
@Transactional(rollbackFor = Exception.class)
public ReturnT<String> MailSentTask(String param){
    ImmutableMap<String, Object> params = ImmutableMap.of("boxType", MailCenterConstant.BoxType.OUTBOX,
            "sentStatus",MailCenterConstant.SentStatus.WAITING, "drawbackTime",MailCenterConstant.DRAW_BACK_TIME);
    //查询出可以发送的邮件
    List<MailCenterOutboxDTO> outboxs = outboxMapper.outboxToMq(params);
    if(CollectionUtils.isEmpty(outboxs)){
        return ReturnT.SUCCESS;
    }

    for(MailCenterOutboxDTO outbox : outboxs){
        //修改邮件的状态为发送中,避免因邮件过大,发送时长超过1分钟时的重复发送
        this.updSending(outbox, MailCenterConstant.SentStatus.SENDING);
        MailOutboxMsg mailOutboxMsg = new MailOutboxMsg();
        BeanUtils.copyProperties(outbox, mailOutboxMsg);
        ImmutableMap<String, Object> attachParams = ImmutableMap.of("mailId", mailOutboxMsg.getId(), "boxType", mailOutboxMsg.getBoxType());
        List<MailAttachMsg> attachList = attachMapper.getAttachList(attachParams);
        mailOutboxMsg.setAttaches(attachList);
        amqpTemplate.convertAndSend(TaskQueueConstant.MAIL_CENTER_OUTBOX_QUEUE, mailOutboxMsg);
        log.info("推送发件至消息队列,时间:{} ,发件: {} ,收件{}", DateUtil.getDateTimeString1(new Date()), mailOutboxMsg.getSentEmail(), mailOutboxMsg.getToEmail());
    }
    return ReturnT.SUCCESS;
}
/**
 * 消费者:发送邮件
 * @param outbox
 */
@RabbitListener(queuesToDeclare = @Queue(TaskQueueConstant.MAIL_CENTER_OUTBOX_QUEUE))
public void sendMail(MailOutboxMsg outbox) throws Exception {
	log.info("发送邮件消息消费,outboxId:" + outbox.getId() + ",sentEmail:" + outbox.getSentEmail() + ",toEmail:" + outbox.getToEmail());
	MailCenterOutbox _outbox = outboxMapper.selectByPrimaryKey(outbox.getId());
	log.info("发送邮件消息消费:" + outbox.toString());
	log.info("发送邮件消息消费:" + _outbox.toString());

	if(_outbox == null){
		log.error("发送邮件消息消费:找不到id为{}对应的发件邮件!", outbox.getId());
		return;
	}
	//防止重复消费
	if(_outbox != null && !MailServerConstant.SentStatus.SENDING.equals(_outbox.getSentStatus())){
		log.error("发送邮件消息消费:id为{}的邮件状态不为8!", outbox.getId());
		return;
	}
	//发送邮件
	outbox = sentService.sendEmail(outbox);
}

一个发送消息,一个消费消息,消费端还做了去重判断,看起来很完美,没什么问题。开发服测试,邮件发送也无误,所以就提测了。

然后测试的时候发现,偶尔会有一两封邮件无法发送,一直停留在“发送中”的状态。

我直接拉出后台打印的日志,一看才发现,卡在“发送中”的邮件,在消费端的状态为“待发送”,说明生产端的DB操作尚未完成,消费端就已经开始进行状态判断了。但是生产端的代码分明就是同步的,为什么会产生异步的效果呢?

原来是因为我的这个事物注解导致的。

这个注解是在方法执行完,并且无异常的时候才会commit到DB,而这个时候消息早就发送出去了,所以可能会导致时间差的出现,这也解释了为什么这个bug是偶发性的。

于是,我把生产端和消费端的代码稍作修改,bug解决。生产端去掉了事物,并增加了发送消息的异常处理;消费端消费消息前,睡眠等待3秒(实际没必要,但我被这个bug整的心有余悸了),增加了更新重置状态的操作。

/**
 * 生产者:定时任务
 * @param outbox
 */
@XxlJob(value = "mailSentTask")
public ReturnT<String> MailSentTask(String param){
    ImmutableMap<String, Object> params = ImmutableMap.of("boxType", MailCenterConstant.BoxType.OUTBOX,
            "sentStatus",MailCenterConstant.SentStatus.WAITING, "drawbackTime",MailCenterConstant.DRAW_BACK_TIME);
    //查询出可以发送的邮件
    List<MailCenterOutboxDTO> outboxs = outboxMapper.outboxToMq(params);
    if(CollectionUtils.isEmpty(outboxs)){
        return ReturnT.SUCCESS;
    }

    for(MailCenterOutboxDTO outbox : outboxs){
        //修改邮件的状态为发送中,避免因邮件过大,发送时长超过1分钟时的重复发送
        this.updSending(outbox, MailCenterConstant.SentStatus.SENDING);
        MailOutboxMsg mailOutboxMsg = new MailOutboxMsg();
        BeanUtils.copyProperties(outbox, mailOutboxMsg);
        ImmutableMap<String, Object> attachParams = ImmutableMap.of("mailId", mailOutboxMsg.getId(), "boxType", mailOutboxMsg.getBoxType());
        List<MailAttachMsg> attachList = attachMapper.getAttachList(attachParams);
        mailOutboxMsg.setAttaches(attachList);
        try {
            amqpTemplate.convertAndSend(TaskQueueConstant.MAIL_CENTER_OUTBOX_QUEUE, mailOutboxMsg);
        }catch (Exception e){
            //发送消息出现异常的话,需要重置发件状态,等待下一分钟重新发送
            this.updSending(outbox, MailCenterConstant.SentStatus.WAITING);
        }
        log.info("推送发件至消息队列,时间:{} ,发件: {} ,收件{}", DateUtil.getDateTimeString1(new Date()), mailOutboxMsg.getSentEmail(), mailOutboxMsg.getToEmail());
    }
    return ReturnT.SUCCESS;
}
/**
 * 消费者:发送邮件
 * @param outbox
 */
@RabbitListener(queuesToDeclare = @Queue(TaskQueueConstant.MAIL_CENTER_OUTBOX_QUEUE))
public void sendMail(MailOutboxMsg outbox) throws Exception {
    Thread.sleep(3000);
	log.info("发送邮件消息消费,outboxId:" + outbox.getId() + ",sentEmail:" + outbox.getSentEmail() + ",toEmail:" + outbox.getToEmail());
	MailCenterOutbox _outbox = outboxMapper.selectByPrimaryKey(outbox.getId());
	log.info("发送邮件消息消费:" + outbox.toString());
	log.info("发送邮件消息消费:" + _outbox.toString());

	if(_outbox == null){
		log.error("发送邮件消息消费:找不到id为{}对应的发件邮件!", outbox.getId());
		return;
	}
	//防止重复消费
	if(_outbox != null && !MailServerConstant.SentStatus.SENDING.equals(_outbox.getSentStatus())){
		log.error("发送邮件消息消费:id为{}的邮件状态不为8!", outbox.getId());
        if(MailServerConstant.SentStatus.WAITING.equals(_outbox.getSentStatus())){
            //如果生产端DB操作还未完成,直接更新会“待发送”,等待下一次发送
            Thread.sleep(3000);
            outboxMapper.updateByPrimaryKey(_outbox);
		}
		return;
	}
	//发送邮件
	outbox = sentService.sendEmail(outbox);
}

我以前写代码很喜欢用事物,觉得这就是万能的,发生异常也不会污染数据。这个bug让我认识到,对事物的使用,也要分具体业务情况。不合理的滥用,可能会导致一些莫名其妙的bug。。。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值