下载APP
登录
关闭
讲堂
算法训练营
Python 进阶训练营
企业服务
极客商城
客户端下载
兑换中心
渠道合作
推荐作者

消息队列期中测试题目答案及解析

2019-09-04 李玥
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 中的 Exactly Once 又是解决的什么问题呢?它解决的是,在流计算中,用 Kafka 作为数据源,并且将计算结果保存到 Kafka 这种场景下,数据从 Kafka 的某个主题中消费,在计算集群中计算,再把计算结果保存在 Kafka 的其他主题中。这样一个过程中,Kafka 的 Exactly Once 机制,保证每条消息都被恰好计算一次,确保计算结果正确。所以,在这个题目中,选项 ABC 提到的这三种消息队列都保证不了消息不重不丢。答案是 D。
3. 在生产者发送消息时,需要使用分片算法把消息均匀地分布到主题的所有分区(队列)上。如果需要保证 Key 相同的消息的严格顺序,并且能支持对分区数量进行水平扩容。可以选择哪些分片算法?
A. 检索表算法:在检索表中存储 Key 和分区的对应关系,通过查表确定分区号。
B. 取模算法:分区号 = Key mod 分区总数
C. 一致性哈希算法
D. 轮询算法
答案:AC
解析:大部分消息队列都只能保证在分区(队列)上的严格顺序,所以,对于这道题目,我们选择的分片算法必须保证具有相同 Key 的消息,都分到同一个分区上。对于这个要求,答案 ABC 都能做到,所以答案 D 先淘汰掉。
然后我们再看,扩容的时候怎么来保证严格顺序呢?那就是要选择具备单调性的分片算法,单调性是指如果已经有一些内容通过哈希分派到了相应的分区中,又有新的分区加入到主题中,哈希的结果应能够保证,原有已分配的内容可以被映射到原有的或者新的分区中去,而不会被映射到旧的分区集合中的其他分区。只要是满足单调性的分片算法,我们就可以按照“先扩容分区 -> 将旧分区中的遗留消息消费完 -> 同时消费所有分区”这样一个方式,确保扩容过程中消息的严格顺序。
选项 C 中的一致性哈希算法是满足单调性的,这个没有问题。选项 A 中的检索表法,由于这个表中映射关系是可以手工配置的,那我们可以把这个映射关系配置成满足单调性,所以选项 A 也是正确的。选项 B 中的取模算法不符合单调性原则,所以这道题的答案是 AC。
4. 以下关于主题、消费组和消费者的说法,哪些是正确的:
A. 同一个主题的多个消费组之间是竞争消费
B. 同一个消费组的多个消费者之间是竞争消费
C. 每个消费组都会消费主题的一份全量消息
D. 每个消费者都会消费主题的一份全量消息
答案:BC
解析:这是一个关于消费模型概念的问题。每个消费组就是一份订阅,它要消费主题的所有分区(队列)的全部消息。注意,队列里的消息并不是消费掉就没有了,这里的消费只是去队列里面读了消息,并没有删除,消费完这条消息还是在队列里面。所以,其他的消费组依然可以消费到同样的消息。
然后我们再说消费组的内部,一个消费组中可以包含多个消费者的实例,同一个消费组内这些消费之间是竞争消费,他们共同消费一份主题的全量消息。
所以,这道题的答案是 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 上。这个做法也是没问题的。
这道题是让我们选择错误的使用方式,所以答案是 BC。
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
解析:在这道题中,具备幂等性的选项只有 CD 两个选项,但是在并发调用的情况下,选项 C 有可能出现 ABA 问题:比如:账户余额是 100 元,先转入 100 元,再转出 100 元,最终账户余额还应该是 100 元。这个过程需要调用 2 次变更余额操作: 第一次,从 100 元变为 200 元,第二次,从 200 元再变为 100 元。
考虑下面这种情况:依次调用这两次操作, 可能会出现这两次操作实际上都执行成功了,这时候账户余额是 100 元,但是第一次操作的响应在网络传输过程中丢失了,请求方执行了重试,这时,再次执行第一次操作,也就是“如果账户余额是 100 元,那就更新为 200 元”,正好是可以执行成功的,这时候账户余额被错误地更新成了 200 元。在并发环境中,选项 C 是不具备幂等性的。
所以,这道题的答案是 D。
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 这种情况,是有可能丢失数据的。
 写留言

精选留言(15)

  • 2019-09-07
    第8题答案A,怎么在mysql事务里更新redis?redis的更新怎样受mysql事务的控制?
    5
    13
  • 2019-09-09
    第8题选A很危险的,实战中基本不会这么设计,因为一单redis出现什么问题,直接延长事务的执行时间,拖累整个数据库的性能。
    1
    2
  • 2019-09-07
    问3个问题。看了下课程目录好像没有,如果课程里面有解答我就马上买。
    1. 如何保证消息发送者在多实例分布式环境下的情况下发出来的消息保证顺序?为了顺序如果不得不用同步方式的话,如何保证性能?场景,用户订单在1分钟内做了创建,修改价格,修改价格三次操作,连续产生了三个信息,三个消息在多个服务实例上触发产生和发送。

    2. rabbit mq 到底有没有办法解决多实例分布式情况下多消费者和queue一一对应,来保证消费消息的顺序?kafka和rocket是可以做到但是rabbitmq独占消费者可以做到但是没法均衡的分部到消费实例上。而且其它消费者要不断重连导致性能消耗大。那rabbitmq要解决多实例消费顺序问题到底咋办。

    3. 任何mq中间件。发送者只要用了异步发送或者重发方式。都可能导致消息乱序。消费者这个时候只能用自己业务特征来判断获得的消息顺序是否正常?那业务特别复杂怎么办?不要说设置个消息序列id就可以了。。这种到处都能搜出来的笼统答案。顺序重排重拾如何解决?最后处理不了丢弃的消息,如何人工介入处理的具体方案。特别是京东级平台上如何保证的?
    展开
    2
  • 2019-11-11
    第5题的解答部分说:“选项 A 描述的场景,因为它需要保证流水和余额严格一致,所以并不适合用事务消息来解决。”
    没看懂,不是正因为“需要保证流水和余额严格一致”,所以需要“流水和余额的改动要么都成功要么都失败”,所以需要事务来解决。而且A、B两个选项从题目考察来说有啥区别吗?没get到有啥区别。望老师回复。
  • 2019-10-05
    第三题感觉答案有问题,mq是怎么做到扩容的时候首先将原有队列消费完再去消费新队列的?
  • 2019-09-24
    第1题、我们就是用JMQ来分发任务,一个生产者生产任务,多个消费者执行任务,再通过缓存判断任务是否全部执行
    第7题、有歧义,我也认为a是指一个数值比如:100,实际a可以是任意值
    有些题点没GET到,明白一多半。
  • 2019-09-17
    感觉第8题答案有点问题吧,如果redis操作成功后,提交事务失败(或者提交事务前程序奔溃、系统宕机等),此时redis是无法回滚的,也会造成不一致。
    1
  • 2019-09-16
    老师的课程更多的是讲述消息队列的原理 使用还得我们自己下功夫呀 不过还是很期待后面的课程😙
  • 2019-09-10
    这几道题,如果理解透彻了,收获也是蛮大的
  • 2019-09-10
    试做一遍错的一塌糊涂,好几个题没理解意思
  • 2019-09-09
    全对的就只有一道题,哎,要好好加油才行。
  • 2019-09-09
    如果要严格实现的话,必须使用分布式事务,比如2PC这种方式。

    或者我们可以简化一下:

    db.beginTransaction()
    try {
      redis.exec(...)
      db.execSql(...)
    } catch(..) {
      redis.exec(...) // 手动执行redis回滚
      db.rollbackTransaction()
    }

    上面这个逻辑可以应付绝大部分情况,极端情况下还是可能出现数据不一致。比如执行过程中进程死掉了,或者redis回滚失败了这些情况。
    展开
    1
  • 2019-09-08
    题目做完了:答案就不留了,觉得可能同时学的东西有点多,自己投入不够-没真实把一些概念理解好;单选做对了,多选基本上都是选对了1个选项、、、后面还要更加努力学习;老师的期中考试出的挺好,刚好帮我们把学到现在的知识梳理一遍,谢谢老师的付出。
           对于最后一题的答案有所困惑:强杀进程不会导致问题么?我的工作一直都是DBA兼系统运维或者系统运维兼DBA多年,曾经碰到过强杀把程序杀崩的情况。故而困惑强杀不会导致问题么?kill +进程号是一层层往上杀其实是没问题的,Kill 9。。。
    展开
    1
  • 2019-09-08
    👍,这种阶段性考察的方式很好
  • 2019-09-07
    第7题,选项B不是幂等的吗?执行多少次,都是将账户 n 的余额更新为 a 元。
    7