当前位置: 首页 > 科技观察

RPC框架泛化调用原理及其实践

时间:2023-03-13 04:16:30 科技观察

RPC框架泛化调用功能在网关、接口测试等场景中有着广泛的需求。本文为读者介绍了主流的泛化调用实现方法和原理。各种实现方案的优缺点,分享泛化调用的实践。一方面帮助RPC框架使用者理解泛化调用,更好的利用泛化调用;另一方面,对于有自研RPC框架需求的开发者选择通用的调用实现方案也有一定的参考意义。1、普通RPC调用基于动态代理技术,RPC框架客户端实现调用RPC方法和调用本地方法相同的体验。一般由服务端定义服务接口,并将接口打包成二方jar包发布。服务端在服务进程中实现这个接口,调用者在进程中根据这个接口创建一个动态代理进行调用,与调用本地方法的体验一致。例如,有一个接口HelloService,它被打包在demo-service-interfaces.jar包中。publicinterfaceHelloService{Stringhello(Stringname);}服务端依赖demo-service-interfaces.jar,创建HelloServiceImpl来实现该接口。公共类HelloServiceImpl实现HelloService{@OverridepublicStringhello(Stringname){return"hello,"+name;}}客户端还依赖demo-service-interface.jar为HelloService创建一个代理类。以下是一个代码示例。实际上,创建代理类、发送接口、参数、接收返回结果等操作都在框架中进行了封装。HelloServicehelloService=(HelloService)Proxy.newProxyInstance(this.getClass().getClassLoader(),HelloService.class,(InvocationHandler)(proxy,method,args)->{//全部封装在框架中//获取方法,参数typeStringmethodName=method.getName();Class[]parameterTypes=method.getParameterTypes();//向服务器发送方法、参数类型和实参并返回结果returnrequest(methodName,parameterTypes,args);});Stringresult=helloService.hello("jack");System.out.println(result);2.网关、接口测试等场景的需求类(参数和返回值如果是POJO类型也需要一起打包)打包在一个jar包中,由服务端和调用方共享。这种方式适用于大部分业务场景,比较方便,因为依赖的接口jar包是可枚举的。但是在一些特殊的场景下依赖接口jar包是不方便的,比如网关,接口测试平台。例如使用http网关代理私有协议RPC请求。如果网关依赖接口jar包,添加新的方法或接口时需要重新编译启动网关。接口测试平台需要测试全公司所有的RPC接口,把全公司所有的接口jar包都添加到测试平台的依赖中显然是行不通的。在这些场景中,产生了对通用调用的需求。3、泛化调用泛化调用是不依赖服务端接口jar包的调用,包括调用方法的泛化、参数的泛化和返回值的泛化。publicinterfaceGenericService{Object$genericInvoke(StringmethodName,String[]parameterTypes,Object[]args);}在没有接口类依赖的情况下,parameterTypes需要用string指定,如果args和返回值是jdk如果是内置类型,和普通调用没什么区别,但是如果是POJO类型,就需要找一个通用的表示方法了。普通RPC调用的序列化和反序列化原理,如下图所示,其实序列化框架在将POJO序列化为字节数组之前,需要先解析POJO的类结构,生成序列化中间体。当然,序列化中间体并不是总能在序列化框架中找到对应的类,有时这个中间体是虚的。常见的RPC调用序列化原理3.1基于JavaBean的泛化调用基于JavaBean的泛化调用通过一个统一的JavaBean描述符(JavaBeanDescriptor)来描述POJO对象,它工作在序列化层。比如dubbo就支持这种泛化调用。使用泛化调用时,直接将JavaBeanDescriptor对象作为参数传递。基本原理如下图所示。JavaBean泛化调用这种泛化调用的实现比较通用,与底层序列化无关,但是复杂度比较高,需要RPC框架来处理POJO和JavaBeanDescriptor之间的转换。3.2基于序列化中间体的广义调用支持基于序列化中间体的广义调用的RPC框架典型的如sofa-rpc,使用sofa-hessian的序列化框架,sofa-hessian是在hessian序列的基础上二次开发的框架的,序列化中间体被抽象出来,比如GenericObject,GenericMap,GenericArray等。转转RPC框架在支持泛化调用的时候也参考了sofa-hessian的实现,对hessian序列化框架进行了二次开发,有做了一些改进。基于序列化中间体的广义调用,json序列化自然有序列化中间体,即JsonObject或jsonString。在使用json序列化时,调用者可以直接使用JsonObject或者jsonString作为参数调用,而不是POJO。转转RPC框架也支持基于json序列化的泛型调用。dubbo除了支持基于JavaBean的泛化调用外,还支持json-protobuf泛化调用,也就是说调用者可以使用json来描述protobuf对象,反序列化时可以将json反序列化为protobuf对象,再转为POJO,而这些功能都是序列化框架提供的,不需要RPC框架额外的开发支持。与基于JavaBean的泛化调用相比,基于序列化中间体的泛化调用实现起来更简单。一些序列化框架原生支持,也可以通过简单的序列化框架二次开发实现。缺点是,加上序列化框架。4.转转泛化调用的实践目前,转转泛化调用应用最广泛的领域是接口测试。我们提供统一的测试API平台。通过该平台,可以使用http+json调用任意服务、任意节点、任意方法,测试API平台无需依赖任何服务的接口jar包。并且API平台不依赖RPC框架jar包,因为RPC框架在同一个端口上同时兼容私有二进制协议和公共http协议,也就是说可以使用http请求来发起RPC电话。泛化调用还支持获取被调用应用中任意服务、任意节点、任意方法参数和返回值的JsonSchema,如下代码所示。{“味精”:“成功”,“数据”:{“模式”:{“returnValue”:{“类型”:“数组”,“项目”:{“类型”:“对象”,“id”:“urn:jsonschema:com:bj58:zhuanzhuan:arch:user:atomic:entity:User","properties":{"id":{"type":"string"},"userName":{"type":"string"},"userNamePinyin":{"type":"string"},"mock":{"type":"boolean"}}}},"parameters":{"pageNum":{"type":"整数"},"pageSize":{"类型":"整数"}}}},"code":0}未来的网关也会基于广义调用来开发。基于JavaBean规范的广义调用和基于序列化中间体的广义调用的优缺点如下:基于JavaBean的广义调用:优点是与序列化无关;缺点是RPC框架需要实现JavaBeanDescriptor到POJO的转换功能,比较复杂。基于序列化中间体的广义调用:优点是RPC框架实现简单,序列化框架原生支持或只需要少量修改;缺点是它与特定的序列化框架耦合。在开发RPC框架时,具体选择哪种泛化调用实现方式要根据实际情况而定。作者简介王建新,转转架构部服务治理负责人,主要负责服务治理、RPC框架、分布式调用跟踪、监控系统等。热爱技术,热爱学习,欢迎联系交流。