您可以导出整个接口,或更准确地说,仅导出接口中的特定方法。
//DemoService 仅导出签名为hi(String s) 的方法
server.export(DemoService.class, demo, ‘hi’, new Class?[] { String.class }, options);
Java 还有另一种特殊的调用,称为多态性。也就是说,由于一个接口可能有多个实现,那么在远程调用时会调用哪一个呢?因为这种本地调用语义是通过JVM提供的引用多态性来隐式实现的,对于RPC来说,是无法隐式实现跨进程调用的。如果您之前的DemoService接口有两个实现,则在导出接口时必须特别标记不同的实现:
DemoService演示=新.
DemoService demo2=新.
RpcServer服务器=新.
server.export(DemoService.class, 演示, 选项);
server.export(‘demo2’, DemoService.class, demo2, 选项);
上面的演示2 是一个不同的实现。将其标记为“demo2”以导出。这将要求您在进行远程调用时通过此标记。这解决了多态调用的语义。
导入远程接口和客户端代理
对于远程接口导入和导出,客户端代码必须先获取远程接口的方法或过程定义,然后才能开始调用。目前,大多数跨语言平台RPC框架都使用代码生成器根据IDL定义生成存根代码。这样,实际的导入过程是在编译时通过代码生成器完成的。我用过的一些跨语言平台的RPC框架,比如CORBAR、WebService、ICE、Thrift,都是这样做的。
代码生成方式是跨语言平台RPC框架的自然选择,并且可以通过同一语言平台上RPC的共享接口定义来实现。 Java 中导入接口的代码片段如下:
RpcClient客户端=新.
DemoService demo=client.refer(DemoService.class);
demo.hi(‘你好吗?’);
由于“import”是Java 中的关键字,因此代码片段使用引用来表示导入的接口。这里的import方法本质上是一种代码生成技术,但由于是在运行时生成的,所以显得比静态编译时的代码生成简单。 Java 至少提供了两种提供动态代码生成的技术。一是jdk动态代理,二是字节码生成。动态代理比字节码生成更容易使用,但动态代理方法的性能低于直接字节码生成,并且字节码生成产生的可读代码明显较差。在权衡两者时,我个人认为牺牲一些性能来换取代码的可读性和可维护性更为重要。
协议编解码器
客户端代理必须在发起呼叫之前对呼叫信息进行编码。这就需要考虑需要将哪些信息编码并发送给服务器,以便服务器能够完成调用。出于效率原因,编码的信息越少越好(发送的数据越少),编码规则越简单越好(执行效率更高)。首先,我们来看看需要编码哪些信息。
— 调用编码–
1. 接口方法
包括接口名称和方法名称
2、方法参数
包含参数类型、参数值
3. 调用属性
包含调用属性信息,例如调用附件的隐式参数、调用超时等。
— 返回编码–
1、返回结果
接口方法中定义的返回值
2.返回码
异常返回码
3.返回异常信息
调用异常信息
除了上面列出的所需的调用信息之外,还可能需要一些元信息,以方便程序编码和解码,以及将来的扩展。这样,编码后的消息就被分成两部分,一部分是元信息,另一部分是调用所需的信息。设计RPC协议消息时,在协议消息头中包含元信息,在协议消息体中包含所需信息。概念性RPC协议消息设计格式如下所示。
— 消息头–
Magic :协议魔数,专为解码而设计
Header size :协议头长度,为扩展而设计
版本: 为兼容性而设计的协议版本
st : 消息体序列化类型
hb : 心跳消息标记,专为长连接传输层心跳而设计
ow : 单向消息标志,
rp : 响应消息标志。如果不设置,默认是请求消息。
状态码: 响应消息状态码
保留: 保留用于字节对齐
消息ID : 消息ID
正文尺寸: 消息正文长度
– 邮件正文 –
使用序列化编码,常见以下格式
Web 服务肥皂XML :
json : 作为JSON-RPC
binary:如hession。
一旦确定了格式,编码和解码就很容易了。 header的长度是固定的,所以我更关心的是如何序列化消息体。我们重点关注序列化的三个方面。
1. 序列化和反序列化越快越好。
2、序列化后的字节长度越小越好。
3. 序列化和反序列化兼容性如果接口参数对象添加了字段,它们是否兼容?
以上三点未必鱼与熊掌兼得。这涉及到具体的序列化库实现细节,本文不再深入分析。
发送服务
协议被编码后,将编码后的RPC 请求消息发送到服务器就有意义了。执行后,服务器返回结果或确认消息给客户端。 RPC应用场景本质上是一个可靠的请求和响应消息流,类似于HTTP。因此,选择TCP协议会更加高效,它与HTTP不同,在协议级别为每条消息定义了唯一的ID,因此可以更轻松地重用连接。
既然使用了长连接,那么第一个问题就是:客户端和服务器之间需要多少个连接?事实上,对于低数据使用率的应用程序,单连接和多连接在使用上没有区别。单个连接和多个连接之间的最大区别是每个连接都有自己的私有发送和接收缓冲区。因此,当发送大量数据时,将其分布在不同的连接缓冲区中可以提高吞吐量效率。因此,如果数据传输量不足以保持单个连接的缓冲区饱和,那么使用多个连接不会提供显着的改进,但会增加连接管理的开销。
连接由客户端发起和维护。当客户端和服务器直接连接时,连接通常不会中断(当然,物理链路故障除外)。如果客户端和服务器之间的连接经过多个负载转移设备,如果连接一段时间不活动,这些中间设备可能会导致连接中断。为了维持连接,每个连接必须定期发送心跳数据,以确保连接保持不间断。心跳消息是RPC框架库使用的内部消息。以前的协议头结构还具有对业务应用程序透明的特殊心跳位。
拨打电话
客户端存根所做的只是对消息进行编码并将其发送到服务器;实际的调用过程发生在服务器上。 Server Stub 从前面的结构分解来看,我们细分了两个组件:RpcProcessor 和RpcInvoker。一个负责控制调用过程,另一个负责实际调用。我们以Java中这两个组件的实现为例,分析一下它们到底需要做什么。
在Java 中实现代码的动态接口调用现在通常通过反射来调用。除了原生JDK自带的反射之外,一些第三方库也提供了性能增强的反射调用,因此RpcInvoker封装了反射调用的实现细节。
控制呼叫流程时需要考虑哪些因素?RpcProcessor应该提供什么样的呼叫控制服务?以下几点激发你的思考:马苏。
1. 提高效率
由于每个请求都必须尽快执行,所以我们不能为每个请求创建一个线程,必须提供线程池服务。
2、资源分离
导出多个远程接口时,如何避免单个接口调用占用所有线程资源并阻塞其他接口执行。
3. 超时控制
如果接口运行缓慢并且客户端超时并放弃等待,那么让服务器端线程继续运行是没有意义的。
RPC异常处理
无论RPC 如何努力将远程调用伪装成本地调用,远程调用仍然有很大不同,并且有一些例外情况是您在进行本地调用时永远不会遇到的。在讲异常处理之前,我们先来比较一下本地调用和RPC调用的一些区别。
1、本地调用保证执行,但远程调用可能因网络原因无法发送到服务器。
2、本地调用只会抛出接口声明的异常,而远程调用在RPC框架执行过程中也会抛出其他异常。
3. 本地和远程调用的性能可能会因特定于RPC 的消耗速率而有很大差异。
使用RPC时必须进一步考虑这些差异。调用远程接口抛出异常时,该异常可以是业务异常,也可以是RPC框架抛出的运行时异常(例如网络中断)。业务异常表示服务器执行了调用,但由于某种原因可能未成功执行。另一方面,RPC 运行时异常表明服务器根本没有运行。调用者的异常处理策略当然必须有所区别。
RPC的内在成本比本地调用高几个数量级,因此本地调用的内在成本是纳秒级的,而RPC的内在成本是毫秒级的。因此,对于过于轻量级的计算任务,只有当计算任务花费的时间远大于RPC 的固有消耗时,才建议导出远程接口来服务。远程接口。
总结
至此,我们已经提出了RPC实现的概念框架,并详细分析了一些需要考虑的实现细节。无论RPC 概念多么复杂,“草丛中仍然藏着几条蛇”。只有深刻理解RPC的本质,才能更好的应用它。
原创文章,作者:小条,如若转载,请注明出处:https://www.sudun.com/ask/85070.html