RPC框架性能大比拼 :
百分点 harpc git:
RPC 简介
RPC(Remote Procedure Call,远程过程调用)是建立在Socket之上的,出于一种类比的愿望,在一台机器上运行的主程序,可以调用另一台机器上的程序,就像LPC(本地过程调用)
越底层,代码越复杂、灵活性越高、效率越高越上层,抽象封装的越好、代码越简单、效率越差 Socket和RPC的区别再次说明了这点。在传统的编程概念中,过程是由程序员在本地编译完成,并只能局限在本地运行的一段代码,也即其主程序和过程之间的运行关系是本地调用关系。因此这种结构在网络日益发展的今天已无法适应实际需求。传统过程调用模式无法充分利用网络上其他主机的资源(如CPU、Memory等),也无法提高代码在实体间的共享程度,使得主机资源大量浪费。 通过RPC我们可以充分利用非共享内存的多处理器环境(例如通过局域网连接得多台工作站),这样可以简便地将你的应用分布在多台工作站上,应用程序就像运行在一个多处理器的计算机上一样。你可以方便的实现过程代码共享,提高系统资源的利用率,也可以将以大量数值处理的操作放在处理能力较强的系统上运行,从而减轻前端机的负担。 RPC作为普遍的C/S开发方法,开发效率高效,可靠.但RPC方法的基本原则是:以模块调用的简单性忽略通讯的具体细节,以便程序员不用关心C/S之间的通讯协议,集中精力对付实现过程.这就决定了 RPC生成的通讯包不可能对每种应用都有最恰当的处理办法,与Socket方法相比,传输相同的有效数据,RPC占用更多的网络带宽.RPC是在Socket的基础上实现的,它比socket需要更多的网络和系统资源.另外,在对程序优化时,程序员虽然可以直接修改由rpcgen产生的令人费解的源程序,但对于追求程序设计高效率的RPC而言,获得的简单性则被大大削弱.RPC 这个概念术语在上世纪 80 年代由 Bruce Jay Nelson 提出。这里我们追溯下当初开发 RPC 的原动机是什么?在 Nelson 的论文 "Implementing Remote Procedure Calls" 中他提到了几点: 1. 简单:RPC 概念的语义十分清晰和简单,这样建立分布式计算就更容易。 2. 高效:过程调用看起来十分简单而且高效。 3. 通用:在单机计算中过程往往是不同算法部分间最重要的通信机制。通俗一点说,就是一般程序员对于本地的过程调用很熟悉,那么我们把 RPC 作成和本地调用完全类似,那么就更容易被接受,使用起来毫无障碍。Nelson 的论文发表于 30 年前,其观点今天看来确实高瞻远瞩,今天我们使用的 RPC 框架基本就是按这个目标来实现的RPC 实现
1、 服务调用方(client)调用以本地调用方式调用服务;2、 client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;在Java里就是序列化的过程3、 client stub找到服务地址,并将消息通过网络发送到服务端;4、 server stub收到消息后进行解码,在Java里就是反序列化的过程;5、 server stub根据解码结果调用本地的服务;6、 本地服务执行处理逻辑;7、 本地服务将结果返回给server stub;8、 server stub将返回结果打包成消息,序列化;9、 server stub将打包后的消息通过网络并发送至消费方10、 client stub接收到消息,并进行解码, 反序列化;11、 服务调用方(client)得到最终结果 RPC框架的目标就是把2-10步封装起来,把调用、编码/解码的过程封装起来,让用户像调用本地服务一样的调用远程服务。要做到对客户端(调用方)透明化服务, RPC框架需要考虑解决如下问题:1、 服务端提供的服务如何发布,客户端如何发现服务;2、 如何对请求对象和返回结果进行序列化和反序列化;3、 如何更高效进行网络通信。rpc vs http
RPC是一种编程模式,把对服务器的调用抽象为过程调用,通常还伴随着框架代码自动生成等功能。使用RPC做网络服务开发时,通常只需要实现服务器端的一个处理函数,其余的客户端调用,序列化反序列化,方法派发(收到什么样的消息,调用服务器端的什么函数)等都由框架或者生成的代码来完成,较大地减轻了网络服务开发和调用的复杂性。
HTTP是一种应用层网络协议,RPC可以采用自定义协议,也可以通过HTTP协议来传输,thrift,grpc,xml-rpc,json-rpc都是通过HTTP传输的。HTTP既支持长连接,也支持短连接。常用的rpc框架
Dubbo阿里巴巴公司开源的一个Java高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。不过,略有遗憾的是,据说在淘宝内部,dubbo由于跟淘宝另一个类似的框架HSF(非开源)有竞争关系,导致dubbo团队已经解散(参见http://www.oschina.net/news/55059/druid-1-0-9 中的评论),反到是当当网的扩展版本仍在持续发展,墙内开花墙外香。其它的一些知名电商如当当、京东、国美维护了自己的分支或者在dubbo的基础开发,但是官方的库缺乏维护,相关的依赖类比如Spring,Netty还是很老的版本(Spring 3.2.16.RELEASE, netty 3.2.5.Final),倒是有些网友写了升级Spring和Netty的插件。
Motan
新浪微博开源的一个Java 框架。它诞生的比较晚,起于2013年,2016年5月开源。Motan 在微博平台中已经广泛应用,每天为数百个服务完成近千亿次的调用。
rpcx
Go语言生态圈的Dubbo, 比Dubbo更轻量,实现了Dubbo的许多特性,借助于Go语言优秀的并发特性和简洁语法,可以使用较少的代码实现分布式的RPC服务。
gRPC
Google开发的高性能、通用的开源RPC框架,其由Google主要面向移动应用开发并基于HTTP/2协议标准而设计,基于ProtoBuf(Protocol Buffers)序列化协议开发,且支持众多开发语言。本身它不是分布式的,所以要实现上面的框架的功能需要进一步的开发。
thrift
Apache的一个跨语言的高性能的服务框架,也得到了广泛的应用
性能比较:
吞吐率:rpcx > thrift > grpc > montan > dubbo
平均响应时间: rpcx > thrift > grpc > montan > dubbo
举个栗子
一个非常简单的非分布式demo
定义 server stub
public class RPCServer { private ExecutorService threadPool; private static final int DEFAULT_THREAD_NUM = 10; public RPCServer(){ threadPool = Executors.newFixedThreadPool(DEFAULT_THREAD_NUM); } public void register(Object service, int port) { try { System.out.println("server starts..."); ServerSocket server = new ServerSocket(port); Socket socket = null; while((socket = server.accept()) != null){ System.out.println("client connected..."); threadPool.execute(new Processor(socket, service)); } } catch (IOException e) { e.printStackTrace(); } } class Processor implements Runnable{ Socket socket; Object service; public Processor(Socket socket, Object service){ this.socket = socket; this.service = service; } public void process(){ } @Override public void run() { try { ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); String methodName = in.readUTF(); Class [] parameterTypes = (Class []) in.readObject(); Object[] parameters = (Object[]) in.readObject(); Method method = service.getClass().getMethod(methodName, parameterTypes); try { Object result = method.invoke(service, parameters); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(result); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (ClassNotFoundException e1) { e1.printStackTrace(); } } }}
定义 client stub
public class RPCClient { @SuppressWarnings("unchecked") public staticT getClient(Class clazz, final String ip, final int port){ return (T) Proxy.newProxyInstance(RPCClient.class.getClassLoader(), new Class []{clazz}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Socket socket = new Socket(ip, port); ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream()); out.writeUTF(method.getName()); out.writeObject(method.getParameterTypes()); out.writeObject(args); System.out.println(method.getName()+method.getParameterTypes()+args); ObjectInputStream in = new ObjectInputStream(socket.getInputStream()); return in.readObject(); } }); } }
定义接口 (server)
public interface HelloService { public String hello(String name);}
定义实现类(server)
public class HelloServiceImpl implements HelloService { @Override public String hello(String name) { return "hello "+name; }}
启动 rpc服务
public class ServerStart { public static void main(String[] args) { HelloService helloService = new HelloServiceImpl(); RPCServer server = new RPCServer(); server.register(helloService, 5001); }}
调用rpc服务 (client)
public class Client { public static void main(String args[]) { HelloService helloService = RPCClient.getClient(HelloService.class, "127.0.0.1", 5001); System.out.println(helloService.hello("young z")); }}