上篇分享介绍了单体应用的缺点以及相应的解决方案 —— 微服务,微服务解决单体应用瓶颈的同时也引入了新的问题,即远程方法调用过程中协议约定、服务发现以及网络传输如何实现,必须要解决这些问题才能保证远程方法调用的可靠性,在工程实践中,我们通常使用一些优秀的开源 RPC 框架(如 Spring Cloud、Dubbo、gRRC、Thrift、Hprose 等)来处理这些底层问题,作为一般开发者,只需要基于这些框架专注上层服务实现和消费即可,但是作为一个有追求的开发者,最好能够理解 RPC 框架底层的实现原理,这样才能更好的使用这些框架。
现有的 RPC 框架都是基于 Andrew D. Birrell 和 Bruce Jay Nelson (两位大神均已仙逝)发表的论文实现的:Implementing Remote Procedure Calls,这篇论文定义了 RPC 的调用标准,对于英文不好的人啃起来有些吃力(这里有一篇中文译文,感兴趣的可以看看),我们把它简化为下面的模型来介绍:
当客户端应用(服务消费方)想发起一个远程调用时,实际是通过本地调用客户端的 Stub,该 Stub 负责将调用的接口、方法和参数,通过约定的协议规范进行编码,并通过本地的 RPCRuntime(RPC 通信包) 进行传输,最终将调用网络包发送到服务器。
服务器端的 RPCRuntime 收到请求后,交给服务提供方 Stub 进行解码,然后调用服务端对应的方法,方法执行后返回结果,服务提供方 Stub 将返回的结果编码后,再发送给客户端,客户端的 RPCRuntime 收到结果,发给调用方 Stub 解码得到结果,返回给客户端。
至此,一个完整的 RPC 调用就完成了。这里面分了三个层次,对于用户层和服务端,都像是本地调用一样,专注于业务逻辑的处理就可以了,实际上,我们在进行微服务开发时,基本上也就是专注在这两块,即服务端如何提供服务和客户端如何消费服务。底层的 Stub 处理双方约定好的语法、语义、封装和解封装(解决了协议约定问题和数据序列化问题),RPCRuntime 则主要处理高性能的传输,以及网络的错误和异常(解决了网络传输问题及容错机制)。
但还有一个问题,就是服务发现问题,即客户端调用远程服务时,如何得知要去哪个服务器调用该服务呢?在这篇论文中,实现的方案是通过一个分布式数据库 Grapevine 来绑定服务名与对应的服务提供方地址,当服务端提供新的服务时,需要将其「注册」到 Grapevine,然后客户端 RPCRuntime 在发起远程调用时可以从 Grapevine 中查询到服务名对应的服务端地址,进而发起网络请求(大体原理如此,不一定完全和论文描述完全一致):
Grapevine 即对应着后面微服务分享中要介绍的 RPC 框架中的「注册中心」,目前比较主流的注册中心实现方案有 zookeeper、eureka、consul、etcd 等。
Sun 公司是第一个提供商业化 RPC 库和 RPC 编译器的公司,最早的 RPC 实现就是该公司提供的 ONC RPC(Open Network Computing Remote Procedure Call,开放网络计算远程方法调用),由于该 RPC 实现用在 Sun 系统中,所以也叫 Sun RPC。我们比较熟悉的 NFS(Network File System,网络文件系统) 协议就是基于 ONC RPC 实现的。
在 PHP 中,也有一些实现了 RPC 协议,可用于 RPC 通信的官方扩展可以使用,比如 XML-RPC、Yar、SOAP ,我们可以基于它们实现一些简单的 RPC 远程调用,比如一些古老的传统 Java 系统,在 PHP 中调用其服务通常需要通过 SOAP 来实现。
此外,还可以基于一些跨语言的开源 RPC 框架在 PHP 项目中实现 RPC 调用,并实现工业级的分布式微服务架构,比如 Thrift、Hprose、gRPC、Dubbo 等,至于基于这些框架集成 PHP 项目来实现微服务架构,学院君将在接下来的微服务系列分享中给大家详细介绍。