diff --git a/notes/pics/rpc-call.png b/notes/pics/rpc-call.png
new file mode 100644
index 00000000..b87503f2
Binary files /dev/null and b/notes/pics/rpc-call.png differ
diff --git a/notes/分布式.md b/notes/分布式.md
index 7b3373fc..1d802082 100644
--- a/notes/分布式.md
+++ b/notes/分布式.md
@@ -28,6 +28,11 @@
* [单个 Candidate 的竞选](#单个-candidate-的竞选)
* [多个 Candidate 竞选](#多个-candidate-竞选)
* [日志复制](#日志复制)
+ * [七、RPC](#七rpc)
+ * [调用过程](#调用过程)
+ * [服务发现](#服务发现)
+ * [RPC 与 HTTP](#rpc-与-http)
+ * [超时与重试](#超时与重试)
* [参考](#参考)
@@ -416,8 +421,107 @@ Leader 会周期性的发送心跳包给 Follower。每个 Follower 都设置了
发生网络分区时,少数派一侧的旧 Leader 得不到大多数节点的确认,无法提交日志,多数派一侧会选出新的 Leader。分区恢复后,旧 Leader 发现更大的任期号就会退回 Follower,它未提交的日志被覆盖,因此不会出现脑裂。
+## 七、RPC
+
+远程过程调用(Remote Procedure Call, RPC)指的是像调用本地方法一样调用另一台机器上的服务。例如订单服务调用库存服务扣减库存:
+
+```java
+boolean ok = inventoryService.deduct(10086, 2);
+```
+
+### 调用过程
+
+调用方写的是普通的方法调用,RPC 框架在背后完成了网络通信:
+
+
+
+1. inventoryService 是框架用动态代理生成的对象,调用它的方法时,代理拿到接口名、方法名和参数;
+2. 把这些信息序列化成字节,加上请求 ID 等首部组成请求帧,从注册中心获取的地址列表中按负载均衡选出一个实例,通过长连接发送;
+3. 服务方解码请求,找到对应的实现类,通过反射调用 deduct(),再把返回值按同样的方式发回;
+4. 一个连接上可能同时有多个请求在等待响应,调用方根据响应中的请求 ID 把结果交给对应的调用。
+
+去掉工程上的细节,核心只有两部分:调用方用动态代理把方法调用转成网络请求,服务方用反射把网络请求转成方法调用。
+
+```java
+// 调用方:生成接口的代理对象,调用方法时把方法名和参数发给服务方
+@SuppressWarnings("unchecked")
+static T refer(Class api, String host, int port) {
+ return (T) Proxy.newProxyInstance(api.getClassLoader(), new Class>[]{api}, (proxy, method, args) -> {
+ try (Socket socket = new Socket(host, port)) {
+ ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
+ out.writeUTF(method.getName());
+ out.writeObject(method.getParameterTypes());
+ out.writeObject(args);
+ out.flush();
+ return new ObjectInputStream(socket.getInputStream()).readObject();
+ }
+ });
+}
+
+// 服务方:读出方法名和参数,通过反射调用本地的实现
+static void export(Object impl, int port) throws Exception {
+ try (ServerSocket server = new ServerSocket(port)) {
+ while (true) {
+ try (Socket socket = server.accept()) {
+ ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
+ Method method = impl.getClass().getMethod(in.readUTF(), (Class>[]) in.readObject());
+ Object result = method.invoke(impl, (Object[]) in.readObject());
+ ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
+ out.writeObject(result);
+ out.flush();
+ }
+ }
+ }
+}
+```
+
+```java
+// 服务方
+export(new InventoryServiceImpl(), 20880);
+
+// 调用方
+InventoryService inventoryService = refer(InventoryService.class, "10.0.0.8", 20880);
+inventoryService.deduct(10086, 2); // 实际执行的是服务方的 InventoryServiceImpl.deduct()
+```
+
+这段代码每次调用都新建连接,使用 Java 自带的序列化,也没有超时处理。Dubbo、gRPC 等框架在此基础上实现了连接复用、高效的序列化、服务发现、负载均衡、超时重试和熔断,设计一个 RPC 框架时也是从这几个模块考虑。
+
+### 服务发现
+
+服务的实例会扩容、缩容或者宕机,地址不能写死在调用方。注册中心(例如 Nacos、ZooKeeper)保存每个服务当前可用的实例:
+
+- 服务方启动时把自己的地址注册到注册中心,之后定期发送心跳,长时间没有心跳的实例会被删除;
+- 调用方订阅需要的服务,把地址列表缓存在本地,实例变化时由注册中心推送通知;
+- 每次调用都从本地缓存的列表中选择实例,注册中心不转发请求。
+
+因此注册中心全部宕机时,调用方仍然可以按缓存的地址继续调用,只是新上线的实例无法被发现。某个实例宕机后、被注册中心删除之前,发往它的请求会失败,需要调用方重试其它实例。
+
+### RPC 与 HTTP
+
+HTTP 是一种协议,RPC 是一种调用方式,两者不是同一层面的概念:RPC 框架可以在 TCP 上使用自定义的协议,例如 Dubbo 协议;也可以基于 HTTP,例如 gRPC 基于 HTTP/2,Spring Cloud 的 OpenFeign 用动态代理把接口调用转换成 HTTP 请求。
+
+面试中通常问的是:为什么对外接口常用 HTTP + JSON,内部服务之间却常用 RPC 框架。这是因为内部调用量大、对延迟敏感,RPC 框架在这些方面做得更好:
+
+- 序列化更紧凑:例如参数 {"skuId":10086,"count":2},JSON 需要 25 字节;Protobuf 不传字段名,只传字段编号和值,编码为 08 E6 4E 10 02,共 5 字节,其中 08 表示字段 1,E6 4E 是 10086 的变长编码,10 02 表示字段 2 的值为 2;
+- 连接复用:在少量长连接上同时发送多个请求,用请求 ID 区分响应,省去频繁建立连接的开销;
+- 服务治理:内置服务发现、负载均衡、超时重试和熔断;
+- 接口约定:用 Java 接口或者 IDL 文件定义服务,自动生成调用方的代码,参数类型在编译期就能检查。
+
+HTTP + JSON 的优势在于通用:浏览器和各种语言都能直接调用,报文可读,便于调试,适合对外开放的接口。
+
+### 超时与重试
+
+- 每次调用都要设置超时时间,否则下游变慢时,调用方的线程会一直等待,最终被耗尽;
+- 调用方超时不代表服务方没有执行:请求可能在网络中延迟,或者在服务方的线程池中排队、遇到 GC 停顿,这些时间都计入了调用方的耗时,却不在服务方记录的处理时间里,于是调用方超时了,服务方却执行成功了;
+- 因此只有幂等的接口才能自动重试,例如查询;扣库存这类写操作要带上请求 ID,由服务方去重,否则重试会扣减两次;
+- 调用链较长时,下游的超时时间应该比上游短。gRPC 会把截止时间(deadline)随请求传给服务方,超时后调用方收到 DEADLINE_EXCEEDED,服务方也可以检查调用是否已经超时,从而停止处理。
+
## 参考
+- [Apache Dubbo: Architecture](https://dubbo.apache.org/docs/v2.7/user/preface/architecture/)
+- [Protocol Buffers: Encoding](https://protobuf.dev/programming-guides/encoding/)
+- [gRPC: Core concepts](https://grpc.io/docs/what-is-grpc/core-concepts/)
+- [gRPC on HTTP/2](https://grpc.io/blog/grpc-on-http2/)
- 倪超. 从 Paxos 到 ZooKeeper : 分布式一致性原理与实践 [M]. 电子工业出版社, 2015.
- [Designing Data-Intensive Applications 中文版](https://github.com/vonng/ddia)
- [Distributed locks with Redis](https://redis.io/topics/distlock)
diff --git a/tools/diagram/src/rpc-call.svg b/tools/diagram/src/rpc-call.svg
new file mode 100644
index 00000000..7bd85d9b
--- /dev/null
+++ b/tools/diagram/src/rpc-call.svg
@@ -0,0 +1,77 @@
+