引言
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。有多种 RPC模式和执行。
RPC 结构
实现 RPC 的程序包括 5 个部分:
1. User
2. User-stub
3. RPCRuntime
4. Server-stub
5. Server
RPC 调用分类
RPC 调用的分类方式有很多种。
从通信协议层面可以分为:
基于 HTTP 协议的 RPC;
基于二进制协议的 RPC;
基于 TCP 协议的 RPC。
从是否跨平台可分为:
单语言 RPC,如 RMI, Remoting;
跨平台 RPC,如 google protobuffer, restful json,http XML。
从调用过程来看,可以分为同步通信RPC和异步通信RPC:
同步 RPC:指的是客户端发起调用后,必须等待调用执行完成并返回结果;
异步 RPC:指客户方调用后不关心执行结果返回,如果客户端需要结果,可用通过提供异步 callback 回调获取返回信息。大部分 RPC 框架都同时支持这两种方式的调用。
RPC 功能目标
RPC 的主要功能目标是让构建分布式计算(应用)更容易,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC 框架需提供一种透明调用机制让使用者不必显式的区分本地调用和远程调用,在前文《浅出篇》中给出了一种实现结构,基于 stub 的结构来实现。下面我们将具体细化 stub 结构的实现。
Java RPC 实现
在进一步拆解了组件并划分了职责之后,这里以一个最简单 Java RPC 框架实现为例,对 RPC 具体逻辑进行分析。
RPC 框架服务发布代码:
服务端发布服务的代码如上,首先校验传入的端口和服务是否合法,然后开启一个 socket 监听,这儿为了简便,没有采用 NIO 方式,同时直接采用 java 的序列化方式,将传入的数据通过反射取出调用的方法和参数,本地执行后将运行结果通过 socket 套接字返回给客户端。RPC 框架服务调用代码:框架中客户端调用的代码中,首先校验对应的端口和主机是否合法,然后通过动态代理生成一个代理对象,在代理对象的方法中,拦截调用,通过建立 socket 连接,将方法和参数传递到远端执行并获取远程执行返回结果。
RPC 调用测试:如上图所示,服务器端发布一个接口服务 HelloService,客户端成功通过 RPC 调用。
怎么做到远程服务调用?
怎么封装通信细节才能让用户像以本地调用方式调用远程服务呢?对java来说就是使用代理!java代理有两种方式:1) jdk 动态代理;2)字节码生成。尽管字节码生成方式实现的代理更为强大和高效,但代码不易维护,大部分公司实现RPC框架时还是选择动态代理方式。
下面简单介绍下动态代理怎么实现我们的需求。我们需要实现RPCProxyClient代理类,代理类的invoke方法中封装了与远端服务通信的细节,消费方首先从RPCProxyClient获得服务提供方的接口,当执行helloWorldService.sayHello(“test”)方法时就会调用invoke方法。
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
publicclassRPCProxyClient implementsjava.lang.reflect.InvocationHandler{
privateObjectobj;
publicRPCProxyClient(Objectobj){
this.obj=obj;
}
/**
* 得到被代理对象;
*/
publicstaticObjectgetProxy(Objectobj){
returnjava.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),newRPCProxyClient(obj));
}
/**
* 调用此方法执行
*/
publicObjectinvoke(Objectproxy,Method method,Object[]args)
throwsThrowable{
//结果参数;
Objectresult=newObject();
// ...执行通信相关逻辑
// ...
returnresult;
}
}
Java
1
2
3
4
5
6
publicclassTest{
publicstaticvoidmain(String[]args){
HelloWorldService helloWorldService=(HelloWorldService)RPCProxyClient.getProxy(HelloWorldService.class);
helloWorldService.sayHello("test");
}
}
异常处理
无论 RPC 怎样努力把远程调用伪装的像本地调用,但它们依然有很大的不同点,而且有一些异常情况是在本地调用时绝对不会碰到的。在说异常处理之前,我们先比较下本地调用和 RPC调用的一些差异:
- 本地调用一定会执行,而远程调用则不一定,调用消息可能因为网络原因并未发送到服务方。
- 本地调用只会抛出接口声明的异常,而远程调用还会抛出 RPC 框架运行时的其他异常。
- 本地调用和远程调用的性能可能差距很大,这取决于 RPC 固有消耗所占的比重。
正是这些区别决定了使用 RPC 时需要更多考量。
当调用远程接口抛出异常时,异常可能是一个业务异常, 也可能是 RPC 框架抛出的运行时异常(如:网络中断等)。
业务异常表明服务方已经执行了调用,可能因为某些原因导致未能正常执行, 而 RPC 运行时异常则有可能服务方根本没有执行,对调用方而言的异常处理策略自然需要区分。
由于 RPC 固有的消耗相对本地调用高出几个数量级,
本地调用的固有消耗是纳秒级,而 RPC 的固有消耗是在毫秒级。
那么对于过于轻量的计算任务就并不合适导出远程接口由独立的进程提供服务,
只有花在计算任务上时间远远高于 RPC 的固有消耗才值得导出为远程接口提供服务。
总结
以 上就是我对Java开发大型互联网RPC远程调用服务实现之问题处理方案 问题及其优化总结,分享给大家,觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!
最后,每一位读到这里的网友,感谢你们能耐心地看完。希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!
想了解学习Java方面的技术内容以及Java技术视频的内容可加群:722040762 验证码:头条(06 必过)欢迎大家的加入哟!