目录
- 非二进制编码(三类)
- 二进制编码(三类)
- REST VS RPC VS 消息传递系统
- 模式升级与演化
1. 非二进制编码
1、在内存中,数据是保存在对象、结构、列表、数组、哈希表、树、等等。这些数据结构在内存之中被优化为CPU可以高效访问和操作的结构(通常这是操作系统的任务,并不需要程序员操心)。
2、而当你想把数据写入一个文件或者通过网络发送它时,你必须把它编码成某种形式的字节序列(例如,一个JSON文档)。
因此,我们需要两种形式之间的某种转换。(内存与其他位置)翻译从内存中表示的数据称之为编码(也称为序列化),反之称为解码(反序列化)。
通常编码有如下几种格式:
特定的语言格式
许多编程语言都对编码有内置的支持,用于将内存对象编码成字节序列。例如:Java的java.io.Serializable , Ruby的Marshal, Python的pickle。但是这些编程语言内置的库存在一些深层次的问题。
编码通常与特定的编程语言捆绑在一起,用另一种语言读取数据是非常困难的
为了在同一对象类型中恢复数据,解码过程需要能够实例化任意类,如果攻击者可以让您的应用程序解码任意字节序列,则它们可以实例化任意类。这常常是安全问题的来源。
效率(用于编码或解码的CPU时间,以及编码结构的大?。?,java内置编码库臭名昭著的就是其糟糕的表现和臃肿的编码
JSON、XML与CSV
上面这几种格式,也是我们在编码之中常见到的。
XML的描述十分精准,但是因过于冗长。
JSON的流行主要归功于它在Web浏览器中的内置支持(由于它是JavaScript的一个子集)和相对于XML的简单性。
CSV是另一种流行的与语言无关的格式,尽管功能不强。
JSON、XML和CSV都是文本格式,因此都具有一定的可读性。但他们也有如下一些微妙的问题:
关于数字的编码有很多歧义。在XML和CSV中,不能区分恰好由数字组成的数字和字符串(除了引用外部模式)。
JSON区分字符串和数字,但它不区分整数和浮点数,也不能确认精度。
JSON与XML为Unicode字符串的支持,但他们不支持二进制字符串(字节序列没有字符编码)。
对于XML和JSON,都有可选的模式支持。这些模式语言非常强大,因此学习和实现起来相当复杂。而CSV没有任何模式,因此需要应用程序定义每个行和列的含义。如果应用程序添加了新行或列,则必须手动处理该更新。CSV是一个相当模糊的格式(出于是分隔符的原因)
2. 二进制编码
二进制的编码格式通常是最紧凑的编码格式,对于一个小的数据集,编码大小的收益是微不足道的,但一旦进入百万兆字节的数据集,数据格式的选择就会有很大的影响了。接下来我们来看一个通过JSON描述的数据结构:
-
MessagPack
我们来看看通过MessagePack进行二进制编码之后的JSON格式:
二进制编码长度为66个字节,这仅比81字节的文本JSON编码小了一点。通过这样的空间减少便丧失了可读性的保障,我们来看看有木有更优秀的解决方式。
-
Thrift
在Thrift中的数据进行编码,需要预先在Thrift接口定义语言(IDL)中描述这样的模式:
在Thrift之中存在两种不同的二进制编码格式,一种是直接使用二进制编码的Binary格式,另一种则是使用压缩之后的Compact格式,我们来一一看两者的区别。
Binary格式编码之后为59个字节大小,并且每个字段都有一个类型注释(用于指示它是字符串、整数、列表等),并在需要时指定长度指示(字符串的长度、列表中项的数量)。但是和MessagePack相比就省去了字段名等信息,取而代之的是字段标记(1,2和3),这些是出现在模式定义中的数字。字段标记类似于字段别名,它们是一种简洁的方式来描述我们所谈论的字段,而不必拼写字段名称。从而减少了二进制编码的大小。
Compact格式它包含相同的信息只有34个字节。它通过将字段类型和标记号打包成一个字节,并使用可变长度整数来实现这一点。它不是为1337号使用八个完整的字节,而是用两个字节编码,每个字节的最高位用来指示是否还有更多的字节要来。这意味着-64到63之间的数字用一个字节编码,-8192到8191之间的数字用两个字节编码,较大的数字使用更多字节。
-
ProtocolBuf
Protocolbuf(只有一个二进制编码格式)相同的数据编码如下图所示。它位包装略有不同,但Thrift的Compact格式大同小异。Protobuf以33字节匹配相同的记录。
-
Avro
Avro是一个二进制编码格式,它是发源于开源项目Hadoop,来作为Thrift的替换方案存在的,我们来看看通过Avro编码之后的记录,又是怎么样的呢?
在Avro模式之中没有标记号。将同样的数据进行编码,Avro二进制编码是32个字节长,是上述编码之中最紧凑的。检查上述的字节序列,并没有标识字段或数据类型。编码简单地由连接在一起的值组成。在解析二进制数据时,通过使用模式来确定每个字段的数据类型。这意味着如果读取数据的代码与写入数据的代码使用完全相同的模式,二进制数据才能被正确地解码。
3. REST VS RPC VS 消息传递系统
REST
同步请求/回复 - HTTP(传输REST的网络协议)本身是一个请求/响应协议,因此REST非常适合请求/回复交互。
面向公众的API - 由于IETF的工作,HTTP是事实上的传输标准,因此使用REST创建的API的传输层可与每种编程语言互操作。此外,可以使用诸如Swagger(OpenAPI规范)之类的工具轻松记录消息有效负载。由于互联网上存在广泛的安全威胁,REST的安全生态系统非常强大,从防火墙到OAUTH(身份验证/授权)。
缺点:
1.紧耦合 - 在调用RESTful服务时,开发人员假设消息只需要传递到一个地方。当另一个服务或组件将来联机并需要数据时会发生什么?当然,您可以更新代码以添加新端点,但这有缺陷:不必要的耦合。很快你的简单微服务就变成了一个协调器,它违背了微服务的属性“单一用途”。
2.阻止 - 调用REST服务时,将阻止您的服务等待响应。这会损害应用程序性能,因为此线程可能正在处理其他请求。在许多方面,当等待RESTful服务响应的应用程序中的线程被阻塞时,会出现同样的挑战。线程昂贵且难以管理!
3.错误处理 - HTTP是为网络构建的,我们都看到我们的浏览器在尝试访问网页时遇到困难。通常我们单击刷新按钮并显示页面。但如果再次失败怎么办?尝试再次刷新?是否有人开始通过喝一杯咖啡并在几分钟内再次尝试来实现人类形式的指数退缩?我们不知道该做什么,因为每个网页都是不同的,并且具有独特的行为。直接调用RESTful服务时会出现相同类型的问题。这个复杂的重试逻辑应该驻留在服务的代码中吗?如果它确实将服务与其他服务紧密耦合 - 再次违反了保持微服务架构单一用途且尺寸小的关键原则。
RPC VS REST
使用二进制编码格式的自定义RPC协议可以实现比通用的JSON over REST更好的性能。但是,RESTful API还有其他一些显着的优点:对于实验和调试(只需使用Web浏览器或命令行工具curl,无需任何代码生成或软件安装即可向其请求),它是受支持的所有的主流编程语言和平台,还有大量可用的工具(服务器,缓存,负载平衡器,代理,防火墙,监控,调试工具,测试工具等)的生态系统。由于这些原因,REST似乎是公共API的主要风格。 RPC框架的主要重点在于同一组织拥有的服务之间的请求,通常在同一数据中心内。
对于RESTful API,常用的方法是在URL或HTTP Accept头中使用版本号。对于使用API密钥来标识特定客户端的服务,另一种选择是将客户端请求的API版本存储在服务器上,并允许通过单独的管理界面更新该版本选项【49】。
消息队列
与直接RPC相比,使用消息代理有几个优点:
如果收件人不可用或过载,可以充当缓冲区,从而提高系统的可靠性。
它可以自动将消息重新发送到已经崩溃的进程,从而防止消息丢失。
避免发件人需要知道收件人的IP地址和端口号(这在虚拟机经常出入的云部署中特别有用)。
它允许将一条消息发送给多个收件人。
将发件人与收件人逻辑分离(发件人只是发布邮件,不关心使用者)。
4. 模式升级与演化
您可以添加新的字段到架构,只要您给每个字段一个新的标签号码。如果旧的代码(不知道你添加的新的标签号码)试图读取新代码写入的数据,包括一个新的字段,其标签号码不能识别,它可以简单地忽略该字段。数据类型注释允许解析器确定需要跳过的字节数。这保持了前向兼容性:旧代码可以读取由新代码编写的记录。
向后兼容性呢?只要每个字段都有一个唯一的标签号码,新的代码总是可以读取旧的数据,因为标签号码仍然具有相同的含义。唯一的细节是,如果你添加一个新的领域,你不能要求。如果您要添加一个字段并将其设置为必需,那么如果新代码读取旧代码写入的数据,则该检查将失败,因为旧代码不会写入您添加的新字段。因此,为了保持向后兼容性,在模式的初始部署之后添加的每个字段必须是可选的或具有默认值。
Protobuf的一个奇怪的细节是,它没有列表或数组数据类型,而是有一个字段的重复标记(这是第三个选项旁边必要和可?。?。如图4-4所示,重复字段的编码正如它所说的那样:同一个字段标记只是简单地出现在记录中。这具有很好的效果,可以将可?。ǖブ担┳侄胃奈馗矗ǘ嘀担┳侄?。读取旧数据的新代码会看到一个包含零个或一个元素的列表(取决于该字段是否存在)。读取新数据的旧代码只能看到列表的最后一个元素。
Thrift有一个专用的列表数据类型,它使用列表元素的数据类型进行参数化。这不允许Protocol Buffers所做的从单值到多值的相同演变,但是它具有支持嵌套列表的优点。