今天要分享的主题是 RPC 协议,所谓 RPC 指的是 Remote Procedure Call,即远程方法调用(也叫远程服务调用、远程过程调用),这也是微服务架构的前导篇,因为微服务里面远程服务之间就是通过 RPC 协议进行数据传输的。
在介绍 RPC 协议之前,我们先厘清几个概念:单体应用、微服务应用、本地方法调用与远程方法调用。
单体应用 vs 微服务
所谓单体应用指的是将应用的所有服务部署到一台机器上,很多中小型公司都是这么做的,这样做的好处是部署和维护起来比较简单,也方便业务早期的快速迭代,但是缺点也是显而易见的,随着业务的增长,会导致代码仓库越来越庞大,与之相伴的,随着开发人员的增加,不同团队和成员之间的协作变得越来越复杂,比如同时有五个以上的开发者在开发功能准备上线,合并代码时代码冲突的风险增加,甚至需要专门的人力来负责合并代码,协助解决冲突,然后统一上线,而随着代码仓库代码量的增加,上线时间也会随之增加,尤其是要部署多台机器的情况下,这就给线上机器的稳定性和出现问题时回滚代码带来隐患。除此之外,因为所有服务都打包在一个应用里面,运行在一种进程中,一旦某个功能涉及的代码或者资源有问题,就会导致整个应用不可用,从而让系统可用性大打折扣。
所以总结起来,单体应用主要有三个问题,第一、随着业务功能的复杂度增加,团队之间的协作和代码合并越来越困难;第二、随着代码仓库代码量的增加,应用部署时间越来越长,这就为上线期间线上系统的稳定性和代码回滚带来风险;第三、单体应用将所有服务打包到一个应用里面,如果某个功能或资源不可用,会导致整个应用不可用。
所以单体应用更适用于项目早期快速迭代和试错阶段,随着业务功能增加,开发团队的扩充,对系统稳定性和可用性的要求变高,单体应用就显得越来越捉襟见肘,那么我们该如何解决单体应用面临的这一系列问题呢?
答案想必你已经知道,那就是通过微服务架构对服务进行拆分,将原来的单体应用拆分成多个子服务,比如对于一个电商应用而言,用户服务专门用于处理与用户相关的功能,商品服务专门用于处理与商品相关的功能,交易服务专门用于处理与交易相关的功能,依次类推,具体的划分维度我们放到后面微服务系列去单独介绍,这些拆分的服务都有着自己独立的代码仓库,独立开发、独立部署、独立维护,不同的服务之间通过 RPC 协议进行通信,某个服务不可用并不会应用其它功能的使用,从而完美解决了单体应用的三个瓶颈。
微服务虽好,但也不是包治百病,因为它也引入了一系列需要解决的问题,比如多个服务独立部署和维护导致上线的复杂度增加,出现问题后调试的复杂度增加,以及远程服务调用过程中参数传递和数据格式的约定等等,所以当团队规模很小,业务规模不大的情况下,并不适合使用微服务架构,反而是单体应用更加灵活,方便快速迭代。
本地方法调用 vs 远程方法调用
我们知道,在单体应用中,所有服务都打包在一个应用里面,我们只需要通过本地方法即可调用其他的服务,比如我们有 UserService 和 ProductService 两个服务,如果要在 UserService 中获取某个用户发布的商品,只需要实例化 ProductService,并传入用户参数到到数据库查询并返回结果即可,一切操作都是在本地内存中完成的,不同服务之间的调用并不涉及到任何网络传输,编译器会帮我们完成所有的函数调用、参数解析和代码执行,以 PHP 为例,底层的执行逻辑如下(如果你对服务不太理解的话,常见的 MVC 模式其实就是一种单体应用架构模式):
而在微服务中则不然,不同的服务部署在不同的机器,需要通过网络传输约定调用方法名、参数和返回数据才能完成一个完整的方法调用,与单体应用本地方法调用相对,我们把这种通过网络传输调用不同服务方法的方式称之为远程方法调用,既然是远程方法调用,那就与本地方法调用完全不同了,我们无法在本地实例化远程的服务类,更无法调用对应的方法,传入相应的参数,此外,不同服务之间甚至是通过不同编程语言实现的,服务方法调用期间出现异常与错误的捕获也要单独约定。。。总之,你会发现本地方法调用的那一套逻辑现在完全不适用了,所以在正式介绍 RPC 协议之前,我们应该知道,远程方法调用至少要解决以下几个问题:
所有这些问题都是 RPC 协议要解决的,解决了这些问题,才能保证远程方法调用的可靠性,进而保证微服务的稳定性和可用性。下一篇,将就以常见的 RPC 框架为例,介绍它们是如何解决以上的问题的。