You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

90 lines
8.2 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 23 | 如何在没有接口的情况下进行RPC调用
你好我是何小锋。上一讲我们学习了RPC如何通过动态分组来实现秒级扩缩容其关键点就是“动态”与“隔离”。今天我们来聊聊如何在没有接口的情况下进行RPC调用。
## 应用场景有哪些?
在RPC运营的过程中让调用端在没有接口API的情况下发起RPC调用的需求不只是一个业务方和我提过这里我列举两个非常典型的场景例子。
**场景一:**我们要搭建一个统一的测试平台可以让各个业务方在测试平台中通过输入接口、分组名、方法名以及参数值在线测试自己发布的RPC服务。这时我们就有一个问题要解决我们搭建统一的测试平台实际上是作为各个RPC服务的调用端而在RPC框架的使用中调用端是需要依赖服务提供方提供的接口API的而统一测试平台不可能依赖所有服务提供方的接口API。我们不能因为每有一个新的服务发布就去修改平台的代码以及重新上线。这时我们就需要让调用端在没有服务提供方提供接口的情况下仍然可以正常地发起RPC调用。
![](https://static001.geekbang.org/resource/image/fc/bc/fc0027ad042768d9aabf68182de5d2bc.jpg "示意图")
**场景二:**我们要搭建一个轻量级的服务网关可以让各个业务方用HTTP的方式通过服务网关调用其它服务。这时就有与场景一相同的问题服务网关要作为所有RPC服务的调用端是不能依赖所有服务提供方的接口API的也需要调用端在没有服务提供方提供接口的情况下仍然可以正常地发起RPC调用。
![](https://static001.geekbang.org/resource/image/09/c5/09bd6312f3bdb5d4e9276bd0cb0025c5.jpg "示意图")
这两个场景都是我们经常会碰到的而让调用端在没有服务提供方提供接口API的情况下仍然可以发起RPC调用的功能在RPC框架中也是非常有价值的。
## 怎么做?
RPC框架要实现这个功能我们可以使用泛化调用。那什么是泛化调用呢我们带着这个问题先学习下如何在没有接口的情况下进行RPC调用。
我们先回想下我在基础篇讲过的内容通过前面的学习我们了解到在RPC调用的过程中调用端向服务端发起请求首先要通过动态代理正如[\[第 05 讲\]](https://time.geekbang.org/column/article/205910) 中我说过的动态代理可以帮助我们屏蔽RPC处理流程真正地让我们发起远程调用就像调用本地一样。
那么在RPC调用的过程中既然调用端是通过动态代理向服务端发起远程调用的那么在调用端的程序中就一定要依赖服务提供方提供的接口API因为调用端是通过这个接口API自动生成动态代理的。那如果没有接口API呢我们该如何让调用端仍然能够发起RPC调用呢
所谓的RPC调用本质上就是调用端向服务端发送一条请求消息服务端接收并处理之后向调用端发送一条响应消息调用端处理完响应消息之后一次RPC调用就完成了。那是不是说我们只要能够让调用端在没有服务提供方提供接口的情况下仍然能够向服务端发送正确的请求消息就能够解决这个问题了呢
没错,只要调用端将服务端需要知道的信息,如接口名、业务分组名、方法名以及参数信息等封装成请求消息发送给服务端,服务端就能够解析并处理这条请求消息,这样问题就解决了。过程如下图所示:
![](https://static001.geekbang.org/resource/image/a3/89/a3c5ddba4960645b77d73e503da34b89.jpg "示意图")
现在我们已经清楚了解决问题的关键但RPC的调用端向服务端发送消息是需要以动态代理作为入口的我们现在得继续想办法让调用端发送我刚才讲过的那条请求消息。
我们可以定义一个统一的接口GenericService调用端在创建GenericService代理时指定真正需要调用的接口的接口名以及分组名而GenericService接口的$invoke方法的入参就是方法名以及参数信息。
这样我们传递给服务端所需要的所有信息包括接口名、业务分组名、方法名以及参数信息等都可以通过调用GenericService代理的$invoke方法来传递。具体的接口定义如下
```
class GenericService {
Object $invoke(String methodName, String[] paramTypes, Object[] params);
}
```
这个通过统一的GenericService接口类生成的动态代理来实现在没有接口的情况下进行RPC调用的功能我们就称之为泛化调用。
通过泛化调用功能我们可以解决在没有服务提供方提供接口API的情况下进行RPC调用那么这个功能是否就完美了呢
回顾下[\[第 17 讲\]](https://time.geekbang.org/column/article/216803) 我过的内容RPC框架可以通过异步的方式提升吞吐量还有如何实现全异步的RPC框架其关键点就是RPC框架对CompletableFuture的支持那么我们的泛化调用是否也可以支持异步呢
当然可以。我们可以给GenericService接口再添加一个异步方法$asyncInvoke方法的返回值就是CompletableFutureGenericService接口的具体定义如下
```
class GenericService {
Object $invoke(String methodName, String[] paramTypes, Object[] params);
CompletableFuture<Object> $asyncInvoke(String methodName, String[] paramTypes, Object[] params);
}
```
学到这里相信你已经对泛化调用的功能有一定的了解了那你有没有想过这样一个问题在没有服务提供方提供接口API的情况下我们可以用泛化调用的方式实现RPC调用但是如果没有服务提供方提供接口API我们就没法得到入参以及返回值的Class类也就不能对入参对象进行正常的序列化。这时我们会面临两个问题
**问题1**调用端不能对入参对象进行正常的序列化,那调用端、服务端在接收到请求消息后,入参对象又该如何序列化与反序列化呢?
回想下[\[第 07 讲\]](https://time.geekbang.org/column/article/207137)在这一讲中我讲解了如何设计可扩展的RPC框架我们通过插件体系来提高RPC框架的可扩展性在RPC框架的整体架构中就包括了序列化插件我们可以为泛化调用提供专属的序列化插件通过这个插件解决泛化调用中的序列化与反序列化问题。
**问题2**调用端的入参对象params与返回值应该是什么类型呢
在服务提供方提供的接口API中被调用的方法的入参类型是一个对象那么使用泛化调用功能的调用端可以使用Map类型的对象之后通过泛化调用专属的序列化方式对这个Map对象进行序列化服务端收到消息后再通过泛化调用专属的序列化方式将其反序列成对象。
## 总结
今天我们主要讲解了如何在没有接口的情况下进行RPC调用泛化调用的功能可以实现这一目的。
这个功能的实现原理就是RPC框架提供统一的泛化调用接口GenericService调用端在创建GenericService代理时指定真正需要调用的接口的接口名以及分组名通过调用GenericService代理的$invoke方法将服务端所需要的所有信息包括接口名、业务分组名、方法名以及参数信息等封装成请求消息发送给服务端实现在没有接口的情况下进行RPC调用的功能。
而通过泛化调用的方式发起调用由于调用端没有服务端提供方提供的接口API不能正常地进行序列化与反序列化我们可以为泛化调用提供专属的序列化插件来解决实际问题。
## 课后思考
在讲解泛化调用时,我讲到服务端在收到调用端通过泛化调用的方式发送过来的请求时,会使用泛化调用专属的序列化插件实现对其进行反序列化,那么服务端是如何判定这个请求消息是通过泛化调用的方式发送过来的消息呢?
欢迎留言和我分享你的答案,也欢迎你把文章分享给你的朋友,邀请他加入学习。我们下节课再见!