如何避免消息队列的消费方重复消费消息(如何保证消息不丢失)

如何避免消息队列的消费方重复消费消息(如何保证消息)

为什么会产生重复消费?

其实无论是哪种消息队列,造成重复消费的原因都是类似的。正常情况下,消费者在消费消息的时候,消费完毕后,会发送一个确认消息给消息队列,消息队列就知道该消息被消费了,就会将该消息从消息队列中删除。只是不同的队列发出的确认消息形式不同,例如rabbitMQ是发送一个ACK确认消息,rocketMQ是返回一个CONSUM_SUCCESS成功标志,kafaka实际上有个offset的概念(就是每个消息都有offset,kafaka消费过消费后,需要提交offset,让消息队列知道自己已经消费过了。

因为网络传输等等故障,确认信息没有传送到消息队列,导致消息队列不知道这个消费者已经消费过该消息了,再次将消息分发给其他的消费者。

怎么保证消息的幂等性

这个问题需要针对业务场景来答,分以下三种情况:

①比如,你拿到这个消息做数据库的insert操作,那就容易了,给这个消息做一个唯一的主键,那么就算出现重复消费的情况,就会导致主键冲突,避免数据库出现脏数据。

②再比如,你拿到这个消息做redis的set操作,那就容易了,不用解决,因为你无论set几次结果都是一样的,set操作本来就是幂等操作。

③如果上面两种情况还不行,需要准备一个第三方介质,来做消费记录。这个时候可以分场景,看是强校验,还是若校验,选择不同的处理方式。

强校验

比如涉及到金钱的场景

新建一个操作流水表,每次消息过来都要拿着一个全局id(如:promo_item_user这样的唯一标识,天猫双十一活动)去流水表查,有就直接return,不要就下面的流程了,没有就执行后面的逻辑。

弱校验

比如一些不重要的场景,给谁发短信什么的(曾经碰到过验证码丢失的情况)

以redis为例,给消息分配一个全局id,只要消费过该消息,将<id,message>以K-V形式写入redis,**并设置一定过期时间。**那消费者开始消费前,先去redis中查询有没有消费记录即可,先根据这个id去redis里查一下,之前消费过吗?如果没有消费,就继续处理,然后将这个id写redis。如果消费过了,就别处理了,保证别重复处理相同的消息即可。

消息的消费结果如何返回给消息发送方

public enum ConsumeConcurrentlyStatus {
    //消息消费成功
    CONSUME_SUCCESS;
    //消息重试,一般消息消费失败后,rocketMQ为了保证数据的可靠性,具有重试机制
    RECOMSUM_LATER;
}

PushConsumer为了保证消息肯定消费成功,只有使用方明确表示消费成功(return ConsumeConcurrentlyStatus.CONSUME_SUCCESS)rocketMQ才会认为消息消费成功。如果这个时候中途断电,抛出异常等都不会认为成功,都需要进行重发。如果消费消息失败,例如数据库异常,余额不足扣款失败等一切业务认为消息需要重试的场景,只要返回ConsumeConcurrentlyStatus.Reconsume_Later,rocketMQ就会认为这笔消息消费失败了,rocketMQ会将这笔消息重新发回给broker(topic不是原topic而是这个消费者的retry topic),在延迟的某个时间点后,再次投递到ConsumerGroup,如果一直这样重复消费都持续失败到一定次数,默认16次,就会投递到DLQ死信队列。应该可以监控死信队列来做人工干预。如果使用顺序消费的回调的话,只有前者消费成功才能继续消费。

重试的次数只有DefaultPullConsumer才能修改,DefaultPushConsumer不能修改

①如果业务回调没有处理好而抛出异常,会认为是消费失败当ConsumerConcurrentlyStatus.RECONSUME_LATER处理。

②当使用顺序消费的回调MessageListenterOrderly时,由于顺序消费是要前者消费成功才能继续消费,所以没有RECONSUME_LATER的这个状态,只有SUSPEND_CURRENT_QUEUE_A_MOMENT来暂停队列的其余消费,直到原消息不断重试成功为止才能继续消费。

关于消息队列的一些面试问题收集?

什么是消息队列?

回答这个问题之前我们要先知道消息是什么,消息—应用之前传输的数据,消息可以非常简单,比如只包含文本字符窗,消息也可以非常复杂,可能包含嵌入对象。

消息队列因此就是应用之间的一种通信方式,消息发送后可以立即返回,有消息系统来确保信息的可靠传递,消息发布者只管把消息发布到MQ中而不管谁来取,消费者只管从MQ中取消息而不管谁发布的。因此使用者和发布者都不知道对方的存在。

使用了消息队列会有什么缺点?

系统可用性较低:当消息队列死机,系统可能崩溃

系统复杂性增加:加入了消息队列,要考虑很多方便的问题,如:一致性问题,如何保证消费不被重复消费,如何保证次消息的可靠传输。

为什么使用消息列队?

异步,削峰,解耦

异步,解耦

比如在秒杀活动中,下单付款流程就走完了,但是为了增加更多流量,增加了优惠券服务,积分服务,短信服务等,导致下单流程越来越长,因此同步进行这些服务,会导致用户体验不太好。

因此使用消息队列异步实现,那么为什么用线程,线程池来实现????

如果使用线程的话,每次增加一个业务就要调用一个接口,并且还要重新发布系统,代码量大,耦合度高,一旦涉及到大量代码就不可避免bug,排除也很麻烦,流程里面随便一个地方出问题都可能影响其他点。

如果使用消息队列的话,我下单了,我就把我支付成功的消息告诉别的系统,他们收到了去处理就好了,我就走完自己的流程,吧自己的消息发出去,那后面要接入什么系统,直接订阅我发送成功的消息,我支付成功了他监听就好了。


存在一个问题----数据一致性问题?(一般分布式服务都会存在这个问题)

当下单服务自己保证自己的逻辑成功处理了,成功发了消息,但是优惠券服务,积分服务等,他们成功或失败我就不知道了,因此无法保证数据的一致性。

所有的服务都成功才能算这一次下单是成功的,因此需要把下单,优惠券,积分等都放在一个事务里面,要成功一起成功,要失败一起失败。

-------回头再巩固一下分布式事务。

削峰

当我的秒杀活动在00:00开始,那一刻流量疯狂涌进来,我的服务器,redis,MySQL各自的承受能力不一样,如果全部流量照单全收肯定会出现问题。因此,可以把请求放到队列里面,根据服务器的能力每秒消费多少个请求,这样就不会使得服务器被击垮。等流量高峰下去了,服务器也就没有压力了。

消息队列的重复消费场景和解决办法?

假设有这样一个场景,用户下单成功后,我需要去活动页面给他加个消费总额,最后根据这个消费总额给用户适当的奖励。

但是消息队列有重试机制,也就是下游的业务(如积分系统)发生异常了,会抛出异常并且要求重新送一次。但是,不止一个服务服务监听这个消息,还有别的服务(优惠券系统等)也在监听,一旦失败,他们也会要求重发,但是我这里是成功的,重发了,我这里就相当于加了两次!!!

接口幂等:同样的参数调用这个接口,调用多少次得到的结果都是一样的。比如对于同一个订单号我加一次是多少,加N次还是多少。

做幂等会分场景去考虑,看是强校验还是弱校验,比如跟金钱有关我们就需要做强校验,不是很重要的场景就可以做弱校验。

强校验:比如我监听到了用户支付成功的消息,去加消费总额,因此就需要调用加钱的接口,加钱接口下面再调用一个加流水的接口。两个放在一个事务,成功一起成功,失败一起失败。

每次消息过来都要拿着订单号+业务场景这样的唯一标识(如天猫双十一活动)去流水表查,查看有没有这条流水,有就return,没有就执行后面的逻辑。

弱校验:一些不重要的场景,比如发短信什么的,可以把这个id+业务场景唯一标识作为redis的key,放到缓存里面,失效时间根据场景设置。一定时间内的这个消息就去redis里判断。

消息顺序消费的场景?

生产者消费者一般需要保证顺序消息的话,可能就是一个业务场景下的,比如订单的创建,支付,发货,收获。------一个订单号

在rocketMQ中,一个topic下有多个队列,为了保证发送有序,rocketMQ提供了MessageQueueSelector队列选择机制,他有三种实现:SelectMessageQueueByHash,SelectMessageQueueByMachineRoom,SelectMessageQueueByRandom.

我们可以使用Hash取模法,让同一个订单发送到同一个队列中,再使用同步发送,只有同个订单的创建消息发送成功,再发送支付消息。这样,就保证了发送有序。

rocketMQ的topic内的队列机制,可以保证存储满足FIFO。因此只需要消费者顺序消费即可。一个订单发送的时候放在一个队列,同一个订单号Hash一下得到的是一样的结果,得到同一个消费者,来保证消费的有序。

如何保证消息队列是高可用的?

preview

我们知道,引入消息队列会使系统可用性下降。在实际开发中,也不会使用单机模式的消息队列。

以rocketMQ为例:他的集群就有多master模式,多master多slave异步复制模式,多master多slave同步双写模式,多master多slave模式部署架构如上图。

通信过程如下:

Producer与NameSever集群中的其中一个节点(随机选择)建立长连接,定期从NameSever获取Topic路由信息,并向Topic服务的Brocker master建立长连接,且定时(默认30s)向Brocker发送心跳。Producer只能把消息发给Brocker master,但是Consumer不一样,他同时和提供topic服务的Brocker master 和Brocker slave建立长连接,既可以从brocker master订阅消息,也可以从brocker slave订阅消息。

如何保证消费的可靠性传输?

我们在使用消息队列的过程中,应该做到消息不能多消费,也不能少消费。

设计到可靠性传输,也就是消息不丢失,可以从三个角度考虑:

  • 生产者弄丢数据
  • 新消息队列弄丢数据
  • 消费者弄丢数据

消息发送通过不同的重试策略来保证消息的可靠发送,消息存储通过不同的刷盘机制以及多副本来保证消息的可靠存储,消息消费通过至少消费成功一次以及消费重试机制来保证消息的可靠消费。

输,也就是消息不丢失,可以从三个角度考虑:

  • 生产者弄丢数据
  • 新消息队列弄丢数据
  • 消费者弄丢数据

消息发送通过不同的重试策略来保证消息的可靠发送,消息存储通过不同的刷盘机制以及多副本来保证消息的可靠存储,消息消费通过至少消费成功一次以及消费重试机制来保证消息的可靠消费。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值