在分布式服务框架中,服务之间通过RPC技术进行通信,而RPC通常采用二进制私有协议。因为公有协议(HTTP、WebService)在性能方面没有私有协议好,所以很多都采用自研的私有协议或者主流的私有协议作为服务之间的通信协议。
主流公有协议
目前主流的公有协议有HTTP、SOAP等。
HTTP
HTTP是一个属于应用层的面向对象协议,HTTP协议有五大特点:
- 支持客户/服务器模式。
- 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型。由于HTTP协议简单,使得HTTP服务器的程序规模小,因此通信速度很快。
- 灵活:HTTP协议允许传输任意类型的数据对象。传输的类型Content-Type加以标记。
- 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,就断开连接。采用这种方式可以节省传输时间。
- 无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。无状态意味着如果后续处理需要前面的信息,它就必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前的信息时,它的应答就较快。
HTTP协议是我们最为熟悉的一种协议,HTTP协议相对更规范、更标准、更通用。无论哪种语言都支持HTTP协议。如果对外开放API,例如开放平台,外部的编程语言多种多样,那将无法拒绝对每种语言的支持。相应的,如果采用HTTP协议,无疑在实现SDK之前支持所有语言。所有现在开源中间件基本最先支持的机柜协议都包含HTTP。
SOAP
SOAP(Simple Object Access Protocol,简单对象访问协议)是一种简单的基于XML的协议,可以使应用程序在分散或分布式的环境中通过HTTP来交换信息。
SOAP协议有以下优点:
- 采用XML支持跨平台远程调用。
- 基于HTTP的SOAP协议,可跨越防火墙,因为SOAP一般使用HTTP协议,而服务器的这个协议一般都是开放的,而且是可以穿过防火墙的。
- 支持面向对象开发。
- 有利于软件和数据重用,实现松耦合。
SOAP协议的缺点也非常明显:
- 由于SOAP是基于XML传输的,使用XML传输会携带一些无关的信息,从而效率不高。
- 随着SOAP协议的完善,SOAP协议增加了许多内容,这样就导致了使用SOAP协议去完成简单的数据传输效率不高。
- SOAP协议通常由HTTP协议承载,HTTP 1.0/1.1不支持双向全双工通信,而且一般使用短连接通信,性能比较差。
在分布式微服务框架中,默认使用性能更高、扩展性更好的二进制私有协议进行通信的,而且一般使用短连接通信,性能比较差。
私有协议设计
在项目,或者互联网公司内部,如果需要自研一套用于RPC通信的私有协议,那么需要定义私有协议的通信模型和消息定义,需要支持服务之间采用点对点长连接通信,需要提供可扩展的编解码框架,支持多种序列化格式,需要保证链路可靠性,提供握手和安全认证机制。
1.私有协议通信模型
私有协议通信模型的过程如下:
- 客户端发送握手请求消息,并携带接点ID等有效身份认证信息。
- 服务端对握手请求信息进行合法性校验,校验通过后,发送握手成功响应消息。
- 链路建立成功后,客户端发送业务消息。
- 链路建立成功后,服务端发送心跳消息。
- 链路建立成功后,客户端发送心跳消息。
- 链路建立成功后,服务端发送业务消息。
- 服务端退出,关闭连接,客户端一段时间连接不上,也自动断开连接。
服务消费者和服务提供者建立连接后可以进行全双工通信,双方之间的心跳采用Ping-Pong机制,当链路处于空闲时,客户端主动发送Ping消息给服务端,服务端收到Ping消息后发送应答消息Pong给客户端,如果发送N条Ping消息都没有接收到服务端返回的Pong消息,就说明链路已经挂死或者对方处于异常状态,客户端主动关闭连接,间隔周期T后发起重连操作,直到重连成功。
2.协议消息定义
标准的协议通常由消息头和消息体组成。消息头用于存放协议公共字段和用户扩展字段。消息体则用于携带消息内容。私有协议的消息模型与标准协议类似,也包含消息头和消息体两部分。
如下简单列举消息头和消息体格式:
名称 | 字段 | 类型 | 长度 | 描述 |
---|---|---|---|---|
Header | crcCode | Int | 32 | 协议校验码,由三部分组成:oxAFBA:固定值。2字节,表示消息是私有协议消息。 主版本号:1~255,1字节 次版本号:1~255,1字节 crcCode=oxAFBA+主版本号+次版本号 |
Length | Int | 32 | 整个消息长度 | |
Type | Byte | 8 | 0:业务请求信息 1:业务响应消息 2:业务单向(One Way)消息 3:握手请求消息 4:握手应答消息 5:心跳请求消息 6:心跳应答消息 | |
Priority | Byte | 8 | 消息优先级:0~255 | |
InterfaceName | String | 变长 | 接口名 | |
MethodName | String | 变长 | 方法名 | |
Attachment | Map<String,Object> | 变长 | 可选字段,用于扩展消息头 | |
Body | byte[] | 变长 | 字节数组:对于请求消息,它是方法的参数;对于响应消息,它是返回值 |
除了定义协议消息格式外,还需要协议层面支持的数据结构类型,防止用户使用不支持的数据结构类型导致编码或者解码失败。数据类型有booleam、byte、int、char、short、long等。
3.协议序列化和反序列化
私有协议消息序列化分为消息头的序列化和消息体的序列化,私有协议可以由不同的序列化框架承载,标识序列化格式的字段在消息头中定义。首先,需要对消息头做通用解码,获取序列化格式,然后根据类型调用对应的解码器对消息体解码。如果消息头的序列化不是通用的,就无法对其做反序列化。
4.链路创建与关闭
服务消费者和服务提供者要通信,服务消费者会主动发起物理链路创建,链路创建需要通过基于IP地址或者号段的黑白名单安全认证机制。服务消费者和服务提供者物理链路创建成功后,服务消费者发送握手请求消息,服务提供者接收到服务消费者的握手请求消息后,如果IP校验通过,就返回握手成功应答消息给服务消费者,应用层链路建立成功。应用层链路建立成功后,客户端和服务端就可以互相发送业务消息了。
服务消费者和服务提供者相互通信时,任何一方宕机或者重启、消息读写出现I/O异常、心跳消息读写出现I/O异常、编码异常以及心跳超时等都会主动关闭连接。
5.协议可靠性
在分布式框架中,私有协议需要支持高可用,可以从以下几个方面进行:
- 客户端连接超时:需要支持可以设置连接超时的参数,传统的同步阻塞I/O模型,连接操作是同步阻塞的,如果不设置超时时间,I/O线程就可以被长时间阻塞,导致系统的可以I/O线程减少。Netty框架在创建NIO客户端时,支持设置连接超时参数,使用起来比较方便。
- 客户端重连机制:连接链路断开后,需要支持失败重连,失败重连并不是失败了马上重新连接,而是等待周期T时间后再发起重连。
- 客户端重复握手保护:客户端握手成功后,在链路处于正常状态下,不允许客户端重复握手,以防止客户端在异常状态下反复重连导致句柄资源被耗尽。
- 消息缓存重发:当链路恢复后重新发送这些消息,保证链路中断期间消息不丢失。同时,也要限制缓存队列大小,当达到上限后,需要拒绝向该缓存队列中添加新的消息。
- 心跳机制:心跳机制可以用来检测链路的互通性,一旦发现网络故障,立即关闭链路,主动重连。
安全协议性
在分布式服务架构下,集群内部的服务之间基于长连接通信,长连接使用IP地址的安全认证机制,服务端对握手请求的IP地址进行合法性校验,如果在白名单之内,就检验通过:否则,拒绝对方连接。
如果服务开放给第三方非信任域的消费者,就需要采用更加严格的安全认证机制,例如基于密钥和AES加密的用户名+密码认证机制,也可以采用SSL/TSL安全传输,如下图: