转载本文请联系楼仔公众号。RPC系列文章是我去年写的。当时写得零散,现在正在整理。对于想学习RPC框架的同学,通过本文,让大家知道它是什么,为什么会这样,方便以后的技术选择。以下为文章内容:RPC什么是RPCRPC(RemoteProcedureCallProtocol)远程过程调用协议。一个流行的描述是:客户端在不知道调用细节的情况下调用一个存在于远程计算机上的对象,就像在本地应用程序中调用一个对象一样。更正式的描述是:一种通过网络向远程计算机程序请求服务而无需了解底层网络技术的协议。那么我们至少可以从这个描述中挖掘出几个关键点:RPC是一种协议:既然是协议,那么它只是一套规范,所以需要有人按照这套规范来实现。目前典型的RPC实现有:Dubbo、Thrift、GRPC、Hetty等。网络协议和网络IO模型对它来说是透明的:因为RPC客户端认为它调用的是本地对象。那么它就不需要关心传输层是使用TCP/UDP还是HTTP协议,或者其他一些网络协议。信息格式对它来说是透明的:我们知道在本地应用中,调用一个对象需要传递一些参数,然后返回一个调用结果。至于被调用对象内部如何使用这些参数并计算出处理结果,调用者无需关心。那么对于远程调用来说,这些参数会以一定的信息格式传递给网络上的另一台计算机,调用者不需要关心这个信息格式是如何组成的。应该有跨语言能力:为什么这么说?因为调用者实际上并不知道远程服务器的应用程序是用什么语言运行的。所以对于调用者来说,不管服务器使用什么语言,调用应该是成功的,并且返回值也应该以一种形式描述调用者的编程语言可以理解。为什么使用RPC其实是应用开发到一定阶段的强烈需求所驱动的。如果我们开发一个简单的单体应用,逻辑简单,用户少,流量低,那我们就不需要了。当我们的系统访问量和业务增加时,我们会发现在单机上运行这个系统是难以忍受的。此时,我们可以将业务拆分成几个不相关的应用,分别部署在各自的机器上,理清逻辑,减轻压力。此时,我们不需要RPC,因为应用程序彼此不相关。当我们的业务越来越大,应用越来越多的时候,我们自然会发现有些功能不能简单划分或者不能划分。至此,可以将公共业务逻辑抽取出来,形成一个独立的服务应用。无论是原有应用还是新增应用,都可以与那些独立的Service应用进行交互,完成完整的业务功能。那么这个时候,我们就迫切需要一种高效的应用程序之间的通信方式来满足这个需求,所以你看,RPC是时候大显身手了!其实描述的场景也是一个面向服务、微服务、分布式的系统架构。基地场景。也就是说,RPC框架是实现上述结构的一种强大方式。常用的RPC框架Thrift:thrift是一个用于开发可伸缩和跨语言服务的软件框架。它结合了强大的软件栈和代码生成引擎,构建在C++、Java、Python、PHP、Ruby、Erlang、Perl、Haskell、C#、Cocoa、JavaScript、Node.js、Smalltalk和OCaml编程语言之上无缝集成,高效服务.gRPC:最初由谷歌开发,是一个语言中立、平台中立、开源的远程过程调用(RPC)系统。Dubbo:Dubbo是一个分布式服务框架和SOA治理解决方案。其功能主要包括:高性能NIO通信与多协议集成、服务动态寻址与路由、软负载均衡与容错、依赖分析与降级等。Dubbo是阿里巴巴内部基于SOA服务的治理解决方案的核心框架.Dubbo自2011年开源以来,已经被很多非阿里巴巴公司使用。构建分布式系统和微服务的工具,如配置管理、服务发现、断路器、智能路由、微代理、控制总线、一次性令牌、全局锁、主选举、分布式会话和集群状态等,满足构建微服务所需的所有解决方案。SpringCloud基于SpringBoot,开发部署极其简单。RPC原理RPC调用流程为了让网络通信细节对用户透明,我们需要对通信细节进行封装。先来看下RPC调用过程中涉及的通信细节:服务消费者(client)调用是localcallservice调用;clientstub接收到调用后,负责将方法、参数等组装成能够网络传输的消息体;客户端存根找到服务地址并将消息发送到服务器;服务器存根在收到消息后对其进行解码;服务器存根根据解码结果调用本地服务;本地服务执行并将结果返回给服务器存根;服务器存根将返回结果打包成消息发送给消费者;客户端存根接收消息并对其进行解码;服务消费者获得最终结果。RPC的目标是封装步骤2到8,以便用户对这些细节透明。下面是网上的另一张图,感觉一目了然:如何做到透明的远程服务调用,如何封装通信细节,让用户像在本地调用一样调用远程服务?对于java来说,就是使用代理!java代理有两种方式:1)jdk动态代理;2)字节码生成。字节码生成方式实现的代理虽然功能更强大、效率更高,但代码维护起来并不容易。大多数公司在实现RPC框架时,仍然选择动态代理的方式。下面简单介绍一下动态代理是如何实现我们的需求的。我们需要实现RPCProxyClient代理类。代理类的invoke方法封装了与远程服务通信的细节。消费者首先从RPCProxyClient获取服务提供者的接口。当执行helloWorldService.sayHello("test")方法时,会调用invoke方法。公共类RPCProxyClient实现java.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,Methodmethod,Object[]args)throwsThrowable{//结果参数;对象结果=新对象();//...执行通信相关逻辑//...返回结果;}}publicclassTest{publicstaticvoidmain(String[]args){HelloWorldServicehelloWorldService=(HelloWorldService)RPCProxyClient.getProxy(HelloWorldService.class);helloWorldService.sayHello("测试");}}其实就是通过动态代理的方式,对方法的Before和after进行数据的封装和解码,让用户有直接调用方法的感觉。众所周知,我们在方法前后都经过了复杂的处理。如何对消息进行编码和解码确定消息数据结构客户端的请求消息结构一般需要包括以下内容:不会知道调用哪个接口;方法名:一个接口中可能有很多方法。如果不传递方法名,服务器将不知道调用哪个方法;参数类型&参数值:参数类型有很多种,bool、int、long、double、string、map、list、evenstruct等,以及对应的参数值;超时时间+requestID(标识唯一请求id)服务端返回的消息结构一般包括以下内容:状态码+返回值requestID序列化一旦消息的数据确定了结构之后,下一步就是考虑序列化和反序列化。什么是序列化?序列化是将数据结构或对象转换成二进制字符串的过程,也就是编码的过程。什么是反序列化?将序列化过程中产生的二进制字符串转换为数据结构或对象的过程。为什么需要序列化?转换为二进制字符串进行网络传输!为什么需要反序列化?将二进制转化为对象进行后续处理!现在的序列化方案越来越多,每种序列化方案都各有优缺点。它们在设计之初都有自己独特的应用场景,那么你应该选择哪一个呢?从RPC的角度来看,主要有3点:通用性:比如是否可以支持Map等复杂数据结构;性能:包括时间复杂度和空间复杂度。由于公司几乎所有的服务都会用到RPC框架,如果能在连载上节省一点时间,对整个公司的收益将是非常可观的。同样,如果序列化可以节省一点内存,节省大量的网络带宽;可扩展性:对于互联网公司来说,业务变化很快。如果序列化协议具有良好的可扩展性,支持在不影响旧业务的情况下自动增加新的业务领域,将大大增加系统的灵活性。目前,互联网公司广泛使用Protobuf、Thrift、Avro等成熟的序列化方案构建RPC框架。这些都是行之有效的解决方案。为什么消息中有requestID?这个问题很简单,我就不解释了。你能回答吗?如何发布自己的服务,在之前的很多文章中都有提到。Java常用zookeeper,Go常用etcd,还有服务端注册和心跳,客户端获取机器列表,没什么高级的,比如zookeeper:gRPC&ThriftgRPCgRPC简介gRPC是一个高性能、通用的开源RPC框架,谷歌于2015年为移动应用开发,基于HTTP/2协议标准设计,基于ProtoBuf序列化协议开发,支持多种开发语言。因为是开源框架,通信双方都可以进行二次开发,所以客户端和服务端的通信会更专注于业务层面的内容,减少对底层通信实现的关注gRPC框架。如下图,DATA部分是业务层面的内容,下面的所有信息都被gRPC封装了。gRPC是语言中立的,支持多种语言;基于IDL文件定义服务,通过proto3工具生成指定语言的数据结构、服务端接口、客户端Stub;通信协议基于标准HTTP/2设计,支持双向流和消息头压缩、单TCP复用、服务器推送等特性,这些特性使得gRPC在移动设备上更省电,节省网络流量;序列化支持PB(ProtocolBuffer)和JSON,PB是一个独立于语言的基于HTTP/2+PB的高性能序列化框架,保证了RPC调用的高性能。gRPC交互流程交换机开启gRPC功能后作为gRPC客户端,采集服务器作为gRPC服务器;交换机会根据订阅的事件构造相应的数据格式(GPB/JSON),并通过ProtocolBuffers写入proto文件,交换机与服务器建立gRPC通道,通过gRPC协议向服务器发送请求消息;服务端收到请求报文后,会通过ProtocolBuffers解释proto文件,还原出最初定义格式的数据结构,进行业务处理;数据处理完成后,服务器需要使用ProtocolBuffers重新编译响应数据,并通过gRPC协议向交换机发送响应消息;交换机收到响应消息后,结束gRPC交互。gRPC简单的说就是在客户端和服务端开启gRPC功能后建立连接,将设备上配置的订阅数据推送到服务端。我们可以看到整个过程需要使用ProtocolBuffers在proto文件中定义待处理数据的结构化数据。什么是协议缓冲区?你可以理解为ProtoBuf是一种更加灵活高效的数据格式,类似于XML和JSON,非常适合一些对性能和响应速度要求较高的数据传输场景。ProtoBuf在gRPC框架中主要有3个功能:定义数据结构和定义服务接口,通过序列化和反序列化提高传输效率。为什么ProtoBuf可以提高传输效率?我们知道在使用XML和JSON进行数据编译时,数据的文本格式是比较容易阅读的,但是在交换数据的时候,设备需要在I/O动作上消耗大量的CPU,自然会影响整体的传输速率.ProtocolBuffers不像前者,它会在传输前将字符串序列化,也就是二进制数据。可以看出两者的内容相差不大,内容也很直观,但是ProtocolBuffers编码后的内容只是提供给操作者阅读的。实际上,传输的不会是这种文本形式,而是序列化后的二进制数据。字节数会比JSON和XML少很多,速度也会更快。如何支持跨平台和多语言?ProtocolBuffers自带的编译器也是一个优势。上面提到的proto文件是由编译器编译的。proto文件需要编译生成类似的库文件。只有基于库文件才能开发出真正的数据应用。这个库文件是用什么编程语言编译生成的?因为现在网络中负责网络设备和服务器设备的运维人员往往不是同一群人,运维人员可能习惯于使用不同的编程语言进行运维开发,那么Protocol可以发挥Buffers的优势之一——跨语言。从上面的介绍中,我们得到ProtocolBuffers在编码方面相比JSON和XML的优势:简单,体积小,数据描述文件的大小仅为1/10到1/3;传输和解析速度快,与XML等相比,解析速度提高20倍甚至更高;可编译性强。基于HTTP2.0标准的设计除了ProtocolBuffers,从交互图和分层框架可以看出,gRPC还有一个优势——它是基于HTTP2.0协议的。由于gRPC是基于HTTP2.0标准设计的,因此带来了更强大的功能,如多路复用、二进制帧、头部压缩、推送机制等。这些功能给设备带来了显着的好处,比如节省带宽、减少TCP连接数、节省CPU使用率等。gRPC既可以应用在客户端,也可以应用在服务端,以透明的方式实现两端的通信,简化通信系统的构建。HTTP版本分为HTTP1.X和HTTP2.0,其中HTTP1.X是目前使用最广泛的HTTP协议,HTTP2.0被称为第二代超文本传输??协议。HTTP1.X定义了四种与服务器交互的方式,分别是:GET、POST、PUT、DELETE,这些都是HTTP2.0保留的。HTTP2.0新特性:双向流、多路复用二进制帧头压缩ThriftThrift简介thrift是一个可扩展的跨语言服务RPC软件框架。它将代码生成引擎与强大的软件堆栈相结合,以构建可跨多种语言高效无缝工作的服务。2007年由facebook贡献给Apache基金会,是Apache下的顶级项目。具有以下特点:支持多种语言:C、C++、C#、D、Delphi、Erlang、Go、Haxe、Haskell、Java、JavaScript、node.js、OCaml、Perl、PHP、Python、Ruby、SmallTalk消息定义文件支持注解,数据结构与传输性能分离,支持多种消息格式包括完整的client/server栈,可以快速实现RPC,支持同步和异步通信Thrift框架结构Thrift是一套RPC(远程服务调用)框架,包括序列化功能,支持服务通信,也是一个微服务框架。它的主要特点是可以跨语言使用,这也是这个框架最吸引人的地方。图中的代码是用户实现的业务逻辑。下面的Service.Client和write()/read()是thrift基于IDL生成的客户端和服务端代码,对应RPC中的Clientstub和Serverstub。TProtocol用于对数据进行序列化和反序列化,具体方法包括二进制、JSON或ApacheThrift定义的格式。TTransport提供数据传输功能,使用ApacheThrift可以轻松定义一个服务并选择不同的传输协议。Thrift网络栈结构thrift使用socket进行数据传输,数据以特定格式发送,接收方解析。我们定义好thriftIDL文件后,就可以使用thrift编译器生成两种语言的接口和模型,生成的模型和接口代码中会有解码和编码代码。Thrift网络栈结构如下:传输层代表Thrift的数据传输方式。Thrift定义了以下几种常用的数据传输方式:TSocket:阻塞套接字;TFramedTransport:以帧为单位传输,用于非阻塞服务;TFileTransport:作为文件传输。TProtocol层表示thrift客户端和服务器之间的数据传输协议。一般来说就是客户端和服务端之间传输数据的格式(比如json等)。thrift定义了以下常用格式:TBinaryProtocol:二进制格式;TCompactProtocol:压缩格式;TJSONProtocol:JSON格式;TSimpleJSONProtocol:提供一个只写的JSON协议。服务器模型TSimpleServer:简单的单线程服务模型,常用于测试;TThreadPoolServer:多线程服务模型,使用标准的阻塞IO;TNonBlockingServer:多线程服务模型,使用非阻塞IO(需要使用TFramedTransport数据传输);THsHaServer:THsHa引入线程池进行处理,其模型读写任务都放在线程池中进行处理,Half-sync/Half-async处理方式,Half-async是处理IO事件(accept/read/writeio),Half-sync用于handler同步rpc;gRPCVSThrift功能对比直接贴两张网上的截图:性能对比也是基于线上测试的结果,仅供参考:总体而言,长连接的性能优于短连接,性能差距更大超过两次;与Go语言的两个RPC框架相比,Thrift的性能明显优于gRPC,性能差距也超过一倍;与Thrift框架下的两种语言相比,Go和C++在长连接下的RPC性能基本处于同一水平。在短连接下,Go的性能大约是C++的两倍。与Thrift&C++下的TSimpleServer和TNonblockingServer相比,在单进程客户端长连接场景下,由于线程管理开销,TNonblockingServer性能较低。TSimpleServer更糟;但在短连接的情况下,主要开销是连接建立,线程池管理开销可以忽略不计;两套RPC框架和两种语言都非常稳定,50000次请求的耗时是10000次的5倍左右;如何选择何时选择gRPC而不是Thrift:需要好的文档,比如例子,搞定习惯于HTTP/2,ProtoBuf对网络传输带宽敏感何时应该选择Thrift而不是gRPC:需要在多种语言之间传输数据切换对CPU敏感。协议层和传输层有不同的控制要求。不需要稳定版本。不需要好的文档和示例。上一节详细介绍了gRPC和Thrift的特点和区别。HTTP协议,所以协议部分不再赘述,重点放在ProtoBuf上。Thrift的数据格式是现成的,没有单独的一套,但是在传输层和服务器端自己造了轮子,所以可以对协议层和传输层有各种控制需求。对于gRPC的例子,除了理论,我们还需要注重实践。gRPC的使用姿势见这篇文章。【RPC基础系列3】GRPC简单实例Dubbo&SpringCloudDubboDubbo是一个分布式服务框架,致力于提供高性能透明的RPC远程服务调用方案,SOA服务治理方案。简单的说,Dubbo是一个服务框架,说白了就是一个远程服务调用的分布式框架。Dubbo整体架构:Dubbo特性:远程通信:提供对各种基于长连接的NIO框架(非阻塞I/O通信方式,Mina/Netty/Grizzly)的抽象封装,包括多线程模型,序列化(Hessian2/ProtoBuf),以及信息交换的“请求-响应”模式。集群容错:提供基于接口方式的透明远程过程调用(RPC),包括多协议支持(自定义RPC协议)、软负载均衡(Random/RoundRobin)、故障容错(Failover/Failback)、地址路由、动态配置和其他集群支持。自动发现:基于注册中心目录服务,服务消费者可以动态发现服务提供者,地址透明化,方便服务提供者平滑增减机器。SpringCloudSpringCloud,基于SpringBoot,针对微服务系统开发中的架构问题提供了一整套的解决方案——服务注册与发现、服务消费、服务保护与熔断、网关、分布式调用跟踪、分布式配置管理等等。DubbovsSpringCloudDubbo搭建的微服务架构就像组装一台电脑。我们在每个环节都有很高的选择自由度,但最后的结果很可能因为记忆质量不好而点不亮,总让人觉得不安,但如果你是高手,那么这些都不是问题;而SpringCloud就像一台品牌机。在SpringSource的集成下,做了很多兼容性测试,保证机器有更高的稳定性,但是如果要使用非正版组件以外的东西,需要好好了解它的基础知识。关于Dubbo和SpringCloud的相关概念和对比,我个人更喜欢SpringCloud,因为真正的微服务框架,完善的组件支持,简单方便的使用,强大的社区支持等等。另外,因为考虑到.NET/.NETCore兼容处理,RPC不能很好的实现跨语言(需要使用跨语言的库,比如gRPC和Thrift,但是由于Dubbo本身就是“gRPC”,包裹一层有点困难gRPC在Dubbo之上的重复封装),而且HTTPREST本身支持跨语言实现,所以SpringCloud还是很不错的(Dubbox也支持,只是性能差了点)。但凡事都没有绝对,凡事都有优点和缺点。总的来说,Dubbo和SpringCloud的主要区别体现在两个方面:服务调用方式不同和侧重点不同(生态不同)。
