在我们实际的开发过程中,我们肯定会用到MQ中间件,常见的MQ中间件有kafka,RabbitMQ,RocketMQ。在使用的过程中,我们必须要考虑这样一个问题,在使用MQ的时候,我们怎么确保消息100%不丢失?

案例背景

以我们熟悉的淘宝系统为例子,在用户下订单的时候,通常会给客户发放一下优惠劵。在整个过程中,交易服务和发优惠劵服务就是通过MQ消息队列进行通信。在交易服务完成后,交易服务可以发送“发一个满100减5的优惠劵”的消息给MQ。优惠劵服务则在消费端消费这个消息,从而实现真正的优惠劵的发放。

MQ的作用

在实际工作中,引入MQ消息最直接的目的就是让系统解耦合流量控制,从而实现系统的高可用和高性能。

  • 系统解耦:用MQ可以隔离系统上下游环境变换带来的不稳定因素。比如无论优惠劵服务需求如何变化,交易服务不用做任何改变。即使优惠劵服务出现故障,交易流程也不会受到影响。从而使交易服务和优惠劵服务达到了解耦的目的。从而使整个系统高可用。
  • 流量控制:当遇到秒杀等流量突增的场景,通过MQ可以实现流量的“削峰填谷”的作用,可以根据下游的处理能力自动调节流量。

但是,引入了MQ虽然实现了系统解耦和流量控制,同时引入引入了新的问题。

  1. 引入MQ实现系统解耦,会影响系统之间的数据传输一致性。在分布式系统中,如果两个节点之间存在数据同步,就会带来数据一致性问题。同理,在使用MQ时,我们也要解决消息生产端和消息消费端的数据一致性问题。
  2. 引入MQ实现流量控制,会使消费端处理能力不足,从而导致消息积压。

在使用MQ时,如何确保消息不丢失?

我们要从如下几个方面分析:如何知道有消息丢失?哪些环节可能丢失消息?如何确保消息不丢失?

网络中传输数据是不可靠的。要想解决如何不丢失消息的问题。首先,我们要知道哪些环节可能丢失消息。

哪些环节可能出现消息的丢失

一条消息从从生产到消费,主要可划为三个阶段:消息的生产阶段,消息的存储阶段,消息的消费阶段。

  1. 消息的生产阶段:从消息被生产出来,然后提交给MQ,只要能正常接收到MQ broker的ack确认响应,就表示发送成功。所以,这个阶段只要处理好返回值和异常,这个阶段是不会出现消息丢失的
  2. 消息的存储阶段:这个阶段一般交由MQ来保证。但是我们需要知道它的原理。比如,Broker会做副本,保证一条消息至少同步给两个节点再返回ack
  3. 消息的消费阶段:消费端从Broker上拉取消息,只要消费端在收到消息后,不立刻发送ack给broker,而是等到执行完业务逻辑之后,在发送消费确认,也能保证消息不丢失。

这个方案看似万无一失,每个阶段都可以保证消息不丢失。但是在分布式系统中,故障是肯定会有的。作为消息生产者,我并不能保证MQ是否弄丢了你的消息,消费者是否消费了你的消息。所以,本着Design For Failure的设计原则,我们需要有一种机制来check消息是否丢失。

如何check消息是否丢失?

总体的方案:在消息生产端,给发出的每一个消息指定一个全局唯一的ID,在消费端做校验。

具体实现:我们可以使用拦截器。在生产端发消息之前,通过拦截器将消息的全局唯一ID注入消息中,然后,在消费端收到消息之后,在通过拦截器检测消息ID或者消费状态。这样实现的好处就是消息的检测不会侵入业务代码中,可以通过ID找到具体丢失的消息,进行进一步的排查。

如何解决消息重复消费的问题

例如,在消息生产过程中,如果出现失败的情况,通过补偿机制会执行重试,重试就可能产生重复的消息,那么我们应该如何解决这个问题?这其实就是消费端幂等性问题(幂等性:就是一条命令执行任意多次所产生的影响和执行一次的影响相同)。只要消费者具备了幂等性,那么重复消费消息的问题也就解决了。



最简单的方案就是,在数据库中建一个消息日志表,这个表记录消息ID和消息执行状态。这个我们消费消息的逻辑变为:在消息日志中增加一个消息记录,再根据消息记录,执行发送优惠劵业务。我们每次都会在插入之前检查该消息是否已存在。这样就不会出现一条消息被多次执行的情况。这里的数据库也可以使用redis/memcache来实现唯一约束方案。

如何解决消息积压问题

消息积压反应的是性能问题。因为消息发送之后才会出现积压,所以这个和消息生产者没有关系。绝大部分MQ单节点每秒几万的处理能力,相对比业务逻辑来说,性能一般不会在MQ的存储上。所以这个问题,我们主要是从消费端入手解决。

如果是线上的突发问题,要临时扩容,增加消费端的数量,与此同时,降级一些非核心业务。通过扩容和降级承担流量。

然后,需要排查异常问题。通过监控/日志等手段分析消费者的业务代码是否出现了问题。

最后,如果是消费端处理能力不足,可以通过水平扩容来提高消费端的并发处理能力。在扩容消费者实例数的时候,必须同步扩容Topic的分区数量,确保消费者实例数和分区数对等。如果只增加消费者数量,不增加分区数。由于分区是单线程消费的,这样扩容没有效果。

比如在Kafka中,一个Topic可以配置多个Partition。数据会被写入多个Partition中,但是kafka约定一个分区只能被一个消费者消费,Topic的partition数量也决定了最大消费者的数量。

除此之外,还有

  • 如何选型消息中间件?
  • 消息中间件中的队列模型与发布订阅模型的区别?
  • 为什么消息队列能实现高吞吐?
  • 序列化、传输协议,以及内存管理等问题?

    等问题。我们下一篇文章再讨论

MQ的消息丢失/重复/积压的问题解决的更多相关文章

  1. RabbitMQ,RocketMQ,Kafka 事务性,消息丢失和消息重复发送的处理策略

    消息队列常见问题处理 分布式事务 什么是分布式事务 常见的分布式事务解决方案 基于 MQ 实现的分布式事务 本地消息表-最终一致性 MQ事务-最终一致性 RocketMQ中如何处理事务 Kafka中如 ...

  2. RabbitMQ:消息丢失 | 消息重复 | 消息积压的原因+解决方案+网上学不到的使用心得

    前言 首先说一点,企业中最常用的实际上既不是RocketMQ,也不是Kafka,而是RabbitMQ. RocketMQ很强大,但主要是阿里推广自己的云产品而开源出来的一款消息队列,其实中小企业用Ro ...

  3. rabbitmq 重复ACK导致消息丢失

    rabbitmq 重复确认导致消息丢失 背景 rabbitmq 在应用场景中,大多采用工作队列 work-queue的模式. 在一个常见的工作队列模式中,消费者 worker 将不断的轮询从队列中拉取 ...

  4. 实际业务处理 Kafka 消息丢失、重复消费和顺序消费的问题

    关于 Kafka 消息丢失.重复消费和顺序消费的问题 消息丢失,消息重复消费,消息顺序消费等问题是我们使用 MQ 时不得不考虑的一个问题,下面我结合实际的业务来和你分享一下解决方案. 消息丢失问题 比 ...

  5. MQ在高并发环境下,如果队列满了,如何防止消息丢失?

    1.为什么MQ能解决高并发环境下的消息堆积问题? MQ消息如果堆积,消费者不会立马消费所有的消息,不具有实时性,所以可以解决高并发的问题. 性能比较好的消息中间件:Kafka.RabbitMQ,Roc ...

  6. Kafka在高并发的情况下,如何避免消息丢失和消息重复?kafka消费怎么保证数据消费一次?数据的一致性和统一性?数据的完整性?

    1.kafka在高并发的情况下,如何避免消息丢失和消息重复? 消息丢失解决方案: 首先对kafka进行限速, 其次启用重试机制,重试间隔时间设置长一些,最后Kafka设置acks=all,即需要相应的 ...

  7. MQ如何解决消息的顺序问题和消息的重复问题?

    一.摘要 分布式消息系统作为实现分布式系统可扩展.可伸缩性的关键组件,需要具有高吞吐量.高可用等特点.而谈到消息系统的设计,就回避不了两个问题: 1.消息的顺序问题 2.消息的重复问题 二.关键特性以 ...

  8. 小程序:前端防止用户重复提交&即时消息(IM)重复发送问题解决

    背景: 最近参与开发的小程序,涉及到即时消息(IM)发送的功能: 聊天界面如下,通过键盘上的[发送]按钮,触发消息发送功能 问题发现: 功能开发完毕,进入测试流程:测试工程师反馈说: 在Android ...

  9. 【MQ】消息队列及常见MQ比较

    一.什么是消息队列 我们可以把消息队列比作是一个存放消息的容器,当我们需要使用消息的时候可以取出消息供自己使用.消息队列是分布式系统中重要的组件,使用消息队列主要是为了通过异步处理提高系统性能和削峰. ...

随机推荐

  1. win10设置Python程序定时运行(设置计划任务)

    今天来设置一下定时执行Pycharm内的脚本: 这个要基于win10 的任务计划程序(设置 > 控制面板 > 系统和安全 > 管理工具 > 任务计划程序) 1. create ...

  2. python requests 使用代理池访问https站点返回乱码

    问题表现: 检查一下正常的请求头里面accept-encoding字段是否包含br,如果包含,果断pip install urllib3[brotli],详见ssl-warnings 记录另外一个问题 ...

  3. ApiDay002_01 正则表达式

    正则表达式 用于检测.测试字符串规则的表达式. 经常用于检测字符串是否符合特定的规则,在网站上经常用于检测用户输入数据是否符合规范: 检测 用户名 是否为 8~10 数字 英文(大小写) 检测 电话号 ...

  4. 005_面试题 Java_传递方式

    面试题: 问:java是值传递还是引用传递? 答:java只有值传递,基本类型传递的是具体的数,引用类型传递的是具体的地址

  5. DDL_操作数据库_创建&查询和DDL_操作数据库_修改&删除&使用

    DDL操作数据库.表 1.操作数据库:CRUD C(Create):创建 创建数据库: create database 数据库名称: 创建数据库判断不存在再创建 create database if ...

  6. 2500-使用MyBatis操作MySQL进行批量更新的注意事项

    原则上一条SQL只更新一条数据库操作,但有时需要批量操作数据,特别是一些DML语句,在操作数据库时,数据库会报出异常,不允许混合语句,此时需要额外配置进行兼容. 例如: Caused by: com. ...

  7. 栈Stack

  8. if条件控制语句和switch语句

    if条件控制语句(判断范围,在一定区间内容进行判断) if 如果(第一个条件) else if 如果(第二个条件 可以无限加) else 否则(只能有一个 上面都不满足的情况下进入) if和else ...

  9. vue中axios配置代理的俩种方式及优缺点

    概述:Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中 当我们使用vue向服务器发送AJAX请求时,我们会遇到跨域问题,一般跨域的解决方案有俩种,一种是官 ...

  10. Redis 15 主从复制

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 概述 主从复制 ...