1. 以下哪些问题可以用消息队列解决?

A. 暂存埋点数据,后续用于统计分析

B. 解耦上下游系统

C. 实现一个分布式锁

D. 实现一个分布式的任务调度系统

答案:AB

解析:消息队列最常被使用的三种场景:异步处理、流量控制(削峰填谷)和服务解耦。答案 A 中描述的问题,使用消息队列解决,符合异步处理和流量控制这两种场景。选项 B,显而易见也是正确的。C、D 选项提到的实现分布式锁和分布式的任务调度系统,并不是消息队列擅长解决的。

2. 哪些消息队列可以保证在“从消息生产直到消费完成”这个过程中,消息不重不丢(Exactly once)?

A. Kafka

B. RocketMQ

C. RabbitMQ

D. 以上都不能

答案:D

解析:关于消息可靠性的服务水平,有下面三种级别:

  • At most once: 至多一次,消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。
  • At least once: 至少一次,消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
  • Exactly once:恰好一次,消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。

Kafka、RocketMQ 和 RabbitMQ 以及我们常见的大部分消息队列,能提供的服务水平都是一样的:At least once,也就是至少一次,消息有可能会重复,但可以保证不丢消息。

这里面比较容易出错的是 Kafka,因为 Kafka 宣称是支持 Exactly Once 特性的,但是,Kafka 支持的这个“Exactly once”特性,并不是保证我们这个题目中所说的“从消息生产直到消费完成”这一过程中消息不重不丢。它解决的是,在流计算中,用 Kafka 作为数据源,并且将计算结果保存到 Kafka 这种场景下,数据从 Kafka 的某个主题中消费,在计算集群中计算,再把计算结果保存在 Kafka 的其他主题中。这样一个过程中,Kafka 的 Exactly Once 机制,保证每条消息都被恰好计算一次,确保计算结果正确。

3. 在生产者发送消息时,需要使用分片算法把消息均匀地分布到主题的所有分区(队列)上。如果需要保证 Key 相同的消息的严格顺序,并且能支持对分区数量进行水平扩容。可以选择哪些分片算法?

A. 检索表算法:在检索表中存储 Key 和分区的对应关系,通过查表确定分区号。

B. 取模算法:分区号 = Key mod 分区总数

C. 一致性哈希算法

D. 轮询算法

答案:AC

解析:大部分消息队列都只能保证在分区(队列)上的严格顺序,所以,对于这道题目,我们选择的分片算法必须保证具有相同 Key 的消息,都分到同一个分区上。对于这个要求,答案 ABC 都能做到,所以答案 D 先淘汰掉。

然后我们再看,扩容的时候怎么来保证严格顺序呢?那就是要选择具备单调性的分片算法,单调性是指如果已经有一些内容通过哈希分派到了相应的分区中,又有新的分区加入到主题中,哈希的结果应能够保证,原有已分配的内容可以被映射到原有的或者新的分区中去,而不会被映射到旧的分区集合中的其他分区。只要是满足单调性的分片算法,我们就可以按照“先扩容分区 -> 将旧分区中的遗留消息消费完 -> 同时消费所有分区”这样一个方式,确保扩容过程中消息的严格顺序。

选项 C 中的一致性哈希算法是满足单调性的,这个没有问题。选项 A 中的检索表法,由于这个表中映射关系是可以手工配置的,那我们可以把这个映射关系配置成满足单调性,所以选项 A 也是正确的。选项 B 中的取模算法不符合单调性原则,所以这道题的答案是 AC。

4. 以下关于主题、消费组和消费者的说法,哪些是正确的:

A. 同一个主题的多个消费组之间是竞争消费

B. 同一个消费组的多个消费者之间是竞争消费

C. 每个消费组都会消费主题的一份全量消息

D. 每个消费者都会消费主题的一份全量消息

答案:BC

解析:这是一个关于消费模型概念的问题。每个消费组就是一份订阅,它要消费主题的所有分区(队列)的全部消息。注意,队列里的消息并不是消费掉就没有了,这里的消费只是去队列里面读了消息,并没有删除,消费完这条消息还是在队列里面。所以,其他的消费组依然可以消费到同样的消息。

然后我们再说消费组的内部,一个消费组中可以包含多个消费者的实例,同一个消费组内这些消费之间是竞争消费,他们共同消费一份主题的全量消息。

5. 以下哪些问题适合使用 RocketMQ 的事务消息来解决?

A. 在执行转账操作时,在数据库中更新账户余额同时发送事务消息,异步记录转账流水。

B. 在支付系统中,完成账户余额变更的同时发送事务消息,异步通知支付发起方。

C. 在同一个事务中发送多条消息,确保这些消息要么都发送成功,要么都发送失败。

D. 配合流计算平台,确保数据在流计算过程中不重不丢。

答案:B

解析:选项 A 描述的场景,因为它需要保证流水和余额严格一致,所以并不适合用事务消息来解决。即使非要用事务消息来解决,也应该先在数据库事务中记录流水,再异步更新余额。这样,即使出现数据不一致的问题,也可以用流水来更正余额,反过来,我们是没有办法通过余额反推出流水记录的。

选项 B 是 RocketMQ 事务消息适用的典型场景,选项 C、D 是 Kafka 事务适用的典型场景,也不适合用 RocketMQ 的事务消息来解决。所以,这道题的答案是 B。

6. 以下哪些使用消息队列的方式是错误的?

A. 在集群模式下,将 Broker 配置为异步刷盘以提升 Broker 的写入性能。

B. 为了保证 Kafka 的 Producer 在任何情况下都不丢消息,需要把参数 acks 设置为 all,并将每次发消息的批量大小 batch.size 设置为 1。

C. 如果不需要严格顺序,为了提升消费性能,可以将 Consumer 设置为自动确认消费位置,然后批量拉取消息放到内存队列中,然后异步多线程并行执行消费业务逻辑。

D. 在采用主从模式的 RocketMQ 集群中,创建主题时为了保证主题的可用性,必须把主题中的队列分布到多个 Broker 上。

答案:BC

解析:选项 A,配置为异步刷盘确实可以提升消息队列的性能,这个是没问题的。由于是在集群模式下,即使节点故障,内存中的数据还没来得及刷盘也不会丢消息,因为在集群模式下,消息的可靠性是靠复制来保证的,我们仍然可以从其他节点上读到故障节点丢失的那部分消息,所以这个做法是没问题的。

选项 B,Kafka 发送消息的可靠性依靠的是“请求 - 确认”机制,即使是批量发送,这个机制依然可以保证不丢消息,所以没必要把批量大小设置为 1,这个做法是错误的。

选项 C,是我们在课程中提到过的典型的错误做法,因为,这种“先确认消费位置再执行消费业务逻辑“的做法,消息队列就没有办法保证消费过程中不丢消息,一旦消费节点宕机,内存中未处理的消息就丢了。这个做法也是错误的。

选项 D,由于 RocketMQ 的主从模式集群时不支持自动选举的,一旦主节点宕机,虽然,消费者可以自动切换到从节点继续消费,但生产者就不能再往这个节点上的队列发消息了。所以,为了保证生产的可用性,必须把主题中的队列分布到多个 Broker 上。这个做法也是没问题的。

7. 如果可以保证以下这些操作的原子性,哪些操作在并发调用的情况下具备幂等性?

A. f(n, a):给账户 n 转入 a 元。

B. f(n, a):将账户 n 的余额更新为 a 元。

C. f(n, b, a):如果账户 n 当前的余额为 b 元,那就将账户的余额更新为 a 元。

D. f(n, v, a):如果账户 n 当前的流水号等于 v,那么给账户的余额加 a 元,并将流水号加 1。

答案:D

解析:选项A显然不对。
选项B在并发下无法控制调用的先后顺序,比如两次调用 f(n,100),f(n,200),结果可能是100也可能是200。

选项 C 在并发下有可能出现 ABA 问题:比如:账户余额是 100 元,先转入 100 元,再转出 100 元,最终账户余额还应该是 100 元。可能会出现这两次操作实际上都执行成功了,这时候账户余额是 100 元,但是第一次操作的响应在网络传输过程中丢失了,请求方执行了重试,这时,再次执行第一次操作,也就是“如果账户余额是 100 元,那就更新为 200 元”,正好是可以执行成功的,这时候账户余额被错误地更新成了 200 元。

8. 设计一个账户管理系统中,提供转账和账户余额查询服务,账户余额信息保存在 MySQL 中,并使用 Redis 进行缓存,以下哪种设计最合理:

A. 在 MySQL 事务中,同步变更数据库和 Redis 中的账户余额。

B. 订阅 MySQL 的 binlog,异步更新 Redis 中账户余额。

C. 使用 RocketMQ 的事务消息,在本地事务中更新 MySQL 中账户余额,异步更新 Redis 中的账户余额。

D. 将转账请求发送到消息队列中,异步并行更新 MySQL 和 Redis 中的账户余额。

答案:A

解析:对于账户管理系统,如果要使用缓存,它需要保证缓存和数据库中数据的严格一致性。转账成功后,查询余额却没有变化,这肯定是不能接受的。所以,必须用事务来保证缓存和数据库同步更新。这道题的答案是 A。

9. 气象台使用消息队列作为接口,提供实时的天气信息发布服务,其他系统可以订阅主题来接收实时的天气变化消息。每条消息都是复杂的结构化的数据,包含发布时间、事件类型、未来 24 小时天气变化、出行建议等各种信息。对于这个需求,气象台应该选择哪种消息序列化方式?

A. Protobuf

B. JSON

C. 专用的二进制序列化

D. 以上都可以

答案:B

解析:这个接口,它的特点是:数据量不大,对性能要求不高,接口数据复杂,接入方众多。针对这种情况,应该选择 JSON 这种可视化的序列化方式,便于接口调试。所以这道题的答案是 B。

10. 数据被写入到 PageCache 之后,如果不强制刷盘,以下哪些操作有可能会丢失数据:

A. kill [pid]

B. kill -9 [pid]

C. shutdown now

D. 拔掉服务器电源线

答案:D

解析:对于这道题,首先需要明白:PageCache 是由操作系统来维护的,而不是使用 PageCache 的应用程序。操作系统可以保证,即使是应用程序意外退出了,操作系统也会把这部分数据同步到磁盘上。操作系统在正常关机过程中,也会保证把 PageCache 中的脏页同步到磁盘中。所以,只有选项 D 这种情况,是有可能丢失数据的。


参考资料:李玥——消息队列高手课