diff --git a/ch1.md b/ch1.md index a69124f..71c2248 100644 --- a/ch1.md +++ b/ch1.md @@ -120,7 +120,7 @@ ### 人为错误 -设计并构建了软件系统的工程师是人类,维持系统运行的运维也是人类。即使他们怀有最大的善意,人类也是不可靠的。举个例子,一项关于大型互联网服务的研究发现,运维配置错误是导致服务中断的首要原因,而硬件故障(服务器或网络)仅导致了10-25%的服务中断【13】。 +设计并构建了软件系统的工程师是人类,维持系统运行的运维也是人类。即使他们怀有最大的善意,人类也是不可靠的。举个例子,一项关于大型互联网服务的研究发现,运维配置错误是导致服务中断的首要原因,而硬件故障(服务器或网络)仅导致了10-25%的服务中断【13】。 尽管人类不可靠,但怎么做才能让系统变得可靠?最好的系统会组合使用以下几种办法: @@ -222,11 +222,11 @@ 通常使用**百分位点(percentiles)** 会更好。如果将响应时间列表按最快到最慢排序,那么**中位数(median)** 就在正中间:举个例子,如果你的响应时间中位数是200毫秒,这意味着一半请求的返回时间少于200毫秒,另一半比这个要长。 -如果想知道典型场景下用户需要等待多长时间,那么中位数是一个好的度量标准:一半用户请求的响应时间少于响应时间的中位数,另一半服务时间比中位数长。中位数也被称为第50百分位点,有时缩写为p50。注意中位数是关于单个请求的;如果用户同时发出几个请求(在一个会话过程中,或者由于一个页面中包含了多个资源),则至少一个请求比中位数慢的概率远大于50%。 +如果想知道典型场景下用户需要等待多长时间,那么中位数是一个好的度量标准:一半用户请求的响应时间少于响应时间的中位数,另一半服务时间比中位数长。中位数也被称为第50百分位点,有时缩写为p50。注意中位数是关于单个请求的;如果用户同时发出几个请求(在一个会话过程中,或者由于一个页面中包含了多个资源),则至少一个请求比中位数慢的概率远大于50%。 -为了弄清异常值有多糟糕,可以看看更高的百分位点,例如第95、99和99.9百分位点(缩写为p95,p99和p999)。它们意味着95%,99%或99.9%的请求响应时间要比该阈值快,例如:如果第95百分位点响应时间是1.5秒,则意味着100个请求中的95个响应时间快于1.5秒,而100个请求中的5个响应时间超过1.5秒。如[图1-4](img/fig1-4.png)所示。 +为了弄清异常值有多糟糕,可以看看更高的百分位点,例如第95、99和99.9百分位点(缩写为p95,p99和p999)。它们意味着95%、99%或99.9%的请求响应时间要比该阈值快,例如:如果第95百分位点响应时间是1.5秒,则意味着100个请求中的95个响应时间快于1.5秒,而100个请求中的5个响应时间超过1.5秒。如[图1-4](img/fig1-4.png)所示。 -响应时间的高百分位点(也称为**尾部延迟**,即**tail latencies**)非常重要,因为它们直接影响用户的服务体验。例如亚马逊在描述内部服务的响应时间要求时以99.9百分位点为准,即使它只影响一千个请求中的一个。这是因为请求响应最慢的客户往往也是数据最多的客户,也可以说是最有价值的客户 —— 因为他们掏钱了【19】。保证网站响应迅速对于保持客户的满意度非常重要,亚马逊观察到:响应时间增加100毫秒,销售量就减少1%【20】;而另一些报告说:慢 1 秒钟会让客户满意度指标减少16%【21,22】。 +响应时间的高百分位点(也称为**尾部延迟**,即**tail latencies**)非常重要,因为它们直接影响用户的服务体验。例如亚马逊在描述内部服务的响应时间要求时以99.9百分位点为准,即使它只影响一千个请求中的一个。这是因为请求响应最慢的客户往往也是数据最多的客户,也可以说是最有价值的客户 —— 因为他们掏钱了【19】。保证网站响应迅速对于保持客户的满意度非常重要,亚马逊观察到:响应时间增加100毫秒,销售量就减少1%【20】;而另一些报告说:慢 1 秒钟会让客户满意度指标减少16%【21,22】。 另一方面,优化第99.99百分位点(一万个请求中最慢的一个)被认为太昂贵了,不能为亚马逊的目标带来足够好处。减小高百分位点处的响应时间相当困难,因为它很容易受到随机事件的影响,这超出了控制范围,而且效益也很小。 @@ -242,7 +242,7 @@ > > 如果你想将响应时间百分点添加到你的服务的监视仪表板,则需要持续有效地计算它们。例如,你可能希望在最近10分钟内保持请求响应时间的滚动窗口。每一分钟,你都会计算出该窗口中的中值和各种百分数,并将这些度量值绘制在图上。 > -> 简单的实现是在时间窗口内保存所有请求的响应时间列表,并且每分钟对列表进行排序。如果对你来说效率太低,那么有一些算法能够以最小的CPU和内存成本(如前向衰减【25】,t-digest【26】或HdrHistogram 【27】)来计算百分位数的近似值。请注意,平均百分比(例如,减少时间分辨率或合并来自多台机器的数据)在数学上没有意义 - 聚合响应时间数据的正确方法是添加直方图【28】。 +> 简单的实现是在时间窗口内保存所有请求的响应时间列表,并且每分钟对列表进行排序。如果对你来说效率太低,那么有一些算法能够以最小的CPU和内存成本(如前向衰减【25】、t-digest【26】或HdrHistogram 【27】)来计算百分位数的近似值。请注意,平均百分比(例如,减少时间分辨率或合并来自多台机器的数据)在数学上没有意义 - 聚合响应时间数据的正确方法是添加直方图【28】。 ![](img/fig1-5.png) @@ -289,7 +289,7 @@ * 可演化性(evolvability) - 使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配。也称为**可伸缩性(extensibility)**,**可修改性(modifiability)** 或**可塑性(plasticity)**。 + 使工程师在未来能轻松地对系统进行更改,当需求变化时为新应用场景做适配。也称为**可伸缩性(extensibility)**、**可修改性(modifiability)** 或**可塑性(plasticity)**。 和之前提到的可靠性、可伸缩性一样,实现这些目标也没有简单的解决方案。不过我们会试着想象具有可操作性,简单性和可演化性的系统会是什么样子。 diff --git a/ch10.md b/ch10.md index aed4dae..846428a 100644 --- a/ch10.md +++ b/ch10.md @@ -468,7 +468,7 @@ MapReduce作业的输出处理遵循同样的原理。通过将输入视为不 #### 存储多样性 -数据库要求你根据特定的模型(例如关系或文档)来构造数据,而分布式文件系统中的文件只是字节序列,可以使用任何数据模型和编码来编写。它们可能是数据库记录的集合,但同样可以是文本,图像,视频,传感器读数,稀疏矩阵,特征向量,基因组序列或任何其他类型的数据。 +数据库要求你根据特定的模型(例如关系或文档)来构造数据,而分布式文件系统中的文件只是字节序列,可以使用任何数据模型和编码来编写。它们可能是数据库记录的集合,但同样可以是文本、图像、视频、传感器读数、稀疏矩阵、特征向量、基因组序列或任何其他类型的数据。 说白了,Hadoop开放了将数据不加区分地转储到HDFS的可能性,允许后续再研究如何进一步处理【53】。相比之下,在将数据导入数据库专有存储格式之前,MPP数据库通常需要对数据和查询模式进行仔细的前期建模。 @@ -510,7 +510,7 @@ MapReduce方式更适用于较大的作业:要处理如此之多的数据并 这种架构允许非生产(低优先级)计算资源被**过量使用(overcommitted)**,因为系统知道必要时它可以回收资源。与分离生产和非生产任务的系统相比,过量使用资源可以更好地利用机器并提高效率。但由于MapReduce作业以低优先级运行,它们随时都有被抢占的风险,因为优先级较高的进程可能需要其资源。在高优先级进程拿走所需资源后,批量作业能有效地“捡面包屑”,利用剩下的任何计算资源。 -在谷歌,运行一个小时的MapReduce任务有大约有5%的风险被终止,为了给更高优先级的进程挪地方。这一概率比硬件问题、机器重启或其他原因的概率高了一个数量级【59】。按照这种抢占率,如果一个作业有100个任务,每个任务运行10分钟,那么至少有一个任务在完成之前被终止的风险大于50%。 +在谷歌,运行一个小时的MapReduce任务有大约有5%的风险被终止,为了给更高优先级的进程挪地方。这一概率比硬件问题、机器重启或其他原因的概率高了一个数量级【59】。按照这种抢占率,如果一个作业有100个任务,每个任务运行10分钟,那么至少有一个任务在完成之前被终止的风险大于50%。 这就是MapReduce被设计为容忍频繁意外任务终止的原因:不是因为硬件很不可靠,而是因为任意终止进程的自由有利于提高计算集群中的资源利用率。 diff --git a/ch12.md b/ch12.md index 483cb7c..d9bd32f 100644 --- a/ch12.md +++ b/ch12.md @@ -32,9 +32,9 @@ 例如,为了处理任意关键词的搜索查询,将OLTP数据库与全文搜索索引集成在一起是很常见的的需求。尽管一些数据库(例如PostgreSQL)包含了全文索引功能,对于简单的应用完全够了【1】,但更复杂的搜索能力就需要专业的信息检索工具了。相反的是,搜索索引通常不适合作为持久的记录系统,因此许多应用需要组合这两种不同的工具以满足所有需求。 -我们在“[保持系统同步](ch11.md#保持系统同步)”中接触过集成数据系统的问题。随着数据不同表示形式的增加,集成问题变得越来越困难。除了数据库和搜索索引之外,也许你需要在分析系统(数据仓库,或批处理和流处理系统)中维护数据副本;维护从原始数据中衍生的缓存,或反规范化的数据版本;将数据灌入机器学习,分类,排名,或推荐系统中;或者基于数据变更发送通知。 +我们在“[保持系统同步](ch11.md#保持系统同步)”中接触过集成数据系统的问题。随着数据不同表示形式的增加,集成问题变得越来越困难。除了数据库和搜索索引之外,也许你需要在分析系统(数据仓库,或批处理和流处理系统)中维护数据副本;维护从原始数据中衍生的缓存,或反规范化的数据版本;将数据灌入机器学习、分类、排名或推荐系统中;或者基于数据变更发送通知。 -令人惊讶的是,我经常看到软件工程师做出这样的陈述:“根据我的经验,99%的人只需要X”或者 “......不需要X”(对于各种各样的X)。我认为这种陈述更像是发言人自己的经验,而不是技术实际上的实用性。可能对数据执行的操作,其范围极其宽广。某人认为鸡肋而毫无意义的功能可能是别人的核心需求。当你拉高视角,并考虑跨越整个组织范围的数据流时,数据集成的需求往往就会变得明显起来。 +令人惊讶的是,我经常看到软件工程师做出这样的陈述:“根据我的经验,99%的人只需要X”或者 “......不需要X”(对于各种各样的X)。我认为这种陈述更像是发言人自己的经验,而不是技术实际上的实用性。可能对数据执行的操作,其范围极其宽广。某人认为鸡肋而毫无意义的功能可能是别人的核心需求。当你拉高视角,并考虑跨越整个组织范围的数据流时,数据集成的需求往往就会变得明显起来。 #### 理解数据流 @@ -507,7 +507,7 @@ COMMIT; 这实在是一个遗憾,因为容错机制很难弄好。低层级的可靠机制(比如TCP中的那些)运行的相当好,因而剩下的高层级错误基本很少出现。如果能将这些剩下的高层级容错机制打包成抽象,而应用不需要再去操心,那该多好呀 —— 但恐怕我们还没有找到这一正确的抽象。 -长期以来,事务被认为是一个很好的抽象,我相信它们确实是很有用的。正如[第七章](ch7.md)导言中所讨论的,它们将各种可能的问题(并发写入,违背约束,崩溃,网络中断,磁盘故障)合并为两种可能结果:提交或中止。这是对编程模型而言是一种巨大的简化,但恐怕这还不够。 +长期以来,事务被认为是一个很好的抽象,我相信它们确实是很有用的。正如[第七章](ch7.md)导言中所讨论的,它们将各种可能的问题(并发写入、违背约束、崩溃、网络中断、磁盘故障)合并为两种可能结果:提交或中止。这是对编程模型而言是一种巨大的简化,但恐怕这还不够。 事务是代价高昂的,当涉及异构存储技术时尤为甚(请参阅“[实践中的分布式事务](ch9.md#实践中的分布式事务)”)。我们拒绝使用分布式事务是因为它开销太大,结果我们最后不得不在应用代码中重新实现容错机制。正如本书中大量的例子所示,对并发性与部分失败的推理是困难且违反直觉的,所以我怀疑大多数应用级别的机制都不能正确工作,最终结果是数据丢失或损坏。 @@ -742,7 +742,7 @@ ACID意义下的一致性(请参阅“[一致性](ch7.md#一致性)”)基 当我们开发预测性分析系统时,不是仅仅用软件通过一系列IF ELSE规则将人类的决策过程自动化,那些规则本身甚至都是从数据中推断出来的。但这些系统学到的模式是个黑盒:即使数据中存在一些相关性,我们可能也压根不知道为什么。如果算法的输入中存在系统性的偏见,则系统很有可能会在输出中学习并放大这种偏见【84】。 -在许多国家,反歧视法律禁止按种族,年龄,性别,性取向,残疾,或信仰等受保护的特征区分对待不同的人。其他的个人特征可能是允许用于分析的,但是如果这些特征与受保护的特征存在关联,又会发生什么?例如在种族隔离地区中,一个人的邮政编码,甚至是他们的IP地址,都是很强的种族指示物。这样的话,相信一种算法可以以某种方式将有偏见的数据作为输入,并产生公平和公正的输出【85】似乎是很荒谬的。然而这种观点似乎常常潜伏在数据驱动型决策的支持者中,这种态度被讽刺为“在处理偏差上,机器学习与洗钱类似”(machine learning is like money laundering for bias)【86】。 +在许多国家,反歧视法律禁止按种族、年龄、性别、性取向、残疾或信仰等受保护的特征区分对待不同的人。其他的个人特征可能是允许用于分析的,但是如果这些特征与受保护的特征存在关联,又会发生什么?例如在种族隔离地区中,一个人的邮政编码,甚至是他们的IP地址,都是很强的种族指示物。这样的话,相信一种算法可以以某种方式将有偏见的数据作为输入,并产生公平和公正的输出【85】似乎是很荒谬的。然而这种观点似乎常常潜伏在数据驱动型决策的支持者中,这种态度被讽刺为“在处理偏差上,机器学习与洗钱类似”(machine learning is like money laundering for bias)【86】。 预测性分析系统只是基于过去进行推断;如果过去是歧视性的,它们就会将这种歧视归纳为规律。如果我们希望未来比过去更好,那么就需要道德想象力,而这是只有人类才能提供的东西【87】。数据与模型应该是我们的工具,而不是我们的主人。 diff --git a/ch2.md b/ch2.md index cd3f63f..af6a8dd 100644 --- a/ch2.md +++ b/ch2.md @@ -15,10 +15,10 @@ 多数应用使用层层叠加的数据模型构建。对于每层数据模型的关键问题是:它是如何用低一层数据模型来**表示**的?例如: -1. 作为一名应用开发人员,你观察现实世界(里面有人员,组织,货物,行为,资金流向,传感器等),并采用对象或数据结构,以及操控那些数据结构的API来进行建模。那些结构通常是特定于应用程序的。 -2. 当要存储那些数据结构时,你可以利用通用数据模型来表示它们,如JSON或XML文档,关系数据库中的表、或图模型。 +1. 作为一名应用开发人员,你观察现实世界(里面有人员、组织、货物、行为、资金流向、传感器等),并采用对象或数据结构,以及操控那些数据结构的API来进行建模。那些结构通常是特定于应用程序的。 +2. 当要存储那些数据结构时,你可以利用通用数据模型来表示它们,如JSON或XML文档、关系数据库中的表或图模型。 3. 数据库软件的工程师选定如何以内存、磁盘或网络上的字节来表示JSON/XML/关系/图数据。这类表示形式使数据有可能以各种方式来查询,搜索,操纵和处理。 -4. 在更低的层次上,硬件工程师已经想出了使用电流,光脉冲,磁场或者其他东西来表示字节的方法。 +4. 在更低的层次上,硬件工程师已经想出了使用电流、光脉冲、磁场或者其他东西来表示字节的方法。 一个复杂的应用程序可能会有更多的中间层次,比如基于API的API,不过基本思想仍然是一样的:每个层都通过提供一个明确的数据模型来隐藏更低层次中的复杂性。这些抽象允许不同的人群有效地协作(例如数据库厂商的工程师和使用数据库的应用程序开发人员)。 @@ -156,7 +156,7 @@ JSON表示比[图2-1](img/fig2-1.png)中的多表模式具有更好的**局部 * 组织和学校作为实体 - 在前面的描述中,`organization`(用户工作的公司)和`school_name`(他们学习的地方)只是字符串。也许他们应该是对实体的引用呢?然后,每个组织,学校或大学都可以拥有自己的网页(标识,新闻提要等)。每个简历可以链接到它所提到的组织和学校,并且包括他们的图标和其他信息(请参阅[图2-3](img/fig2-3.png),来自LinkedIn的一个例子)。 + 在前面的描述中,`organization`(用户工作的公司)和`school_name`(他们学习的地方)只是字符串。也许他们应该是对实体的引用呢?然后,每个组织、学校或大学都可以拥有自己的网页(标识,新闻提要等)。每个简历可以链接到它所提到的组织和学校,并且包括他们的图标和其他信息(请参阅[图2-3](img/fig2-3.png),来自LinkedIn的一个例子)。 * 推荐 @@ -274,7 +274,7 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL #### 查询的数据局部性 -文档通常以单个连续字符串形式进行存储,编码为JSON,XML或其二进制变体(如MongoDB的BSON)。如果应用程序经常需要访问整个文档(例如,将其渲染至网页),那么存储局部性会带来性能优势。如果将数据分割到多个表中(如[图2-1](img/fig2-1.png)所示),则需要进行多次索引查找才能将其全部检索出来,这可能需要更多的磁盘查找并花费更多的时间。 +文档通常以单个连续字符串形式进行存储,编码为JSON、XML或其二进制变体(如MongoDB的BSON)。如果应用程序经常需要访问整个文档(例如,将其渲染至网页),那么存储局部性会带来性能优势。如果将数据分割到多个表中(如[图2-1](img/fig2-1.png)所示),则需要进行多次索引查找才能将其全部检索出来,这可能需要更多的磁盘查找并花费更多的时间。 局部性仅仅适用于同时需要文档绝大部分内容的情况。数据库通常需要加载整个文档,即使只访问其中的一小部分,这对于大型文档来说是很浪费的。更新文档时,通常需要整个重写。只有不改变文档大小的修改才可以容易地原地执行。因此,通常建议保持相对小的文档,并避免增加文档大小的写入【9】。这些性能限制大大减少了文档数据库的实用场景。 @@ -529,7 +529,7 @@ db.observations.aggregate([ 可以将那些众所周知的算法运用到这些图上:例如,汽车导航系统搜索道路网络中两点之间的最短路径,PageRank可以用在网络图上来确定网页的流行程度,从而确定该网页在搜索结果中的排名。 -在刚刚给出的例子中,图中的所有顶点代表了相同类型的事物(人,网页或交叉路口)。不过,图并不局限于这样的同类数据:同样强大地是,图提供了一种一致的方式,用来在单个数据存储中存储完全不同类型的对象。例如,Facebook维护一个包含许多不同类型的顶点和边的单个图:顶点表示人,地点,事件,签到和用户的评论;边缘表示哪些人是彼此的朋友,哪个签到发生在何处,谁评论了哪条消息,谁参与了哪个事件,等等【35】。 +在刚刚给出的例子中,图中的所有顶点代表了相同类型的事物(人、网页或交叉路口)。不过,图并不局限于这样的同类数据:同样强大地是,图提供了一种一致的方式,用来在单个数据存储中存储完全不同类型的对象。例如,Facebook维护一个包含许多不同类型的顶点和边的单个图:顶点表示人,地点,事件,签到和用户的评论;边缘表示哪些人是彼此的朋友,哪个签到发生在何处,谁评论了哪条消息,谁参与了哪个事件,等等【35】。 在本节中,我们将使用[图2-5](img/fig2-5.png)所示的示例。它可以从社交网络或系谱数据库中获得:它显示了两个人,来自爱达荷州的Lucy和来自法国Beaune的Alain。他们已婚,住在伦敦。 @@ -640,7 +640,7 @@ RETURN person.name 答案是肯定的,但有些困难。在关系数据库中,你通常会事先知道在查询中需要哪些连接。在图查询中,你可能需要在找到待查找的顶点之前,遍历可变数量的边。也就是说,连接的数量事先并不确定。 -在我们的例子中,这发生在Cypher查询中的`() -[:WITHIN*0..]-> ()`规则中。一个人的`LIVES_IN`边可以指向任何类型的位置:街道,城市,地区,地区,国家等。城市可以在一个地区,在一个州内的一个地区,在一个国家内的一个州等等。`LIVES_IN`边可以直接指向正在查找的位置,或者一个在位置层次结构中隔了数层的位置。 +在我们的例子中,这发生在Cypher查询中的`() -[:WITHIN*0..]-> ()`规则中。一个人的`LIVES_IN`边可以指向任何类型的位置:街道、城市、地区、地区、国家等。城市可以在一个地区,在一个州内的一个地区,在一个国家内的一个州等等。`LIVES_IN`边可以直接指向正在查找的位置,或者一个在位置层次结构中隔了数层的位置。 在Cypher中,用`WITHIN * 0`非常简洁地表述了上述事实:“沿着`WITHIN`边,零次或多次”。它很像正则表达式中的`*`运算符。 diff --git a/ch3.md b/ch3.md index 0b0ba84..6be6faf 100644 --- a/ch3.md +++ b/ch3.md @@ -430,7 +430,7 @@ Teradata、Vertica、SAP HANA和ParAccel等数据仓库供应商通常使用昂 **图3-9 用于数据仓库的星型模式的示例** -通常情况下,事实被视为单独的事件,因为这样可以在以后分析中获得最大的灵活性。但是,这意味着事实表可以变得非常大。像苹果,沃尔玛或eBay这样的大企业在其数据仓库中可能有几十PB的交易历史,其中大部分保存在事实表中【56】。 +通常情况下,事实被视为单独的事件,因为这样可以在以后分析中获得最大的灵活性。但是,这意味着事实表可以变得非常大。像苹果、沃尔玛或eBay这样的大企业在其数据仓库中可能有几十PB的交易历史,其中大部分保存在事实表中【56】。 事实表中的一些列是属性,例如产品销售的价格和从供应商那里购买的成本(可以用来计算利润余额)。事实表中的其他列是对其他表(称为维度表)的外键引用。由于事实表中的每一行都表示一个事件,因此这些维度代表事件发生的对象、内容、地点、时间、方式和原因。 diff --git a/ch4.md b/ch4.md index 33ba479..19b257f 100644 --- a/ch4.md +++ b/ch4.md @@ -290,9 +290,9 @@ Avro的关键思想是Writer模式和Reader模式不必是相同的 - 他们只 #### 代码生成和动态类型的语言 -Thrift和Protobuf依赖于代码生成:在定义了模式之后,可以使用你选择的编程语言生成实现此模式的代码。这在Java,C ++或C#等静态类型语言中很有用,因为它允许将高效的内存中结构用于解码的数据,并且在编写访问数据结构的程序时允许在IDE中进行类型检查和自动完成。 +Thrift和Protobuf依赖于代码生成:在定义了模式之后,可以使用你选择的编程语言生成实现此模式的代码。这在Java、C++或C#等静态类型语言中很有用,因为它允许将高效的内存中结构用于解码的数据,并且在编写访问数据结构的程序时允许在IDE中进行类型检查和自动完成。 -在动态类型编程语言(如JavaScript,Ruby或Python)中,生成代码没有太多意义,因为没有编译时类型检查器来满足。代码生成在这些语言中经常被忽视,因为它们避免了显式的编译步骤。而且,对于动态生成的模式(例如从数据库表生成的Avro模式),代码生成对获取数据是一个不必要的障碍。 +在动态类型编程语言(如JavaScript、Ruby或Python)中,生成代码没有太多意义,因为没有编译时类型检查器来满足。代码生成在这些语言中经常被忽视,因为它们避免了显式的编译步骤。而且,对于动态生成的模式(例如从数据库表生成的Avro模式),代码生成对获取数据是一个不必要的障碍。 Avro为静态类型编程语言提供了可选的代码生成功能,但是它也可以在不生成任何代码的情况下使用。如果你有一个对象容器文件(它嵌入了Writer模式),你可以简单地使用Avro库打开它,并以与查看JSON文件相同的方式查看数据。该文件是自描述的,因为它包含所有必要的元数据。 diff --git a/ch5.md b/ch5.md index 46750ce..fa0c4d6 100644 --- a/ch5.md +++ b/ch5.md @@ -128,7 +128,7 @@ #### 基于语句的复制 -在最简单的情况下,主库记录下它执行的每个写入请求(**语句**,即statement)并将该语句日志发送给其从库。对于关系数据库来说,这意味着每个`INSERT`,`UPDATE`或`DELETE`语句都被转发给每个从库,每个从库解析并执行该SQL语句,就像从客户端收到一样。 +在最简单的情况下,主库记录下它执行的每个写入请求(**语句**,即statement)并将该语句日志发送给其从库。对于关系数据库来说,这意味着每个`INSERT`、`UPDATE`或`DELETE`语句都被转发给每个从库,每个从库解析并执行该SQL语句,就像从客户端收到一样。 虽然听上去很合理,但有很多问题会搞砸这种复制方式: diff --git a/ch6.md b/ch6.md index 355dc8a..3a15e76 100644 --- a/ch6.md +++ b/ch6.md @@ -93,7 +93,7 @@ > > 正如我们将在“[分区再平衡](#分区再平衡)”中所看到的,这种特殊的方法对于数据库实际上并不是很好,所以在实际中很少使用(某些数据库的文档仍然会使用一致性哈希的说法,但是它往往是不准确的)。 因为有可能产生混淆,所以最好避免使用一致性哈希这个术语,而只是把它称为**散列分区(hash partitioning)**。 -不幸的是,通过使用键散列进行分区,我们失去了键范围分区的一个很好的属性:高效执行范围查询的能力。曾经相邻的键现在分散在所有分区中,所以它们之间的顺序就丢失了。在MongoDB中,如果你使用了基于散列的分区模式,则任何范围查询都必须发送到所有分区【4】。Riak 【9】,Couchbase 【10】或Voldemort不支持主键上的范围查询。 +不幸的是,通过使用键散列进行分区,我们失去了键范围分区的一个很好的属性:高效执行范围查询的能力。曾经相邻的键现在分散在所有分区中,所以它们之间的顺序就丢失了。在MongoDB中,如果你使用了基于散列的分区模式,则任何范围查询都必须发送到所有分区【4】。Riak【9】、Couchbase 【10】或Voldemort不支持主键上的范围查询。 Cassandra采取了折衷的策略【11, 12, 13】。 Cassandra中的表可以使用由多个列组成的复合主键来声明。键中只有第一列会作为散列的依据,而其他列则被用作Casssandra的SSTables中排序数据的连接索引。尽管查询无法在复合主键的第一列中按范围扫表,但如果第一列已经指定了固定值,则可以对该键的其他列执行有效的范围扫描。 @@ -190,7 +190,7 @@ Cassandra采取了折衷的策略【11, 12, 13】。 Cassandra中的表可以使 我们在前面说过([图6-3](img/fig6-3.png)),最好将可能的散列分成不同的范围,并将每个范围分配给一个分区(例如,如果$0≤hash(key) * 当电源突然断电时,特别是固态硬盘,有证据显示有时会违反应有的保证:甚至fsync也不能保证正常工作【12】。硬盘固件可能有错误,就像任何其他类型的软件一样【13,14】。 > * 存储引擎和文件系统之间的微妙交互可能会导致难以追踪的错误,并可能导致磁盘上的文件在崩溃后被损坏【15,16】。 > * 磁盘上的数据可能会在没有检测到的情况下逐渐损坏【17】。如果数据已损坏一段时间,副本和最近的备份也可能损坏。这种情况下,需要尝试从历史备份中恢复数据。 -> * 一项关于固态硬盘的研究发现,在运行的前四年中,30%到80%的硬盘会产生至少一个坏块【18】。相比固态硬盘,磁盘的坏道率较低,但完全失效的概率更高。 +> * 一项关于固态硬盘的研究发现,在运行的前四年中,30%到80%的硬盘会产生至少一个坏块【18】。相比固态硬盘,磁盘的坏道率较低,但完全失效的概率更高。 > * 如果SSD断电,可能会在几周内开始丢失数据,具体取决于温度【19】。 > > 在实践中,没有一种技术可以提供绝对保证。只有各种降低风险的技术,包括写入磁盘,复制到远程机器和备份——它们可以且应该一起使用。与往常一样,最好抱着怀疑的态度接受任何理论上的“保证”。 diff --git a/ch8.md b/ch8.md index 355a3bb..bc5e866 100644 --- a/ch8.md +++ b/ch8.md @@ -82,7 +82,7 @@ > 你可能想知道这是否有意义——直观地看来,系统只能像其最不可靠的组件(最薄弱的环节)一样可靠。事实并非如此:事实上,从不太可靠的潜在基础构建更可靠的系统是计算机领域的一个古老思想【11】。例如: > > * 纠错码允许数字数据在通信信道上准确传输,偶尔会出现一些错误,例如由于无线网络上的无线电干扰【12】。 -> * **互联网协议(Internet Protocol, IP)** 不可靠:可能丢弃,延迟,重复或重排数据包。 传输控制协议(Transmission Control Protocol, TCP)在互联网协议(IP)之上提供了更可靠的传输层:它确保丢失的数据包被重新传输,消除重复,并且数据包被重新组装成它们被发送的顺序。 +> * **互联网协议(Internet Protocol, IP)** 不可靠:可能丢弃、延迟、重复或重排数据包。 传输控制协议(Transmission Control Protocol, TCP)在互联网协议(IP)之上提供了更可靠的传输层:它确保丢失的数据包被重新传输,消除重复,并且数据包被重新组装成它们被发送的顺序。 > > 虽然这个系统可以比它的底层部分更可靠,但它的可靠性总是有限的。例如,纠错码可以处理少量的单比特错误,但是如果你的信号被干扰所淹没,那么通过信道可以得到多少数据,是有根本性的限制的【13】。 TCP可以隐藏数据包的丢失,重复和重新排序,但是它不能神奇地消除网络中的延迟。 > @@ -278,7 +278,7 @@ 在具有多个CPU插槽的服务器上,每个CPU可能有一个单独的计时器,但不一定与其他CPU同步。操作系统会补偿所有的差异,并尝试向应用线程表现出单调钟的样子,即使这些线程被调度到不同的CPU上。当然,明智的做法是不要太把这种单调性保证当回事【40】。 -如果NTP协议检测到计算机的本地石英钟比NTP服务器要更快或更慢,则可以调整单调钟向前走的频率(这称为**偏移(skewing)** 时钟)。默认情况下,NTP允许时钟速率增加或减慢最高至0.05%,但NTP不能使单调时钟向前或向后跳转。单调时钟的分辨率通常相当好:在大多数系统中,它们能在几微秒或更短的时间内测量时间间隔。 +如果NTP协议检测到计算机的本地石英钟比NTP服务器要更快或更慢,则可以调整单调钟向前走的频率(这称为**偏移(skewing)** 时钟)。默认情况下,NTP允许时钟速率增加或减慢最高至0.05%,但NTP不能使单调时钟向前或向后跳转。单调时钟的分辨率通常相当好:在大多数系统中,它们能在几微秒或更短的时间内测量时间间隔。 在分布式系统中,使用单调钟测量**经过时间**(elapsed time,比如超时)通常很好,因为它不假定不同节点的时钟之间存在任何同步,并且对测量的轻微不准确性不敏感。 @@ -339,7 +339,7 @@ NTP同步是否能足够准确,以至于这种不正确的排序不会发生 你可能能够以微秒或甚至纳秒的精度读取机器的时钟。但即使可以得到如此细致的测量结果,这并不意味着这个值对于这样的精度实际上是准确的。实际上,大概率是不准确的——如前所述,即使你每分钟与本地网络上的NTP服务器进行同步,几毫秒的时间漂移也很容易在不精确的石英时钟上发生。使用公共互联网上的NTP服务器,最好的准确度可能达到几十毫秒,而且当网络拥塞时,误差可能会超过100毫秒【57】。 -因此,将时钟读数视为一个时间点是没有意义的——它更像是一段时间范围:例如,一个系统可能以95%的置信度认为当前时间处于本分钟内的第10.3秒和10.5秒之间,它可能没法比这更精确了【58】。如果我们只知道±100毫秒的时间,那么时间戳中的微秒数字部分基本上是没有意义的。 +因此,将时钟读数视为一个时间点是没有意义的——它更像是一段时间范围:例如,一个系统可能以95%的置信度认为当前时间处于本分钟内的第10.3秒和10.5秒之间,它可能没法比这更精确了【58】。如果我们只知道±100毫秒的时间,那么时间戳中的微秒数字部分基本上是没有意义的。 不确定性界限可以根据你的时间源来计算。如果你的GPS接收器或原子(铯)时钟直接连接到你的计算机上,预期的错误范围由制造商告知。如果从服务器获得时间,则不确定性取决于自上次与服务器同步以来的石英钟漂移的期望值,加上NTP服务器的不确定性,再加上到服务器的网络往返时间(只是获取粗略近似值,并假设服务器是可信的)。 diff --git a/glossary.md b/glossary.md index f906199..d823595 100644 --- a/glossary.md +++ b/glossary.md @@ -169,7 +169,7 @@ ### 百分位点(percentile) -通过计算有多少值高于或低于某个阈值来衡量值分布的方法。 例如,某个时间段的第95个百分位响应时间是时间t,则该时间段中,95%的请求完成时间小于t,5%的请求完成时间要比t长。 请参阅“[描述性能](ch1.md#描述性能)”。 +通过计算有多少值高于或低于某个阈值来衡量值分布的方法。 例如,某个时间段的第95个百分位响应时间是时间t,则该时间段中,95%的请求完成时间小于t,5%的请求完成时间要比t长。 请参阅“[描述性能](ch1.md#描述性能)”。 ### 主键(primary key) diff --git a/zh-tw/ch1.md b/zh-tw/ch1.md index c49d076..b0f3aca 100644 --- a/zh-tw/ch1.md +++ b/zh-tw/ch1.md @@ -120,7 +120,7 @@ ### 人為錯誤 -設計並構建了軟體系統的工程師是人類,維持系統執行的運維也是人類。即使他們懷有最大的善意,人類也是不可靠的。舉個例子,一項關於大型網際網路服務的研究發現,運維配置錯誤是導致服務中斷的首要原因,而硬體故障(伺服器或網路)僅導致了10-25%的服務中斷【13】。 +設計並構建了軟體系統的工程師是人類,維持系統執行的運維也是人類。即使他們懷有最大的善意,人類也是不可靠的。舉個例子,一項關於大型網際網路服務的研究發現,運維配置錯誤是導致服務中斷的首要原因,而硬體故障(伺服器或網路)僅導致了10-25%的服務中斷【13】。 儘管人類不可靠,但怎麼做才能讓系統變得可靠?最好的系統會組合使用以下幾種辦法: @@ -160,7 +160,7 @@ 使用者可以查閱他們關注的人釋出的推文(300k請求/秒)。 -處理每秒12,000次寫入(發推文的速率峰值)還是很簡單的。然而推特的伸縮性挑戰並不是主要來自推特量,而是來自**扇出(fan-out)**——每個使用者關注了很多人,也被很多人關注。 +處理每秒12,000次寫入(發推文的速率峰值)還是很簡單的。然而推特的伸縮性挑戰並不是主要來自推特量,而是來自**扇出(fan-out)**[^ii]——每個使用者關注了很多人,也被很多人關注。 [^ii]: 扇出:從電子工程學中借用的術語,它描述了輸入連線到另一個門輸出的邏輯閘數量。 輸出需要提供足夠的電流來驅動所有連線的輸入。 在事務處理系統中,我們使用它來描述為了服務一個傳入請求而需要執行其他服務的請求數量。 @@ -222,11 +222,11 @@ 通常使用**百分位點(percentiles)** 會更好。如果將響應時間列表按最快到最慢排序,那麼**中位數(median)** 就在正中間:舉個例子,如果你的響應時間中位數是200毫秒,這意味著一半請求的返回時間少於200毫秒,另一半比這個要長。 -如果想知道典型場景下使用者需要等待多長時間,那麼中位數是一個好的度量標準:一半使用者請求的響應時間少於響應時間的中位數,另一半服務時間比中位數長。中位數也被稱為第50百分位點,有時縮寫為p50。注意中位數是關於單個請求的;如果使用者同時發出幾個請求(在一個會話過程中,或者由於一個頁面中包含了多個資源),則至少一個請求比中位數慢的概率遠大於50%。 +如果想知道典型場景下使用者需要等待多長時間,那麼中位數是一個好的度量標準:一半使用者請求的響應時間少於響應時間的中位數,另一半服務時間比中位數長。中位數也被稱為第50百分位點,有時縮寫為p50。注意中位數是關於單個請求的;如果使用者同時發出幾個請求(在一個會話過程中,或者由於一個頁面中包含了多個資源),則至少一個請求比中位數慢的概率遠大於50%。 -為了弄清異常值有多糟糕,可以看看更高的百分位點,例如第95、99和99.9百分位點(縮寫為p95,p99和p999)。它們意味著95%,99%或99.9%的請求響應時間要比該閾值快,例如:如果第95百分位點響應時間是1.5秒,則意味著100個請求中的95個響應時間快於1.5秒,而100個請求中的5個響應時間超過1.5秒。如[圖1-4](../img/fig1-4.png)所示。 +為了弄清異常值有多糟糕,可以看看更高的百分位點,例如第95、99和99.9百分位點(縮寫為p95,p99和p999)。它們意味著95%、99%或99.9%的請求響應時間要比該閾值快,例如:如果第95百分位點響應時間是1.5秒,則意味著100個請求中的95個響應時間快於1.5秒,而100個請求中的5個響應時間超過1.5秒。如[圖1-4](../img/fig1-4.png)所示。 -響應時間的高百分位點(也稱為**尾部延遲**,即**tail latencies**)非常重要,因為它們直接影響使用者的服務體驗。例如亞馬遜在描述內部服務的響應時間要求時以99.9百分位點為準,即使它隻影響一千個請求中的一個。這是因為請求響應最慢的客戶往往也是資料最多的客戶,也可以說是最有價值的客戶 —— 因為他們掏錢了【19】。保證網站響應迅速對於保持客戶的滿意度非常重要,亞馬遜觀察到:響應時間增加100毫秒,銷售量就減少1%【20】;而另一些報告說:慢 1 秒鐘會讓客戶滿意度指標減少16%【21,22】。 +響應時間的高百分位點(也稱為**尾部延遲**,即**tail latencies**)非常重要,因為它們直接影響使用者的服務體驗。例如亞馬遜在描述內部服務的響應時間要求時以99.9百分位點為準,即使它隻影響一千個請求中的一個。這是因為請求響應最慢的客戶往往也是資料最多的客戶,也可以說是最有價值的客戶 —— 因為他們掏錢了【19】。保證網站響應迅速對於保持客戶的滿意度非常重要,亞馬遜觀察到:響應時間增加100毫秒,銷售量就減少1%【20】;而另一些報告說:慢 1 秒鐘會讓客戶滿意度指標減少16%【21,22】。 另一方面,最佳化第99.99百分位點(一萬個請求中最慢的一個)被認為太昂貴了,不能為亞馬遜的目標帶來足夠好處。減小高百分位點處的響應時間相當困難,因為它很容易受到隨機事件的影響,這超出了控制範圍,而且效益也很小。 @@ -242,7 +242,7 @@ > > 如果你想將響應時間百分點新增到你的服務的監視儀表板,則需要持續有效地計算它們。例如,你可能希望在最近10分鐘內保持請求響應時間的滾動視窗。每一分鐘,你都會計算出該視窗中的中值和各種百分數,並將這些度量值繪製在圖上。 > -> 簡單的實現是在時間視窗內儲存所有請求的響應時間列表,並且每分鐘對列表進行排序。如果對你來說效率太低,那麼有一些演算法能夠以最小的CPU和記憶體成本(如前向衰減【25】,t-digest【26】或HdrHistogram 【27】)來計算百分位數的近似值。請注意,平均百分比(例如,減少時間解析度或合併來自多臺機器的資料)在數學上沒有意義 - 聚合響應時間資料的正確方法是新增直方圖【28】。 +> 簡單的實現是在時間視窗內儲存所有請求的響應時間列表,並且每分鐘對列表進行排序。如果對你來說效率太低,那麼有一些演算法能夠以最小的CPU和記憶體成本(如前向衰減【25】、t-digest【26】或HdrHistogram 【27】)來計算百分位數的近似值。請注意,平均百分比(例如,減少時間解析度或合併來自多臺機器的資料)在數學上沒有意義 - 聚合響應時間資料的正確方法是新增直方圖【28】。 ![](../img/fig1-5.png) @@ -289,7 +289,7 @@ * 可演化性(evolvability) - 使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為**可伸縮性(extensibility)**,**可修改性(modifiability)** 或**可塑性(plasticity)**。 + 使工程師在未來能輕鬆地對系統進行更改,當需求變化時為新應用場景做適配。也稱為**可伸縮性(extensibility)**、**可修改性(modifiability)** 或**可塑性(plasticity)**。 和之前提到的可靠性、可伸縮性一樣,實現這些目標也沒有簡單的解決方案。不過我們會試著想象具有可操作性,簡單性和可演化性的系統會是什麼樣子。 @@ -354,7 +354,7 @@ 本章探討了一些關於資料密集型應用的基本思考方式。這些原則將指導我們閱讀本書的其餘部分,那裡將會深入技術細節。 -一個應用必須滿足各種需求才稱得上有用。有一些**功能需求**(functional requirements,即它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些**非功能性需求*(nonfunctional,即通用屬性,例如安全性、可靠性、合規性、可伸縮性、相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。 +一個應用必須滿足各種需求才稱得上有用。有一些**功能需求**(functional requirements,即它應該做什麼,比如允許以各種方式儲存,檢索,搜尋和處理資料)以及一些**非功能性需求**(nonfunctional,即通用屬性,例如安全性、可靠性、合規性、可伸縮性、相容性和可維護性)。在本章詳細討論了可靠性,可伸縮性和可維護性。 **可靠性(Reliability)** 意味著即使發生故障,系統也能正常工作。故障可能發生在硬體(通常是隨機的和不相關的)、軟體(通常是系統性的Bug,很難處理)和人類(不可避免地時不時出錯)。 **容錯技術** 可以對終端使用者隱藏某些型別的故障。 diff --git a/zh-tw/ch10.md b/zh-tw/ch10.md index 123030a..9274355 100644 --- a/zh-tw/ch10.md +++ b/zh-tw/ch10.md @@ -468,7 +468,7 @@ MapReduce作業的輸出處理遵循同樣的原理。透過將輸入視為不 #### 儲存多樣性 -資料庫要求你根據特定的模型(例如關係或文件)來構造資料,而分散式檔案系統中的檔案只是位元組序列,可以使用任何資料模型和編碼來編寫。它們可能是資料庫記錄的集合,但同樣可以是文字,影象,影片,感測器讀數,稀疏矩陣,特徵向量,基因組序列或任何其他型別的資料。 +資料庫要求你根據特定的模型(例如關係或文件)來構造資料,而分散式檔案系統中的檔案只是位元組序列,可以使用任何資料模型和編碼來編寫。它們可能是資料庫記錄的集合,但同樣可以是文字、影象、影片、感測器讀數、稀疏矩陣、特徵向量、基因組序列或任何其他型別的資料。 說白了,Hadoop開放了將資料不加區分地轉儲到HDFS的可能性,允許後續再研究如何進一步處理【53】。相比之下,在將資料匯入資料庫專有儲存格式之前,MPP資料庫通常需要對資料和查詢模式進行仔細的前期建模。 @@ -510,7 +510,7 @@ MapReduce方式更適用於較大的作業:要處理如此之多的資料並 這種架構允許非生產(低優先順序)計算資源被**過量使用(overcommitted)**,因為系統知道必要時它可以回收資源。與分離生產和非生產任務的系統相比,過量使用資源可以更好地利用機器並提高效率。但由於MapReduce作業以低優先順序執行,它們隨時都有被搶佔的風險,因為優先順序較高的程序可能需要其資源。在高優先順序程序拿走所需資源後,批次作業能有效地“撿麵包屑”,利用剩下的任何計算資源。 -在谷歌,執行一個小時的MapReduce任務有大約有5%的風險被終止,為了給更高優先順序的程序挪地方。這一概率比硬體問題、機器重啟或其他原因的概率高了一個數量級【59】。按照這種搶佔率,如果一個作業有100個任務,每個任務執行10分鐘,那麼至少有一個任務在完成之前被終止的風險大於50%。 +在谷歌,執行一個小時的MapReduce任務有大約有5%的風險被終止,為了給更高優先順序的程序挪地方。這一概率比硬體問題、機器重啟或其他原因的概率高了一個數量級【59】。按照這種搶佔率,如果一個作業有100個任務,每個任務執行10分鐘,那麼至少有一個任務在完成之前被終止的風險大於50%。 這就是MapReduce被設計為容忍頻繁意外任務終止的原因:不是因為硬體很不可靠,而是因為任意終止程序的自由有利於提高計算叢集中的資源利用率。 diff --git a/zh-tw/ch12.md b/zh-tw/ch12.md index f35e285..20c3c06 100644 --- a/zh-tw/ch12.md +++ b/zh-tw/ch12.md @@ -32,9 +32,9 @@ 例如,為了處理任意關鍵詞的搜尋查詢,將OLTP資料庫與全文搜尋索引整合在一起是很常見的的需求。儘管一些資料庫(例如PostgreSQL)包含了全文索引功能,對於簡單的應用完全夠了【1】,但更復雜的搜尋能力就需要專業的資訊檢索工具了。相反的是,搜尋索引通常不適合作為持久的記錄系統,因此許多應用需要組合這兩種不同的工具以滿足所有需求。 -我們在“[保持系統同步](ch11.md#保持系統同步)”中接觸過整合資料系統的問題。隨著資料不同表示形式的增加,整合問題變得越來越困難。除了資料庫和搜尋索引之外,也許你需要在分析系統(資料倉庫,或批處理和流處理系統)中維護資料副本;維護從原始資料中衍生的快取,或反規範化的資料版本;將資料灌入機器學習,分類,排名,或推薦系統中;或者基於資料變更傳送通知。 +我們在“[保持系統同步](ch11.md#保持系統同步)”中接觸過整合資料系統的問題。隨著資料不同表示形式的增加,整合問題變得越來越困難。除了資料庫和搜尋索引之外,也許你需要在分析系統(資料倉庫,或批處理和流處理系統)中維護資料副本;維護從原始資料中衍生的快取,或反規範化的資料版本;將資料灌入機器學習、分類、排名或推薦系統中;或者基於資料變更傳送通知。 -令人驚訝的是,我經常看到軟體工程師做出這樣的陳述:“根據我的經驗,99%的人只需要X”或者 “......不需要X”(對於各種各樣的X)。我認為這種陳述更像是發言人自己的經驗,而不是技術實際上的實用性。可能對資料執行的操作,其範圍極其寬廣。某人認為雞肋而毫無意義的功能可能是別人的核心需求。當你拉高視角,並考慮跨越整個組織範圍的資料流時,資料整合的需求往往就會變得明顯起來。 +令人驚訝的是,我經常看到軟體工程師做出這樣的陳述:“根據我的經驗,99%的人只需要X”或者 “......不需要X”(對於各種各樣的X)。我認為這種陳述更像是發言人自己的經驗,而不是技術實際上的實用性。可能對資料執行的操作,其範圍極其寬廣。某人認為雞肋而毫無意義的功能可能是別人的核心需求。當你拉高視角,並考慮跨越整個組織範圍的資料流時,資料整合的需求往往就會變得明顯起來。 #### 理解資料流 @@ -507,7 +507,7 @@ COMMIT; 這實在是一個遺憾,因為容錯機制很難弄好。低層級的可靠機制(比如TCP中的那些)執行的相當好,因而剩下的高層級錯誤基本很少出現。如果能將這些剩下的高層級容錯機制打包成抽象,而應用不需要再去操心,那該多好呀 —— 但恐怕我們還沒有找到這一正確的抽象。 -長期以來,事務被認為是一個很好的抽象,我相信它們確實是很有用的。正如[第七章](ch7.md)導言中所討論的,它們將各種可能的問題(併發寫入,違背約束,崩潰,網路中斷,磁碟故障)合併為兩種可能結果:提交或中止。這是對程式設計模型而言是一種巨大的簡化,但恐怕這還不夠。 +長期以來,事務被認為是一個很好的抽象,我相信它們確實是很有用的。正如[第七章](ch7.md)導言中所討論的,它們將各種可能的問題(併發寫入、違背約束、崩潰、網路中斷、磁碟故障)合併為兩種可能結果:提交或中止。這是對程式設計模型而言是一種巨大的簡化,但恐怕這還不夠。 事務是代價高昂的,當涉及異構儲存技術時尤為甚(請參閱“[實踐中的分散式事務](ch9.md#實踐中的分散式事務)”)。我們拒絕使用分散式事務是因為它開銷太大,結果我們最後不得不在應用程式碼中重新實現容錯機制。正如本書中大量的例子所示,對併發性與部分失敗的推理是困難且違反直覺的,所以我懷疑大多數應用級別的機制都不能正確工作,最終結果是資料丟失或損壞。 @@ -742,7 +742,7 @@ ACID意義下的一致性(請參閱“[一致性](ch7.md#一致性)”)基 當我們開發預測性分析系統時,不是僅僅用軟體透過一系列IF ELSE規則將人類的決策過程自動化,那些規則本身甚至都是從資料中推斷出來的。但這些系統學到的模式是個黑盒:即使資料中存在一些相關性,我們可能也壓根不知道為什麼。如果演算法的輸入中存在系統性的偏見,則系統很有可能會在輸出中學習並放大這種偏見【84】。 -在許多國家,反歧視法律禁止按種族,年齡,性別,性取向,殘疾,或信仰等受保護的特徵區分對待不同的人。其他的個人特徵可能是允許用於分析的,但是如果這些特徵與受保護的特徵存在關聯,又會發生什麼?例如在種族隔離地區中,一個人的郵政編碼,甚至是他們的IP地址,都是很強的種族指示物。這樣的話,相信一種演算法可以以某種方式將有偏見的資料作為輸入,併產生公平和公正的輸出【85】似乎是很荒謬的。然而這種觀點似乎常常潛伏在資料驅動型決策的支持者中,這種態度被諷刺為“在處理偏差上,機器學習與洗錢類似”(machine learning is like money laundering for bias)【86】。 +在許多國家,反歧視法律禁止按種族、年齡、性別、性取向、殘疾或信仰等受保護的特徵區分對待不同的人。其他的個人特徵可能是允許用於分析的,但是如果這些特徵與受保護的特徵存在關聯,又會發生什麼?例如在種族隔離地區中,一個人的郵政編碼,甚至是他們的IP地址,都是很強的種族指示物。這樣的話,相信一種演算法可以以某種方式將有偏見的資料作為輸入,併產生公平和公正的輸出【85】似乎是很荒謬的。然而這種觀點似乎常常潛伏在資料驅動型決策的支持者中,這種態度被諷刺為“在處理偏差上,機器學習與洗錢類似”(machine learning is like money laundering for bias)【86】。 預測性分析系統只是基於過去進行推斷;如果過去是歧視性的,它們就會將這種歧視歸納為規律。如果我們希望未來比過去更好,那麼就需要道德想象力,而這是隻有人類才能提供的東西【87】。資料與模型應該是我們的工具,而不是我們的主人。 diff --git a/zh-tw/ch2.md b/zh-tw/ch2.md index fa7415d..cc89654 100644 --- a/zh-tw/ch2.md +++ b/zh-tw/ch2.md @@ -15,10 +15,10 @@ 多數應用使用層層疊加的資料模型構建。對於每層資料模型的關鍵問題是:它是如何用低一層資料模型來**表示**的?例如: -1. 作為一名應用開發人員,你觀察現實世界(裡面有人員,組織,貨物,行為,資金流向,感測器等),並採用物件或資料結構,以及操控那些資料結構的API來進行建模。那些結構通常是特定於應用程式的。 -2. 當要儲存那些資料結構時,你可以利用通用資料模型來表示它們,如JSON或XML文件,關係資料庫中的表、或圖模型。 +1. 作為一名應用開發人員,你觀察現實世界(裡面有人員、組織、貨物、行為、資金流向、感測器等),並採用物件或資料結構,以及操控那些資料結構的API來進行建模。那些結構通常是特定於應用程式的。 +2. 當要儲存那些資料結構時,你可以利用通用資料模型來表示它們,如JSON或XML文件、關係資料庫中的表或圖模型。 3. 資料庫軟體的工程師選定如何以記憶體、磁碟或網路上的位元組來表示JSON/XML/關係/圖資料。這類表示形式使資料有可能以各種方式來查詢,搜尋,操縱和處理。 -4. 在更低的層次上,硬體工程師已經想出了使用電流,光脈衝,磁場或者其他東西來表示位元組的方法。 +4. 在更低的層次上,硬體工程師已經想出了使用電流、光脈衝、磁場或者其他東西來表示位元組的方法。 一個複雜的應用程式可能會有更多的中間層次,比如基於API的API,不過基本思想仍然是一樣的:每個層都透過提供一個明確的資料模型來隱藏更低層次中的複雜性。這些抽象允許不同的人群有效地協作(例如資料庫廠商的工程師和使用資料庫的應用程式開發人員)。 @@ -156,7 +156,7 @@ JSON表示比[圖2-1](../img/fig2-1.png)中的多表模式具有更好的**區 * 組織和學校作為實體 - 在前面的描述中,`organization`(使用者工作的公司)和`school_name`(他們學習的地方)只是字串。也許他們應該是對實體的引用呢?然後,每個組織,學校或大學都可以擁有自己的網頁(標識,新聞提要等)。每個簡歷可以連結到它所提到的組織和學校,並且包括他們的圖示和其他資訊(請參閱[圖2-3](../img/fig2-3.png),來自LinkedIn的一個例子)。 + 在前面的描述中,`organization`(使用者工作的公司)和`school_name`(他們學習的地方)只是字串。也許他們應該是對實體的引用呢?然後,每個組織、學校或大學都可以擁有自己的網頁(標識,新聞提要等)。每個簡歷可以連結到它所提到的組織和學校,並且包括他們的圖示和其他資訊(請參閱[圖2-3](../img/fig2-3.png),來自LinkedIn的一個例子)。 * 推薦 @@ -274,7 +274,7 @@ UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL #### 查詢的資料區域性性 -文件通常以單個連續字串形式進行儲存,編碼為JSON,XML或其二進位制變體(如MongoDB的BSON)。如果應用程式經常需要訪問整個文件(例如,將其渲染至網頁),那麼儲存區域性性會帶來效能優勢。如果將資料分割到多個表中(如[圖2-1](../img/fig2-1.png)所示),則需要進行多次索引查詢才能將其全部檢索出來,這可能需要更多的磁碟查詢並花費更多的時間。 +文件通常以單個連續字串形式進行儲存,編碼為JSON、XML或其二進位制變體(如MongoDB的BSON)。如果應用程式經常需要訪問整個文件(例如,將其渲染至網頁),那麼儲存區域性性會帶來效能優勢。如果將資料分割到多個表中(如[圖2-1](../img/fig2-1.png)所示),則需要進行多次索引查詢才能將其全部檢索出來,這可能需要更多的磁碟查詢並花費更多的時間。 區域性性僅僅適用於同時需要文件絕大部分內容的情況。資料庫通常需要載入整個文件,即使只訪問其中的一小部分,這對於大型文件來說是很浪費的。更新文件時,通常需要整個重寫。只有不改變文件大小的修改才可以容易地原地執行。因此,通常建議保持相對小的文件,並避免增加文件大小的寫入【9】。這些效能限制大大減少了文件資料庫的實用場景。 @@ -529,7 +529,7 @@ db.observations.aggregate([ 可以將那些眾所周知的演算法運用到這些圖上:例如,汽車導航系統搜尋道路網路中兩點之間的最短路徑,PageRank可以用在網路圖上來確定網頁的流行程度,從而確定該網頁在搜尋結果中的排名。 -在剛剛給出的例子中,圖中的所有頂點代表了相同型別的事物(人,網頁或交叉路口)。不過,圖並不侷限於這樣的同類資料:同樣強大地是,圖提供了一種一致的方式,用來在單個數據儲存中儲存完全不同型別的物件。例如,Facebook維護一個包含許多不同型別的頂點和邊的單個圖:頂點表示人,地點,事件,簽到和使用者的評論;邊緣表示哪些人是彼此的朋友,哪個簽到發生在何處,誰評論了哪條訊息,誰參與了哪個事件,等等【35】。 +在剛剛給出的例子中,圖中的所有頂點代表了相同型別的事物(人、網頁或交叉路口)。不過,圖並不侷限於這樣的同類資料:同樣強大地是,圖提供了一種一致的方式,用來在單個數據儲存中儲存完全不同型別的物件。例如,Facebook維護一個包含許多不同型別的頂點和邊的單個圖:頂點表示人,地點,事件,簽到和使用者的評論;邊緣表示哪些人是彼此的朋友,哪個簽到發生在何處,誰評論了哪條訊息,誰參與了哪個事件,等等【35】。 在本節中,我們將使用[圖2-5](../img/fig2-5.png)所示的示例。它可以從社交網路或系譜資料庫中獲得:它顯示了兩個人,來自愛達荷州的Lucy和來自法國Beaune的Alain。他們已婚,住在倫敦。 @@ -640,7 +640,7 @@ RETURN person.name 答案是肯定的,但有些困難。在關係資料庫中,你通常會事先知道在查詢中需要哪些連線。在圖查詢中,你可能需要在找到待查詢的頂點之前,遍歷可變數量的邊。也就是說,連線的數量事先並不確定。 -在我們的例子中,這發生在Cypher查詢中的`() -[:WITHIN*0..]-> ()`規則中。一個人的`LIVES_IN`邊可以指向任何型別的位置:街道,城市,地區,地區,國家等。城市可以在一個地區,在一個州內的一個地區,在一個國家內的一個州等等。`LIVES_IN`邊可以直接指向正在查詢的位置,或者一個在位置層次結構中隔了數層的位置。 +在我們的例子中,這發生在Cypher查詢中的`() -[:WITHIN*0..]-> ()`規則中。一個人的`LIVES_IN`邊可以指向任何型別的位置:街道、城市、地區、地區、國家等。城市可以在一個地區,在一個州內的一個地區,在一個國家內的一個州等等。`LIVES_IN`邊可以直接指向正在查詢的位置,或者一個在位置層次結構中隔了數層的位置。 在Cypher中,用`WITHIN * 0`非常簡潔地表述了上述事實:“沿著`WITHIN`邊,零次或多次”。它很像正則表示式中的`*`運算子。 diff --git a/zh-tw/ch3.md b/zh-tw/ch3.md index 9b4fee2..d68515b 100644 --- a/zh-tw/ch3.md +++ b/zh-tw/ch3.md @@ -430,7 +430,7 @@ Teradata、Vertica、SAP HANA和ParAccel等資料倉庫供應商通常使用昂 **圖3-9 用於資料倉庫的星型模式的示例** -通常情況下,事實被視為單獨的事件,因為這樣可以在以後分析中獲得最大的靈活性。但是,這意味著事實表可以變得非常大。像蘋果,沃爾瑪或eBay這樣的大企業在其資料倉庫中可能有幾十PB的交易歷史,其中大部分儲存在事實表中【56】。 +通常情況下,事實被視為單獨的事件,因為這樣可以在以後分析中獲得最大的靈活性。但是,這意味著事實表可以變得非常大。像蘋果、沃爾瑪或eBay這樣的大企業在其資料倉庫中可能有幾十PB的交易歷史,其中大部分儲存在事實表中【56】。 事實表中的一些列是屬性,例如產品銷售的價格和從供應商那裡購買的成本(可以用來計算利潤餘額)。事實表中的其他列是對其他表(稱為維度表)的外來鍵引用。由於事實表中的每一行都表示一個事件,因此這些維度代表事件發生的物件、內容、地點、時間、方式和原因。 diff --git a/zh-tw/ch4.md b/zh-tw/ch4.md index bce6c79..dbca08e 100644 --- a/zh-tw/ch4.md +++ b/zh-tw/ch4.md @@ -290,9 +290,9 @@ Avro的關鍵思想是Writer模式和Reader模式不必是相同的 - 他們只 #### 程式碼生成和動態型別的語言 -Thrift和Protobuf依賴於程式碼生成:在定義了模式之後,可以使用你選擇的程式語言生成實現此模式的程式碼。這在Java,C ++或C#等靜態型別語言中很有用,因為它允許將高效的記憶體中結構用於解碼的資料,並且在編寫訪問資料結構的程式時允許在IDE中進行型別檢查和自動完成。 +Thrift和Protobuf依賴於程式碼生成:在定義了模式之後,可以使用你選擇的程式語言生成實現此模式的程式碼。這在Java、C++或C#等靜態型別語言中很有用,因為它允許將高效的記憶體中結構用於解碼的資料,並且在編寫訪問資料結構的程式時允許在IDE中進行型別檢查和自動完成。 -在動態型別程式語言(如JavaScript,Ruby或Python)中,生成程式碼沒有太多意義,因為沒有編譯時型別檢查器來滿足。程式碼生成在這些語言中經常被忽視,因為它們避免了顯式的編譯步驟。而且,對於動態生成的模式(例如從資料庫表生成的Avro模式),程式碼生成對獲取資料是一個不必要的障礙。 +在動態型別程式語言(如JavaScript、Ruby或Python)中,生成程式碼沒有太多意義,因為沒有編譯時型別檢查器來滿足。程式碼生成在這些語言中經常被忽視,因為它們避免了顯式的編譯步驟。而且,對於動態生成的模式(例如從資料庫表生成的Avro模式),程式碼生成對獲取資料是一個不必要的障礙。 Avro為靜態型別程式語言提供了可選的程式碼生成功能,但是它也可以在不生成任何程式碼的情況下使用。如果你有一個物件容器檔案(它嵌入了Writer模式),你可以簡單地使用Avro庫開啟它,並以與檢視JSON檔案相同的方式檢視資料。該檔案是自描述的,因為它包含所有必要的元資料。 diff --git a/zh-tw/ch5.md b/zh-tw/ch5.md index 8b8c229..bc25750 100644 --- a/zh-tw/ch5.md +++ b/zh-tw/ch5.md @@ -128,7 +128,7 @@ #### 基於語句的複製 -在最簡單的情況下,主庫記錄下它執行的每個寫入請求(**語句**,即statement)並將該語句日誌傳送給其從庫。對於關係資料庫來說,這意味著每個`INSERT`,`UPDATE`或`DELETE`語句都被轉發給每個從庫,每個從庫解析並執行該SQL語句,就像從客戶端收到一樣。 +在最簡單的情況下,主庫記錄下它執行的每個寫入請求(**語句**,即statement)並將該語句日誌傳送給其從庫。對於關係資料庫來說,這意味著每個`INSERT`、`UPDATE`或`DELETE`語句都被轉發給每個從庫,每個從庫解析並執行該SQL語句,就像從客戶端收到一樣。 雖然聽上去很合理,但有很多問題會搞砸這種複製方式: diff --git a/zh-tw/ch6.md b/zh-tw/ch6.md index ac43e0c..c2393ea 100644 --- a/zh-tw/ch6.md +++ b/zh-tw/ch6.md @@ -93,7 +93,7 @@ > > 正如我們將在“[分割槽再平衡](#分割槽再平衡)”中所看到的,這種特殊的方法對於資料庫實際上並不是很好,所以在實際中很少使用(某些資料庫的文件仍然會使用一致性雜湊的說法,但是它往往是不準確的)。 因為有可能產生混淆,所以最好避免使用一致性雜湊這個術語,而只是把它稱為**雜湊分割槽(hash partitioning)**。 -不幸的是,透過使用鍵雜湊進行分割槽,我們失去了鍵範圍分割槽的一個很好的屬性:高效執行範圍查詢的能力。曾經相鄰的鍵現在分散在所有分割槽中,所以它們之間的順序就丟失了。在MongoDB中,如果你使用了基於雜湊的分割槽模式,則任何範圍查詢都必須傳送到所有分割槽【4】。Riak 【9】,Couchbase 【10】或Voldemort不支援主鍵上的範圍查詢。 +不幸的是,透過使用鍵雜湊進行分割槽,我們失去了鍵範圍分割槽的一個很好的屬性:高效執行範圍查詢的能力。曾經相鄰的鍵現在分散在所有分割槽中,所以它們之間的順序就丟失了。在MongoDB中,如果你使用了基於雜湊的分割槽模式,則任何範圍查詢都必須傳送到所有分割槽【4】。Riak【9】、Couchbase 【10】或Voldemort不支援主鍵上的範圍查詢。 Cassandra採取了折衷的策略【11, 12, 13】。 Cassandra中的表可以使用由多個列組成的複合主鍵來宣告。鍵中只有第一列會作為雜湊的依據,而其他列則被用作Casssandra的SSTables中排序資料的連線索引。儘管查詢無法在複合主鍵的第一列中按範圍掃表,但如果第一列已經指定了固定值,則可以對該鍵的其他列執行有效的範圍掃描。 @@ -190,7 +190,7 @@ Cassandra採取了折衷的策略【11, 12, 13】。 Cassandra中的表可以使 我們在前面說過([圖6-3](../img/fig6-3.png)),最好將可能的雜湊分成不同的範圍,並將每個範圍分配給一個分割槽(例如,如果$0≤hash(key) * 當電源突然斷電時,特別是固態硬碟,有證據顯示有時會違反應有的保證:甚至fsync也不能保證正常工作【12】。硬碟韌體可能有錯誤,就像任何其他型別的軟體一樣【13,14】。 > * 儲存引擎和檔案系統之間的微妙互動可能會導致難以追蹤的錯誤,並可能導致磁碟上的檔案在崩潰後被損壞【15,16】。 > * 磁碟上的資料可能會在沒有檢測到的情況下逐漸損壞【17】。如果資料已損壞一段時間,副本和最近的備份也可能損壞。這種情況下,需要嘗試從歷史備份中恢復資料。 -> * 一項關於固態硬碟的研究發現,在執行的前四年中,30%到80%的硬碟會產生至少一個壞塊【18】。相比固態硬碟,磁碟的壞道率較低,但完全失效的概率更高。 +> * 一項關於固態硬碟的研究發現,在執行的前四年中,30%到80%的硬碟會產生至少一個壞塊【18】。相比固態硬碟,磁碟的壞道率較低,但完全失效的概率更高。 > * 如果SSD斷電,可能會在幾周內開始丟失資料,具體取決於溫度【19】。 > > 在實踐中,沒有一種技術可以提供絕對保證。只有各種降低風險的技術,包括寫入磁碟,複製到遠端機器和備份——它們可以且應該一起使用。與往常一樣,最好抱著懷疑的態度接受任何理論上的“保證”。 diff --git a/zh-tw/ch8.md b/zh-tw/ch8.md index d6726d0..cc2ae67 100644 --- a/zh-tw/ch8.md +++ b/zh-tw/ch8.md @@ -82,7 +82,7 @@ > 你可能想知道這是否有意義——直觀地看來,系統只能像其最不可靠的元件(最薄弱的環節)一樣可靠。事實並非如此:事實上,從不太可靠的潛在基礎構建更可靠的系統是計算機領域的一個古老思想【11】。例如: > > * 糾錯碼允許數字資料在通訊通道上準確傳輸,偶爾會出現一些錯誤,例如由於無線網路上的無線電干擾【12】。 -> * **網際網路協議(Internet Protocol, IP)** 不可靠:可能丟棄,延遲,重複或重排資料包。 傳輸控制協議(Transmission Control Protocol, TCP)在網際網路協議(IP)之上提供了更可靠的傳輸層:它確保丟失的資料包被重新傳輸,消除重複,並且資料包被重新組裝成它們被傳送的順序。 +> * **網際網路協議(Internet Protocol, IP)** 不可靠:可能丟棄、延遲、重複或重排資料包。 傳輸控制協議(Transmission Control Protocol, TCP)在網際網路協議(IP)之上提供了更可靠的傳輸層:它確保丟失的資料包被重新傳輸,消除重複,並且資料包被重新組裝成它們被傳送的順序。 > > 雖然這個系統可以比它的底層部分更可靠,但它的可靠性總是有限的。例如,糾錯碼可以處理少量的單位元錯誤,但是如果你的訊號被幹擾所淹沒,那麼透過通道可以得到多少資料,是有根本性的限制的【13】。 TCP可以隱藏資料包的丟失,重複和重新排序,但是它不能神奇地消除網路中的延遲。 > @@ -278,7 +278,7 @@ 在具有多個CPU插槽的伺服器上,每個CPU可能有一個單獨的計時器,但不一定與其他CPU同步。作業系統會補償所有的差異,並嘗試嚮應用執行緒表現出單調鐘的樣子,即使這些執行緒被排程到不同的CPU上。當然,明智的做法是不要太把這種單調性保證當回事【40】。 -如果NTP協議檢測到計算機的本地石英鐘比NTP伺服器要更快或更慢,則可以調整單調鍾向前走的頻率(這稱為**偏移(skewing)** 時鐘)。預設情況下,NTP允許時鐘速率增加或減慢最高至0.05%,但NTP不能使單調時鐘向前或向後跳轉。單調時鐘的解析度通常相當好:在大多數系統中,它們能在幾微秒或更短的時間內測量時間間隔。 +如果NTP協議檢測到計算機的本地石英鐘比NTP伺服器要更快或更慢,則可以調整單調鍾向前走的頻率(這稱為**偏移(skewing)** 時鐘)。預設情況下,NTP允許時鐘速率增加或減慢最高至0.05%,但NTP不能使單調時鐘向前或向後跳轉。單調時鐘的解析度通常相當好:在大多數系統中,它們能在幾微秒或更短的時間內測量時間間隔。 在分散式系統中,使用單調鍾測量**經過時間**(elapsed time,比如超時)通常很好,因為它不假定不同節點的時鐘之間存在任何同步,並且對測量的輕微不準確性不敏感。 @@ -339,7 +339,7 @@ NTP同步是否能足夠準確,以至於這種不正確的排序不會發生 你可能能夠以微秒或甚至納秒的精度讀取機器的時鐘。但即使可以得到如此細緻的測量結果,這並不意味著這個值對於這樣的精度實際上是準確的。實際上,大概率是不準確的——如前所述,即使你每分鐘與本地網路上的NTP伺服器進行同步,幾毫秒的時間漂移也很容易在不精確的石英時鐘上發生。使用公共網際網路上的NTP伺服器,最好的準確度可能達到幾十毫秒,而且當網路擁塞時,誤差可能會超過100毫秒【57】。 -因此,將時鐘讀數視為一個時間點是沒有意義的——它更像是一段時間範圍:例如,一個系統可能以95%的置信度認為當前時間處於本分鐘內的第10.3秒和10.5秒之間,它可能沒法比這更精確了【58】。如果我們只知道±100毫秒的時間,那麼時間戳中的微秒數字部分基本上是沒有意義的。 +因此,將時鐘讀數視為一個時間點是沒有意義的——它更像是一段時間範圍:例如,一個系統可能以95%的置信度認為當前時間處於本分鐘內的第10.3秒和10.5秒之間,它可能沒法比這更精確了【58】。如果我們只知道±100毫秒的時間,那麼時間戳中的微秒數字部分基本上是沒有意義的。 不確定性界限可以根據你的時間源來計算。如果你的GPS接收器或原子(銫)時鐘直接連線到你的計算機上,預期的錯誤範圍由製造商告知。如果從伺服器獲得時間,則不確定性取決於自上次與伺服器同步以來的石英鐘漂移的期望值,加上NTP伺服器的不確定性,再加上到伺服器的網路往返時間(只是獲取粗略近似值,並假設伺服器是可信的)。 diff --git a/zh-tw/glossary.md b/zh-tw/glossary.md index 79880fe..f52bd19 100644 --- a/zh-tw/glossary.md +++ b/zh-tw/glossary.md @@ -169,7 +169,7 @@ ### 百分位點(percentile) -透過計算有多少值高於或低於某個閾值來衡量值分佈的方法。 例如,某個時間段的第95個百分位響應時間是時間t,則該時間段中,95%的請求完成時間小於t,5%的請求完成時間要比t長。 請參閱“[描述效能](ch1.md#描述效能)”。 +透過計算有多少值高於或低於某個閾值來衡量值分佈的方法。 例如,某個時間段的第95個百分位響應時間是時間t,則該時間段中,95%的請求完成時間小於t,5%的請求完成時間要比t長。 請參閱“[描述效能](ch1.md#描述效能)”。 ### 主鍵(primary key)