之前在网上看很多公司在面试的时候都会问到Dubbo等一些RPC框架,更有甚者直接要求面试者手写PRC,今天就来撩下RPC。
什么是RPC
RPC(Remote Procedure Call),远程过程调用,意思是可以在一台机器上调用远程的服务。在非分布式环境下,我们的程序调用服务都是本地调用,但是随着分布式结构的普遍,越来越多的应用需要解耦,将不同的独立功能部署发布成不同的服务供客户端调用,RPC就是为了解决这个问题的。RPC是一种规范,和TCP、UDP都没有关系,RPC可以采用TCP协议完成数据传输,甚至可以使用HTTP应用协议。RPC是C端模式,包含了服务端(服务提供方)、客户端(服务使用方),采用特定的网络传输协议,把数据按照特定的协议包装后进行传输操作等操作。
RPC原理
首先,我们心里带着这样的问题:要怎么样去调用远程的服务呢?
①肯定要知道IP和端口吧(确定唯一一个进程);
②肯定要知道调用什么服务吧(方法名和参数);
③调用服务后可能需要结果吧。
这三点又怎么实现呢?
RPC的设计由Client,Client stub,Network,Server stub,Server构成。
其中Client就是用来调用服务的,Cient stub是用来把调用的方法和参数序列化的(因为要在网络中传输,必须要把对象转变成字节),网络用来传输这些信息到服务器存根,服务器存根用来把这些信息反序列化的,服务器就是服务的提供者,最终调用的就是服务器提供的方法。
RPC的结构如下图:
图中1-10序号的含义如下:
- 客户端像调用本地服务似的调用远程服务;
- 客户端stub接收到调用后,将类名,方法名,参数列表序列化;
- 客户端通过插座将消息发送到服务端;
- Server stub收到消息后进行解码(将消息对象反序列化);
- 服务器存根根据解码结果调用本地的服务;
- 本地服务执行(对于服务端来说是本地执行)并将结果返回给服务器存根;
- 服务器存根将返回结果打包成消息(将结果消息对象序列化);
- 服务端通过插座将消息发送到客户端;
- 客户端stub接收到结果消息,并进行解码(将结果消息发序列化);
- 客户端得到最终结果。
这就是一个完成PRC调用过程,对使用方而言就只暴露了本地代理对象,剩下的数据解析、运输等都被包装了,从服务提供方的角度看还有服务暴露,如下是Dubbo的架构图:
简易RPC实现
项目目录
MethodParameter对象
1 | package com.springboot.whb.study.rpc.rpc_v1; |
服务端-服务暴露
1 | package com.springboot.whb.study.rpc.rpc_v1; |
服务端-网络数据处理
1 | package com.springboot.whb.study.rpc.rpc_v1; |
客户端-服务订阅
1 | package com.springboot.whb.study.rpc.rpc_v1; |
客户端-网络处理
1 | package com.springboot.whb.study.rpc.rpc_v1; |
实践-服务端
1 | package com.springboot.whb.study.rpc.rpc_v1; |
实践-客户端
1 | package com.springboot.whb.study.rpc.rpc_v1; |
服务接口
1 | package com.springboot.whb.study.rpc.rpc_v1.expore; |
服务接口实现
1 | package com.springboot.whb.study.rpc.rpc_v1.expore; |
运行效果
总结
这只是一个非常简单的RPC实践,包含了服务暴露、服务注册(Proxy生成)、BIO模型进行网络传输,java默认的序列化方法,对RPC有一个初步的认识和了解,知道RPC必须包含的模块。
不过还是有很多需要优化的点以改进:
- IO模型:使用的是BIO模型,可以改进换成NIO模型,引入netty;
- 池化:不要随意新建线程,所有的线程都应有线程池统一管理;
- 服务发现:本地模拟的小demo,并没有服务发现,可以采用zk管理;
- 序列化:java本身自带的序列化效率很低,可以换成Hessian(DUBBO默认采用其作为序列化工具)、Protobuf(Protobuf是由Google提出的一种支持多语言的跨平台的序列化框架)等;
- 还有例如服务统计、优雅下线、负载均衡等也都是一个成熟的RPC框架必须要考虑到的点。