RPC框架简介
- 什么叫RPC?
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。 - 为什么会出现RPC?
应用之间的交互不可避免,将核心业务抽取出来,作为独立的服务。同时将通用的API抽取出来,供其他系统调用者使用。应用拆分之后会按照模块独立部署,接口调用本地接口变成了跨进程的远程调用,这个时候就出现了RPC框架。 - 说白了RPC是一种进程间的通信方式,允许像调用本地方法一样调用远程服务。
RPC框架原理
RPC框架的目标是是远程服务调用更加简单,透明。它屏蔽了底层的传输方式(TCP或者UDP),序列化方式(XML/JSON/二进制)和通信细节。其实框架的作用就是屏蔽了底层的操作细节,让我们程序员更加方便高效的编程。但是了解底层的实现对我们熟练使用框架是有很大的帮助的。下图简述RPC框架的原理:
运行时,一次客户机对服务器的RPC调用,其内部操作大致有如下十步:
1.调用客户端句柄;执行传送参数
2.调用本地系统内核发送网络消息
3.消息传送到远程主机
4.服务器句柄得到消息并取得参数
5.执行远程过程
6.执行的过程将结果返回服务器句柄
7.服务器句柄返回结果,调用远程系统内核
8.消息传回本地主机
9.客户句柄由内核接收消息
10.客户接收句柄返回的数据
简单的PRC框架的实现
下面通过Java原生的序列化,Socket通信,动态代理和反射机制实现最简单的RPC框架。
组成
- 服务提供者,运行在服务端,负责提供服务接口定义和服务实现类。
- 服务发布者,运行在RPC服务端,负责将本地服务发布成远程服务,供消费者使用。
- 本地服务代理,运行在RPC客户端通过代理调用远程的服务提供者,然后将结果进行封装返还给本地消费者。
实现
服务端接口定义
public interface EchoService { String echo( String ping ); }
服务端接口实现类
public class EchoServiceImpl implements EchoService { @Override public String echo(String ping) { return ping != null ? ping + " -->I am ok." : " I am ok."; } }
RPC服务端服务发布者代码
//ObjectInputStream 对象输入流,可以将序列化过的对象反序列化到内存中,对应的还有个ObjectOutputStream 可以将一个对象序列化到本地! public class RpcExporter { static Executors executors = (Executors) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public static void exporter( String hostName, int port ) throws Exception { ServerSocket server = new ServerSocket(); server.bind( new InetSocketAddress( hostName, port ) ); } private static class ExporterTask implements Runnable { Socket client = null; public ExporterTask ( Socket client ) { this.client = client; } @Override public void run() { ObjectInputStream input = null; ObjectOutputStream output = null; try { input = new ObjectInputStream( client.getInputStream() ); String interfaceName = input.readUTF(); Class<?> service = Class.forName( interfaceName ); String methodName = input.readUTF(); Class< ? > parameterTypes = (Class<?>) input.readObject(); Object[] arguments = (Object[]) input.readObject(); Method method = service.getMethod(methodName, parameterTypes); Object result = method.invoke( service.newInstance() , arguments ); output = new ObjectOutputStream( client.getOutputStream() ); output.writeObject( result ); } catch (Exception e) { e.printStackTrace(); } finally { if( output != null ) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } if( input != null ) { try { input.close(); } catch (IOException e) { e.printStackTrace(); } } if( client != null ) { try { client.close(); } catch (IOException e) { e.printStackTrace(); } } } } } }
- 服务发布者的主要职责:
- 做为服务器,监听客户端的TCP连接,接收到新的客户端连接后,将其封装成Task,由线程池执行。
- 将客户端发送的码反序列化成对象,反射调用服务实现者,获得执行结果。
- 将执行结果对象反序列化,通过Socket发送给客户端。
- 远程调用完成之后,释放Socket等连接资源,防止句柄泄漏。
- 服务发布者的主要职责:
RPC客户端本地服务代理代码
public class RpcImporter<S> { public S importer ( final Class<?> serviceClass ,final InetSocketAddress addr ) { return (S) Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[] { serviceClass.getInterfaces()[0] }, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = null; ObjectOutputStream output = null; ObjectInputStream input = null; try { socket = new Socket(); socket.connect(addr); output = new ObjectOutputStream(socket.getOutputStream()); output.writeUTF(serviceClass.getName()); output.writeUTF(method.getName()); output.writeObject(method.getParameterTypes()); output.writeObject(args); input = new ObjectInputStream(socket.getInputStream());//同步阻塞等待 } finally { if( socket != null ) { socket = null; } if( output != null ) { output = null; } if( input != null ) { input = null; } } return input.readObject(); } }); } }
- 本地服务代理的职责
- 将本地接口调用转化成JDK的动态代理,在代理类中实现接口的远程调用。
- 创建Socket客户端,根据指定地址连接远程服务提供者
- 将远程服务调用所需的接口和方法名等编码后发送给服务提供者。
- 同步阻塞等待服务器返回应答,获取应答之后返回。
- 本地服务代理的职责
测试代码
public class RpcTest { public static void main(String[] args) { //用于接受PRC客户端的请求 new Thread(new Runnable() { @Override public void run() { try { RpcExporter.exporter("127.0.0.1", 8080); } catch (Exception e) { e.printStackTrace(); } } }).start(); RpcImporter<EchoService> importer = new RpcImporter< EchoService >(); EchoService echo = importer.importer( EchoServiceImpl.class, new InetSocketAddress("127.0.0.1", 8080) ); System.out.println( echo.echo("Are you OK?") ); } }
RPC的不足
纯粹的RPC框架服务与治理能力都不健全,当应用大规模服务化之后会面临许多服务治理方面的挑战,要解决这些问题,必须通过服务框架 + 服务治理来完成,单凭RPC框架无法解决服务治理问题。
什么是服务治理