使用消息中间件时,如何保证消息不丢失且仅仅被消费一次

1、如何保证消息不丢失

一条消息从生产到消费这条链路中,有三个地方可能会造成消息丢失,分别如下:

  • 消息从生产者写入到消息队列的过程投递失败。
  • 消息在消息队列中,持久化失败
  • 消息被消费者消费的过程出现异常

1.1 在消息生产过程中投递失败
消息生产者和消息系统一般都是独立部署在不同的服务器上,两台服务器之间要通信就要通过网络来完成,网络不稳定可能会发生抖动,那么数据就有可能会丢失,网络发生抖动会有以下两种情况:

在这里插入图片描述

  • 情形一:消息在传给消息系统的过程中会发生网络抖动,数据直接丢失。
  • 情形二:消息已经达到消息系统,但是消息系统再给生产这服务器返回信息室,网络发生抖动,此时的数据不一定真正的丢失,很可能只是生产者认为数据丢失。

针对消息在消息生产时丢失,可以采用重投机制,当程序检测到网络异常时,小消息再次投到消息系统。但是当重新投递在情形二情况下,可能造成数据重复,如何解决这个问题后面会提到。

1.2 在消息队列中持久化失败
消息系统是可以对消息进行持久化的,一般都是讲消息存储到本地磁盘中,当然也有少数消息中间件支持数据持久化到数据库中,那么消息系统的性能可能会下降。
如果你对redis的持久化有一定的了解的话,你会发现redis在持久化数据时并不是每次新增一条就立即存入本地磁盘,而是将数据先写入到操作系统的page cache中,当满足一定条件时,再将page cache中的数据刷入到磁盘。因为这样可以减少对磁盘的随机I/O操作,我们知道随机I/O操作时非常耗时的,这样也提高了系统的性能,消息中间件也不例外,在持久化时也采用这种方式。

在某些极端情况下,可能会造成page cache中的数据丢失,比如突然断电或者机器异常重启操作。要解决pagecache中数据丢失问题,可采用集群部署的方式,来尽量保证数据不丢失。

1.3 在消费过程中存在消息丢失
消息在消费过程中也是会发生丢失的,而且在消费过程中丢失的概率要比前两种大很多。一条消息消费过程大概分为三步:

  • 消费者拉取消息
  • 消费者处理消息
  • 消费系统更新消费进度
    在这里插入图片描述

第一步在消息拉取消息时会发生网络抖动异常,第二步在处理消息的时候可能发生一些业务异常,而导致而导致流程并没有走完,如果在第一步第二步发生异常的情况下通知消息系统更新消费进度,那么这条失败的消息就永远不会再处理了,自然就丢失了,其实我们的业务并没有跑完。

要避免消息在消费时丢失的情况,可以在消息接收和处理完成之后才更新消费进度,但是在极端情况下会出现消息重复消费的问题,比如某一条消息在处理完成之后消费者宕机了,这时还没有更新消费进度,消费者重启后,这条消息还是会被消费到。

2、如何保证消息只被消费一次
消息系统本身不能保证消息仅被消费一次,因为:

  • 消费本身可能重复
  • 下游系统启动拉取重复
  • 失败重试带来的重复
  • 补偿逻辑导致的重复
    以上几点都有可能造成重复消息,要保证消息仅被消费一次可以利用幂等性来实现

等幂是数学上的概念,就是多次执行同一操作和执行一次操作,最终得到的结果是相同的
从等幂的概念上就可以看出来,就算消息执行多次也不会对系统造成影响,那么在使用消息系统时如何保证幂等性呢?因为生产者和消费者都有可能产生重复消息,所以要在生产者和消费者两端都保证等幂性。

  • 保证生产者等幂性

保证生产者等幂性,再生产消息的时候,利用雪花算法给消息生成一个全局id,在消息系统中维护消息与id的映射关系,如果在映射表中已经存在相同id,则丢掉这条消息,虽然消息被投递了两次,但实际上就保存了一条,避免了消息重复问题。
生产者等幂性跟所选的消息中间件有关系,因为绝大多数情况下消息系统不需要我们自己实现,所以等幂性不太好控制的,消费者等幂性才是我们开发人员控制的重点方向。

  • 保证消费者等幂性

在通用层面,在消费消息时产生全局唯一id,消息被处理成功后,把这个全局id存入数据库中,在处理下一条消息之前,先从数据库中查询这个全局id是否存在,如果存在,则直接放弃该消息。

利用这个全局id就实现了消息等幂性,伪代码如下:

boolean isIDExisted = selectByID(ID); // 判断ID是否存在
if(isIDExisted) {
  return; //存在则直接返回
} else {
  process(message); //不存在,则处理消息
  saveID(ID);   //存储ID
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Good.J

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

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

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

打赏作者

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

抵扣说明:

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

余额充值