如何在没有接口的情况下进行 RPC 调用?

简介: 本文介绍了RPC框架中“泛化调用”的实现原理与应用场景。在无接口API依赖的情况下,通过统一的GenericService接口和动态代理机制,调用端可封装接口名、方法名、参数等信息发起RPC调用,适用于测试平台、服务网关等场景。结合专属序列化插件,解决参数序列化难题,并支持异步调用,提升系统灵活性与扩展性。

在 RPC 运营的过程中,让调用端在没有接口 API 的情况下发起 RPC 调用的需求,不只是一个业务方和我提过,这里我列举两个非常典型的场景例子。
场景一:我们要搭建一个统一的测试平台,可以让各个业务方在测试平台中通过输入接口、分组名、方法名以及参数值,在线测试自己发布的 RPC 服务。这时我们就有一个问题要解决,我们搭建统一的测试平台实际上是作为各个 RPC 服务的调用端,而在 RPC 框架的使用中,调用端是需要依赖服务提供方提供的接口 API 的,而统一测试平台不可能依赖所有服务提供方的接口 API。我们不能因为每有一个新的服务发布,就去修改平台的代码以及重新上线。这时我们就需要让调用端在没有服务提供方提供接口的情况下,仍然可以正常地发起 RPC 调用。

场景二:我们要搭建一个轻量级的服务网关,可以让各个业务方用 HTTP 的方式,通过服务网关调用其它服务。这时就有与场景一相同的问题,服务网关要作为所有 RPC 服务的调用端,是不能依赖所有服务提供方的接口 API 的,也需要调用端在没有服务提供方提供接口的情况下,仍然可以正常地发起 RPC 调用。

这两个场景都是我们经常会碰到的,而让调用端在没有服务提供方提供接口 API 的情况下仍然可以发起 RPC 调用的功能,在 RPC 框架中也是非常有价值的。
怎么做?
RPC 框架要实现这个功能,我们可以使用泛化调用。那什么是泛化调用呢?我们带着这个问题,先学习下如何在没有接口的情况下进行 RPC 调用。
我们先回想下我在基础篇讲过的内容,通过前面的学习我们了解到,在 RPC 调用的过程中,调用端向服务端发起请求,首先要通过动态代理,正如 第 05 讲 中我说过的,动态代理可以帮助我们屏蔽 RPC 处理流程,真正地让我们发起远程调用就像调用本地一样。
那么在 RPC 调用的过程中,既然调用端是通过动态代理向服务端发起远程调用的,那么在调用端的程序中就一定要依赖服务提供方提供的接口 API,因为调用端是通过这个接口 API 自动生成动态代理的。那如果没有接口 API 呢?我们该如何让调用端仍然能够发起 RPC 调用呢?
所谓的 RPC 调用,本质上就是调用端向服务端发送一条请求消息,服务端接收并处理,之后向调用端发送一条响应消息,调用端处理完响应消息之后,一次 RPC 调用就完成了。那是不是说我们只要能够让调用端在没有服务提供方提供接口的情况下,仍然能够向服务端发送正确的请求消息,就能够解决这个问题了呢?
没错,只要调用端将服务端需要知道的信息,如接口名、业务分组名、方法名以及参数信息等封装成请求消息发送给服务端,服务端就能够解析并处理这条请求消息,这样问题就解决了。过程如下图所示:

现在我们已经清楚了解决问题的关键,但 RPC 的调用端向服务端发送消息是需要以动态代理作为入口的,我们现在得继续想办法让调用端发送我刚才讲过的那条请求消息。
我们可以定义一个统一的接口(GenericService),调用端在创建 GenericService 代理时指定真正需要调用的接口的接口名以及分组名,而 GenericService 接口的 $invoke 方法的入参就是方法名以及参数信息。
这样我们传递给服务端所需要的所有信息,包括接口名、业务分组名、方法名以及参数信息等都可以通过调用 GenericService 代理的 $invoke 方法来传递。具体的接口定义如下:
class GenericService {
Object $invoke(String methodName, String[] paramTypes, Object[] params);

}
这个通过统一的 GenericService 接口类生成的动态代理,来实现在没有接口的情况下进行 RPC 调用的功能,我们就称之为泛化调用。
通过泛化调用功能,我们可以解决在没有服务提供方提供接口 API 的情况下进行 RPC 调用,那么这个功能是否就完美了呢?
回顾下 第 17 讲 我过的内容,RPC 框架可以通过异步的方式提升吞吐量,还有如何实现全异步的 RPC 框架,其关键点就是 RPC 框架对 CompletableFuture 的支持,那么我们的泛化调用是否也可以支持异步呢?
当然可以。我们可以给 GenericService 接口再添加一个异步方法 $asyncInvoke,方法的返回值就是 CompletableFuture,GenericService 接口的具体定义如下:

class GenericService {
Object $invoke(String methodName, String[] paramTypes, Object[] params);
CompletableFuture $asyncInvoke(String methodName, String[] paramTypes, Object[] params);
}
学到这里相信你已经对泛化调用的功能有一定的了解了,那你有没有想过这样一个问题?在没有服务提供方提供接口 API 的情况下,我们可以用泛化调用的方式实现 RPC 调用,但是如果没有服务提供方提供接口 API,我们就没法得到入参以及返回值的 Class 类,也就不能对入参对象进行正常的序列化。这时我们会面临两个问题:
问题 1:调用端不能对入参对象进行正常的序列化,那调用端、服务端在接收到请求消息后,入参对象又该如何序列化与反序列化呢?
回想下 第 07 讲,在这一讲中我讲解了如何设计可扩展的 RPC 框架,我们通过插件体系来提高 RPC 框架的可扩展性,在 RPC 框架的整体架构中就包括了序列化插件,我们可以为泛化调用提供专属的序列化插件,通过这个插件,解决泛化调用中的序列化与反序列化问题。
问题 2:调用端的入参对象(params)与返回值应该是什么类型呢?
在服务提供方提供的接口 API 中,被调用的方法的入参类型是一个对象,那么使用泛化调用功能的调用端,可以使用 Map 类型的对象,之后通过泛化调用专属的序列化方式对这个 Map 对象进行序列化,服务端收到消息后,再通过泛化调用专属的序列化方式将其反序列成对象。
总结
今天我们主要讲解了如何在没有接口的情况下进行 RPC 调用,泛化调用的功能可以实现这一目的。
这个功能的实现原理,就是 RPC 框架提供统一的泛化调用接口(GenericService),调用端在创建 GenericService 代理时指定真正需要调用的接口的接口名以及分组名,通过调用 GenericService 代理的 $invoke 方法将服务端所需要的所有信息,包括接口名、业务分组名、方法名以及参数信息等封装成请求消息,发送给服务端,实现在没有接口的情况下进行 RPC 调用的功能。
而通过泛化调用的方式发起调用,由于调用端没有服务端提供方提供的接口 API,不能正常地进行序列化与反序列化,我们可以为泛化调用提供专属的序列化插件,来解决实际问题。
课后思考
在讲解泛化调用时,我讲到服务端在收到调用端通过泛化调用的方式发送过来的请求时,会使用泛化调用专属的序列化插件实现对其进行反序列化,那么服务端是如何判定这个请求消息是通过泛化调用的方式发送过来的消息呢?
笔者认为:可以在协议中设置序列化类型,在服务端接受到后,就知道用什么插件反序列化了

相关文章
|
Kubernetes 负载均衡 网络协议
Kubernetes 跨集群流量调度实战
Kubernetes 问世于 2015 年,从一开始秉持着松耦合和可扩展的设计理念,也因此带来了 Kubernetes 生态的蓬勃发展。但这些大部分先限制在单一集群内,然后由于种种原因和目的企业内部创建的集群越来越多,比如单集群故障、监管要求、异地多机房可用区容灾、出于敏捷、降本考虑的混合云、多云部署、单一集群的承载能力受限、多版本 Kubernetes 集群共存等。多集群之后除了提升管理的难度外,首当其冲的就是多集群间的流量调度,这也是多集群部署的基础。没有跨集群的通信,多集群的收益也会大打折扣。
1048 1
Kubernetes 跨集群流量调度实战
|
SQL 存储 Oracle
6 张图带你彻底搞懂分布式事务 XA 模式
XA 协议是由 X/Open 组织提出的分布式事务处理规范,主要定义了事务管理器 TM 和局部资源管理器 RM 之间的接口。目前主流的数据库,比如 oracle、DB2 都是支持 XA 协议的。
14650 1
6 张图带你彻底搞懂分布式事务 XA 模式
|
8月前
|
监控 Java Spring
AOP切面编程快速入门
AOP(面向切面编程)通过分离共性逻辑,简化代码、减少冗余。它通过切点匹配目标方法,在不修改原方法的前提下实现功能增强,如日志记录、性能监控等。核心概念包括:连接点、通知、切入点、切面和目标对象。Spring AOP支持多种通知类型,如前置、后置、环绕、返回后、异常通知,灵活控制方法执行流程。通过@Pointcut可复用切点表达式,提升维护性。此外,结合自定义注解,可实现更清晰的切面控制。
652 5
|
存储 关系型数据库 MySQL
gbase mysql 协议
【4月更文挑战第19天】
565 6
|
9月前
|
存储 负载均衡 算法
我们来说一说 Java 的一致性 Hash 算法
我是小假 期待与你的下一次相遇 ~
461 1
|
消息中间件 存储 Java
招行面试:10Wqps场景,RocketMQ 顺序消费 的性能 如何提升 ?
45岁资深架构师尼恩在其读者群中分享了关于如何提升RocketMQ顺序消费性能的高并发面试题解析。面对10W QPS的高并发场景,尼恩详细讲解了RocketMQ的调优策略,包括专用方案如增加ConsumeQueue数量、优化Topic设计等,以及通用方案如硬件配置(CPU、内存、磁盘、网络)、操作系统调优、Broker配置调整、客户端配置优化、JVM调优和监控与日志分析等方面。通过系统化的梳理,帮助读者在面试中充分展示技术实力,获得面试官的认可。相关真题及答案将收录于《尼恩Java面试宝典PDF》V175版本中,助力求职者提高架构、设计和开发水平。
招行面试:10Wqps场景,RocketMQ 顺序消费 的性能 如何提升 ?
|
设计模式 消息中间件 安全
面试官:说说读写锁的实现原理?
面试官:说说读写锁的实现原理?
462 1
|
SQL 关系型数据库 MySQL
阿里面试:1000万级大表, 如何 加索引?
45岁老架构师尼恩在其读者交流群中分享了如何在生产环境中给大表加索引的方法。文章详细介绍了两种索引构建方式:在线模式(Online DDL)和离线模式(Offline DDL),并深入探讨了 MySQL 5.6.7 之前的“影子策略”和 pt-online-schema-change 方案,以及 MySQL 5.6.7 之后的内部 Online DDL 特性。通过这些方法,可以有效地减少 DDL 操作对业务的影响,确保数据的一致性和完整性。尼恩还提供了大量面试题和解决方案,帮助读者在面试中充分展示技术实力。
|
缓存 监控 Java
深入解析Nacos配置中心的动态配置更新技术
深入解析Nacos配置中心的动态配置更新技术
|
机器学习/深度学习 人工智能 JSON
在LLM浪潮下,prompt工程师需要很懂算法吗?
最近AI大神吴恩达推出prompt教程并给出了prompt构建三大原则,Prompt Engineering Guide也提出了诸多技巧,受AI技术的快速发展的影响,你觉得在LLM浪潮下,prompt工程师需要很懂算法吗?欢迎一起来聊一聊~
26463 50
在LLM浪潮下,prompt工程师需要很懂算法吗?

热门文章

最新文章