2.RPC和线程

在以后的课题中我们来使用go语言
RPC
它的全程叫做远程调用(Remote Procedure Call)
它的基本思想是让我们像使用本地函数一样去使用远程函数,封装掉网络通信之间的复杂性
本地调用就是我们日常写代码时候的方法(call(xxx type) return …)
远程调用就是我们需要的函数在别的计算机里面,调用者发送请求然后网络传输到对方主机执行后返回数据到我们的计算机里
关键组件
- Stub
- 调用者把参数封装成为双非约定好的网络格式发送出去,接收端进行解析然后在它本地进行调用
- 序列化/反序列化
- 将参数转化为可在网络传递的数据流(序列化)
- 讲网络数据流转化为原始字节流(反序列化)
- 网络传输层
- 使用TCP/UDP等等信息协议进行数据传递
- 保证消息的可达性,重试机制
- 调用公约
- 同步调用:调用线程阻塞,直到收到返回结果
- 异步调用:调用线程不阻塞,可以通过回调或 future 获取结果
RPC 的调用流程
假设客户端调用远程函数 Foo(a, b):
- 客户端调用 stub.Foo(a, b)
- stub 将参数 a、b 序列化成网络消息
- 通过网络发送到服务器
- 服务器 stub 接收消息,反序列化得到参数 a、b
- 调用本地函数 Foo(a, b)
- 函数执行完成,返回结果
- 服务器 stub 将结果序列化,发送回客户端
- 客户端 stub 接收结果,反序列化,返回给调用者
这个流程隐藏了网络细节,让调用者感知不到函数在远程执行
RPC 的常见问题
- 网络不可达
- RPC 调用可能失败,需要重试机制
- 半同步半异步问题
- 网络延迟不可预测,调用者可能阻塞很久
- 失败与幂等
- 如果 RPC 调用失败,重试可能导致重复操作
- 分布式系统里往往要求服务端函数幂等(重复调用结果相同)
- 序列化兼容性
- 客户端和服务端必须约定好参数和返回类型格式
- 常见方案:JSON、Protobuf、Thrift
多线程
在分布式系统中,RPC 是不同节点之间进行通信的主要方式。而多线程则是提升 RPC 性能和并发处理能力的重要手段
IO并发
RPC的调用会涉及到大量的网络IO,磁盘IO的使用,如果只使用单个网络就会在遇到网络延迟的时候整个服务被阻塞
我们使用多线程可以让每个RPC进行单独处理,提高我们服务的吞吐量
客户端线程1 -> 发送请求 -> 等待响应(阻塞)
客户端线程2 -> 发送请求 -> 等待响应(阻塞)
多线程同时进行,CPU 不空闲,IO 并发完成。并行化
- RPC不仅仅需要等待
我们的任务一定不是瞬间就可以完成的,服务器或者对方主机肯定是需要进行处理然后才能返回给我们
我们使用多线程可以大大整体吞吐量,减少每个请求的等待时间
不过需要注意公共资源之间的协调(内存,日志)
便捷性(不是很重要)
比如你写了一个程序,你需要间隔一段时间知道对方主机是否进行链接,这时候你又不想写在主线程
这时候我们可以多开一个线程来进行心跳,这个也是最不重要的一个理由