分布式&高并发&高可用
# 消息队列连环问
# 在项目中用过消息队列吗?
# 简单介绍下在项目里是怎么使用消息队列的?
# 为什么使用消息队列?
其实就是想问消息队列都有哪些使用场景,然后项目里具体是什么场景,并结合场景说明消息队列是什么。
消息队列比较核心的使用场景有3个:解耦、异步、削峰。
解耦:
考虑一下负责的系统中是否有类似的场景,一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦。 但是这个调用不需要直接同步调用接口,这时就可以用MQ给异步解耦,需要在简历中体现出来这个内容。
异步:
削峰:
# 使用消息队列都有什么优点和缺点?
优点:解耦、异步、削峰;缺点:降低系统可用性。
# Kafka\ActiveMQ\RabbitMQ\RocketMQ 都有什么区别?
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级 | 万级 | 十万级 | 十万级,一般配合大数据类系统进行实时计算、日志采集等场景 |
Topic数量对吞吐量的影响 | Topic达到几百,几千个时,吞吐量会较小幅度下降,优势,在同等硬件配置下,支撑更多的Topic | Topic从几十到几百个时,吞吐量会大幅下降,在同等配置下,尽量保证Topic数量不过多,如果要支撑大规模Topic,需要增加更多的硬件资源 | ||
时效性 | 毫秒级 | 微秒级,这是RabbitMQ的一大特点,延迟是最低的。 | 毫秒级 | 毫秒级 |
可用性 | 高,基于主从架构实现高可用 | 高,基于主从架构实现高可用 | 非常高,分布式架构 | 非常高,分布式架构,一个数据多个副本,少数机器宕机,不丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 经过参数优化配置可以做到零丢失 | 经过参数优化配置可以做到零丢失 | |
功能支持 | MQ领域的功能极其完备 | 基于ErLang开发,所以并发能力很强,性能极好,延时很低 | MQ功能较为完善,分布式架构,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域实时计算以及日志采集领域被大规模使用,是事实上的标准 |
优劣势总结 | 非常成熟,功能强大,在业内有大量的公司和项目在用,偶尔会有较低概率丢失消息,不过现在社区以及国内应用越来越少,官方社区现在对ActiveMQ 5.x更新维护越来越少,其主要用于解耦和异步,较少在大规模吞吐的场景中使用 | ErLang语言开发,性能好,延时低,吞吐量到万级,MQ功能比较完备,而且开源社区提供的管理界面也非常好用,社区比较活跃。近几年国内使用RabbitMQ的也多了一些,缺点也比较明显,吞吐量低,ErLang在国内开发基础弱,难于研究和定制。 | 接口简单易用,阿里系品牌保证,日处理消息上百亿之多,可以做到大规模吞吐,性能好,分布式扩展也方便,可靠性和可用性都能得到保证,支持大规模Topic数量,支持复杂MQ业务场景,社区活跃度还可以。 | 仅提供较少的核心功能,但是提供超高的吞吐量,毫秒级延时,极高的可用性和可靠性,分布式便于扩展,不过Kafka最好时候支撑较少的Topic数量,保证其超高吞吐量的特性,Kafka唯一的劣势是有可能重复消费消息,那么对数据准确性会造成影响,但在大数据和日志采集中,这些轻微影响可以忽略不计。其天然适合大数据实时计算和日志采集。 |
# 如何保证消息队列的高可用?
MQ高可用是必问的
# *RabbitMQ**的高可用
RabbitMQ是比较有代表性的,因为它基于主从做高可用,RabbitMQ有三种模式:单机模式、普通集群模式、镜像集群模式。
- 单机模式
- 普通集群模式:
在多台集群上启动多个RabbitMQ实例,但是创建的Queue只会放在一个RabbitMQ实例上,但每个实例都同步Queue的元数据。 实际上如果连接到另外一个实例,那么那个实例会从Queue所在实例上拉取数据过去。
这种方式比较麻烦,没做到所谓的分布式,就是普通集群。因为这会导致消费者每次随机连接一个实例然后拉取数据, 要么固定连接那个Queue所在实例消费数据,前者有数据拉取的开销,后者将导致单实例性能瓶颈。而且如果存放Queue的实例宕机了, 会导致接下来其他实例无法从那个实例拉取数据,如果开启了消息持久化,让RabbitMQ落地存储消息的话,消息不一定丢失,但是得等这个实例恢复, 然后才可以继续从Queue拉取数据。
这种方案就没有所谓的高可用可言了,普通集群方案主要是提高吞吐量,就是说让集群中的多个节点来服务某个Queue的读写操作。
- 镜像集群模式
这种模式,才是所谓的RabbitMQ的高可用模式,跟普通集群模式不一样的是,创建的Queue,无论元数据还是Queue里的消息都会存在于多个 实例,然后每次写消息到Queue时,都会自动把消息通过到多个实例的Queue中。
这样,好处在于,任何一个机器宕机,别的机器都可以用。缺点在于,这个消息同步的性能开销太大,网络带宽压力较大,而且也没有所谓的扩展性。 如果某个Queue的负载很重,加机器也没有立杆见影的效果,没有办法线性扩展Queue。
开启RabbitMQ的镜像集群模式很简单,只要在其管理控制台新增一个策略,这个策略是镜像集群模式的策略,指定要求数据同步到所有的节点, 也可以要求只同步到指定数量的节点,然后再创建Queue的时候,应用这个策略,就会自动将数据同步到其他节点实例上。
# Kafka高可用
Kafka: 由多个broker组成,每个broker是一个节点;当创建一个Topic,这个Topic可以划分为多个partition, 每个partition,可以存在于不同的Broker上,每个partition存放一部分数据。这就是分布式消息队列,就是一个Topic数据, 是分散放在多个机器上的,每个机器只放一部分数据。
实际上RabbitMQ,并不是分布式消息队列,它其实是传统的消息队列,只不过提供了一些集群、HA的机制而已,因为无论怎么操作, RabbitMQ的一个Queue的数据都是放在一个节点的,镜像集群下,也是每个节点都放对应Queue的完整数据。
Kafka 0.8以前,是没有HA机制的,就是任何一个broker宕机了,那个broker上的partition就废了,不能写也不能读,无高可用可言。
Kafka 0.8以后,提供了HA机制,就是replica副本机制。每个partition的数据都会同步到其他机器上,形成自己的多个replica副本。 然后所有replica会选举一个leader出来提供服务,其他replica就是follower。写的时候,leader会负责把数据同步到所有follower, 读的时候直接读leader上数据即可。因此,只能读写leader,原因是如果可以随意读写每个follower,那么就需要关注数据一致性问题, 系统复杂度太高,容易出现问题。Kafka会均匀的将一个partition的所有replica分布在不同的机器上,这样容错性才高。
乳沟某个broker宕机了,那么这个broker上面的partition在其他机器上都有副本,如果正好是某个partition的leader, 那么此时会重新选举一个新的leader出来,大家继续读写新选举出的leader即可。这就是所谓的高可用。
写数据的时候,生产者只向leader中写,然后leader将数据落地写本地磁盘,接着其他follower主动从leader中pull数据。 一旦所有follower同步好数据,就会发送ack给leader,leader收到所有follower的ack之后,就会返回成功的消息给生产者。 这只是其中一种模式,当然还可以适当调整这个行为。
消费的时候,只会从leader中读,但是只有一个消息已经被所有follower都同步成功且返回ack的时候,这个消息才会被消费者读到。
# 如何保证消息不被重复消费?
会不会重复消费?能不能避免重复消费?或者重复消费了如何不造成系统异常?这些是MQ领域的基本问题,其实本质上还是问使用消息队列如何保证幂等?
RabbitMQ、RocketMQ、Kafka,都有可能会出现消息重复消费的问题,这是正常的。因为这个问题通常不是MQ自己来保证的, 这需要研发者自己给出解决方案。以Kafka为例:
Kafka实际上有offset的概念,这是每个消息写成功后,都有一个offset,代表消息的序号,然后consumer消费了数据之后, 每隔一段时间,会把自己消费国的消息的offset提交一下,表示已经消费过了,后面如果重启可以让消费者继续从上次消费到的offset来继续消费。
但凡事儿总有意外,比如重启或意外宕机时,会导致consumer有些消息消费了,但还没来得及提交offset,重启之后,少数消息会再次消费一次。
其实重复消费并不可怕,可怕的是没考虑重复消费后,怎么保证幂等性。
# 如何保证消费的时候是幂等的?
确保幂等,还需要结合业务来思考,比如:
- 如果要写库,可以先根据主键查询一下,如果这条数据已经存在了,就不要insert,改为update。
- 如果向redis中写,每次都是
set
,天然幂等 - 如果不是上面的两个场景,就需要在复杂一点,让生产者发送每条数据的时候,里面加一个全局唯一的
id
,类似订单id
之类的, 然后消费到之后,先根据这个id
去比如redis
里查一下,之前是否消费过,如果没有消费过,就处理,消费过,就放过。 保证不处理重复的消息即可。
# 如何保证消息的可靠性传输,消息不丢失?
MQ基本原则:数据不能多一条,也不能少一条。不能多,就是刚才说的重复消费和幂等性问题,不能少,就是说消息丢失问题。
假设需要用MQ传递非常核心的消息,比如计费消息。广告平台的计费系统,是很重的一个业务,操作很耗时,在广告系统整体的架构里面, 实际上是将计费做成异步的,然后中间就加了一个MQ。
MQ丢数据,一般分为两种,要么是MQ自己弄丢了,要么是消费的时候弄丢了。下面分别从RabbitMQ和Kafka来分析一下。
# RabbitMQ数据丢失
RabbitMQ这种MQ一般来说都是承载公司的核心业务的,数据是绝不能丢失的。
- 生产者弄丢了数据
生产者将数据发送到RabbitMQ时,因为网络问题,可能在半路就丢失了。此时可以选择用RabbitMQ提供的事务功能,
就是生产者发送数据之前开启RabbitMQ事务(channel.txSelect
),然后发送消息,如果消息没有成功被RabbitMQ接收到,
那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback
),然后重试发送消息;如果收到了消息,那么可以提交事务(channel.txCommit
)。
但是用事务机制,吞吐量会下降,性能变低。
一般情况下,如果要确保RabbitMQ的消息不丢,可以开启confirm
模式,在生产者哪里设置开启confirm
模式后,每次写的消息都会分配一个唯一的id
,
然后如果写入了RabbitMQ中,RabbitMQ会给生产者回传一个ack
消息,告诉生产者这个消息ok
了。如果RabbitMQ没处理这个消息,
会回调生产者的nack
接口,说明这个消息接收失败了,可以再次重试发送。而且可以结合这个机制在内存里维护每个消息id
的状态,
如果超过一定时间还没接收到这个消息回调,那么就可以重发消息了。
事务机制和confirm机制最大的不同在于,事务机制是同步的,提交一个事务后会阻塞,但是confirm
机制是异步的,
发送消息之后可以发送下一个消息,然后消息被RabbitMQ接收后会异步回调接口通知生产者消息接收到了。
因此一般生产者为了避免丢失数据,都采用confirm
机制。
- RabbitMQ丢失了数据
为了确保RabbitMQ不丢失数据,必须开启持久化功能,就是消息写入之后会持久化到磁盘,即是RabbitMQ挂了,恢复之后会自动读取之前存储的数据, 一般数据不会丢。除非极其罕见的情况,RabbitMQ还没持久化,就挂了,可能导致少量数据会丢失,但这个概率极小。
设置持久化有两个步骤,第一个是创建Queue的时候将其设置为持久化,这样可以保证RabbitMQ持久化Queue的元数据,
但是不会持久化Queue里的数据;第二个是发送消息的时候将消息的deliveryMode
设置为2
,将消息设置为持久化的,
此时RabbitMQ就会将消息持久化到磁盘上。注意,必须同时设置这两个持久化才可以,RabbitMQ即使挂了,再次重启,也会从磁盘上恢复Queue,
以及里面的数据。
而且持久化可以根生产者的confirm
机制配合一起使用,只有消息持久化到磁盘之后,才会通知生产者ack
,因此即使在持久化磁盘前挂了,
数据丢了,生产者收不到ack
,也可以重发消息。
- 消费端丢失数据
消费端丢失数据主要是因为在消费的时候,刚消费到,还没有进行业务处理,消费端进程挂了,此时,RabbitMQ会认为消息被消费了,数据就丢失了。
这个时候依旧需要用到RabbitMQ的ack
机制,简单说,就是需要关闭RabbitMQ的消息消费的自动ack
功能,可以通过一个api调用来配置即可,
然后每次在确保业务处理完成时,由程序手动ack
。这样,可以确保消费端不丢失数据。
# Kafka数据丢失
- 消费端丢失数据
唯一可能导致消费者丢失数据的情况,就是,消费到消息后,在业务处理完成前,自动提交了offset
,导致Kafka误以为该条消息已经被消费。
因此为了确保消费者不丢失消息,需要关闭kafka的自动提交offset
功能,在业务处理完后,在手动提交offset
,这样就可以保证数据不丢。
但是此时又会出现重复消费的问题,比如业务处理完,还没提交offset
,结果服务挂了,再次重启后会重复消费一次,因此需要保证消费者业务处理的幂等性。
生产环境有时会碰到这样的问题,Kafka消费者消费到数据之后写到一个内存的Queue里缓冲,结果有的时候,将消息写入内存后,消费者会自动提交offset
。
此时我们重启系统,就会导致内存Queue里的消息还没处理就丢失了。
- Kafka丢失数据
这是一种比较常见的场景,就是kafka某个broker宕机了,然后重新选举partition
的leader时,如果此时其他的follower刚好还有数据没有同步,
leader挂了,然后选举某个follower称为leader之后,就少了一些数据。
此时,我们为了确保kafka不丢失数据,需要设置如下4个参数:
- 给topic设置
replication.factor
参数:这个值必须大于1,要求每个partition必须至少有2个副本; - 在kafka服务端设置
min.insync.replicas
参数,这个值必须大于1,这个要求一个leader至少要感知到有至少一个follower还跟自己保持联系 - 在producer端设置
acks=all
,这个要求每条数据,必须是写入所有replica
后,才能被认为写入成功了。 - 在producer端设置
retries=MAX
,这要要求一旦写入失败,就无限重试。
生产环境按照上述要求配置,至少在kakfa broker端就可以保证在leader所在broker发生故障,进行leader切换时,数据不会丢失。
- 生产者不会丢失数据
如果按照上述第二步的配置,设置了acks=all
以及retries=MAX
,一定不会丢,leader接收消息,所有的follower都同步到消息之后,
才认为消息写入成功,如果不满足这个条件,生产者将会不断的重试。
# 如何保证消息的顺序性?
这个也是MQ必问问题,第一看是否了解消息顺序这个事情,而是看有没有办法保证消息的顺序。
比如:一个mysql binlog同步系统,压力比较大,日同步数据上亿条,常见的场景比如说大数据团队,需要同步业务系统的数据库, 再针对同步过来的数据做各种复杂的操作。
在mysql中增删改一条数据,对应出来就会出现3条增删改的binlog
,接着三条binlog
发送到MQ里面,这个时候,消费者消费消息一次执行,
就要保证它必须按照顺序来消费消息并执行业务操作。否则,增加、修改、删除 会被换为删除、修改、增加,这个时候业务数据就乱了。
MQ顺序错乱的两个场景:
- RabbitMQ: 一个Queue,多个consumer
- Kafka: 一个Topic,一个partition,一个consumer,内部多线程
如何保证消息的顺序?
- RabbitMQ:拆分多个Queue,每个Queue一个consumer,就是多一些Queue而已,确实麻烦点;或者就一个Queue对应一个consumer, 然后这个consumer内部用内存队列做排队,然后分发给底层不同的worker来处理
- Kafka: 一个topic,一个partition,一个consumer,内部单线程消费,写N个内存Queue,然后N个线程分别消费一个内存Queue即可。
# 大量消息积压如何解决?
如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几小时,该怎么解决?
上面的问题其本质针对的场景,还是消费端出了问题,不消费,或者消费的机器缓慢,此时我们的消息队列集群的磁盘都可能块写满了,都没人消费, 这个时候该如何解决问题?或者消息积压了几个小时,怎么处理?或者积压的时间太长,导致比如RabbitMQ设置了消息过期时间,消息丢了怎么办?
这种情况有时还挺常见的,比如消费者写MySQL,如果MySQL挂了,消费者就hang住不再进行消息消费,此时消息就会积压。
- 大量消息在MQ里积压了几个小时还没解决
假设有几千万条数据积压在MQ,此时即使消费者消费速度恢复,假如一个消费者一秒消费1000条,总共有3个消费者,那么一分钟就是18万条, 大概也需要几个小时才能将积压的消息处理完。
如果出现了上面的问题,一般只能操作临时紧急扩容了,具体操作步骤和思路如下:
先修复consumer问题,确保其恢复消费速度,然后将现有consumer都停掉
新建一个topic,partition是原来的10倍,临时建立好原先10倍或者20倍的Queue数量
然后写一个临时分发数据的consumer程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,均匀轮询写入临时建立好的10倍数量的Queue
接着临时征用10倍的机器来部署consumer,每一批consumer消费一个临时Queue的数据
这种做法相当于临时将queue资源和consumer资源扩大10倍,以正常的10倍速度来消费数据
等快速消费完积压数据之后,需要恢复原先部署架构,重新用原先的consumer机器来消费消息。
假设第二个问题,消息积压,并且消息设置了过期时间?
假设使用的是RabbitMQ,RabbitMQ是可以设置过期时间的,即TTL,如果消息在Queue中积压超过一定的时间就会被清理掉,这个数据就没了。
这种情况,就不是要增加consumer消费积压消息这么简单了,而是丢失了大量的消息数据。这时可以采取一个方案,批量重导,当消息大量积压并丢失时, 可以等高峰期过后,写程序将丢失的数据查出来重新灌入MQ中,将丢失的数据补回来。
# 如果让你写一个消息队列,该如何架构设计?
这问题一般考察两块:
- 有没有对某一个消息队列做过较为深入的原理理解,或者从整体了解把握MQ的架构原理;
- 考察一下设计能力,给一个常见的系统,就是消息队列系统,能不能从全局把我一下整体架构设计。
类似的问题如:如果让你设计一个Spring框架怎么做?设计一个dubbo框架怎么做?设计一个mybatis框架怎么做?
技术的基本原理,核心组成部分,基本架构构成,然后参照一些开源的技术把一个系统设计出来。
比如消息队列:
- 首先MQ得支持可伸缩,也就是需要的时候能够进行快速扩容,可以增加吞吐量和容量,可以参照一下kafak的设计理念, broker -> topic -> partition,每个partition放在一个机器上,只存部分数据。当资源不够时,给topic增加partition, 然后做数据迁移,增加硬件资源,可以存放更多的数据,提供更高的吞吐量;
- MQ消息数据的持久化,确保进程挂掉可以恢复数据。顺序写,这样就没有磁盘随机读写的寻址开销,可以提高性能,这就是kafka的思路
- MQ的可用性,参考kafka的高可用保障机制。多副本 -> leader&follower -> broker挂了重新选举leader即可对位服务
- 是否可支持数据零丢失,可以参考kafka数据零丢失方案。
# 分布式搜索引擎
# 分布式搜索引擎的架构师怎么设计的?为啥是分布式的?
# 分布式搜索引擎写入和查询的工作流程是什么样的?
# 分布式搜索引擎在几十亿数据量的场景下如何优化查询性能?
# 生产环境的分布式搜索引擎是怎么部署的?
# 分布式缓存
# 为什么使用缓存?
- 消息队列连环问
- 在项目中用过消息队列吗?
- 简单介绍下在项目里是怎么使用消息队列的?
- 为什么使用消息队列?
- 使用消息队列都有什么优点和缺点?
- Kafka\ActiveMQ\RabbitMQ\RocketMQ 都有什么区别?
- 如何保证消息队列的高可用?
- 如何保证消息不被重复消费?
- 如何保证消费的时候是幂等的?
- 如何保证消息的可靠性传输,消息不丢失?
- 如何保证消息的顺序性?
- 大量消息积压如何解决?
- 如果让你写一个消息队列,该如何架构设计?
- 分布式搜索引擎
- 分布式搜索引擎的架构师怎么设计的?为啥是分布式的?
- 分布式搜索引擎写入和查询的工作流程是什么样的?
- 分布式搜索引擎在几十亿数据量的场景下如何优化查询性能?
- 生产环境的分布式搜索引擎是怎么部署的?
- 分布式缓存
- 为什么使用缓存?