Merge pull request #88 from kemingy/fix-typo

fix typo for ch1, ch2, ch3, ch4
This commit is contained in:
Feng Ruohang 2020-10-23 22:58:00 +08:00 committed by GitHub
commit 4ebcb8b7a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 26 additions and 26 deletions

16
ch1.md
View File

@ -46,7 +46,7 @@
**图1-1 一个可能的组合使用多个组件的数据系统架构**
当你将多个工具组合在一起提供服务时,服务的接口或**应用程序编程接口API, Application Programming Interface**通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的、专用的数据系统。这个新的复合数据系统可能会提供特定的保证,例如:缓存在写入时会作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。
当你将多个工具组合在一起提供服务时,服务的接口或**应用程序编程接口API, Application Programming Interface** 通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的、专用的数据系统。这个新的复合数据系统可能会提供特定的保证,例如:缓存在写入时会作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。
设计数据系统或服务时可能会遇到很多棘手的问题例如当系统出问题时如何确保数据的正确性和完整性当部分系统退化降级时如何为客户提供始终如一的良好性能当负载增加时如何扩容应对什么样的API才是好的API
@ -121,7 +121,7 @@
导致这类软件故障的BUG通常会潜伏很长时间直到被异常情况触发为止。这种情况意味着软件对其环境做出了某种假设——虽然这种假设通常来说是正确的但由于某种原因最后不再成立了【11】。
虽然软件中的系统性故障没有速效药,但我们还是有很多小办法,例如:仔细考虑系统中的假设和交互;彻底的测试;进程隔离;允许进程崩溃并重启;测量、监控并分析生产环境中的系统行为。如果系统能够提供一些保证(例如在一个消息队列中,进入与发出的消息数量相等),那么系统就可以在运行时不断自检,并在出现**差异discrepancy**时报警【12】。
虽然软件中的系统性故障没有速效药,但我们还是有很多小办法,例如:仔细考虑系统中的假设和交互;彻底的测试;进程隔离;允许进程崩溃并重启;测量、监控并分析生产环境中的系统行为。如果系统能够提供一些保证(例如在一个消息队列中,进入与发出的消息数量相等),那么系统就可以在运行时不断自检,并在出现**差异discrepancy** 时报警【12】。
### 人为错误
@ -228,7 +228,7 @@
通常报表都会展示服务的平均响应时间。 (严格来讲“平均”一词并不指代任何特定公式,但实际上它通常被理解为**算术平均值arithmetic mean**:给定 n 个值,加起来除以 n )。然而如果你想知道“**典型typical**”响应时间,那么平均值并不是一个非常好的指标,因为它不能告诉你有多少用户实际上经历了这个延迟。
通常使用**百分位点percentiles**会更好。如果将响应时间列表按最快到最慢排序,那么**中位数median**就在正中间举个例子如果你的响应时间中位数是200毫秒这意味着一半请求的返回时间少于200毫秒另一半比这个要长。
通常使用**百分位点percentiles** 会更好。如果将响应时间列表按最快到最慢排序,那么**中位数median** 就在正中间举个例子如果你的响应时间中位数是200毫秒这意味着一半请求的返回时间少于200毫秒另一半比这个要长。
如果想知道典型场景下用户需要等待多长时间那么中位数是一个好的度量标准一半用户请求的响应时间少于响应时间的中位数另一半服务时间比中位数长。中位数也被称为第50百分位点有时缩写为p50。注意中位数是关于单个请求的如果用户同时发出几个请求在一个会话过程中或者由于一个页面中包含了多个资源则至少一个请求比中位数慢的概率远大于50
@ -274,7 +274,7 @@
举个例子用于处理每秒十万个请求每个大小为1 kB的系统与用于处理每分钟3个请求每个大小为2GB的系统看上去会非常不一样尽管两个系统有同样的数据吞吐量。
一个良好适配应用的可扩展架构,是围绕着**假设assumption**建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为扩展所做的工程投入就白费了,最糟糕的是适得其反。在早期创业公司或非正式产品中,通常支持产品快速迭代的能力,要比可扩展至未来的假想负载要重要的多。
一个良好适配应用的可扩展架构,是围绕着**假设assumption** 建立的:哪些操作是常见的?哪些操作是罕见的?这就是所谓负载参数。如果假设最终是错误的,那么为扩展所做的工程投入就白费了,最糟糕的是适得其反。在早期创业公司或非正式产品中,通常支持产品快速迭代的能力,要比可扩展至未来的假想负载要重要的多。
尽管这些架构是应用程序特定的,但可扩展的架构通常也是从通用的积木块搭建而成的,并以常见的模式排列。在本书中,我们将讨论这些构件和模式。
@ -284,7 +284,7 @@
众所周知,软件的大部分开销并不在最初的开发阶段,而是在持续的维护阶段,包括修复漏洞、保持系统正常运行、调查失效、适配新的平台、为新的场景进行修改、偿还技术债、添加新的功能等等。
不幸的是,许多从事软件系统行业的人不喜欢维护所谓的**遗留legacy**系统,——也许因为涉及修复其他人的错误、和过时的平台打交道,或者系统被迫使用于一些份外工作。每一个遗留系统都以自己的方式让人不爽,所以很难给出一个通用的建议来和它们打交道。
不幸的是,许多从事软件系统行业的人不喜欢维护所谓的**遗留legacy** 系统,——也许因为涉及修复其他人的错误、和过时的平台打交道,或者系统被迫使用于一些份外工作。每一个遗留系统都以自己的方式让人不爽,所以很难给出一个通用的建议来和它们打交道。
但是我们可以,也应该以这样一种方式来设计软件:在设计之初就尽量考虑尽可能减少维护期间的痛苦,从而避免自己的软件系统变成遗留系统。为此,我们将特别关注软件系统的三个设计原则:
@ -339,11 +339,11 @@
因为复杂度导致维护困难时,预算和时间安排通常会超支。在复杂的软件中进行变更,引入错误的风险也更大:当开发人员难以理解系统时,隐藏的假设、无意的后果和意外的交互就更容易被忽略。相反,降低复杂度能极大地提高软件的可维护性,因此简单性应该是构建系统的一个关键目标。
简化系统并不一定意味着减少功能;它也可以意味着消除**额外的accidental**的复杂度。 Moseley和Marks【32】把 **额外复杂度** 定义为:由具体实现中涌现,而非(从用户视角看,系统所解决的)问题本身固有的复杂度。
简化系统并不一定意味着减少功能;它也可以意味着消除**额外的accidental** 的复杂度。 Moseley和Marks【32】把 **额外复杂度** 定义为:由具体实现中涌现,而非(从用户视角看,系统所解决的)问题本身固有的复杂度。
用于消除**额外复杂度**的最好工具之一是**抽象abstraction**。一个好的抽象可以将大量实现细节隐藏在一个干净,简单易懂的外观下面。一个好的抽象也可以广泛用于各类不同应用。比起重复造很多轮子,重用抽象不仅更有效率,而且有助于开发高质量的软件。抽象组件的质量改进将使所有使用它的应用受益。
用于消除**额外复杂度** 的最好工具之一是**抽象abstraction**。一个好的抽象可以将大量实现细节隐藏在一个干净,简单易懂的外观下面。一个好的抽象也可以广泛用于各类不同应用。比起重复造很多轮子,重用抽象不仅更有效率,而且有助于开发高质量的软件。抽象组件的质量改进将使所有使用它的应用受益。
例如高级编程语言是一种抽象隐藏了机器码、CPU寄存器和系统调用。 SQL也是一种抽象隐藏了复杂的磁盘/内存数据结构、来自其他客户端的并发请求、崩溃后的不一致性。当然在用高级语言编程时,我们仍然用到了机器码;只不过没有**直接directly**使用罢了,正是因为编程语言的抽象,我们才不必去考虑这些实现细节。
例如高级编程语言是一种抽象隐藏了机器码、CPU寄存器和系统调用。 SQL也是一种抽象隐藏了复杂的磁盘/内存数据结构、来自其他客户端的并发请求、崩溃后的不一致性。当然在用高级语言编程时,我们仍然用到了机器码;只不过没有**直接directly** 使用罢了,正是因为编程语言的抽象,我们才不必去考虑这些实现细节。
抽象可以帮助我们将系统的复杂度控制在可管理的水平,不过,找到好的抽象是非常困难的。在分布式系统领域虽然有许多好的算法,但我们并不清楚它们应该打包成什么样抽象。

8
ch2.md
View File

@ -531,7 +531,7 @@ db.observations.aggregate([
可以将那些众所周知的算法运用到这些图上例如汽车导航系统搜索道路网络中两点之间的最短路径PageRank可以用在网络图上来确定网页的流行程度从而确定该网页在搜索结果中的排名。
在刚刚给出的例子中图中的所有顶点代表了相同类型的事物网页或交叉路口。不过图并不局限于这样的同类数据同样强大地是图提供了一种一致的方式用来在单个数据存储中存储完全不同类型的对象。例如Facebook维护一个包含许多不同类型的顶点和边的单个图顶点表示人地点事件签到和用户的评论;边缘表示哪些人是彼此的朋友哪个签到发生在何处谁评论了哪条消息谁参与了哪个事件等等【35】。
在刚刚给出的例子中图中的所有顶点代表了相同类型的事物网页或交叉路口。不过图并不局限于这样的同类数据同样强大地是图提供了一种一致的方式用来在单个数据存储中存储完全不同类型的对象。例如Facebook维护一个包含许多不同类型的顶点和边的单个图顶点表示人地点事件签到和用户的评论边缘表示哪些人是彼此的朋友哪个签到发生在何处谁评论了哪条消息谁参与了哪个事件等等【35】。
在本节中,我们将使用[图2-5](img/fig2-5.png)所示的示例。它可以从社交网络或系谱数据库中获得它显示了两个人来自爱达荷州的Lucy和来自法国Beaune的Alain。他们已婚住在伦敦。
@ -543,7 +543,7 @@ db.observations.aggregate([
### 属性图
在属性图模型中,每个**顶点vertex**包括:
在属性图模型中,每个**顶点vertex** 包括:
* 唯一的标识符
* 一组 **出边outgoing edges**
@ -558,7 +558,7 @@ db.observations.aggregate([
* 描述两个顶点之间关系类型的标签
* 一组属性(键值对)
可以将图存储看作由两个关系表组成:一个存储顶点,另一个存储边,如[例2-2]()所示该模式使用PostgreSQL json数据类型来存储每个顶点或每条边的属性)。头部和尾部顶点用来存储每条边;如果你想要一组顶点的输入或输出边,你可以分别通过`head_vertex`或`tail_vertex`来查询`edges`表。
可以将图存储看作由两个关系表组成:一个存储顶点,另一个存储边,如[例2-2]()所示该模式使用PostgreSQL JSON数据类型来存储每个顶点或每条边的属性)。头部和尾部顶点用来存储每条边;如果你想要一组顶点的输入或输出边,你可以分别通过`head_vertex`或`tail_vertex`来查询`edges`表。
**例2-2 使用关系模式来表示属性图**
@ -926,7 +926,7 @@ Cypher和SPARQL使用SELECT立即跳转但是Datalog一次只进行一小步
虽然我们已经覆盖了很多层面,但仍然有许多数据模型没有提到。举几个简单的例子:
* 使用基因组数据的研究人员通常需要执行**序列相似性搜索**这意味着需要一个很长的字符串代表一个DNA分子并在一个拥有类似但不完全相同的字符串的大型数据库中寻找匹配。这里所描述的数据库都不能处理这种用法这就是为什么研究人员编写了像GenBank这样的专门的基因组数据库软件的原因【48】。
* 粒子物理学家数十年来一直在进行大数据类型的大规模数据分析像大型强子对撞机LHC这样的项目现在可以工作在数百亿兆字节的范围内在这样的规模下需要定制解决方案来阻硬件成本的失控【49】。
* 粒子物理学家数十年来一直在进行大数据类型的大规模数据分析像大型强子对撞机LHC这样的项目现在可以工作在数百亿兆字节的范围内在这样的规模下需要定制解决方案来阻硬件成本的失控【49】。
* **全文搜索**可以说是一种经常与数据库一起使用的数据模型。信息检索是一个很大的专业课题,我们不会在本书中详细介绍,但是我们将在第三章和第三章中介绍搜索索引。
让我们暂时将其放在一边。在[下一章](ch3.md)中,我们将讨论在**实现**本章描述的数据模型时会遇到的一些权衡。

12
ch3.md
View File

@ -71,7 +71,7 @@ $ cat database
为了高效查找数据库中特定键的值,我们需要一个数据结构:**索引index**。本章将介绍一系列的索引结构,并它们进行对比。索引背后的大致思想是,保存一些额外的元数据作为路标,帮助你找到想要的数据。如果您想在同一份数据中以几种不同的方式进行搜索,那么你也许需要不同的索引,建在数据的不同部分上。
索引是从主数据衍生的**附加additional**结构。许多数据库允许添加与删除索引,这不会影响数据的内容,它只影响查询的性能。维护额外的结构会产生开销,特别是在写入时。写入性能很难超过简单地追加写入文件,因为追加写入是最简单的写入操作。任何类型的索引通常都会减慢写入速度,因为每次写入数据时都需要更新索引。
索引是从主数据衍生的**附加additional** 结构。许多数据库允许添加与删除索引,这不会影响数据的内容,它只影响查询的性能。维护额外的结构会产生开销,特别是在写入时。写入性能很难超过简单地追加写入文件,因为追加写入是最简单的写入操作。任何类型的索引通常都会减慢写入速度,因为每次写入数据时都需要更新索引。
这是存储系统中一个重要的权衡精心选择的索引加快了读查询的速度但是每个索引都会拖慢写入速度。因为这个原因数据库默认并不会索引所有的内容而需要你程序员或DBA通过对应用查询模式的了解来手动选择索引。你可以选择能为应用带来最大收益同时又不会引入超出必要开销的索引。
@ -105,7 +105,7 @@ $ cat database
**图3-3 同时执行压缩和分段合并**
每个段现在都有自己的内存散列表,将键映射到文件偏移量。为了找到一个键的值,我们首先检查最近段的哈希映射;如果键不存在,我们检查第二个最近的段,依此类推。合并过程保持细分的数量,所以查找不需要检查许多哈希映射。
每个段现在都有自己的内存散列表,将键映射到文件偏移量。为了找到一个键的值,我们首先检查最近段的哈希映射如果键不存在,我们检查第二个最近的段,依此类推。合并过程保持细分的数量,所以查找不需要检查许多哈希映射。
大量的细节进入实践这个简单的想法工作。简而言之,一些真正实施中重要的问题是:
***文件格式***
@ -201,7 +201,7 @@ Lucene是Elasticsearch和Solr使用的一种全文搜索的索引引擎它使
#### 性能优化
与往常一样大量的细节使得存储引擎在实践中表现良好。例如当查找数据库中不存在的键时LSM树算法可能会很慢您必须检查内存表然后将这些段一直回到最老的可能必须从磁盘读取每一个然后才能确定键不存在。为了优化这种访问存储引擎通常使用额外的Bloom过滤器【15】。 (布隆过滤器是用于近似集合内容的内存高效数据结构,它可以告诉您数据库中是否出现键,从而为不存在的键节省许多不必要的磁盘读取操作。
与往常一样大量的细节使得存储引擎在实践中表现良好。例如当查找数据库中不存在的键时LSM树算法可能会很慢您必须检查内存表然后将这些段一直回到最老的可能必须从磁盘读取每一个然后才能确定键不存在。为了优化这种访问存储引擎通常使用额外的Bloom过滤器【15】。 (布隆过滤器是用于近似集合内容的内存高效数据结构,它可以告诉您数据库中是否出现键,从而为不存在的键节省许多不必要的磁盘读取操作。)
还有不同的策略来确定SSTables如何被压缩和合并的顺序和时间。最常见的选择是大小分层压实。 LevelDB和RocksDB使用平坦压缩LevelDB因此得名HBase使用大小分层Cassandra同时支持【16】。在规模级别的调整中更新和更小的SSTables先后被合并到更老的和更大的SSTable中。在水平压实中关键范围被拆分成更小的SSTables而较旧的数据被移动到单独的“水平”这使得压缩能够更加递增地进行并且使用更少的磁盘空间。
@ -243,7 +243,7 @@ Lucene是Elasticsearch和Solr使用的一种全文搜索的索引引擎它使
#### 让B树更可靠
B树的基本底层写操作是用新数据覆盖磁盘上的页面。假定覆盖不改变页面的位置;当页面被覆盖时对该页面的所有引用保持完整。这与日志结构索引如LSM树形成鲜明对比后者只附加到文件并最终删除过时的文件但从不修改文件。
B树的基本底层写操作是用新数据覆盖磁盘上的页面。假定覆盖不改变页面的位置当页面被覆盖时对该页面的所有引用保持完整。这与日志结构索引如LSM树形成鲜明对比后者只附加到文件并最终删除过时的文件但从不修改文件。
您可以考虑将硬盘上的页面覆盖为实际的硬件操作。在磁性硬盘驱动器上这意味着将磁头移动到正确的位置等待旋转盘上的正确位置出现然后用新的数据覆盖适当的扇区。在固态硬盘上由于SSD必须一次擦除和重写相当大的存储芯片块所以会发生更复杂的事情【19】。
@ -259,7 +259,7 @@ B树的基本底层写操作是用新数据覆盖磁盘上的页面。假定覆
* 一些数据库如LMDB使用写时复制方案【21】而不是覆盖页面并维护WAL进行崩溃恢复。修改的页面被写入到不同的位置并且树中的父页面的新版本被创建指向新的位置。这种方法对于并发控制也很有用我们将在“[快照隔离和可重复读](ch7.md#快照隔离和可重复读)”中看到。
* 我们可以通过不存储整个键来节省页面空间,但可以缩小它的大小。特别是在树内部的页面上,键只需要提供足够的信息来充当键范围之间的边界。在页面中包含更多的键允许树具有更高的分支因子,因此更少的层次
* 通常,页面可以放置在磁盘上的任何位置;没有什么要求附近的键范围页面附近的磁盘上。如果查询需要按照排序顺序扫描大部分关键字范围那么每个页面的布局可能会非常不方便因为每个读取的页面都可能需要磁盘查找。因此许多B树实现尝试布局树使得叶子页面按顺序出现在磁盘上。但是随着树的增长维持这个顺序是很困难的。相比之下由于LSM树在合并过程中一次又一次地重写存储的大部分所以它们更容易使顺序键在磁盘上彼此靠近。
* 通常,页面可以放置在磁盘上的任何位置;没有什么要求附近的键放在页面附近的磁盘上。如果查询需要按照排序顺序扫描大部分关键字范围那么每个页面的布局可能会非常不方便因为每个读取的页面都可能需要磁盘查找。因此许多B树实现尝试布局树使得叶子页面按顺序出现在磁盘上。但是随着树的增长维持这个顺序是很困难的。相比之下由于LSM树在合并过程中一次又一次地重写存储的大部分所以它们更容易使顺序键在磁盘上彼此靠近。
* 额外的指针已添加到树中。例如,每个叶子页面可以在左边和右边具有对其兄弟页面的引用,这允许不跳回父页面就能顺序扫描。
* B树的变体如分形树【22】借用一些日志结构的思想来减少磁盘寻道而且它们与分形无关
@ -267,7 +267,7 @@ B树的基本底层写操作是用新数据覆盖磁盘上的页面。假定覆
尽管B树实现通常比LSM树实现更成熟但LSM树由于其性能特点也非常有趣。根据经验通常LSM树的写入速度更快而B树的读取速度更快【23】。 LSM树上的读取通常比较慢因为它们必须在压缩的不同阶段检查几个不同的数据结构和SSTables。
然而,基准通常对工作量的细节不确定敏感。 您需要测试具有特定工作负载的系统,以便进行有效的比较。 在本节中,我们将简要讨论一些在衡量存储引擎性能时值得考虑的事情。
然而,基准通常对工作量的细节不确定敏感。 您需要测试具有特定工作负载的系统,以便进行有效的比较。 在本节中,我们将简要讨论一些在衡量存储引擎性能时值得考虑的事情。
#### LSM树的优点

16
ch4.md
View File

@ -11,13 +11,13 @@
[TOC]
应用程序不可避免地随时间而变化。新产品的推出,对需求的深入理解,或者商业环境的变化,总会伴随着**功能feature**的增增改改。[第一章](ch1.md)介绍了[**可演化性(evolvability)**](ch1.md#可演化性:拥抱变化)的概念:应该尽力构建能灵活适应变化的系统(参阅“[可演化性:拥抱变化]()”)。
应用程序不可避免地随时间而变化。新产品的推出,对需求的深入理解,或者商业环境的变化,总会伴随着**功能feature** 的增增改改。[第一章](ch1.md)介绍了[**可演化性(evolvability)**](ch1.md#可演化性:拥抱变化)的概念:应该尽力构建能灵活适应变化的系统(参阅“[可演化性:拥抱变化]()”)。
在大多数情况下,修改应用程序的功能也意味着需要更改其存储的数据:可能需要使用新的字段或记录类型,或者以新方式展示现有数据。
我们在[第二章](ch2.md)讨论的数据模型有不同的方法来应对这种变化。关系数据库通常假定数据库中的所有数据都遵循一个模式:尽管可以更改该模式(通过模式迁移,即`ALTER`语句),但是在任何时间点都有且仅有一个正确的模式。相比之下,**读时模式schema-on-read**(或 **无模式schemaless**)数据库不会强制一个模式,因此数据库可以包含在不同时间写入的新老数据格式的混合(参阅 “文档模型中的模式灵活性” )。
当数据**格式format**或**模式schema**发生变化时,通常需要对应用程序代码进行相应的更改(例如,为记录添加新字段,然后修改程序开始读写该字段)。但在大型应用程序中,代码变更通常不会立即完成:
当数据**格式format** 或**模式schema** 发生变化时,通常需要对应用程序代码进行相应的更改(例如,为记录添加新字段,然后修改程序开始读写该字段)。但在大型应用程序中,代码变更通常不会立即完成:
* 对于 **服务端server-side** 应用程序,可能需要执行 **滚动升级 rolling upgrade** (也称为 **阶段发布staged rollout** ),一次将新版本部署到少数几个节点,检查新版本是否运行正常,然后逐渐部完所有的节点。这样无需中断服务即可部署新版本,为频繁发布提供了可行性,从而带来更好的可演化性。
* 对于 **客户端client-side** 应用程序,升不升级就要看用户的心情了。用户可能相当长一段时间里都不会去升级软件。
@ -191,7 +191,7 @@ Thrift有一个专用的列表数据类型它使用列表元素的数据类
Apache Avro 【20】是另一种二进制编码格式与Protocol Buffers和Thrift有趣的不同。 它是作为Hadoop的一个子项目在2009年开始的因为Thrift不适合Hadoop的用例【21】。
Avro也使用模式来指定正在编码的数据的结构。 它有两种模式语言一种Avro IDL用于人工编辑一种基于JSON更易于机器读取。
Avro也使用模式来指定正在编码的数据的结构。 它有两种模式语言一种Avro IDL用于人工编辑一种基于JSON更易于机器读取。
我们用Avro IDL编写的示例模式可能如下所示
@ -225,7 +225,7 @@ record Person {
**图4-5 使用Avro编码的记录**
为了解析二进制数据,您按照它们出现在架构中的顺序遍历这些字段,并使用架构来告诉您每个字段的数据类型。这意味着如果读取数据的代码使用与写入数据的代码完全相同的模式,则只能正确解码二进制数据。阅读器和作者之间的模式不匹配意味着错误地解码数据。
为了解析二进制数据,您按照它们出现在架构中的顺序遍历这些字段,并使用架构来告诉您每个字段的数据类型。这意味着如果读取数据的代码使用与写入数据的代码完全相同的模式,则只能正确解码二进制数据。读者和作者之间的模式不匹配意味着错误地解码数据。
那么Avro如何支持模式演变呢
@ -300,7 +300,7 @@ Avro为静态类型编程语言提供了可选的代码生成功能但是它
正如我们所看到的Protocol BuffersThrift和Avro都使用模式来描述二进制编码格式。他们的模式语言比XML模式或者JSON模式简单得多它支持更详细的验证规则例如“该字段的字符串值必须与该正则表达式匹配”或“该字段的整数值必须在0和100之间“。由于Protocol BuffersThrift和Avro实现起来更简单使用起来也更简单所以它们已经发展到支持相当广泛的编程语言。
这些编码所基于的想法绝不是新的。例如它们与ASN.1有很多相似之处它是1984年首次被标准化的模式定义语言【27】。它被用来定义各种网络协议其二进制编码DER仍然被用于编码SSL证书X.509例如【28】。 ASN.1支持使用标签号码的模式演进类似于Protocol Buf-fers和Thrift 【29】。然而这也是非常复杂和严重的文件记录所以ASN.1可能不是新应用程序的好选择。
这些编码所基于的想法绝不是新的。例如它们与ASN.1有很多相似之处它是1984年首次被标准化的模式定义语言【27】。它被用来定义各种网络协议其二进制编码DER仍然被用于编码SSL证书X.509例如【28】。 ASN.1支持使用标签号码的模式演进类似于Protocol Buffers和Thrift 【29】。然而这也是非常复杂和严重的文件记录所以ASN.1可能不是新应用程序的好选择。
许多数据系统也为其数据实现某种专有的二进制编码。例如大多数关系数据库都有一个网络协议您可以通过该协议向数据库发送查询并获取响应。这些协议通常特定于特定的数据库并且数据库供应商提供将来自数据库的网络协议的响应解码为内存数据结构的驱动程序例如使用ODBC或JDBC API
@ -382,7 +382,7 @@ Web浏览器不是唯一的客户端类型。例如在移动设备或桌面
此外服务器本身可以是另一个服务的客户端例如典型的Web应用服务器充当数据库的客户端。这种方法通常用于将大型应用程序按照功能区域分解为较小的服务这样当一个服务需要来自另一个服务的某些功能或数据时就会向另一个服务发出请求。这种构建应用程序的方式传统上被称为**面向服务的体系结构service-oriented architectureSOA**,最近被改进和更名为**微服务架构**【31,32】。
在某些方面,服务类似于数据库:它们通常允许客户端提交和查询数据。但是,虽然数据库允许使用我们在第2章 中讨论的查询语言进行任意查询但是服务公开了一个特定于应用程序的API它只允许由服务的业务逻辑应用程序代码预定的输入和输出【33】。这种限制提供了一定程度的封装服务可以对客户可以做什么和不可以做什么施加细粒度的限制。
在某些方面,服务类似于数据库:它们通常允许客户端提交和查询数据。但是,虽然数据库允许使用我们在[第2章](./ch2.md)中讨论的查询语言进行任意查询但是服务公开了一个特定于应用程序的API它只允许由服务的业务逻辑应用程序代码预定的输入和输出【33】。这种限制提供了一定程度的封装服务可以对客户可以做什么和不可以做什么施加细粒度的限制。
面向服务/微服务架构的一个关键设计目标是通过使服务独立部署和演化来使应用程序更易于更改和维护。例如每个服务应该由一个团队拥有并且该团队应该能够经常发布新版本的服务而不必与其他团队协调。换句话说我们应该期望服务器和客户端的旧版本和新版本同时运行因此服务器和客户端使用的数据编码必须在不同版本的服务API之间兼容——正是我们所做的本章一直在谈论。
@ -394,7 +394,7 @@ Web浏览器不是唯一的客户端类型。例如在移动设备或桌面
2. 一种服务向同一组织拥有的另一项服务提出请求,这些服务通常位于同一数据中心内,作为面向服务/微型架构的一部分。 (支持这种用例的软件有时被称为 **中间件middleware**
3. 一种服务通过互联网向不同组织所拥有的服务提出请求。这用于不同组织后端系统之间的数据交换。此类别包括由在线服务如信用卡处理系统提供的公共API或用于共享访问用户数据的OAuth。
有两种流行的Web服务方法REST和SOAP。他们在哲学方面几乎是截然相反的往往是各自支持者之间的激烈辩论即使在每个阵营内也有很多争论。 例如,**HATEOAS超媒体作为应用程序状态的引擎**经常引发讨论【35】。
有两种流行的Web服务方法REST和SOAP。他们在哲学方面几乎是截然相反的往往是各自支持者之间的激烈辩论即使在每个阵营内也有很多争论。 例如,**HATEOAS超媒体作为应用程序状态的引擎** 经常引发讨论【35】。
REST不是一个协议而是一个基于HTTP原则的设计哲学【34,35】。它强调简单的数据格式使用URL来标识资源并使用HTTP功能进行缓存控制身份验证和内容类型协商。与SOAP相比REST已经越来越受欢迎至少在跨组织服务集成的背景下【36】并经常与微服务相关[31]。根据REST原则设计的API称为RESTful。
@ -410,7 +410,7 @@ REST风格的API倾向于更简单的方法通常涉及较少的代码生成
#### 远程过程调用RPC的问题
Web服务仅仅是通过网络进行API请求的一系列技术的最新版本其中许多技术受到了大量的炒作但是存在严重的问题。 Enterprise JavaBeansEJB和Java的**远程方法调用RMI**仅限于Java。**分布式组件对象模型DCOM**仅限于Microsoft平台。**公共对象请求代理体系结构CORBA**过于复杂不提供前向或后向兼容性【41】。
Web服务仅仅是通过网络进行API请求的一系列技术的最新版本其中许多技术受到了大量的炒作但是存在严重的问题。 Enterprise JavaBeansEJB和Java的**远程方法调用RMI** 仅限于Java。**分布式组件对象模型DCOM** 仅限于Microsoft平台。**公共对象请求代理体系结构CORBA** 过于复杂不提供前向或后向兼容性【41】。
所有这些都是基于 **远程过程调用RPC** 的思想该过程调用自20世纪70年代以来一直存在【42】。 RPC模型试图向远程网络服务发出请求看起来与在同一进程中调用编程语言中的函数或方法相同这种抽象称为位置透明。尽管RPC起初看起来很方便但这种方法根本上是有缺陷的【43,44】。网络请求与本地函数调用非常不同