RPC框架原理与实践

RPC(Remote Process Call)即远程过程调用,允许一台计算机调用另一台计算机上的程序得到结果,就像调用本地程序一样。RPC是分布式系统中一个基础概念,它是相对于本地调用来说的,指调用非本地的服务器上的方法。


RPC基本原理

rpc规范只规定了 Client 与 Server 之间的点对点调用流程,包括 stub(代码存根)、通信协议、消息解析等部分。

RPC框架简介

基于rpc规范,一次RPC调用的过程如下:

在这里插入图片描述
简单来说,就是服务调用方发送请求到服务提供方,服务提供方根据请求参数执行服务并获得结果,然后将结果返回给服务请求方。

请求数据在网络上最终是以二进制的数据进行传输的,因此涉及到序列化的问题。当然,最基本的还有网络通信问题。也就是说,最基本的rpc包含传输协议序列化协议

而在实际生产过程中,由于服务提供方一般是以集群形式存在的,因此涉及到服务发现,负载均衡的问题。服务上线后,还涉及到服务治理的问题。

最基本的RPC

基于Java的Socket API,就可以实现一个简单的RPC调用:

// 被调用方
public class Provider {
    private static Map<String, Object> services = new HashMap<>();
    static {
        services.put("ISayHelloService", new SayHelloServiceImpl());
    }

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        while (true) {
            Socket socket = serverSocket.accept(); // 建立socket监听
            ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); // 获得socket端口的输入流
            String interfaceName = input.readUTF(); // 接口名
            String methodName = input.readUTF(); // 方法名
            Class<?>[] parameterTypes = (Class<?>[]) input.readObject(); // 参数类型
            Object[] argus = (Object[]) input.readObject(); // 参数对象

            Class serviceInterfaceClass = Class.forName(interfaceName);
            Object service = services.get(interfaceName); // 取得服务实现的对象
            Method method = serviceInterfaceClass.getMethod(methodName, parameterTypes); // 获得调用方法
            Object result = method.invoke(service, argus);

            ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
            output.writeObject(result);
        }
    }
}
// 调用方
public class Consumer {

    public static void main(String[] args) throws Exception {
        String interfaceName = ISayHelloService.class.getName();
        Method method = ISayHelloService.class.getMethod("sayHello", String.class);
        Object[] argus = {"hello"};
        Socket socket = new Socket("127.0.0.1", 8888);

        ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
        output.writeUTF(interfaceName);
        output.writeUTF(method.getName());
        output.writeObject(method.getParameterTypes());
        output.writeObject(argus);

        ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
        Object result = input.readObject();
        System.out.println(result.toString());
    }
}
// 服务定义
public interface ISayHelloService {
    String sayHello(String str);
}
// 服务实现
public class SayHelloServiceImpl implements ISayHelloService {
    @Override
    public String sayHello(String str) {
        return str;
    }
}

这个例子中通过socket进行通信,服务请求方将方法名和参数发送给服务提供方,服务提供方收到方法名和方法参数之后,通过发射机制获取到执行方法,发起调用得到结果并返回给服务请求方(这个例子为了简单方便,没有使用代码存根stub)。

传输协议

RPC的传输协议可以基于tcp,也可以基于http。基于tcp的rpc,一般使用私有协议,在tcp的基础上进行定制开发。而http是一种应用层的公有协议,可以直接拿来使用,无需太多额外的工作。

序列化

Java自带的序列化接口Serializable在RPC中很少用到,原因有两个:一是因为Java序列化无法跨语言,只在Java内部支持。更重要的是,Java序列化后的数组体积大,从而导致存储和传输效率低。

当前主流的序列化方式有两类:

文本类序列化方式
主要包括 JSON 和 XML,优点是:支持跨语言、可读性好、配套的支持工具比较全。缺点:序列化之后的码流比较大、冗余内容多,性能相对比较差。

二进制序列化方式
比较流行的有Thrift 序列化框架、MessagePack 和谷歌的 Protobuf 框架。优点:性能高。缺点:可读性差,支撑的工具链不健全。

RPC与Restful API

分布式进程之间可以使用rpc进行通信,也可以使用http,或者更严谨的说法是restful api进行通信。那么这两者之间有何区别呢?

历史进程

RPC在1984年就被人用来做分布式系统的通信,Java在1.1版本提供了Java版本的RPC框架(RMI)。HTTP协议在1990年才开始作为主流协议出现,它发明的目的是用于web架构的前后端通信,而不是分布式系统间通信。

这导致了在很长一段时间内,HTTP都是浏览器程序和后端web系统通信用的东西,使用的文档格式都是HTML,没有人会把HTTP作为分布式系统通信的协议。随着技术的发展,AJAX技术和JSON文档在前端界逐渐成为主流,HTTP调用才摆脱HTML,开始使用JSON这一相对简洁的文档格式,为后面用于系统间调用定下基础。最后随着Restful思潮的兴起,越来越多系统考虑用HTTP来提供服务。与此同时,RPC也已经是各种大型分布式系统调用的标配了。

区别

RPC和Restful API都可以用来作为进程之间通信的选择。

基本的 RPC 实现一般会包含有 传输协议 和 序列化协议 。

Restful API也包含传输协议和序列化协议,只不过都是使用的公有协议,即HTTP和JSON。而RPC一般使用私有协议,也就是自定义tcp协议,主要目的是为了提高效率。当然,RPC也可以使用HTTP作为传输协议,只不过一般不这样做。

下面通过一个例子来简单说明。

通用定义的http1.1协议的tcp报文包含很多信息,一个POST协议的格式大致如下:

HTTP/1.1 200 OK 
Content-Type: text/plain
Content-Length: 137582
Expires: Thu, 05 Dec 1997 16:00:00 GMT
Last-Modified: Wed, 5 August 1996 15:55:28 GMT
Server: Apache 0.84

<html>
  <body>Hello World</body>
</html>

如上图所示,使用的报文中有效字节(报文体)数仅仅占约 30%,剩下的70%报文头对后端进程通信来说都是无效的。

假如我们使用自定义tcp协议的报文如下:

报头占用的字节数也就只有16个byte,极大地精简了传输内容。

显而易见,传输相同的内容,使用自定义的tcp协议比使用http协议效率更高

但随着http2.0版本的推出,对编码的效率进行了优化,使用http2.0作为传输协议实际上也是能接受的。Google推出的grpc的传输协议就是使用的http2.0协议。

既然如此,为什么还要用Rpc呢?

效率是一个方面,另一方面还是由于rpc在长期的发展过程中,封装了服务发现负载均衡熔断降级这类面向服务的高级特性,针对服务的可用性方面做了很多优化,而Restful API则缺少这些特性。

RPC框架现状与发展

目前常用的RPC框架如下:

dubbo,由阿里巴巴开源的,现已成为apache的顶级项目,https://dubbo.apache.org。
motan,新浪微博使用,https://github.com/weibocom/motan。
rpcx,基于 golang 的,https://github.com/smallnest/rpcx。
thrift,facebook开源的跨语言框架,https://thrift.apache.org。
finagle,twitter开源的,https://twitter.github.IO/finagle。
gRPC,Google开源的,http://www.grpc.IO (Google inside use Stuppy)。

这些RPC 框架大致有两种不同的侧重方向,一种偏重于服务治理,另一种偏重于跨语言调用。

服务治理型 RPC 框架

服务治理型的 RPC 框架有 dubbo、dubboX(当当基于dubbo的发展)等。
这类的 RPC 框架的特点是功能丰富,提供高性能的远程调用以及服务发现及治理功能,适用于大型服务的微服务化拆分以及管理,对于特定语言(Java)的项目可以十分友好的透明化接入。但缺点是语言耦合度较高,跨语言支持难度较大。

跨语言调用型

跨语言调用型的 RPC 框架有 Thrift、gRPC 等。
这一类的 RPC 框架重点关注于服务的跨语言调用,能够支持大部分的语言进行语言无关的调用,非常适合于为不同语言提供通用远程服务的场景。但这类框架没有服务发现相关机制,实际使用时一般需要代理层进行请求转发和负载均衡策略控制。

Dubbo

Dubbo原理与实践

Thrift

thrift基础

gRPC

gRPC指南

RPC框架与微服务

很多场景下,微服务框架与rpc框架的概念是混用的。但从严格意义上来讲,rpc框架只是微服务框架的一个子集。

微服务更多的是一种设计理念。为了实现这一设计理念,必然要使用多种不同的开发工具,比如rpc,分布式配置中心,服务熔断,服务网关等。

SpringCloud就是典型的微服务框架,提供了一整套的微服务解决方案,Rpc只是它的一个子集,主要解决了微服务之间的通信问题。

微服务的发展趋势

在当前阶段,rpc框架还是主流的微服务通信的解决方案。但rpc框架也存在一些本质的问题:

  • 框架本身屏蔽了分布式系统通信的一些通用功能实现细节,但开发者却要花更多精力去掌握和管理复杂的框架本身,在实际应用中,追踪和解决框架出现的问题也绝非易事;
  • 框架通常只支持一种或几种特定的语言,而微服务的设计理念里面,一个重要的特性就是语言无关。如果某个服务的编写语言不被框架支持,那就很难融入到面向微服务的架构体系中去;
  • 框架以lib库的形式和服务联编,复杂项目依赖时的库版本兼容问题非常麻烦。同时,lib库的升级也无法对服务透明,服务会因为和业务无关的lib库升级而被迫升级。

因此Service Mesh应运而生。

Service Mesh将分布式服务的通信抽象为单独一层,在这一层中实现负载均衡、服务发现、认证授权、监控追踪、流量控制等分布式系统所需的功能,作为一个和服务对等的代理服务(Sidecar),和服务部署在一起,接管服务的流量,通过代理之间的通信间接完成服务之间的通信请求。

Service Mesh功能在于处理服务间通信,职责是实现请求的可靠传递。事实上,Service Mesh由服务与轻量级网络代理(Sidecar)组合而成。当微服务集群扩大到一定规模后,就会形成网格状,即形成了Service Mesh形态。如下所示:

在这里插入图片描述

Service Mesh接管整个网络,在服务之间转发所有的请求。业务服务只负责业务处理,而服务间的通信就从服务里面剥离出来,呈现出一个抽象的基础设施层。

Service Mesh的核心价值在于:

  • 微服务基础设施下沉:微服务架构支撑,网络通信,治理等相关能力下沉到基础设施层,业务部门无需投入专人开发和维护。
  • 降低升级成本:sidecar支持热升级,降低中间件和技术框架客户端,sdk升级成本。
  • 语言无关

存在的问题:

  • 服务间调用增加了两层sidecar,请求链路变长,带来网络延迟,CPU消耗增加的问题。
  • 代理的稳定性成为关键路径,中心化的控制平面也会带来稳定性风险。
  • 将系统复杂性转移到了基础设施层,需要专人进行维护和问题排查。

不适用场景:

  • 技术栈稳定,无需服务治理/监控等强迭代需求。
  • 无长期技术/运维投入。

参考资料

[1]. https://blog.csdn.net/yjp198713/article/details/79410521
[2]. https://zhuanlan.zhihu.com/p/59516670
[3]. https://www.zhihu.com/question/25536695

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值