整天跟微服务打交道,你不会连RPC都不知道吧?
Posted 程序员每日必读
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了整天跟微服务打交道,你不会连RPC都不知道吧?相关的知识,希望对你有一定的参考价值。
RPC 功能目标
RPC 调用分类
1. 同步调用
客户方等待调用执行完成并返回结果。
2. 异步调用
客户方调用后不用等待执行结果返回,但依然可以通过回调通知等方式获取返回结果。
若客户方不关心调用返回结果,则变成单向异步调用,单向调用不用返回结果。
RPC 结构拆解
RpcServer
去导出(export)远程接口方法,而客户方通过
RpcClient
去引入(import)远程接口方法。客户方像调用本地方法一样去调用远程接口方法,RPC 框架提供接口的代理实现,实际的调用将委托给代理
RpcProxy
。代理封装调用信息并将调用转交给
RpcInvoker
去实际执行。在客户端的
RpcInvoker
通过连接器
RpcConnector
去维持与服务端的通道
RpcChannel
,并使用
RpcProtocol
执行协议编码(encode)并将编码后的请求消息通过通道发送给服务方。
RpcAcceptor
接收客户端的调用请求,同样使用
RpcProtocol
执行协议解码(decode)。解码后的调用信息传递给
RpcProcessor
去控制处理调用过程,最后再委托调用给
RpcInvoker
去实际执行并返回调用结果。
RPC 组件职责
1. RpcServer
负责导出(export)远程接口
2. RpcClient
负责导入(import)远程接口的代理实现
3. RpcProxy
远程接口的代理实现
4. RpcInvoker
客户方实现:负责编码调用信息和发送调用请求到服务方并等待调用结果返回
服务方实现:负责调用服务端接口的具体实现并返回调用结果
5. RpcProtocol
负责协议编/解码
6. RpcConnector
负责维持客户方和服务方的连接通道和发送数据到服务方
7. RpcAcceptor
负责接收客户方请求并返回请求结果
8. RpcProcessor
负责在服务方控制调用过程,包括管理调用线程池、超时时间等
9. RpcChannel
数据传输通道
RPC 实现分析
导出远程接口
DemoService demo = new ...;
RpcServer server = new ...;
server. export(DemoService.class, demo, options);
// 只导出 DemoService 中签名为 hi(String s) 的方法
server.export(DemoService.class, demo, "hi", new Class<?>[] { String.class }, options);
DemoService
接口有 2 个实现,那么在导出接口时就需要特殊标记不同的实现,如:
DemoService demo = new ...;
DemoService demo2 = new ...;
RpcServer server = new ...;
server. export(DemoService.class, demo, options);
server. export( "demo2", DemoService.class, demo2, options);
导入远程接口与客户端代理
RpcClient client = new ...;
DemoService demo = client.refer(DemoService.class);
demo.hi( "how are you?");
协议编解码
-- 调用编码 --
1. 接口方法
包括接口名、方法名
2. 方法参数
包括参数类型、参数值
3. 调用属性
包括调用属性信息,例如调用附件隐式参数、调用超时时间等
-- 返回编码 --
1. 返回结果
接口方法中定义的返回值
2. 返回码
异常返回码
3. 返回异常信息
调用异常信息
-- 消息头 --
magic : 协议魔数,为解码设计
header size: 协议头长度,为扩展设计
version : 协议版本,为兼容设计
st : 消息体序列化类型
hb : 心跳消息标记,为长连接传输层心跳设计
ow : 单向消息标记,
rp : 响应消息标记,不置位默认是请求消息
status code: 响应消息状态码
reserved : 为字节对齐保留
message id : 消息 id
body size : 消息体长度
-- 消息体 --
采用序列化编码,常见有以下格式
xml : 如 webservie soap
json : 如 JSON-RPC
binary: 如 thrift; hession; kryo 等
1. 序列化和反序列化的效率,越快越好。
2. 序列化后的字节长度,越小越好。
3. 序列化和反序列化的兼容性,接口参数对象若增加了字段,是否兼容。
上面这三点有时是鱼与熊掌不可兼得,这里面涉及到具体的序列化库实现细节,就不在本文进一步展开分析了。
传输服务
执行调用
RpcProcessor
和
RpcInvoker
两个组件,一个负责控制调用过程,一个负责真正调用。这里我们还是以 java 中实现这两个组件为例来分析下它们到底需要做什么?
RpcInvoker
就是封装了反射调用的实现细节。
RpcProcessor
需要提供什么样地调用控制服务呢?下面提出几点以启发思考:
1. 效率提升
每个请求应该尽快被执行,因此我们不能每请求来再创建线程去执行,需要提供线程池服务。
2. 资源隔离
当我们导出多个远程接口时,如何避免单一接口调用占据所有线程资源,而引发其他接口执行阻塞。
3. 超时控制
当某个接口执行缓慢,而 client 端已经超时放弃等待后,server 端的线程继续执行此时显得毫无意义。
RPC 异常处理
1. 本地调用一定会执行,而远程调用则不一定,调用消息可能因为网络原因并未发送到服务方。
2. 本地调用只会抛出接口声明的异常,而远程调用还会跑出 RPC 框架运行时的其他异常。
3. 本地调用和远程调用的性能可能差距很大,这取决于 RPC 固有消耗所占的比重。
正是这些区别决定了使用 RPC 时需要更多考量。当调用远程接口抛出异常时,异常可能是一个业务异常,也可能是 RPC 框架抛出的运行时异常(如:网络中断等)。业务异常表明服务方已经执行了调用,可能因为某些原因导致未能正常执行,而 RPC 运行时异常则有可能服务方根本没有执行,对调用方而言的异常处理策略自然需要区分。
总结
以上是关于整天跟微服务打交道,你不会连RPC都不知道吧?的主要内容,如果未能解决你的问题,请参考以下文章
都 2021 年了还不会连 ES6/ES2015 更新了什么都不知道吧