MIT 6.824 Distributed Systems 第二讲学习笔记,包括 Go 语言和并发编程的简单介绍,以及对 RPC 和故障情况下的 RPC 语义的说明。

Lecture 2: RPC and Threads

为什么选择 Go 语言

  • 对于线程和 RPC 的良好支持
  • 拥有垃圾回收机制
  • 类型安全
  • 简单
  • 可以编译

并行编程入门

在 Go 中,线程被称为 go routine,线程具有独立的 PC、栈和寄存器。线程之间共享内存地址,处于同一片内存空间内。

线程还可以被视为由 Go 运行时提供 start、exit、stop、resume 操作的一组结构。

为什么要使用线程

在本课程中,使用线程是基于并发的考虑:

  • IO 并发:当一个线程发起例如网络 IO 请求时,在等待回复时可以将该线程阻塞,并调度其它线程;
  • 多核并行:在多核处理器上,可以在不同的核心上运行不同的线程,显著提升吞吐量;
  • 方便:在实验中可能需要周期性地执行某一任务,go routine 可以轻松地实现这一功能。

线程中的挑战

  • 条件竞争:程序执行结果取决于线程之间具体的执行顺序
    • 规避方案一:线程之间不要共享变量
    • 规避方案二:使用锁
    • Go 中具有一个竞争检测器,可以检测出许多潜在的竞争行为
  • 协调(似乎是指同步)
    • 通道
    • 条件变量
  • 死锁

Go 的解决方案

针对上述挑战,Go 中提供了对应的解决方案:

  • 通道(不共享内存)
  • 锁和条件变量

RPC 远程过程调用

  • 目标
    • 将远程调用透明化,使之表现得像在进行本地调用。所谓过程调用就是指调用可执行代码,例如函数、方法、子程序等等。
  • 实现
    • 在本地将有一个与远程调用对应的函数桩 stub,stub 记录了远程调用的必要信息,例如函数签名,其通过网络将调用信息发送给服务器。
    • 在服务器也有一个类似的函数桩,负责对通过网络接收到的调用信息进行解码,并调用服务器端函数实现,返回计算结果。
    • 本地 stub 接收到服务器返回的结果后,进行解码并返回给本地调用者。
    • 上述过程全部由编译器和运行时负责实现,对于程序员而言是透明的。

故障情况下的 RPC 语义/行为

故障情况下 RPC 可以有多种不多的行为:

  • at least once
    • 如果服务器挂了,客户端将不断试直至至少执行一次
  • at most once
    • 最多执行一次
    • 服务器需要确保不对重复的请求多次执行
    • Go 采用的模式
    • 使用 TCP 协议来实现最多发送一次 RPC
  • exact once
    • 恰好一次
    • 这比较难以实现