一、系统里为什么要用消息队列
答题思路:
1、你们公司项目里有个什么场景?
2、这个场景有什么技术挑战,如果不用MQ会很麻烦,用了MQ会带来很多好处
参考:
1、比较核心的使用场景有3个
1)解耦:A要发送数据给B,过几天又要发送数据给C,过几天又要发送数据给D,这会导致A频繁改动代码。并且现在往B、C、D发送,如果B挂了,其他没挂,那我发的数据需要缓存重试吗?
2)异步:A调用B、B调用C、C调用D,如果全用接口,只有有一个地方调用慢,整个请求都会变慢,客户使用就会觉的很卡
3)削峰:0到12点每秒并发只有100个,12点后每秒并发达到10000个,并发暴增导致接口调用延迟,客户使用就会觉的很卡
当然还有一些其他场景,比如实现分布式事务最终一致性,但主要是上面3个场景
二、消息队列有什么缺点
1、缺点
1)系统变复杂,可用性降低。比如MQ挂了导致系统崩溃
2)系统要考虑的东西变多,进而导致系统复杂性变高。比如怎么防止消息丢失?怎么防止消息重复消费?怎么防止消息被顺序消费?
3)会有一致性问题。比如A给B、C、D都发消息,B和C成功了,D失败了,此时该怎么办?
三、四种MQ的区别,各自适应什么场景
四、如何保证消息中间件的高可用
1、RabbitMQ的高可用(RabbitMQ不是分布式的)
1)单机模式
2)普通集群模式
由于RabbitMQ不是分布式的,只能集群部署,所以实际上数据只能存在一台机器上。比如有3个RabbitMQ节点,数据存在节点A上,当消费者连接节点B、C消费时,这2个节点会从A拉取消息给消费者。
缺点:
- 会在RabbitMQ集群内部产生大量的数据传输
- 几乎没有可用性保证,如果节点A宕机,那么整个集群就挂了
3)镜像集群模式
与普通集群模式不同,镜像集群模式会把数据同步到每一个节点。可以在RabbitMQ的管理控制台中设置策略是镜像集群模式
缺点:
- 没有扩展性,当数据量很大时,加机器也没用,因为它会同步所有数据,占据了很大的硬盘空间
2、Kafka的高可用(Kafka是分布式的)
一个Topic分为多个partition,每个partiion存在不同的broker上,每个partition就放一部分数据。主从备份、leader选举,防止节点宕机导致数据缺失
3、RocketMQ的高可用
一个Topic分为多个MessageQueue,这些MessageQueue分布在不同的Broker上,每个MessageQueue对应一份ConsumeQueue文件(路径名:store/consumequeue/{TopicName}/{QueueId})。一个Broker上可以有多个Topic,但数据都存储在同一个CommitLog文件中。ConsumeQueue文件不存储具体的数据,只存储数据的tag和在CommitLog文件中的offset
五、如何保证不重复消费数据
消费者端通过业务来保证不重复消费数据。比如每次消费时,先查数据库看有没有这条记录了,没有再insert,有的话就不消费
六、如何保证消息不丢失
数据丢失可能的原因:
- 生产者发送数据时,数据丢失
- 生产者发送消息到了中间件,中间件暂存在内存中,但此时却宕机了,导致内存中的消息没了
- 消费者消费了消息,但还没处理就挂掉了,中间件以为消费者消费完了
解决方案:
- 使用消息中间件提供的事务消息功能,保证生产者发送消息不丢失
- 让中间件收到消息后回调生产者,告诉生产者接收成功还是失败,保证生产者发送消息不丢失
- RabbitMQ开启持久化,保证写入到中间件的数据不会丢失
- RocketMQ开启同步刷盘,保证写入到中间件的数据不会丢失
- Kafka设置4个参数:在topic设置replication.factor > 1,保证至少2个副本;在kafka服务端设置min.insync.replicas > 1,要求一个leader至少有一个follower还存活;在生产者端设置acks = all,要求每条数据必须写入所有副本才算成功;在生产者端设置retries = MAX,如果写入失败,就不断重试,卡在这里了
- 消费者端消费成功后,才返回ack给中间件,保证消费者不会丢失数据
七、如何保证消息是顺序的
生产者只写入一个Queue/Partition,消费端只让一台机器来消费,消费时使用单线程或内存队列来消费
八、发生生产事故,导致挤压了几百万的消息未消费该怎么办
- 如果消息不重要,可以丢失,那么修改消费者代码,将积压的消息都不做任何处理直接消费掉。然后再改回来,正常处理实时消息
- 如果MessageQueue数量明显大于消费者机器数,那么就临时增加机器给消费者,提高消费速度,尽快将积压的消息处理完,但要注意数据库等能否抗住
- 如果MessageQueue数量跟消费者机器数差不多,那么增加机器给消费者也没有用。可以修改消费者代码,将积压的消息写入新的Topic,Topic的MessageQueue多弄些,再增加机器给消费者消费这个新的Topic。最后再改回来
九、如果让你自己设计一个消息中间件,你会怎么做
支持扩容,数据要落入磁盘,落入磁盘要顺序写(效率高),Raft协议实现高可用,数据零丢失
十、缓存+数据库不一致解决方案
初步方案:先删除缓存,再更新数据库。但这样在高并发下会有问题,比如线程A删除了缓存,正准备更新数据库,此时线程B需要查询数据,发现缓存里没有,就去数据库里查询并放入缓存。这样依旧会导致缓存与数据库不一致。
进阶方案:将请求放入队列中,串行执行。比如一个更新请求,一个查询请求,都进入了队列中。更新请求先删缓存,再更新数据库,查询请求查数据库,再更新缓存。这样就能做到缓存与数据库一致。但效率不高,所以为了提高效率,不能所有的查询请求都入队列。最好是如果有更新请求进来了,就入队列,然后只放一个查询请求进队列,其他的查询请求等待,等到队列中的更新和查询请求执行完了,再把其他的查询请求放行。如果更新的并发操作比较多,都放在一个串行队列中,那么最后一个更新操作就会花费较多的时间等待前面的完成,所以可以串行队列也弄几个,更新同一个表的进同一个队列,提高一下效率。
本文探讨了消息队列在系统解耦、异步处理和流量削峰等核心场景中的应用,以及其带来的系统复杂性和一致性问题。同时,对比了RabbitMQ、Kafka和RocketMQ在高可用性上的策略。针对消息丢失和重复消费,提出了相应的解决方案,并分析了大量未消费消息的应急策略。最后,讨论了设计消息中间件的关键考虑因素,以及缓存与数据库一致性问题的处理方案。
172万+

被折叠的 条评论
为什么被折叠?



