说实话,可能是因为我的理解能力有点小问题,这篇文章读了好几遍才大概明白了点其中的道道。为了防止大家跟我一样学习的时候会晕菜。所以我决定先把作者的行文思路给大家画出来。

file00

  1. 介绍了下架构师的职责
  2. RPC在众眼里是什么样的呢?为什么一直这么火呢
  3. 借用本地调用过程的例子来引入,同时做出假设调用者和被调用者不在同一个进程之内
  4. 解决上面步骤2问题的方法,有六种。尤其是最后一种本地套接字接口,它的设计理念简直直RPC最初的目的不谋而合
  5. 但在那个时候对于透明的分布式系统而言,别说存在着大量的问题需要解决的,就连认识上都存在大量误解。
  6. 最终,施乐公司的Palo Alto研究中心,发布了第一个基于RPC的应用,并正式提出了RPC的概念。

​ 在这篇文章中有几个提法比较有意思:比如RPC作为分布式前置的基础条件,再比如RPC应该是一种高层次的,或者说语言层次的特征,而不是像IPC那样,是低层次的,或者说系统层次的特征;还有RPC以模拟进程间方法调用为起点,许多思想和概念都借鉴的是IPC,都能给人耳目一新的感觉。

好了下面咱们开始正式的内容:

0. 架构师的职责

1
2
3
4
5
6
7
8
什么架构师呢?
有做企业战略设计的架构师,有做业务流程分析的架构师……
在这里呢?特指软件系统中技术模型的设计者。

架构师工作的三个问题?
* 做架构设计的时候,架构师都应该思考哪些问题呢?
* 都可以选择哪些主流的解决方案和行业标准做法呢?
* 以及这些方案都有什么优缺点,都会给架构设计带来什么影响呢?

1.大众眼中的RPC(远程服务调用)?

关于RPC三个小问题:

1. RPC本身可以解决什么问题呢
 2. 这些问题又是怎么被解决的呢?
 3. 为什么要这样解决呀

RPC为什么这么火热的原因:

  • 可能是微服务风潮带来的热度
  • 作为开发者,我们很多人对RPC本身可以解决什么问题、如何解决这些问题、为什么要这样解决,都或多或少存在些认知模糊的情况

2. 本地方法调用

​ 本地方法调用几个概念

1
2
3
4
5
6
7
8
// 调用者(Caller)      : main()
// 被调用者(Callee) : println()
// 调用点(Call Site) : 发生方法调用的指令流位置
// 调用参数(Parameter) : 由Caller传递给Callee的数据,即“hello world”
// 返回值(Retval) : 由Callee传递给Caller的数据,如果方法正常完成,返回值是void,否则是对应的异常
public static void main(String[] args) {
System.out.println("hello world");
}

做一个假设:如果在调用println()的时候,发现它并不在当前内存地址空间中,又会出现什么问题呢?

  1. 前面的传递参数、传回结果都依赖于栈内存的帮助,如果Caller与Callee分属不同的进程,就不会拥有相同的栈内存,那么在Caller进程的内存中将参数压栈,对于Callee进程的执行毫无意义。
  2. println()方法版本选择依赖于语言规则的定义,而如果Caller与Callee不是同一种语言实现的程序,方法版本选择就将是一项模糊的不可知行为。

如何解决两个进程间通讯的问题问题:

1. 管道(Pipe)或具名管道(Named Pipe): 管道其实类似于两个进程间的桥梁,用于进程间传递少量的字符流或字节流。
2. 信号(Signal): 信号是用来通知目标进程有某种事件发生的。除了用于进程间通信外,信号还可以被进程发送给进程自身。信号的典型应用是kill命令,比如“kill -9 pid”,意思就是由Shell进程向指定PID的进程发送SIGKILL信号。 
3. 信号量(Semaphore):信号量是用于两个进程之间同步协作的手段,相当于操作系统提供的一个特殊变量。我们可以在信号量上,进行wait()和notify()操作。 
4. 消息队列(Message Queue):进程可以向队列中添加消息,而被赋予读权限的进程则可以从队列中消费消息。消息队列就克服了信号承载信息量少、管道只能用于无格式字节流,以及缓冲区大小受限等缺点 ,但实时性相对受限。 
5. 共享内存(Shared Memory): 允许多个进程可以访问同一块内存空间,这是效率最高的进程间通讯形式。进程的内存地址空间是独立隔离的,但操作系统提供了让进程主动创建、映射、分离、控制某一块内存的接口。由于内存是多进程共享的,所以往往会与其它通信机制,如信号量等结合使用,来达到进程间的同步及互斥。 
6. <font color="red">本地套接字接口(IPC Socket): 消息队列和共享内存这两种方式,只适合单机多进程间的通讯。而套接字接口,是更为普适的进程间通信机制,可用于不同机器之间的进程通信。</font>

3.竟然不谋而合

​ RPC可以作为IPC的一种特例来看待。

​ IPC Socket是操作系统提供的标准接口,所以它完全有可能把远程方法调用的通讯细节,隐藏在操作系统底层,从应用层面上来看,可以做到远程调用与本地方法调用几乎完全一致

还记得远程服务调用最初的目的吗?与调用本地方法一致

4.透明RPC调用存在的问题

  • 两个进程通讯,谁作为服务端,谁作为客户端?
  • 怎样进行异常处理?异常该如何让调用者获知?
  • 服务端出现多线程竞争之后怎么办?
  • 如何提高网络利用的效率,比如连接是否可被多个请求复用以减少开销?是否支持多播?
  • 参数、返回值如何表示?应该有怎样的字节序?
  • 如何保证网络的可靠性,比如调用期间某个链接忽然断开了怎么办?
  • 服务端发送请求后,收不到回复该怎么办?

分布式运算的八宗罪:

  1. 网络是可靠的(The network is reliable)
  2. 延迟是不存在的(Latency is zero )
  3. 带宽是无限的(Bandwidth is infinite)
  4. 网络是安全的(The network is secure)
  5. 拓扑结构是一成不变的(Topology doesn’t change)
  6. 总会有一个管理员(There is one administrator)
  7. 不考虑传输成本(Transport cost is zero)
  8. 网络是同质化的(The network is homogeneous)

5.RPC的概念

​ 传奇的施乐Palo Alto研究中心,发布了基于Cedar语言的RPC框架Lupine,并实现了世界上第一个基于RPC的商业应用Courier。并提出了RPC的概念,也就我们今天看到:

1
RPC是一种语言级别的通讯协议,它允许运行于一台计算机上的程序以某种管道作为通讯媒介(即某种传输协议的网络),去调用另外一个地址空间(通常为网络上的另外一台计算机)

思考题

1
“远程方法不应该无视通讯成本”这个观点,从性能的角度来看是有益的,但从简单的角度看则是有害的。在现代的软件系统开发中,你用过什么RPC框架吗?它们有没有把“像本地方法一样调用远程方法”作为卖点?