MQ问题收集

MQ问题收集

MQ的优缺点

优点:

  • 异步处理:例如短信通知、终端状态推送、App推送、用户注册等
  • 数据同步:业务数据推送同步
  • 重试补偿:记账失败重试
  • 系统解耦:通讯上下行、终端异常监控、分布式事件中心
  • 流量消峰:秒杀场景下的下单处理
  • 发布订阅:HSF的服务状态变化通知、分布式事件中心
  • 高并发缓冲:日志服务、监控上报

使用消息队列比较核心的作用就是:解耦异步削峰

缺点:

  • 系统可用性降低系统引入的外部依赖越多,越容易挂掉?如何保证消息队列的高可用?
  • 系统复杂度提高怎么保证消息没有重复消费?怎么处理消息丢失的情况?怎么保证消息传递的顺序性?
  • 一致性问题A系统处理完了直接返回成功了,人都以为你这个请求就成功了;但是问题是,要是BCD三个系统那里,BD两个系统写库成功了,结果C系统写库失败了,咋整?你这数据就不一致了。

重复消费

现在消息队列一般都能保证atleastonce的,也就是消息至少一次投递。
为什么会出现重复消费的问题呢?
通常都是由于网络原因造成的?
通常消息被成功消费后消费者都会发送一个成功标志给MQ,MQ收到这个标志就表示消息已经成功消费了,就不会在发送给其他消费者了。
但是如果因为网络这个标志没有送到MQ就丢失了,MQ就认为这个消息没有被成功消费,就会再次发送给其他消费者消费,就造成了重复了。

幂等性需要根据业务需求来具体看,但是主要的原理就是去重一般可分为强校验、弱校验

  • 强校验一般与金融相关的操作都是强校验
    比如消费者是一个打款服务,在付款成功后都加一条流水纪录。且两个操作放入一个事务中。
    再次消费的时候就去流水表查一下有没有这条纪录,如果有表示已经消费过了,直接返回。流水表也能起到对账的作用!
    一些简单的场景也可以依赖数据库唯一约束实现
  • 弱校验这个就没那么严格,重复一下也没那么重要的情况。
    可以将ID保存在redisset中,过期时间看情况设置。
    如果ID不能保证唯一可以选择生产方生成一个token存入redis,消费方在消费后将其删除(redis的操作能够保证其原子性,删除失败会返回0)

RocketMQDedupListener

消息丢失

一般来讲消息丢失的途径有三个:

  • 生产者弄丢数据
    主流的MQ都有确认机制或者事务机制,可以保证生产者将消息送达到MQ。比如RabbitMQ就有事务模式和生产者confirm模式。
  • 消息队列弄丢数据
    一般只要开启MQ的持久化磁盘配置就能解决这个问题,写入了磁盘就可以.
  • 消费者弄丢数据
    消费者丢数据一般是因为采用了自动确认消息模式。MQ收到确认消息后会删除消息,如果这时消费者异常了,那消息就没了。改用手动确认就能解决这个问题!

顺序消息

顺序消息的场景可能用的比较少,但是还是有的比如一个电商的下单操作,下单后先减库存然后生成订单,这个操作就需要顺序执行的
那怎么保证顺序呢?

  1. 首先生产者需要保证入队的顺序,入队都是乱的那再厉害的MQ也不行.
  2. 一般的MQ都能保证内部Queue是FIFO的(先进先出),但是只是针对一个Queue,所以在发送消息的时候可以使用Hash取模法将同一个操作的消息发送到同一个Queue里面,这样就能保证出队时是顺序的了。
  3. 消费者也需要注意,如果多个消费者同时消费一个队列。一样可能出现顺序错乱的情况。这就相当于是多线程消费了!

消息堆积如何处理?

答案:主要是消息的消费速度跟不上生产速度,从而导致消息堆积。解决思路:

1、可能是刚上线的业务,或者大促活动,流量评估不到位,这时需要增加消费组的机器数量,提升整体消费能力

2、也可能是消费端的问题,正常情况,一条消息处理需要10ms,但是优化不到位或者线上bug,现在要500ms,那么消费端的整体处理速度会下降50倍。这时,我们就要针对性的排查业务代码。以前就出现过这个问题,当时是数据库的一条sql没有命中索引,导致单条消息处理耗时拉长,进而导致消息堆积,线上报警。

如何保证数据一致性问题?

答案:为了解耦,引入异步消息机制。先进行本地数据库操作,处理成功后,再发送MQ消息,由消费端进行后续操作。比如:电商订单下单成功后,要通知扣减库存。

这两者一定要保证事务操作,否则就会出现数据不一致问题。这时候,我们就需要引入事务消息来解决这个问题。

另外,在消费环节,也可能出现数据不一致情况。我们可以采用最终一致性原则,增加重试机制

事务消息是如何实现?

  1. 生产者先发送一条半事务消息到MQServer
  2. MQServer收到消息后返回ack确认
  3. 生产者开始执行本地事务
  4. 如果本地事务执行成功,发送commitMQServer,如果失败,发送rollbackMQserver
  5. 如果MQServer时间未收到生产者的二次确认commitrollbackMQServer对生产者发起反向回查
  6. 生产者查询事务执行最终状态
  7. 根据查询事务状态,再次提交二次确认到MQServer

img

Kafka如何实现高吞吐量?

答案:

1、消息的批量处理

2、消息压缩,节省传输带宽和存储空间

3、零拷贝

4、磁盘的顺序写入

5、pagecache页缓存,kafka仅写入内存,由操作系统异步将缓存中的数据刷到磁盘,以及高效的内存读取

6、分区设计,一个逻辑topic下面挂载N个分区,每个分区可以对应不同的机器消费消息,并发设计。

Kafka为什么性能这么快?4大核心原因详解

页缓存技术

为什么Kafka性能这么高?当遇到这个问题的时候很多人都会想到上面的顺序写盘这一点。其实在顺序写盘前面还有页缓存(PageCache)这一层的优化。

页缓存是操作系统实现的一种主要的磁盘缓存,以此用来减少对磁盘I/O的操作。具体来说,就是把磁盘中的数据缓存到内存中,把对磁盘的访问变为对内存的访问。为了弥补性能上的差异,现代操作系统越来越“激进地”将内存作为磁盘缓存,甚至会非常乐意将所有可用的内存用作磁盘缓存。

当一个进程准备读取磁盘上的文件内容时,操作系统会先查看待读取的数据所在的页(page)是否在页缓存(pagecache)中,如果存在(命中)则直接返回数据,从而避免了对物理磁盘的I/O操作;如果没有命中,则操作系统会向磁盘发起读取请求并将读取的数据页存入页缓存,之后再将数据返回给进程。同样,如果一个进程需要将数据写入磁盘,那么操作系统也会检测数据对应的页是否在页缓存中,如果不存在,则会先在页缓存中添加相应的页,最后将数据写入对应的页。被修改过后的页也就变成了脏页,操作系统会在合适的时间把脏页中的数据写入磁盘,以保持数据的一致性。

对一个进程而言,它会在进程内部缓存处理所需的数据,然而这些数据有可能还缓存在操作系统的页缓存中,因此同一份数据有可能被缓存了两次。并且,除非使用Direct/IO的方式,否则页缓存很难被禁止。此外,用过Java的人一般都知道两点事实,对象的内存开销非常大,通常会是真实数据大小的几倍甚至更多,空间使用率低下;Java的垃圾回收会随着堆内数据的增多而变得越来越慢。基于这些因素,使用文件系统并依赖于页缓存的做法明显要优于维护一个进程内缓存或其他结构,至少我们可以省去了一份进程内部的缓存消耗,同时还可以通过结构紧凑的字节码来替代使用对象的方式以节省更多的空间。如此,我们可以在32GB的机器上使用28GB至30GB的内存而不用担心GC所带来的性能问题。此外,即使Kafka服务重启,页缓存还是会保持有效,然而进程内的缓存却需要重建。这样也极大地简化了代码逻辑,因为维护页缓存和文件之间的一致性交由操作系统来负责,这样会比进程内维护更加安全有效。

Kafka中大量使用了页缓存,这是Kafka实现高吞吐的重要因素之一。虽然消息都是先被写入页缓存,然后由操作系统负责具体的刷盘任务的。

Kafka的零拷贝

先来看看非零拷贝的情况,如下图所示:

img

可以看到数据的拷贝从内存拷贝到Kafka服务进程那块,又拷贝到Socket缓存那块,整个过程耗费的时间比较高。

Kafka利用了LinuxsendFile技术(NIO),省去了进程切换和一次数据拷贝,让性能变得更好,如下图所示:

img

通过零拷贝技术,就不需要把oscache里的数据拷贝到应用缓存,再从应用缓存拷贝到Socket缓存了,两次拷贝都省略了,所以叫做零拷贝。

Kafka为什么不支持读写分离?

答案:我们知道,生产端写入消息、消费端拉取消息都是与leader副本交互的,并没有像mysql数据库那样,master负责写,slave负责读。

这种设计主要是从两个方面考虑:

1、数据一致性。一主多从,leader副本的数据同步到follower副本有一定的延时,因此每个follower副本的消息位移也不一样,而消费端是通过消费位移来控制消息拉取进度,多个副本间要维护同一个消费位移的一致性。如果引入分布式锁,保证并发安全,非常耗费性能。

2、实时性。leader副本的数据同步到follower副本有一定的延时,如果网络较差,延迟会很严重,无法满足实时性业务需求。

综上考虑,读写操作都是针对leader副本进行的,而follower副本主要是用于数据的备份。

MQ框架如何做到高可用性?

答案:以Kafka框架为例,其他的MQ框架原理类似。

Kafka由多个broker组成,每个broker是一个节点。你创建一个topic,这个topic可以划分为多个partition,每个partition存放在不同的broker上,每个partition存放一部分数据,每个partition有多个replica副本。

写的时候,leader会负责把数据同步到所有follower上去,读的时候就直接读leader上的数据即可。

如果某个broker宕机了,没事儿,那个broker上面的partition在其他机器上都有副本,此时会从follower中重新选举一个新的leader出来,大家继续读写那个新的leader即可。这就是所谓的高可用性。

关于Kafka,面试官一般喜欢考察哪些问题?

答案:

  • 消息压缩
  • 消息解压缩
  • 分区策略
  • 生产者如何实现幂等、事务
  • KafkaBroker是如何存储数据?备份机制
  • 为什么要引入消费组?

RabbitMQ延时队列,死信

【RabbitMQ】一文带你搞定RabbitMQ死信队列

【RabbitMQ】一文带你搞定RabbitMQ延迟队列

【RabbitMQ】如何进行消息可靠投递【上篇】

【RabbitMQ】如何进行消息可靠投递【下篇】

参考:

字节跳动面试官这样问消息队列:高可用、不重复消费、可靠传输、顺序消费、消息堆积,我整理了下

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章