16.1.6 协议传输格式

消息可以通过不同的传输机制进行传输,而传输中的消息则是一些字节序列,那么传输机制需要支持:

请求信息的传送。

对应响应信息的接收。

服务器会对客户机的请求信息发送响应信息,这种响应机制就是特定传输,例如在HTTP中,由于HTTP直接支持请求和响应,所以这种传输是透明的,但是利用同一套接字传输多种不同客户线程的时候需要用特定的标识来区分不同客户的信息。

传输可能是无状态的也可能是有状态的。在无状态传输中,是假定消息发送没有建立连接状态。而有状态传输则建立了连接,这个连接可以用来传输不同的消息。下面我们会在握手(handshake)部分中深入分析。

当用HTTP协议进行传输时,每个Avro消息交换都是一对HTTP请求/响应。一个Avro协议的所有消息共享一个HTTP服务器上的URL,正常的和错误的Avro消息都应该使用200(OK)响应代码。尽管Avro请求和响应是HTTP请求和响应的整个内容,但也可能使用大量的编码。HTTP请求和响应的内容类型应该指定为“avro/binary”而且请求应该使用POST方法生成。Avro使用HTTP作为无状态传输。

Avro消息经过框架处理后由一系列缓冲区组成,消息框架是消息和传输之间的一层,用来优化某些操作。经过框架处理后的消息数据格式如下(见图16-4)。

16.1.6 协议传输格式 - 图1

图 16-4 消息的封装

1)由一系列缓冲区组成,其中缓冲区包括:

4个字节,用大端字节(big-endian)方法[1]表示的缓冲区长度。

缓冲区数据。

2)最后以空字节(zero-length)的缓冲区结束。

对于请求和响应消息格式,框架是透明的,任何消息可以表示为一个或多个缓冲。框架使得消息接收者更高效地从不同的渠道获取不同的缓冲,也使得开发者更高效地向不同的目的地存储不同的缓冲。特别是当复制大量二进制对象时,它可以减少读/写的次数。例如,如果RPC参数中包含一个MB大小的文件数据,那么一方面,数据可以从文件描述符直接复制到套接字上,另一方面,数据可以直接写入文件描述符而不需要进入用户空间。

一个简单且值得推荐的框架策略是:相对于那些大于正常输出缓冲区的单个二进制对象建立新的段。小的对象可以附加在缓冲区中,而较大的对象可以写入自己的缓冲区中。当读者需要读取大的对象时,可以直接处理整个缓冲区而不用复制。

使用握手的目的是确保客户机和服务器有对方的协议定义,这样客户机可以正确地对响应反序列化,且服务器可以正确地对请求反序列化。客户机和服务器都应在高速缓冲区中保留最近的协议,这样在大多数情况下,可以不需要额外的往返网络交换或重新获取全部传输协议就能完成握手。

在完成握手过程后执行RPC请求和响应,对于无状态的传输,在所有请求和响应之前都要进行握手,而对于有状态的传输,在成功响应之前,握手过程应该附加在请求和响应上,之后就不需要握手了。

握手过程使用以下记录模式,代码如下:


{

"type":"record",

"name":"HandshakeRequest","namespace":"org.apache.avro.ipc",

"fields":[

{"name":"clientHash",

"type":{"type":"fixed","name":"MD5","size":16}},

{"name":"clientProtocol","type":["null","string"]},

{"name":"serverHash","type":"MD5"},

{"name":"meta","type":["null",{"type":"map","values":"bytes"}]}

]

}

{

"type":"record",

"name":"HandshakeResponse","namespace":"org.apache.avro.ipc",

"fields":[

{"name":"match",

"type":{"type":"enum","name":"HandshakeMatch",

"symbols":["BOTH","CLIENT","NONE"]}},

{"name":"serverProtocol",

"type":["null","string"]},

{"name":"serverHash",

"type":["null",{"type":"fixed","name":"MD5","size":16}]},

{"name":"meta","type":["null",{"type":"map","values":"bytes"}]}

]

}


客户机在每个请求前面加上HandshakeRequest,表示包含客户机和服务器协议(clientHash!=null, clientProtocol=null, serverHash!=null)的哈希值,这里哈希值是JSON协议内容的128位MD5哈希值。如果客户机没有连接到给定的服务器,那么它发送的哈希值就是对服务器哈希值的猜测,否则它会发送之前从服务器中获得的哈希值。服务器响应的HandshakeResponse包含以下内容之一。

1)match=BOTH, serverProtocol=null, serverHash=null。如果客户机发送的是服务器协议的有效哈希值,并且服务器知道响应客户机哈希值的协议,那么请求是完整的,并且响应数据加在HandshakeResponse后面。

2)match=CLIENT, serverProtocol!=null, serverHash!=null。如果服务器事前知道客户机的协议,而客户机却发送了一个错误的服务器协议哈希值,那么请求是完整的并且响应数据加在HandshakeResponse之后。之后客户机必须使用返回的协议来处理响应,并且在高速缓存中保留这个协议和与服务器通信的哈希值。

3)match=NONE。如果服务器事先不知道客户机的协议,且服务器的协议哈希值是错误的,则serverHash和serverProtocol的值可能也为non-null。在这种情况下,客户机必须使用其协议文本(clientHash!=null, clientProtocol!=null, serverHash!=null)重新提交它的请求,并且服务器应该以正确的方式响应(match=BOTH, serverProtocol=null, serverHash=null)。另外meta字段是保留字段,用于以后增加握手的功能。

一次调用包括请求消息和与之对应的结果响应或错误消息。请求和响应包含可扩展的元数据,两种消息都会如上进行框架处理。调用请求的格式包括以下几种:

1)请求元数据,即类型值的映射。

2)消息名称,即一个Avro字符串。

3)消息参数,根据消息请求声明对参数进行序列化。

当消息声明为单向的并通过成功握手响应建立有状态的连接,那么不需要发送响应数据。否则需要发送,发送的调用请求的格式如下:

1)响应元数据,即类型字节的映射。

2)单字节的错误标志布尔值,然后,如果错误标志为假,消息响应,序列化每个消息响应模式。

如果错误标志为真,即为错误,序列化每个消息有效错误联合模式。

[1]存放字节顺序的方法,大端方式将高位存放在低地址,小端方式将高位存放在高地址。