目录
中华石杉老师-互联网Java工程师面试突击训练第1季:https://www.bilibili.com/video/BV1FE411y79Y?p=4
1. 为什么使用消息队列?
消息队列的常见使用场景:解耦、异步、削峰
- 场景一:解耦
使用 MQ 之前的耦合场景
假如在这么一个场景下,A 系统发送数据到 BCD 三个系统中,通过接口调用发送
之后又出现了一个 E 系统也需要 A 系统发送这个数据
那么 A 系统又不得不修改代码去调用 E 系统
假如 C 系统现在又不需要 A 系统发送这个数据了
那么 A 系统又得修改代码取消对 C 系统的调用
由此可见,当 A 系统中产生了一个比较关键的数据时,使得很多系统都可能会用到,这个时候 A 系统就需要将这个数据发送给不同的系统中,这就造成了 A 系统和这些系统耦合起来了,而且 A 系统调用不同的系统其参数可能也不一样,并且还需要考虑其它系统宕机时的处理、或者是访问超时时该怎么处理,是否还需要做一个重试机制?
使用 MQ 之后的解耦场景
在上述场景下添加一个 MQ,A 系统的关键数据直接发送到 MQ 中,其它的系统如果需要这个数据,就直接从 MQ 中消费即可
假如 E 系统也需要这个数据,那么 E 系统就去 MQ 中消费
假如 C 系统此时又不需要这个数据了,也比较好办,直接取消对 MQ 该消息的消费即可
这样 A 系统就不需要考虑该给哪个系统发送数据,也不需要去维护调用各个系统之间的代码,更不需要关系其它系统是否宕机、或者失败超时等问题
通过 MQ 的发布订阅(Pub/Sub 模型)就能让 MQ 跟其它系统彻底解耦了
面试技巧:你需要去考虑一下你负责的系统中是否有类似的场景,就是一个系统或者一个模块,调用了多个系统或者模块,互相之间的调用很复杂,维护起来很麻烦,但是其实这个调用是不需要直接同步调用接口的,如果用 MQ 给它异步化解耦,也是可以的。
- 场景二:异步
不用 MQ 的同步高延时的请求场景
假如有这么一个场景,用户向 A 系统发送了一个请求,A 系统先在本系统中执行了一段逻辑花费了 20 ms,而后有调用了 B 系统,而 B 系统又需要耗费 300 ms,接着 A 系统还要调用 C 系统和 D 系统,整个过程则需要花费 970 ms
也就相当于用户在访问某个数据时需要等待几乎 1 秒钟才能看到结果,这是很难接受的
一般的互联网企业,对用户的直接操作一般要求是每个请求都必须在 200ms 以内完成,对用户几乎是无感知的。
使用 MQ 进行异步化之后的接口性能优化
在上述场景中引入 MQ,A 系统连续发送三条消息到三个 MQ 队列中去,每个系统自己从对应的 MQ 队列中去消费对应的消息,从消息中提取参数在自己本地执行对应的操作
从这一个过程中系统 A 从接受到请求到返回给用户,总时长实际上就是 20 + 5 = 25 ms,BCD 系统从 MQ 中获取到消息之后在后台异步处理各自的操作,这部分消耗的时间不在接口响应里,对于用户而言体验是极好的
异步化可以大幅度的提升高延迟接口的性能
- 场景三:削峰
未使用 MQ 时高峰期系统被打死的场景
假如在这样一种场景下,有大量的用户(100万用户)通过游览器在中午高峰期时,同时在进行大量的操作,使得大量的请求涌入到系统中,高峰期达到每秒 5000 个请求,而该系统是基于 MySQL 的,导致大量的请求涌入 MySQL,要求 MySQL 每秒要执行 5000 条 SQL,而一般的 MySQL 抗到每秒 2000 个请求就差不多了,如果每秒请求 5000 的话,可能会直接把 MySQL 给打死
而高峰期过了之后,大概每秒只有 50 个请求,对于整个系统几乎没有任何压力
使用 MQ 进行削峰
在上述场景中引入 MQ,用户的请求会先写入到 MQ 中,系统 A 从 MQ 中拉去请求
比如在高峰期时每秒的请求是 5000 个,这些请求会先被写到 MQ 中,系统 A 从 MQ 中慢慢拉取请求,每秒拉取 2000 个请求,不要超过能处理的最大请求数量就行,这样 MySQL 中每秒处理的请求也不会超过 2000 个,就不用担心即使在高峰期 MySQL 因为扛不住大量请求而宕机的问题
不过会出现一个问题就是,在高峰期时 MQ 每秒有 5000 个请求进来,但是只有 2000 个请求出去,可能会导致在高峰期时 MQ 中会积压大量的请求,不过只要等到高峰期一过,系统 A 就能快速的将 MQ 中的请求给处理完
2. 消息队列有什么优点和缺点?
以下图举例说明 MQ 的优缺点:
-
缺点
- ① 系统可用性降低:MQ 一旦出现故障,系统 A 就没有办法发送消息到 MQ 中,导致 BCD 系统没法消费到消息,整个系统都崩溃了,就没有办法运转了
- ② 系统复杂性升高:引入 MQ 之后会导致系统需要考虑的问题变多了,进而导致系统的复杂性升高,比如系统 A 不小心把同一条消息发送给系统 B 两次,导致系统 B 内部插入了两条一摸一样的数据,或者是消息丢失了怎么办?怎么保证消息传递的顺序性?
- ③ 一致性问题:有人给系统 A 发送个请求,本来 该请求需要系统 ABCD 都执行成功才能返回的,结果系统 ABC 都执行成功了,而系统 D 执行失败了,就导致整个请求给用户返回的结果是成功的,但实际上是没有完全执行完的
-
优点:
- ① 解耦
- ② 异步
- ③ 削峰
3. Kafka、ActiveMQ、RabbitMQ、RocketMQ 都有什么优点和缺点?
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,吞吐量比 RocketMQ 和 Kafka 要低一个数量级 | 万级,吞吐量比 RocketMQ 和 Kafka 要低一个数量级 | 10 万级,RocketMQ 也是可以支撑高吞吐量的一种 MQ | 10 万级,这是 Kafka 的最大优点,就是吞吐量高。 一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic 数量对吞吐量的影响 | topic 可以达到几百,几千的级别,吞吐量会有较小幅度的下降 这是 RocketMQ 的一大优势,在同等机器下,可以支撑大量的 topic | topic 从几十到几百个的时候,吞吐量会大幅度下降 所以在同等机器下,kafka 尽量保证 topic 数量不要过多。如果要支撑大规模 topic,需要增加更多的机器资源 | ||
时效性 | ms 级 | 微秒级,这是 RabbitMQ 的一大特点,延迟是最低的 | ms 级 | 延迟在 ms 级以内 |
可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka 是分布式的,一个数据多个副本,少数机器宕机不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 经过参数优化配置,可以做到 0 丢失 | 经过参数优化配置,消息可以做到 0 丢失 | |
功能支撑 | MQ 领域的功能极其完备 | 基于 erlang 开发,所以并发能力强,性能极好,延时很低 | MQ 功能较为完善,还是分布式的、扩展好 | 功能较为简单,主要支撑简单的 MQ 功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
优劣势总结 | (1)非常成熟,功能强大,在业内大量的公式以及项目中都有应用 (2)偶尔会有较低概率丢失消息 (3)而且现在社区以及国内应用都越来越少,官方社区现在对 ActiveMQ 5.x 维护越来越少,而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | (1)erlang 语言开发,性能极其好,而且开源提供的管理界面非常棒,用起来很好用,在国内一些互联网公司近几年用 RabbitMQ 也比较多一些 (2)但是问题也是显而易见的,RabbitMQ 确实吞吐量低一些,这时因为他做的实现机制比较重。而且 erlang 开发,国内没有几个公司有实力做 erlang 源码级别的研究和制定?而且 RabbitMQ 集群扩展很麻烦。最主要还是 erlang 语言本身带来的访问问题,很难读源码,很难指定与掌控 | (1)接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障 (2)日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和实用性都挺不错的,还可以支撑大规模的 topic 数量,支持复杂 MQ 业务场景 (3)而且一个很大的优势在于,阿里出品都是 java 系的,我们可以自己阅读源码,定制自己公司的 MQ,可以掌控 (4)社区活跃度相对一般,不过也还可以,文档相对来说简单了一些,然后接口这块不是按照简单的 JMS 规范走的有些系统迁移需要修改大量的代码 (5)还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险 | (1)Kafka 的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms 级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展 (2)同时 Kafka 最好是支撑较少的 topic 数量即可,保证其超高吞吐量 (3)而且 Kafka 唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域以及日子采集中这点轻微的影响是可以忽略的 (3)这个特性天然适合大数据实时计算以及日志收集 |
综上所述,如果是中小型企业,技术实力一般,技术挑战也不是特别的高,用 RabbitMQ 是不错的选择;大型公司,基础架构研发能力较强,推荐使用 RocketMQ;如果是大数据领域的实时计算、日志采集等场景,用 Kafka 是业内标准的,绝对没有问题,社区活跃度也很高,绝对不会黄掉的
4. 引入消息队列之后该如何保证其高可用性?
这里例举两种 MQ 进行说明,一个是 RabbitMQ,一个是 Kafka,RabbitMQ 不是分布式的,而 Kafka 是分布式的。
(1)RabbitMQ 的高可用性
RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式
- 单机模式
就是 demo 级别的,一般就是你本地启动玩玩,没人生产上使用单机模式
- 普通集群模式
在多台机器上部署多个 RabbitMQ 实例,每个机器启动一个,相当于多个节点,所创建的 queue 只会放在一个 RabbitMQ 的节点上,但是 queue 的元数据会同步到其它的节点上
如果消费者来消费,连接的是没有存在真正数据的节点,那么那个实例就会通过 queue 的元数据信息找到 queue 所在实例,并拉取数据过来
这种方式的好处在于如果消费者有很多的话,可以让该消费者连接到任意节点上去,实行消费,可以提高整个消费的吞吐量,但是除了 queue 所在的节点外,连接到别的节点上去,别的节点还是回去找 queue 所在的节点将数据拉取过来,这种模式就是一个普通集群,没有做到分布式,这种模式还有非常明显的两个缺点:① 可能会在 RabbitMQ 集群内部产生大量的数据传输;② 可用性几乎没有任何保障,如果 queue 所在的节点宕机,就会导致对应 queue 中的数据丢失,就没法消费
- 镜像集群模式
这种模式是 RabbitMQ 的高可用模式,跟普通集群模式不一样的是,所创建的 queue 无论是元数据还是 queue 里的真实数据都会存在在每一个节点上,然后每次写消息到 queue 的时候,都会自动把消息到多个实例的 queue 里进行消息同步
假如消费者需要消费消息,从任何一个节点去消费消息都没有问题,因为每一个节点都包含了 queue 所有的数据,就相当于消费者在进行消费时不需要走网络传输来获取消息,只有在写消息的时候才需要进行网络传输来同步数据
它的高可用体现在每一个节点都有 queue 的完整数据,任何一个节点宕机了也没问题,因为其它节点上还包含了整个 queue 的完整数据,消费者可以到其它节点上去消费数据
这种模式的主要缺点还是它不是分布式的,如果这个 queue 的数据量很大,大到这个机器的容量无法容纳,此时该怎么办呢?
RabbitMQ 如何开启镜像集群模式?
RabbitMQ 有很好的控制台,就是后台新增一个策略,这个策略就是镜像集群模式的策略,指定的时候可以要求数据同步到所有节点,也可以要求就同步到指定数量的节点,然后创建 queue 的时候应用这个策略,就会自动将数据同步到其它的节点上去了
(1)Kafka 的高可用性
像 RabbitMQ 之类的传统消息队列,只不过是提供了一些集群,HA 的机制而已,不管怎样它的数据都是放在一个节点上的,包括镜像集群模式,也是将每个节点都存放 queue 的完整数据
Kafka 一个最基本的架构:多个 broker 组成,每个 broker 是一个节点;创建一个 topic 可以划分多个 partition,每个 partition 可以存在不同的 broker 上,每个 partition 就放一部分数据
broker 进程就是 Kafka 在每台机器上启动的自己的一个进程,每个机器 + 机器上的 broker 进程,就可以认为是 Kafka 集群中的一个节点
这就是天然的分布式消息队列,就是说一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据
比如说生产者往 topic 中写了 3 条数据,会分别存放在不同的机器上的
在 Kafka 8.0 以前,是没有 HA 机制的,就是任何一个 broker 宕机了,那个 broker 上的 partition 就废掉了,没法写也没法读,没有什么高可用可言
在 Kafka 8.0 以后,提供了 HA 机制,就是 replica 副本机制,每个 partition 的数据都会同步到其它机器上,形成自己的多个 replica 副本,然后所有的 replica 会选举一个 leader 出来,那么生产和消费都跟这个 leader 打交道,然后其它的 replica 就是 follower。
也就是说生产者写消息的时候只能往 leader 中写入数据,消费者消费的时候也是从 leader 中获取消息进行消费
写的时候 leader 会负责把数据同步到所有的 follower 上去,读的时候直接读 leader 上数据即可,Kafka 会均匀的将一个 partition 的所有 replica 分布在不同的机器上,提高了容错性
这样就实现了高可用性了,因为如果某个 leader 宕机了,其它的 follower 也存在相应 partition 的副本,此时会重新选举一个新的 leader 出来,之后读写会与新的 leader 打交道,这就是所谓的高可用性
写数据的时候,生产者就写 leader,然后 leader 将数据落地写本地磁盘,接着其他 follower 自动主动从 leader 来 pull 数据,一旦所有 follower 同步好数据了,就会发送 ack 给 leader,leader 收到所有 follower 的 ack 之后,就会返回写成功的消息给生产者
5. 如何保证消息不被重复消费(如何保证消息的幂等性)?
RabbitMQ、RocketMQ、Kafka 都有可能出现消息被重复消费的情况,MQ 不能保证消息是否被重复消费,而是要你去保证的
以 Kafka 来举例说明怎么重复消费
生产者向 Kafka 发送数据的时候,Kafka 会给每条数据分配一个 offset,这个 offset 就代表了该数据的序号
消费者从 Kafka 中消费消息也是按照顺序去消费的
消费者消费完之后会定时提交 offset 到 Zookeeper 中,Zookeeper 会记录消费者所消费的消息并告知 Kafka,如果消费者系统重启,重启之后消费者会继续向 Kafka 继续消费消息,那么 Kafka 就会接着向消费者发送重启前未消费的数据
因为消费者是定时提交 offset 的,就可能出现这样一种情况,假如消费者消费完 offset=1 和 offset=2 这两条消息,此时正准备去提交这两个 offset,但是还没有提交的时候,消费者的进程被重启了,导致已经被消费过的 offset 消息并没有被提交,Kafka 不知道这两条消息已经被消费了,消费者一旦重启,Kafka 又会向消费者传递这两条消息,这就出现了消息被重复消费的情况,假如每消费一条消息就会向数据库中插入一条数据,那么就会有相同的数据出现
要解决这个问题可以在消费完一条消息之后,往内存(Redis)插入已消费过这条消息的记录,每次消费消息之前都去查询一下该消息是否已被处理,如果已经处理过了这条消息就不做操作,这样就能保证消息不被重复消费
或者是基于数据库的唯一键来保证数据不会被重复插入多条,在消费消息之后往数据库插入数据之前,先去判断下该数据是否在数据库中已存在,如果存在则不插入该数据
6. 如何保证消息的可靠性传输(如何处理消息丢失的问题)?
消息的丢失可能会出现在 3 个场景下:
- ① 生产者弄丢了数据:生产者向 MQ 写消息的过程中,消息还没有到 MQ 在网络传输的过程中就已经丢失了,或者是消息到了 MQ,但是 MQ 内部出错导致消息没有保存下来
- ② MQ 弄丢了数据:MQ 接受到消息之后先暂存在自己的内存中,结果消费者还没来得及消费,MQ 就挂掉了,导致内存中的数据丢失了
- ③ 消费者弄丢了数据:消费者消费到了消息,但还没有来的及处理,消费者自己就挂掉了,但是 MQ 以为消息已经处理完了
这里分别以 RabbitMQ 和 Kafka 来说明这个问题
(一)RabbitMQ
1)生产者弄丢了数据
生产者将数据发送到 RabbitMQ 的时候,可能数据在半路就丢失了
可以选择使用 RabbitMQ 提供的事务功能,就是生产者发送数据之前开启 RabbitMQ 事务(channel.txSelect),然后再发送消息,如果消息没有成功被 RabbitMQ 接受到,那么生产者会收到异常报错,此时就可以回滚事务(channel.txRollback),然后重试发送消息,如果收到了消息,那么可以提交事务(channel.txCommit),但是这种机制是同步的,生产者发送消息会同步卡住,等待消息发送是否成功,这就导致了 RabbitMQ 开启事务机制的话吞吐量就会下降很多,因为太耗费性能了
...
channel.txSelect
try {
// 发送消息
} catch (Exception e) {
channel.txRollback
// 再次重试发送这条消息
}
channel.txCommit
...
所以一般来说,如果要确保写到 RabbitMQ 的消息不丢失,可以开启 confirm 模式,在生产者那边设置开启 confirm 模式之后,每次写消息都会分配一个唯一的 id,然后如果写入到 RabbitMQ 中,RabbitMQ 会回调 ack 接口,表明生产者写入成功;如果没有写入到 MQ 或者是 MQ 无法处理这个消息,RabbitMQ 就会调一个 nack 接口,告诉你这个消息接受失败,可以去重试,这种方式是异步的,吞吐量会高一些,一般采用这种方式会比较多
2)MQ 弄丢了数据
这种情况就是 RabbitMQ 将消息存放在内存中,当 RabbitMQ 挂掉之后,内存中的消息也丢失了,通常情况下,需要开启 RabbitMQ 持久化,就是消息写入之后会持久化到磁盘中,哪怕 RabbitMQ 自己挂掉了,恢复之后会自动读取之前存储的数据,一般数据不会丢失,除非及其罕见的是,RabbitMQ 在持久化的过程中挂掉了,就可能导致少量的数据会丢失,但这个概率是很小的
设置持久化有两个步骤,第一个是创建 queue 的时候将其设置为持久化的,这样就可以保证 RabbitMQ 持久化 queue 的元数据,但是不会持久化 queue 里的数据;第二个是发送消息的时候将消息的 deliveryMode 设置为 2,就是将消息设置为持久化的,此时 RabbitMQ 就会将消息持久化到磁盘上去,必须要同时设置这两个持久化才行
而且持久化可以跟生产者那边的 confirm 机制配合起来,只有消息被持久化到磁盘之后,才会通知生产者 ack,所以哪怕是在持久化到磁盘之前,RabbitMQ 挂了,数据丢了,生产者收不到 ack,也可以重发消息的
3)消费者弄丢了数据
消费者丢失了消息的原因主要是开启了消费者的 autoAck 机制,该机制会在消费者消费到消息之后,通过 autoAck 通知 RabbitMQ 该消息已经消费完成,就可能会出现消费者拿到消息但是还没有处理完,就宕机了,但是消费者者已经 autoAck,使得 RabbitMQ 误以为消息已经处理掉了
所以在消费者这个层面需要将 autoAck 这个机制给关闭掉,在每次处理完消息之后手动发送 ack 消息给 RabbitMQ ,这样即使消费者在消费消息时宕机了,RabbitMQ 也能将这条消息发送给其他的消费者去处理
(二)Kafka
1)消费者弄丢了数据
Kafka 唯一可能导致消费者消息丢失的情况就是使用了自动提交 offset,就是说消费者消费到消息,然后自动提交了 offset,让 Kafka 以为这个消息已经处理好了,但实际上还没有进行处理消费者就挂掉了,此时这条消息就丢失了
所以需要关闭 Kafka 的自动提交 offset,在处理完消息之后手动提交 offset,就能保证数据不被丢失
2)MQ 弄丢了数据
在这样的一个场景下
生产者向 Kafka 发送一条数据,该数据存在于 broker 1 中的 partition 1 中,而此时该 broker 宕机了,并且该数据还没有同步到其 follower 上去,然后重新选举 partition 的 leader,而新选取出来的 leader 是不存在该数据的,就导致数据丢失了
所以此时一般是要求设置如下 4 个参数:
- topic 设置 replication.factor 参数:这个值必须大于 1,要求每个 partition 必须要有至少 2 个副本
- 在 Kafka 服务端设置 min.insync.replicas 参数:这个值必须大于 1,这个是要求一个 leader 至少感知到有至少一个 follower 还跟自己保持联系,确保 leader 挂了还有一个 follower
- 在 producer 端设置 ack=all:这个是要求每条数据,必须是写入所有 replica 之后,才能认为是写成功了
- 在 producer 端设置 retries=MAX:这个是要求一旦写入失败,就无限重试
3)生产者弄丢了数据
如果按照上述的方式设置参数,生产者是一定不会丢失消息的,如果消息发送失败,就会无限重试
7. 如何保证消息的顺序性?
RabbitMQ 如何保证消息的顺序性
当多个消费者从同一个 queue 中消费消息时,每一个消息只能被一个消费者消费,如果每个消息的处理都会向数据库中插入一条数据,但是消费者处理消息时会导致向数据库中插入的数据与 queue 中的数据顺序不一致的情况
针对以上的情况,只需要让每个消费者只消费一个 queue,把需要保证顺序的数据都放在一个 queue 中,这样消费者从 queue 中拿到的消息就是顺序性的了
Kafka 如何保证消息的顺序性
生产者向 Kafka 中的 partition 写入的数据是能保证其顺序的,也就是说生产者在写数据的时候,可以指定一个 key,比如订单的 id 作为 key,将相关的数据全部分发到一个 partition 中,是能够保证这些数据一定是顺序的
因为 partition 中的数据是顺序的,所以消费者从 partition 中取出的数据也一定是顺序的
会出先消息顺序不一致的地方在于消费者拿到 partition 中的消息之后,通过多线程的方式去处理这些消息,就会导致这些消息被处理的顺序发生变化,假如这些消息经过处理都需要向数据库中操作数据,就会出现问题
解决方案就是消费者将需要保证顺序的消息放到一个内存队列当中,然后线程从消息队列中拿取消息去处理,这就能保证消息的顺序了
比如说消费者从 partition 中获取到消息之后,将需要保证顺序的订单消息,将相同的订单 id 的数据分发到同一个 内存队列中,每一个线程从一个内存队列中拉数据然后再处理,就能保证消息的有序执行了
8. 如何解决消息队列的延时以及过期失效问题?消息队列满了以后该怎么处理?有几百万消息持续积压几个小时该怎么解决?
假如有这么一场景,由于之前消费者端系统故障,导致 MQ 中积压了大量的消息
一般这个时候就需要考虑紧急扩容了
在这种情况下首先要处理的就是修复消费者端的问题,确保其恢复正常的消费速度
临时建立好原先 10 倍或者 20 倍的 queue 数量,然后写一个临时分发数据的消费者,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接将这些消息均匀轮询写入临时建立好的 10 倍数量的 queue 中
接着临时征用 10 倍的机器部署消费者,每一批消费者消费一个临时 queue 的数据
这种做法相当于是临时将 queue 资源和消费者资源都扩大了数倍以上,以正常情况下的数倍速度来消费数据
如果使用的 RabbitMQ 设置了过期时间,就是 TTL,如果消息在 queue 中积压了一定的时间被 RabbitMQ 清理掉了的话
这个时候就需要临时写一个程序,将丢失的数据一点点的查出来,然后重新放到 MQ 中,一般情况下不会将 MQ 的消息设置过期时间,如果 MQ 中的消息有过期时间会存在很多问题
9. 如何设计一个消息队列?
首先需要考虑 MQ 的伸缩性,就是需要能支持快速扩容,可以参照 Kafka 的设计理念,采取分布式架构,broker —> topic —> partition,每个 partition 放一个机器,就存一部分数据,如果资源不够就给 topic 增加 partition,然后做数据迁移,增加机器,就可以放更多的数据,提高更高的吞吐量
其次需要考虑 MQ 的数据要落地磁盘,这样才能保证 MQ 挂掉之后数据也不丢失,落磁盘的时候要顺序写,这样就没有磁盘随机读写的寻址开销,磁盘顺序读写的性能是很高的,这也是 Kafka 的思路
接着得考虑 MQ 的高可用性,可以参照 Kafka 的高保障机制,多副本、leader — followe 模式,broker 挂了之后重新选举 leader 即可对外服务
然后还要考虑 MQ 的数据不丢失,比方说数据不丢可以让每部分数据都有多个副本、在生产者向 MQ 写数据时必须保证每个副本都同步到数据才行,否则就要重新写入
保证 MQ 消息写入的顺序性等等