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
- 恰好一次
- 这比较难以实现