RPC封装需要注意的一些技术细节

什么是RPC?

RPC(Remote Procedure Call,即远程过程调用)是建立在Socket之上的,在一台机器上运行的主程序,可以调用另一台机器上准备好的子程序,就像LPC(本地过程调用)。也就是说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。对于RPC架构来说,应用越底层,代码越复杂、灵活性越高、效率越高;应用越上层,抽象封装的越好、代码越简单、效率越差。

通过RPC,我们可以充分利用非共享内存的多处理器环境(例如通过局域网连接的多台应用服务器),这样可以简便地将你的应用分布在多台应用服务器上,应用程序就像运行在一个多处理器的计算机上一样。我们可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。

RPC方法的基本原则:

以模块调用的简单性忽略通讯的具体细节,以便程序员不用关心C/S之间的通讯协议,集中精力实现应用过程。

由于RPC基本原则的影响,决定了 RPC生成的通讯包不可能对每种应用都有最恰当的处理办法,与Socket方法相比,传输相同的有效数据,RPC占用更多的网络带宽。RPC是在Socket的基础上实现的,它比socket需要更多的网络和系统资源。

1 透明化的远程过程调用

可以通过代理来远程调用。

分为两种代理:

(1)jdk动态代理。jdk代理的方式是对接口做代理,所以必须先定义接口,只需简单地指定一组接口及目标类对象就能动态的获得代理对象。

(2)字节码生成。字节码生成方式一般使用的是cglib代理,cglib代理使用的是asm字节码框架,可以直接对类生成代理对象。虽然字节码生成的方式更加方便和高效,但是由于代码维护不易,一般还是采用jdk动态代理的方式。

2 消息的数据结构

通信的第一步就是要确认客户端和服务器相互通信的消息结构。

调用编码:

(1)接口方法:包括接口名、方法名

(2)方法参数:包括参数类型、参数值

(3)调用属性:包括调用属性信息,例如调用附件隐式参数、调用超时时间等

返回编码:

(1)返回结果:接口方法中定义的返回值

(2)返回码:异常返回码

(3)返回异常信息:调用异常信息

3 序列化

序列化类型包括基于文本和基于二进制方式。在分布式服务通信框架中,序列化方式应该包含以下特性:

(1)通用性:比如能否支持Map等复杂数据结构

(2)性能:包括时间复杂度和空间复杂度,通信框架被会公司全部服务使用,即使性能提升一点也会引起质变

(3)可扩展性:比如支持自动增加新的业务字段

(4)多语言支持:通过定义idl,生成方式为静态编译和动态编译

4 通信

通信框架需要支持同步阻塞IO(BIO)和异步非阻塞IO(NIO)方式,一般底层使用Netty。

BIO:使用ServerSocket绑定IP地址和监听端口,客户端发起连接,通过三次握手建立连接,用socket来进行通信,通过输入输出流的方式来进行同步阻塞的通信。每次客户端发起连接请求,都会启动一个线程。

NIO:使用多路复用器Selector来轮询每个通道Channel,当通道中有事件时就通知处理,不会阻塞,使用较复杂。

5 请求ID

如果请求是异步的,对于客户端来说请求发出后线程即可向下执行。服务端处理完成后再以消息的形式发送给客户端。于是这里会出现以下两个问题:

(1)如何让当前线程“暂停”,等待到结果后,再向后执行

(2)如果多个线程同时进行远程方法调用,这时建立在client server之间的socket连接上会有很多双方发送的消息传递,前后顺序也可能是随机的,server处理完结果后将结果发送给client,client收到很多的消息,无法知道哪个消息是原先哪个线程所调用的。

那么这时可通过唯一自增的一个请求ID来解决这两个问题:在调用callback的get方法时,在get内部获取callback的锁,如果没有获取就等待;以请求ID为Key将callback对象存放在全局ConcurrentHashMap中,先通过请求ID获取callback对象,然后再获取callback的锁,获取之后再调用notify。

猜你喜欢

转载自blog.csdn.net/zryoo_k/article/details/88667293
今日推荐