期末测试|消息队列100分试卷等你来挑战!

1、定义一个有状态的、通用的服务类:

public class StateServer {
    // 当前状态
    private ServerState serverState;
    // 启动
    public void start() {/*...*/}
    // 停止
    public void stop() {/*...*/}
    // 服务是否可用
    public boolean isAvailable() {/*...*/}
}

public enum ServerState {
  CREATED, // 刚刚创建,从未启动过;
  STARTING, // 正在启动中
  RUNNING, // 运行中
  STOPPING, // 停止中
  STOPPED,  // 已停止
  START_FAILED, // 启动失败
  STOP_FAILED // 停止失败
}

要求:

  1. 只有启动成功后才能正常提供服务,即 isAvailable() 返回 true。
  2. 这个类是一次性的,无论启动成功或者失败,只允许启动一次。
  3. 不允许定义其他变量记录任何状态。

要满足上述要求,实现 StateServer,至少需要下哪些状态:

A、RUNNING, STOPPED

B、CREATED, RUNNING, STOPPED

C、CREATED, STARTING, RUNNING, STOPPED

D、CREATED, STARTING, RUNNING, STOPPING, STOPPED

答案:B

题目解析

这个题要求我们使用最少的状态来实现一个一次性的有状态服务。我们来看看哪些状态是可以去掉的,剩下的就是必要的。首先,题目并没有要求做错误处理,所以用于记录错误的状态 START_FAILED 和 STOP_FAILED 是可以去掉的。然后我们看再来剩下的状态,它的状态转换图应该是什么样的:

img

通过这个图可以看出来,这里面只有当状态是 RUNNING 时,系统才能正常提供服务。停止的时候,无论停止成功或失败,会转换为 STOPPED,所以这个 STOPPING 也是可以省略的。那相应的,状态 STARTING 能不能省掉呢?虽然,STARTING 状态后续状态是有分支的,但其实也是可以省掉的,用 STOPPED 状态替换掉 STARTING 状态,并不影响类的业务逻辑,所以满足要求的最小状态图是这样的:

img

所以,这道题的正确答案是:B

2、一个存储集群,共有 7 个副本,集群采用一主多从的集群模式。当主副本失效的时候,可以保证在剩余存活的副本中,选出一个有最新数据的副本,作为主副本继续提供服务。要求:

  1. 写入成功的数据不允许丢失;
  2. 集群最多容忍 4 个副本故障,仍然可以提供服务。

客户端更新数据的时候,至少需要成功写入多少个副本才能返回“更新成功响应”?

A、2

B、3

C、4

D、不可能做得到

答案:D

题目解析

由于这个题中,要求最多可以容忍 4 个副本故障并且不能丢数据,那就必须保证每次更新数据,都更新到最少 5 个副本上,再返回响应。但是,当集群的故障副本数达到 4 个的时候,只剩 3 副本,达不到写入要求的最少 5 副本,这时候是无法提供数据更新服务的。这与题目的要求“4 个副本故障,仍然可以提供服务”是矛盾的,所以这道题的答案是 D,不可能做得到。

3、在分布式事务中,关于二阶段提交这个经典算法,以下哪个描述是正确的?

A、一个分布式事务中只能包含两个本地事务,超过两个本地事务需要用到三阶段提交或其他分布式事务算法。

B、在提交阶段,一旦某个本地事务提交失败,应该立即回滚整个分布式事务。

C、在提交阶段,如果没有宕机和网络传输信息丢失等硬件故障,提交肯定可以完成。

D、可以保证事务的隔离性。

答案:C

题目解析

选项 A,二阶段提交中的二阶段,并不是指两个本地事务,而是准备阶段和提交阶段这两个阶段。实际上,二阶段提交算法,在一个分布式事务中可以包含任意多个本地事务的。所以,选项 A 错误。

选项 B,在提交阶段,可能某些本地事务已经完成了提交,是没有办法回滚的。在提交阶段如果发生提交失败应该通过重试,完成分布式事务的提交。选项 B 错误。

选项 C,题交阶段,对于每个本地事务,只是修改本地事务的提交状态,不涉及变更业务数据,所以,排除网络传输信息丢失和宕机等硬件故障,提交肯定是可以成功的。选项 C 是正确的。

4、关于 RPC 框架中的“桩(Stub)”,以下哪个说法是正确的:

A、所有的桩都是 RPC 框架在运行时动态生成的。

B、桩和对应的服务实现类具有完全相同的方法签名。

C、桩运行在客户端。

D、桩运行在服务端。

答案:C

题目解析

在 RPC 框架中,桩是由 RPC 框架提供的一个服务端代理类的实例,它运行在客户端,选项 C 正确,D 错误。再来看选项 A,桩的确是由 RPC 框架生成的,但不是所有的 RPC 框架都会在运行时动态生成桩,有些 RPC 框架(比如 gRPC)是在编译 IDL 阶段生成的桩,所以选项 A 错误。

选项 B,桩和服务的实现类都实现了服务接口定义的所有方法,但不等于桩和对应的服务实现类具有完全相同的方法签名,服务实现类在实现服务接口定义的方法之外,还可以有其他业务方法,而桩是没有这些业务方法的,选项 B 也是错误的。

所以,这道题的正确答案是:C。

5、在 RocketMQ 的一个 Broker 上,使用一个消息序号消费某个队列的消息。其中,消息文件(commitlog)的数量是 m 个,所有消息文件中的消息条数是 k 条,目标队列对应的索引文件(consumerQueue)的数量是 n 个,队列中共有 j 条消息。查找消息的最快时间复杂度是:

A、O(1)

B、O(m) + O(n)

C、O(log m) + O(log n)

D、O(log k) + O(log j)

答案:A

题目解析

查找消息的时候,可以直接根据队列的消息序号,计算出索引的全局位置(索引序号 x 索引固定长度 20),然后直接读取这条索引,再根据索引中记录的消息的全局位置,找到消息。这里面比较耗时两个操作就是分别找到索引和消息所在文件,这两次查找是差不多的,都可以抽象成:

在一堆数字(文件名)中,找到一个比当前数字(索引或消息的全局位置)小且最接近当前数字的那个数字。

通常的做法是,把这些文件名组织成跳表、红黑树或者其他的树,这些数据结构最快查找时间都是 O(log n),所以,二次文件查找最快的时间复杂度是:O(log m) + O(log n)。RocketMQ 采用了一种更高效的方式组织文件:每个索引文件或者消息文件的长度的是固定的,对于每一组文件,都维护了一个由小到大有序的文件数组。查找文件的时候,直接通过计算即可获取文件在数组中的序号:

文件在数组中的序号 = (全局位置 – 第一个文件的文件名)/ 文件固定大小

在通过序号在数组中获取数据的时间复杂度是 O(1),二次查找文件的时间复杂度是:O(1) + O(1)= O(1),所以这道题的答案是 A。

6、假设一个 RocketMQ 集群部署在两个机房,每个机房都有一些 NameServer、Broker 和客户端节点,当两个机房间的链路中断时:

A、所有的 NameServer 都无法提供服务。

B、所有的 NameServer 都可以提供服务。

C、客户端只能在本机房的 NameServer 中找到本机房的 Broker。

D、客户端能在本机房的 NameServer 中找到所有的 Broker,但是部分 Broker 的数据已经过期。

答案:BC

题目解析

RocketMQ 集群中,NameServer 之间是不需要互相通信的,所以网络分区对 NameServer 本身的可用性是没有影响的,那选项 A 和 B 中,B 是正确的。如果 NameServer 检测到与 Broker 的连接中断了,NameServer 会认为这个 Broker 不再能提供服务。NameServer 会立即把这个 Broker 从路由信息中移除掉,避免客户端连接到一个不可用的 Broker 上去。

网络分区后,NameServer 收不到对端机房那些 Broker 的心跳,这时候,每个 NameServer 上都只有本机房的 Broker 信息,所以选项“C. 客户端只能在本机房的 NameServer 中找到本机房的 Broker”也是正确的,相应的,选项 D 就是错误的。

所以,这道题的答案是:BC。

7、以下是 Kafka 客户端源代码中 KafkaConsumer 类中的一段代码:

  private final AtomicLong currentThread = new AtomicLong(-1L);
  private final AtomicInteger refcount = new AtomicInteger(0);
  private void acquire() {
      long threadId = Thread.currentThread().getId();
      if (threadId != currentThread.get() && !currentThread.compareAndSet(-1L, threadId))
          throw new Exception("...");
      refcount.incrementAndGet();
  }
  private void release() {
      if (refcount.decrementAndGet() == 0)
          currentThread.set(-1L);
  }


  void foo() {
    acquire()
    try {
      // 更新数据
    } finally {
      release();
    }
  }

关于 foo() 方法,以下哪些说法是正确的:

A、并发调用会导致数据错误

B、并发调用不会导致数据错误

C、只能单线程调用

D、可以并发调用提升性能

答案:BC

题目解析

这里面的的 acquire() 和 release(),其实是一种并发检测防呆机制,作用是,当检测到并发调用的时候抛出异常。所以,本质上,foo() 方法还是只能单线程访问,但有了这种检测机制,就可以保证,当有一个线程在更新数据的时候,其他线程的调用都会抛出异常。

Kafka“主动检测不支持的情况并抛出异常,避免系统产生不可预期的行为”这种模式,对于增强系统的健壮性是一种非常有效的做法。

所以,这道题的答案是:BC。

8、ZooKeeper 是一个分布式一致性的存储系统,如果不考虑性能差异,以下这些场景,哪些场景下可以用 MySQL 替代 ZooKeeper?

A、作为分布式锁保护共享资源

B、保存元数据

C、选举

D、监控集群节点存活状态

答案:ABC

题目解析

这个题考察的其实是对 ZooKeeper 的理解,你需要知道这 4 个场景都是如何用 ZooKeeper 来实现的,然后就很容易分析出答案了。

选项 A,利用 ZooKeeper 来实现分布式锁,实际上利用的是 ZooKeeper 操作的原子性,那 MySQL 同样可以利用数据库事务保证执行 SQL 的原子性,所以选项 A 是正确的。

选项 B,保存元数据需要一个一致性的存储系统,这一点 ZooKeeper 和 MySQL 都可以保证数据一致性,所以选项 B 也是正确的。

选项 C,虽然 ZooKeeper 本身的选举机制很复杂,但使用 ZooKeeper 进行选举就比较简单了,各业务节点在 ZooKeeper 中抢占创建同一个 ZNode,ZooKeeper 可以保证至多只有一个节点创建成功,这个节点就是选举出来的主节点。这个选举方法实际上也是利用了 ZooKeeper 操作的原子性,同样 MySQL 也可以替代。所以选项 C 也是正确的。

选项 D,监控节点存活状态,利用的是 ZooKeeper 的临时节点加上 watch 机制这两个特性,这两个特性都是 MySQL 不具备的,所以选项 D 是不正确的。

所以,这道题的答案是:ABC。

9、在设计一个使用 MQTT 协议通信的海量 IoT 集群时,需要重点考虑哪些问题?

A、海量连接数

B、海量 MQTT Topic 数量

C、网络连接频繁中断的问题

D、如何解决服务端向 IoT 设备下发指令的问题

答案:AB

题目解析

这个题考察的是对 MQTT 协议的理解以及 IoT 场景的特性。由于 MQTT 协议采用的是长连接,海量的 IoT 设备必然带来海量的连接数,另外,每个 IoT 设备都会建立一个专属的 MQTT Topic,所以海量的 IoT 设备也会导致海量的 Topic 数量,所以选项 A、B 都是需要考虑的问题。

选项 C、D,这两个问题都是在 MQTT 协议内就已经解决的问题,所以并不需要我们在设计的时候过多的关注。

所以,这道题的答案是:AB。

10、以下哪些场景采用了存储计算分离的设计思想:

A、标准的微服务集群

B、Kafka 集群

C、Hadoop 集群

D、Pulsar 集群

答案:ACD

题目解析

存储计算分离的设计思想,就是将系统的存储职责和计算职责分离开,存储节点只负责数据存储,而计算节点只负责计算,也就是执行业务逻辑。这 4 个选项中,ACD 这三个选项采用的都是这个设计思想。

所以,这道题的答案是:ACD。

发表评论

后才能评论