五、协议栈

1 关键技术点分析

1.1 是否必须支持多协议

分布式服务框架需要具备通过扩展的方式支持多协议的能力,协议栈应该作为一个架构扩展点开放出来。

1.2 公有协议还是私有协议

以 Web Service 公有协议为例,它的性能存在如下缺陷:

  1. SOAP 消息使用 XML 进行序列化,相比于 PB 等二进制序列化框架,性能低很多。
  2. SOAP 通常由 HTTP 协议承载,HTTP 1.0 不支持双向全工通信,而且一般使用短连接通信,性能比较差。

如果没有特殊需求,分布式服务框架默认使用性能更高、扩展性更好的私有协议(二进制)进行通信。对 HTTP/Restful 等公有协议进行扩展

1.3 集成开元还是自研

  1. 如果已经有可以满足需求的框架,优先选择继承开源框架。
  2. 如果使用到的功能不多,或者对性能要求极高,可以考虑基于 Netty 自研。

2 功能设计

2.1 功能描述

私有协议栈承载了业务内部各模块之间的消息交互和服务调用,主要功能如下:

  1. 定义了私有协议的通信模型和消息定义。
  2. 支持服务提供者和消费者之间采用点对点长连接通信
  3. 基于 Java NIO 通信框架,提供高性能的异步通信能力。
  4. 提供可扩展的编解码框架,支持多种序列化格式。
  5. 握手和安全认证机制。
  6. 链路的高可靠性。

2.2 通信模型

私有协议栈通信模型如下:

  1. 客户端发送握手请求消息,携带节点ID 等有效神风认证信息。
  2. 服务端对握手请求消息进行合法性校验,包括节点ID 有效性校验、节点重复登录校验和IP 地址合法性校验,校验通过后,返回登录成功的握手应答消息。
  3. 链路建立成功之后,客户端发送业务消息。
  4. 链路成功之后,服务端发送心跳消息。
  5. 链路建立成功之后,客户端发送心跳消息。
  6. 链路建立成功之后,服务端发送业务消息。
  7. 服务端退出时,服务端关闭连接,客户端感知对方关闭连接后,被动关闭客户端连接。

2.3 协议消息定义

通信协议栈的消息模型分为两部分,消息头和消息体。消息头存放协议公共字段和用户扩展字段,消息体则用于承载消息内容。以 HTTP 协议为例,请求消息头允许客户端向服务端传递请求的附加信息以及客户端自身的信息,常见的消息头关键字有 Accept、Authorization、Host 等。

2.4 协议消息的序列化和反序列化

消息的序列化分为两部分,消息头的序列化和消息体的序列化,两者采用的机制不一样。原因是协议栈可以由不同的序列化框架承载,标识序列化格式的字段在消息头中定义,因此我们必须首先对消息头做通用解码,获取序列化格式,然后根据类型再调用对应的解码器对消息体做解码。消息头是通用编解码。

2.5 链路创建

协议栈包括服务端和客户端,对于上层应用来说,一个节点可能既是服务端也是客户端。
考虑到安全,链路简历需要通过基于 IP 地址或者号段的黑白名单安全认证机制,以及通过秘钥等。

2.6 链路关闭

由于采用长连接通信,在正常的业务运行期间,双方通过心跳和业务消息维持链路,任何一方都不需要主动关闭连接。以下情况,客户端和服务端需要关闭连接:

  1. 当对方宕机或重启时,会主动关闭链路。
  2. 消息读写过程中,发生了 I/O 异常,需要主动关闭连接。
  3. 心跳消息读写过程中发生了 I/O 异常,需要主动关闭连接。
  4. 心跳超时,需要主动关闭连接。
  5. 发生编码异常或其它不可恢复错误时,需要主动关闭连接。

3 可靠性分析

3.1 客户端连接超时

客户端业务需要、以及资源的长时间占有等,需要设置超时时间。

3.2 客户端重连机制

客户端通过链路关闭监听器监听链路状态,如果链路中断,等待 INTERVAL 时间后,由客户端发起重连操作,如果重连失败,间隔周期 INTERVAL 后再次发起重连,直到重连成功。

为了保证服务端能够有充足的时间释放句柄资源,在首次断连时客户端需要等待 INTERVAL 时间之后再发起重连,而不是失败后就立即重连。

3.3 客户端重复握手保护

当客户端握手成功之后,在链路处于正常状态下,不允许客户端重复握手,以防止客户端在异常状态下反复重连导致句柄资源被耗尽。

服务端接收到客户端的握手请求消息之后,首先对IP 地址进行合法性检验,如果校验成功,在缓存的地址表中查看客户端是否已经登录,如果已经登录,则拒绝登录,返回错误码 -1,同时关闭 TCP 链路,并在服务端的日志中打印握手失败的原因。

客户端接收到握手失败的应答消息之后,关闭客户端的 TCP连接,等待 INTERVAL 时间之后,再次发起 TCP连接,直到认证成功。

3.4 消息缓存重发

无论客户端还是服务端,当发生链路中断之后,在链路恢复之前,缓存在消息队列中待发送的消息不能丢失,等链路恢复之后,重新发送这些消息,保证链路中断期间消息不丢失。

考虑到内存溢出的风险,建议消息缓存队列设置上限,当达到上限之后,应该拒绝继续向该队列添加新的消息。

3.5 心跳机制

在凌晨等业务低谷期时段,如果发生网络闪断、连接被 Hang 住等网络问题时,由于没有业务消息,应用金城很难发现。到了白天业务高峰期时,会发生大量的网络通信失败。为了解决这个问题,在网络空闲时采用心跳机制来检测链路的互通性,一旦发现网络网络故障,立即关闭链路,主动重连。

4 最佳实现–协议的前后兼容性

考虑到协议的前向兼容性,核心的设计原则有2 个:

  1. 消息头第一个字段中携带协议的版本号,用于标识消息协议版本。
  2. 消息头最后一个字段是 Map 类型的扩展字段,用于服务框架自身或者用户扩展消息头。

5 个人总结

协议栈描述了分布式服务框架的通信契约,序列化和反序列化框架用于协议消息对象和二进制数组之间的相互转换,通信框架在技术上承载协议,协议依赖通信。