今天开始聊一些 微服务的实践 ,第一块, RPC 框架的原理及实践 ,为什么说要搞定微服务架构,先搞定RPC框架呢?
服务化的一个好处就是,不限定服务的提供方使用什么技术选型,能够实现大公司跨团队的技术解耦 ,如下图:
服务 A 是欧洲团队提供服务,欧洲团队的技术背景是 Java ,可以用 Java 实现服务;
服务 B 是美洲团队提供服务,可以用 C++ 实现服务;
服务 C 是中国团队提供服务,可以用 Go 实现服务;
服务的上游调用方,按照接口、协议即可完成对远端服务的调用。
但实际上, 99.9% 的公司的团队规模有限,技术团队人数也有限,基本是使用同一套技术体系来调用和提供服务 的:
这样的话, 如果没有统一的服务框架, RPC 框架 , 各个团队的服务提供方就需要各自实现一套序列化、反序列化、网络框架、连接池、收发线程、超时处理、状态机等“业务之外”的重复技术劳动 ,造成整体的低效。所以, 统一 RPC 框架 把上述“业务之外”的技术劳动统一处理, 是服务化首要解决的问题 。
在达成【“使用统一的 RPC 框架”是正确的道路】这个一致的前提下,本文期望用简单通俗的言语简述一下一个通用 RPC 框架的技术点与实现。
什么是 RPC ( Remote Procedure Call Protocol ),远程过程调用?
先来看下什么是本地函数调用,当我们写下:
int result = Add(1, 2);
这段代码的时候,我们知道,我们传入了 1 , 2 两个入参数,调用了本地代码段中的一个 Add 函数,得到了 result 出参。此时, 传入数据,传出数据,代码段在同一个进程空间里,这是本地函数调用 。
那有没有办法,我们能够调用一个跨进程(所以叫 “ 远程 ” ,典型的,这个进程部署在另一台服务器上)的函数呢 ?
最容易想到的, 两个进程约定一个协议格式,使用 Socket 通信,来传输【入参】【调用哪个函数】【出参】 。
假设请求报文协议是一个 11 字节的字节流:
( 1 )前 3 个字节填入函数名
( 2 )中间 4 个字节填入第一个参数
( 3 )末尾 4 个字节填入第二个参数
同时可以设计响应报文协议是一个 4 字节的字节流:
即处理结果。
调用方的代码可能变为:
request = MakePacket(“add”, 1, 2);
SendRequest_ToService_B(request);
response = RecieveRespnse_FromService_B();
int result = unMakePacket(respnse);
简单解释一下:
( 1 )讲传入参数变为字节流
( 2 )将字节流发给服务 B
( 3 )从服务 B 接受返回字节流
( 4 )将返回字节流变为传出参数
服务方的代码可能变为:
request = RecieveRequest();
args/function = unMakePacket(request);
result = Add(1, 2);
response = MakePacket(result);
SendResponse(response);
这个过程也很好理解:
( 1 )服务端收到字节流
( 2 )将字节流转为函数名与参数
( 3 )本地调用函数得到结果
( 4 )将结果转变为字节流
( 5 )将字节流发送给调用方
这个过程用一张图描述如上,调用方与服务方的处理步骤都是非常清晰的。 这个过程存在最大的问题是什么呢?
回答:调用方太麻烦了,每次都要关注很多底层细节
( 1 )入参到字节流的转化,即序列化应用层协议细节
( 2 ) socket 发送,即网络传输协议细节
( 3 ) socket 接受
( 4 )字节流到出参的转化,即反序列化应用层协议细节
能不能调用层不关注这个细节呢?
回答:可以, RPC 框架就是解决这个问题的,它能够让调用方“像调用本地函数一样调用远端的函数(服务)” 。
通过上面的讨论, RPC 框架要向调用方屏蔽各种复杂性,要向服务提供方也屏蔽各类复杂性 :
( 1 )调用方感觉就像调用本地函数一样
( 2 )服务提供方感觉就像实现一个本地函数一样来实现服务
所以整个 RPC 框架又分为 client 部分与 server 部分,负责把整个非( 1 )( 2 )的各类复杂性屏蔽,这些复杂性就是 RPC 框架的职责。
再细化一些, client 端又包含:序列化、反序列化、连接池管理、负载均衡、故障转移、队列管理,超时管理、异步管理等等等等职责。
server 端包含:服务端组件、服务端收发包队列、 io 线程、工作线程、序列化反序列化、上下文管理器、超时管理、异步回调等等等等职责。
however ,因为篇幅有限,这些细节不做深入展开。
( 1 ) RPC 框架是架构微服务化的首要基础组件 ,它能大大降低架构微服务化的成本,提高调用方与服务提供方的研发效率,屏蔽跨进程调用函数(服务)的各类复杂细节
( 2 ) RPC 框架的 职责 是: 让调用方感觉就像调用本地函数一样调用远端函数、让服务提供方感觉就像实现一个本地函数一样来实现服务