Java面试题~消息中间件RabbitMQ如何保证消息不丢失且100%投递成功

摘要

RabbitMQ是一款可以用于实现消息通信、服务解耦的分布式中间件,在实际项目中具有许多典型的应用场景,如异步记录日志、业务服务模块异步通信解耦、高并发抢购场景下流量削峰、限流等等,本文我们将以实际项目中典型的业务场景:“发送邮件” 为案例,一起探讨RabbitMQ在发送消息时如何保证消息不丢失且保证其100%投递成功。

题外话~在Java技术面时这无疑是一道高频必问之题

内容

  • 整体业务流程介绍

“发送邮件”这一业务场景想必各位小伙伴都经历过,在业务要求不是很严格的情况下,“邮件发送”可以采用异步的方式加以实现,传统的做法是直接在发送邮件的方法上加上注解 @Async 即可实现;

而在微服务、分布式时代,更多的是利用中间件加以实现,此时MQ便排上了用场,目前市面上比较典型的产品包括:ActiveMQ、RabbitMQ、Kafka 以及 RocketMQ等等,在这里我们以RabbitMQ为主,并由此介绍一下RabbitMQ在发送消息期间如何保证消息不丢失以及保证其100%投递成功呢?

对这一问题,我们还是先给出答案吧,后续我们还会采用代码实战一一验证这些答案:

(1)“发送确认”模式:即生产者通过MQ发送消息后,MQ需要将“已发送成功/失败”反馈给生产者,告知生产者消息已投递成功,此方式可确保消息正确地发送至RabbitMQ

(2)“消费确认”模式:即消费者监听到MQ中队列的消息并执行完对应的业务逻辑后,需要发送“消息已被成功监听、消费”反馈给MQ,此方式可保证接收方正确接收并消费了消息,消费成功后消息将从队列中移除

(3)“避免消息重复投递”:生产者在生产消息时,MQ内部会针对每条消息生成一个MsgId,该标识可以作为去重的依据(消息投递失败并重传),避免重复的消息进入队列;

(4)“消息消费时保证幂等性”:这一点可以利用业务本身的特性来实现,即每个业务实体一般都会有一个唯一的ID,就像数据库表中唯一的主键一样,在监听消费处理时根据ID作为去重的依据;

(5)“持久化”:将队列、交换机、消息都设置为持久化模式,确保消息在投递、发送期间出现MQ服务宕机后重启恢复过来时消息依旧存在;

(6)“消息消费重试机制”:指的是消费者在监听、消费、处理消息的过程中出现了异常,导致业务逻辑没有处理成功,此时可以开启“消息重入队列”机制,设置消息重入队列N次 进行 重试消费;

(7)“消息投递补偿机制”:指的是消息在生产、投递期间出现“投递失败”,也就是“发送不成功”的情况,此时可以将其加入到DB中,并开启定时任务,拉取那些投递不成功的消息,重新投递入队列,如此一来便可以保证消息不丢失且准备被投递。

交代完了答案,接下来我们以“发送邮件”这一业务场景为案例,结合实际的代码实战一一验证上述的答案,如下图所示为这一业务场景以及“RabbitMQ保证消息不丢失和100%投递成功”的整体实现流程图:   

  • 整体业务流程核心代码实战

(1)首先,需要在数据库中创建一数据库表msg_log,用于记录RabbitMQ消息的投递以及消费过程,其DDL如下所示:

CREATE TABLE `msg_log` (
  `msg_id` varchar(155) 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='消息投递日志';

紧接着,需要利用Mybatis逆向工程生成该数据库表对应的实体类Entity、SQL操作接口Mapper以及Mapper.xml,在这里就不贴出来了,各位小伙伴自行生成即可(也可以在文末下载对应的源码直接拷贝出来使用即可)

(2)接下来是创建用于发送邮件的控制器MailController,其完整源码如下所示:

@RestController
@RequestMapping("mail")
public class MailController {

    @Autowired
    private MailService mailService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private Environment env;

    @Autowired
    private MsgLogMapper msgLogMapper;

    private static final Snowflake snowflake=new Snowflake(3,2);

    //发送邮件
    @RequestMapping(value = "send/v2",method = RequestMethod.POST)
    public BaseResponse sendMailV2(@RequestBody @Validated MailDto dto, BindingResult result){
        String checkRes=ValidatorUtil.checkResult(result);
        if (StringUtils.isNotBlank(checkRes)){
            return new BaseResponse(StatusCode.InvalidParams.getCode(),checkRes);
        }
        BaseResponse response=new BaseResponse(StatusCode.Success);
        try {
            MsgLog entity=new MsgLog();
            String msgId=snowflake.nextIdStr();

            entity.setMsgId(msgId);
            entity.setExchange(env.getProperty("mq.email.v2.exchange"));
            entity.setRoutingKey(env.getProperty("mq.email.v2.routing.key"));
            entity.setStatus(Constant.MsgStatus.Sending.getStatus());
            entity.setTryCount(0);
            entity.setCreateTime(DateTime.now().toDate());

            dto.setMsgId(msgId);
            final String msg=new Gson().toJson(dto);

            entity.setMsg(msg);
            //在发送消息之前先创建并入库
            msgLogMapper.insertSelective(entity);

            //发送消息
            rabbitTemplate.convertAndSend(entity.getExchange(), entity.getRoutingKey(),msg,new CorrelationData(entity.getMsgId()));

        }catch (Exception e){
            response=new BaseResponse(StatusCode.Fail.getCode(),e.getMessage());
        }
        return response;
    }
}

从该代码中可以得知在RabbitMQ真正发送消息之前,需要将该消息插入数据库,并设置其状态为 “投递中”;与此同时,需要利用“雪花算法”工具生成该消息的全局唯一标识MsgId,并用其充当消息的标识;

更多请见:http://www.mark-to-win.com/tutorial/51081.html

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值