面向面试编程:消息队列——幂等性和顺序性

443 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情

面试官:如何保证消息不被重复消费?

幂等性

重复消费不可怕,可怕的是没考虑到重复消费之后怎么保证幂等性,一条数据重复出现两次,数据库里只有一条数据,这就保证了幂等性。需要结合业务来思考。

  • 假如拿一个数据要写库,先根据主键查一下,如果有这个数据,那就不插入,update就可以;
  • 如果是写redis,那没问题,每次都是set,天然幂等性;
  • 如果是其他场景,那做的稍微复杂一点,需要让生产者发送每条数据时,里面加一个全局唯一id,然后这边消费带了之后,先根据这个id去redis中查一下,之前是否消费过,如果没消费过,就继续处理然后写入redis,如果消费过了,那就不处理了,保证不重复处理相同的消息即可;
  • 基于数据库的唯一键来保证重复数据不会重复插入多条,拿到数据时,每次重启可能会有重复,因为kafka消费还没来得及提交offset,重复数据拿到了以后,我们插入时因为有唯一键的约束,所以只会插入报错,不会导致数据库中出现脏数据;

所以说MQ的幂等性,需要结合具体业务来看。

面试官:如何保证消息的顺序性?

顺序性

RabbitMQ

如果有多个消费者,给每一个消费者指定一个queue,将需要保证顺序性的消息,只发送给一个queue,这个queue对应的消费者去顺序消费。

kafka

写入一个partition中的数据一定是有顺序性的。

生产者在写的时候,可以指定与一个key,比如说订单key,这个订单相关的数据一定会被发送到一个partition中,而且这个partition中的数据是有顺序的。

一个消息从partition中取出来的时候,一定是有顺序的。

当消费者内部多线程并发处理消息时,可能会出现顺序问题。此时如果要保证顺序性,需要内存队列来处理,将相同key需要顺序处理的消息放入同一个队列,指定一个线程去从这个内存队列中取数据,这样来保证顺序性。

如果消费者是单线程处理消息,处理一条消息是几十毫秒,那么一秒钟只能处理几十条数据,这个吞吐量太低了。

如果消费者多线程并发处理消息,4核8G的机器,单机开32个线程,最高每秒能处理上千条消息。

RocketMQ

消息有序指的是可以按照消息的发送顺序来消费(FIFO)。RocketMQ可以严格的保证消息有序,可以分为分区有序或者全局有序。

顺序消费原理

在默认的情况下消息发送会采取Round Robin轮询方式把消息发送到不同的queue(分区队列);而消费消息的时候从多个queue上拉取消息,这种情况发送和消费是不能保证顺序。但是如果控制发送的顺序消息只依次发送到同一个queue中,消费的时候只从这个queue上依次拉取,则就保证了顺序。当发送和消费参与的queue只有一个,则是全局有序;如果多个queue参与,则为分区有序,即相对每个queue,消息都是有序的。

使用顺序消息

首先要保证消息是有序进入MQ的,消息放入MQ之前,对id等关键字进行取模,放入指定messageQueue,consume消费消息失败时,不能返回reconsume——later,这样会导致乱序,应该返回suspend_current_queue_a_moment,意思是先等一会,一会儿再处理这批消息,而不是放到重试队列里。

顺序消息消费

消费时,同一个OrderId获取到的肯定是同一个队列。从而确保一个订单中处理的顺序。