资讯专栏INFORMATION COLUMN

简易RPC框架:需求与设计

Ajian / 3353人阅读

摘要:报文类型对于框架来说,报文可能有多种类型心跳类型报文认证类型报文请求类型报文响应类型报文等。接口调用请求的发送,在多条连接之间进行负载均衡。

1 需求分析

RPC 全称 Remote Procedure Call ,简单地来说,它能让使用者像调用本地方法一样,调用远程的接口,而不需要关注底层的具体细节。

例如车辆违章代办功能,如果车辆因为某种原因违章,只需要通过这个违章代办功能(它也许是个APP),我们就能动动手指,而省去了一些跑腿的工作。

不像微服务背景下大家所说的 RPC 框架,如 Dubbo 之类。这个 RPC 框架不提供过多的关于服务注册、服务发现、服务管理等功能。它针对的是这样的一些场景:在内部网络,或者局域网内,两个属于同个业务的系统之间需要通信,而我们又觉得去设计多一种二进制网络协议过于繁琐并且没有必要,这时候如果给客户端开发者一些明确的接口,让他知道实现什么功能该调用什么接口,那么省去的工作量以及开发效率上的提升不言而喻。

这个 RPC 系统基于 Java 语言实现,需求如下:

RPC 服务端可以通过一条长连接发布多个接口(Interface),客户端按需生成对应接口的代理。

RPC 客户端也可以发布接口,以便在必要的时候,服务端可以主动调用客户端的接口实现

客户端与服务端之间保持长连接并且维持心跳

服务端针对不同的接口实现,可以指定不同的线程池去处理

序列化协议支持扩展

通信协议与具体编程语言无关

支持并发调用,一个RPC客户端实例要求是线程安全的

2. 通信协议设计

高效的通信协议一般是二进制格式的,比较常见的还有文本协议比如说HTTP,为了追求效率,这个 RPC 框架就采用二进制格式。

协议的基本要素 魔数

要了解到,报文是在网络上传输的,安全性比较低,因此有必要采取一些措施使得并不是任何人都可以随随便便往我们的端口上发东西,因此我们对报文要有一个初步的识别功能,这时候“魔数(magic number)”就派上用场了。魔数并不受任何规范约束,没有人可以要求你的魔数应该遵循什么规范,实际上魔数只是我们通信双方都约定的一个“暗号”,不知道这个暗号的人就无法参与进通信中。例如 Java 源文件编译后的 class 文件开头就有一个魔数:0xCAFEBABE,随随便便打开一个class文件用十六进制编辑器查看,就能看到。

Java 虚拟机加载 class 的时候会先验证魔数。如果不是 CAFEBABE 就认为是不合法的 class 文件,并拒绝加载。

不过魔数起到的安全防范作用是非常有限的,“有心人”可以通过抓取网络包就识别出魔数了。因此魔数这个东西其实是“防君子不防小人”。

协议版本

一个协议可能也会有多个版本,例如说 HTTP1.0 和 HTTP1.1,不同版本的协议元素可能发生了改变,解析方式也会发生改变,因此协议设计这一块,需要预留出地方声明协议的版本,通信双方在解析协议或者拼装协议的时候才有迹可循。

报文类型

对于RPC框架来说,报文可能有多种类型:心跳类型报文、认证类型报文、请求类型报文、响应类型报文等。

上下文 ID

RPC 调用其实是一个“请求-响应”的过程,并且跨物理机器,因此每次请求和响应,都必须带上上下文 ID,通信双方才能把请求和响应对应起来。

状态

状态用来标识一次调用时正常结束还是异常结束,通常由被调用方置状态。

请求数据

即发送到服务端的调用请求,通常是序列化后的二进制流,长度不定。

长度编码字段

收报文的一方怎么知道发报文的那一方发了多少字节呢?因此发送方必须在协议里告诉接收方需要接受多少字节才算一个完整的报文。

保留字段

协议一旦被设计,并非一成不变的,日后可能有变动的可能,因此还需要考虑保留一些字节空间作为保留字段,以备日后协议的扩展。

协议设计

结合以上的一些设计原则,具体协议设计如下:

 ------------------------------------------------------------------------
 | magic (2bytes) | version (1byte) |  type (1byte)  | reserved (7bits) | 
 ------------------------------------------------------------------------
 | status (1byte) |    id (8bytes)    |        body length (4bytes)     |
 ------------------------------------------------------------------------
 |                                                                      |
 |                   body ($body_length bytes)                          |
 |                                                                      |
 ------------------------------------------------------------------------
3. 链路可靠性

客户端与服务端之间的连接采用 TCP 长连接,一个客户端与服务端之间保持至少一条长连接。接口调用请求的发送,在多条连接之间进行负载均衡。

每条连接在空闲的时候,由客户端主动向服务端发送心跳报文,并且客户端在发现连接失效或断开的时候,自动进行重连。

每个客户端向服务端建立连接后,在正式发起接口调用请求之前,都需要进行check in 操作, check in 操作主要是将客户端的身份标识(identifier)和客户端的心跳间隔告诉服务端。利用 netty 的 handler 责任链机制和自带的 IdleStateHandler,自动检测出连接是否空闲,并在空闲时触发心跳报文的发送。而服务端在客户端 checkin 后,根据客户端的心跳频率,在自己的 handler pipeline 上动态加入一个 IdleStateHandler,来检测出客户端是否已经失联,如果是,则主动关闭连接。

同时,客户端本地将会起一个定时执行任务的线程,定期检查连接是否失效,如果失效,则关闭旧连接,并进行连接的重建。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72040.html

相关文章

  • 简易RPC框架:基于 netty 的协议编解码

    摘要:概述在简易框架需求与设计这篇文章中已经给出了协议的具体细节,协议类型为二进制协议,如下协议的解码我们称为,编码我们成为,下文我们将直接使用和术语。直接贴代码,参考前文提到的协议格式阅读以下代码协议编码器 概述 在《简易RPC框架:需求与设计》这篇文章中已经给出了协议的具体细节,协议类型为二进制协议,如下: ---------------------------------------...

    Loong_T 评论0 收藏0
  • 简易RPC框架:序列化机制

    摘要:由于我们还未谈到具体的调用机制,因此暂且认为就是把一个包含了调用信息的对象,从经过序列化,变成一串二进制流,发送到了端。 概述 在上一篇文章《简易RPC框架:基于 netty 的协议编解码》中谈到对于协议的 decode 和 encode,在谈 decode 之前,必须先要知道 encode 的过程是什么,它把什么东西转化成了二进制协议。由于我们还未谈到具体的 RPC 调用机制,因此暂...

    walterrwu 评论0 收藏0
  • 你还不知“dubbo”是个什么东西吗???

    摘要:服务提供者在启动时,向注册中心注册自己提供的服务。注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。 先来了解一下这些年架构的变化,下面的故事是我编的。。。。 传统架构:很多年前,刚学完JavaWeb开发的我凭借一人之力就开发了一个网站,网站 所有的功能和应用都集中在一起,方便了我的开发同时也节省了成本。但是后来我的网站访问流量突然加大,我通...

    keithxiaoy 评论0 收藏0
  • PowerDotNet平台化软件架构设计实现系列(04):服务治理平台

    摘要:的服务治理平台发源于早期的个人项目。客户端发现模式要求客户端负责查询注册中心,获取服务提供者的列表信息,使用负载均衡算法选择一个合适的服务提供者,发起接口调用请求。系统和系统之间,少不了数据的互联互通。随着微服务的流行,一个系统内的不同应用进行互联互通也是常态。 PowerDotNet的服务治理平台发源于早期的个人项目Power.Apix。这个项目借鉴了工作过的公司的服务治理方案,站在...

    reclay 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<