mirror of
https://github.com/Vonng/ddia.git
synced 2025-01-05 15:30:06 +08:00
progress: add lock for ch10,ch11
This commit is contained in:
parent
e520097e7b
commit
b2e6651c97
48
README.md
48
README.md
@ -15,10 +15,6 @@
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## 译序
|
||||
|
||||
> 不懂数据库的全栈工程师不是好架构师
|
||||
@ -70,7 +66,7 @@
|
||||
8. [分布式系统的麻烦](ddia/ch8.md)
|
||||
9. [一致性与共识](ddia/ch9.md)
|
||||
|
||||
#### [III. 派生数据](ddia/part-iii.md)
|
||||
#### [III. 衍生数据](ddia/part-iii.md)
|
||||
|
||||
10. [批处理](ddia/ch10.md)
|
||||
11. [流处理](ddia/ch11.md)
|
||||
@ -93,28 +89,28 @@
|
||||
|
||||
精翻可以看,机翻基本没法看,初翻对于业内人士能凑合看。
|
||||
|
||||
| 章节 | 进度 |
|
||||
| :--------------------------------: | :------: |
|
||||
| 序言 | 初翻 |
|
||||
| 第一部分:数据系统基础 ——概览 | 精翻 |
|
||||
| 第一章:可靠性、可扩展性、可维护性 | 精翻 |
|
||||
| 第二章:数据模型与查询语言 | 初翻 |
|
||||
| 第三章:存储与检索 | 初翻 |
|
||||
| 第四章:编码与演化 | 初翻 |
|
||||
| 第二部分:分布式数据——概览 | 精翻 |
|
||||
| 第五章:复制 | 精翻 30% |
|
||||
| 第六章:分片 | 初翻 |
|
||||
| 第七章:事务 | 精翻 60% |
|
||||
| 第八章:分布式系统中的问题 | 初翻 |
|
||||
| 第九章:一致性与共识 | 初翻30% |
|
||||
| 第三部分:前言 | 精翻 |
|
||||
| 第十章:批处理 | 机翻 |
|
||||
| 第十一章:流处理 | 机翻 |
|
||||
| 第十二章:数据系统的未来 | 机翻 |
|
||||
| 术语表 | - |
|
||||
| 后记 | 机翻 |
|
||||
| 章节 | 进度 | 锁 |
|
||||
| :--------------------------------: | :------: | :---: |
|
||||
| 序言 | 初翻 | |
|
||||
| 第一部分:数据系统基础 ——概览 | 精翻 | |
|
||||
| 第一章:可靠性、可扩展性、可维护性 | 精翻 | |
|
||||
| 第二章:数据模型与查询语言 | 初翻 | |
|
||||
| 第三章:存储与检索 | 初翻 | |
|
||||
| 第四章:编码与演化 | 初翻 | |
|
||||
| 第二部分:分布式数据——概览 | 精翻 | |
|
||||
| 第五章:复制 | 精翻 30% | |
|
||||
| 第六章:分片 | 初翻 | |
|
||||
| 第七章:事务 | 精翻 60% | |
|
||||
| 第八章:分布式系统中的问题 | 初翻 | |
|
||||
| 第九章:一致性与共识 | 初翻30% | Vonng |
|
||||
| 第三部分:前言 | 精翻 | |
|
||||
| 第十章:批处理 | 机翻 | 于鑫 |
|
||||
| 第十一章:流处理 | 机翻 | 于鑫 |
|
||||
| 第十二章:数据系统的未来 | 机翻 | |
|
||||
| 术语表 | - | |
|
||||
| 后记 | 机翻 | |
|
||||
|
||||
最近比较忙,精翻计划可能会延后,但初翻会尽量先过一遍。早期的几章初翻质量很一般,后续会重新过一遍。
|
||||
计划在3月25日前完成所有章节的初翻。
|
||||
|
||||
|
||||
|
||||
|
@ -78,26 +78,26 @@
|
||||
|
||||
精翻可以看,初翻凑合看,机翻没法看。精翻太累了,看心情吧。
|
||||
|
||||
| 章节 | 文件 | 进度 |
|
||||
| ------ | ------ | ---- |
|
||||
| 序言 | [preface.md](preface.md) | 初翻 |
|
||||
| 第一部分:数据系统基础 ——概览 | [part-i.md](part-i.md) | 精翻 |
|
||||
| 第一章:可靠性、可扩展性、可维护性 | [ch1.md](ch1.md) | 精翻 |
|
||||
| 第二章:数据模型与查询语言 | [ch2.md](ch2.md) | 初翻 |
|
||||
| 第三章:存储与检索 | [ch3.md](ch3.md) | 初翻 |
|
||||
| 第四章:编码与演化 | [ch4.md](ch4.md) | 初翻 |
|
||||
| 第二部分:分布式数据——概览 | [part-ii.md](part-ii.md) | 精翻 |
|
||||
| 第五章:复制 | [ch5.md](ch5.md) | 初翻 |
|
||||
| 第六章:分片 | [ch6.md](ch6.md) | 初翻 |
|
||||
| 第七章:事务 | [ch7.md](ch7.md) | 初翻 |
|
||||
| 第八章:分布式系统的麻烦 | [ch8.md](ch8.md) | 初翻 |
|
||||
| 第九章:一致性与共识 | [ch9.md](ch9.md) | 初翻 30% |
|
||||
| 第三部分:前言 | [part-iii.md](part-iii.md) | 精翻 |
|
||||
| 第十章:批处理 | [ch10.md](ch10.md) | 机翻 |
|
||||
| 第十一章:流处理 | [ch11.md](ch11.md) | 机翻 |
|
||||
| 第十二章:数据系统的未来 | [ch12.md](ch12.md) | 机翻 |
|
||||
| 术语表 | [glossary.md](glossary.md) | - |
|
||||
| 后记 | [colophon.md](colophon.md) | 机翻 |
|
||||
| 章节 | 文件 | 进度 | 锁定 |
|
||||
| ------ | :----: | :--: | :--: |
|
||||
| 序言 | [preface.md](preface.md) | 初翻 | |
|
||||
| 第一部分:数据系统基础 ——概览 | [part-i.md](part-i.md) | 精翻 | |
|
||||
| 第一章:可靠性、可扩展性、可维护性 | [ch1.md](ch1.md) | 精翻 | |
|
||||
| 第二章:数据模型与查询语言 | [ch2.md](ch2.md) | 初翻 | |
|
||||
| 第三章:存储与检索 | [ch3.md](ch3.md) | 初翻 | |
|
||||
| 第四章:编码与演化 | [ch4.md](ch4.md) | 初翻 | |
|
||||
| 第二部分:分布式数据——概览 | [part-ii.md](part-ii.md) | 精翻 | |
|
||||
| 第五章:复制 | [ch5.md](ch5.md) | 初翻 | |
|
||||
| 第六章:分片 | [ch6.md](ch6.md) | 初翻 | |
|
||||
| 第七章:事务 | [ch7.md](ch7.md) | 初翻 | |
|
||||
| 第八章:分布式系统的麻烦 | [ch8.md](ch8.md) | 初翻 | |
|
||||
| 第九章:一致性与共识 | [ch9.md](ch9.md) | 初翻 30% | |
|
||||
| 第三部分:前言 | [part-iii.md](part-iii.md) | 精翻 | |
|
||||
| 第十章:批处理 | [ch10.md](ch10.md) | 机翻 | 于鑫 |
|
||||
| 第十一章:流处理 | [ch11.md](ch11.md) | 机翻 | 于鑫 |
|
||||
| 第十二章:数据系统的未来 | [ch12.md](ch12.md) | 机翻 | |
|
||||
| 术语表 | [glossary.md](glossary.md) | - | |
|
||||
| 后记 | [colophon.md](colophon.md) | 机翻 | |
|
||||
|
||||
|
||||
|
||||
|
96
ddia/ch12.md
96
ddia/ch12.md
@ -28,11 +28,11 @@
|
||||
|
||||
但是,即使您完全理解工具与其使用环境之间的映射,还有一个挑战:在复杂的应用程序中,数据通常以多种不同的方式使用。不太可能存在适用于所有不同数据使用环境的软件,因此您不可避免地需要拼凑几个不同的软件以提供您的应用程序的功能。
|
||||
|
||||
### 组合使用派生数据的工具
|
||||
### 组合使用衍生数据的工具
|
||||
|
||||
例如,为了处理任意关键字的查询,需要将OLTP数据库与全文搜索索引集成在一起是很常见的。尽管一些数据库(如PostgreSQL)包含了全文索引功能,可以满足简单的应用需求[1],但更复杂的搜索工具需要专业的信息检索工具。相反,搜索索引通常不适合作为一个持久的记录系统,因此许多应用程序需要结合两种不同的工具来满足所有要求。
|
||||
|
||||
我们谈到了将数据系统集成到“使系统保持同步”(第452页)的问题。随着数据的不同表示数量的增加,集成问题变得更加困难。除了数据库和搜索索引之外,也许您需要保留分析系统(数据仓库或批处理和流处理系统)中的数据副本。维护从原始数据派生的对象的高速缓存或非规范化版本;通过机器学习,分类,排名或推荐系统传递数据;或根据对数据的更改发送通知。
|
||||
我们谈到了将数据系统集成到“使系统保持同步”(第452页)的问题。随着数据的不同表示数量的增加,集成问题变得更加困难。除了数据库和搜索索引之外,也许您需要保留分析系统(数据仓库或批处理和流处理系统)中的数据副本。维护从原始数据衍生的对象的高速缓存或非规范化版本;通过机器学习,分类,排名或推荐系统传递数据;或根据对数据的更改发送通知。
|
||||
|
||||
令人惊讶的是,我经常看到软件工程师做出如下陈述:“根据我的经验,99%的人只需要X”或“......不需要X”(对于X的各种值)。我认为这样的陈述更多地讲述了讲话者的经验,而不是技术的实际有用性。您可能想要对数据执行的各种操作范围非常广泛。一个人认为是一个模糊和毫无意义的功能,可能是别人的核心要求。如果缩小数据流并考虑整个组织的数据流,那么对数据集成的需求往往就会变得明显。
|
||||
|
||||
@ -44,21 +44,21 @@
|
||||
|
||||
允许应用程序直接写入搜索索引和数据库引入了图11-4所示的问题,其中两个客户端同时发送冲突写入,并且两个存储系统按不同顺序处理它们。在这种情况下,既不是数据库也不是
|
||||
|
||||
基于事件日志更新派生数据系统通常可以做出决定性的和幂等的(参见第478页的“幂等性”),使得从故障中恢复相当容易。
|
||||
基于事件日志更新衍生数据系统通常可以做出决定性的和幂等的(参见第478页的“幂等性”),使得从故障中恢复相当容易。
|
||||
|
||||
#### 派生数据与分布式事务
|
||||
#### 衍生数据与分布式事务
|
||||
|
||||
保持不同数据系统彼此一致的经典方法涉及分布式事务,如第354页的“原子提交和两阶段提交(2PC)”中所述。与分布式事务相比,使用派生数据系统的方法如何?
|
||||
保持不同数据系统彼此一致的经典方法涉及分布式事务,如第354页的“原子提交和两阶段提交(2PC)”中所述。与分布式事务相比,使用衍生数据系统的方法如何?
|
||||
|
||||
在抽象层面,他们通过不同的方式达到类似的目标。分布式事务通过使用锁定进行互斥来决定写入的顺序(请参阅第257页上的“两阶段锁定(2PL)”),而CDC和事件源使用日志进行排序。分布式事务使用原子提交来确保更改只生效一次,而基于日志的系统通常基于确定性重试和幂等性。
|
||||
|
||||
最大的不同之处在于事务系统通常提供线性(请参阅第324页的“线性化”),这意味着有用的保证,例如读取自己的写入(请参阅第162页的“读取自己的写入”)。另一方面,派生数据系统通常是异步更新的,因此它们不会默认提供相同的时间保证。
|
||||
最大的不同之处在于事务系统通常提供线性(请参阅第324页的“线性化”),这意味着有用的保证,例如读取自己的写入(请参阅第162页的“读取自己的写入”)。另一方面,衍生数据系统通常是异步更新的,因此它们不会默认提供相同的时间保证。
|
||||
|
||||
在愿意支付分布式交易成本的有限环境中,它们已被成功使用。但是,我认为XA的容错能力和性能特征较差(请参阅第364页的“实践中的分布式事务”),这严重限制了它的实用性。我相信有可能为分布式事务创建一个更好的协议,但是将这样一个协议广泛采用并与现有工具集成将是具有挑战性的,并且不太可能很快发生。
|
||||
|
||||
在没有广泛支持良好分布式事务协议的情况下,我认为基于日志的派生数据是集成不同数据系统的最有前途的方法。然而,诸如阅读自己写作的保证是有用的,我认为告诉每个人“最终的一致性是不可避免的 - 吸收它并学会处理它”是没有成果的(至少不是没有良好的指导如何处理它)。
|
||||
在没有广泛支持良好分布式事务协议的情况下,我认为基于日志的衍生数据是集成不同数据系统的最有前途的方法。然而,诸如阅读自己写作的保证是有用的,我认为告诉每个人“最终的一致性是不可避免的 - 吸收它并学会处理它”是没有成果的(至少不是没有良好的指导如何处理它)。
|
||||
|
||||
在第51页的“瞄准正确性”中,我们将讨论一些在异步派生系统之上实现更强保障的方法,并在分布式事务和异步基于对数系统之间建立中间立场。
|
||||
在第51页的“瞄准正确性”中,我们将讨论一些在异步衍生系统之上实现更强保障的方法,并在分布式事务和异步基于对数系统之间建立中间立场。
|
||||
|
||||
#### 全局有序的限制
|
||||
|
||||
@ -89,31 +89,31 @@
|
||||
|
||||
* 逻辑时间戳可以提供没有协调的全部订购(请参见“序列号排序”第页343),因此它们可能有助于总订单广播不可行的情况。但是,他们仍然要求收件人处理不按顺序发送的事件,并且需要传递其他元数据。
|
||||
* 如果您可以记录一个事件来记录用户在做出决定之前所看到的系统状态,并给该事件一个唯一的标识符,那么后面的任何事件都可以引用该事件标识符来记录因果关系[4] 。我们将在第513页的“Reads are events too”中回到这个想法。
|
||||
* 冲突解决算法(请参阅“自动冲突解决”(第165页))有助于处理以意外顺序传递的事件。它们对于维护状态很有用,但如果行为有外部副作用(例如,也许,随着时间的推移,应用程序开发模式将出现,使得能够有效地捕获因果依赖关系,并且保持正确的派生状态,而不会迫使所有事件经历全部命令广播的瓶颈。
|
||||
* 冲突解决算法(请参阅“自动冲突解决”(第165页))有助于处理以意外顺序传递的事件。它们对于维护状态很有用,但如果行为有外部副作用(例如,也许,随着时间的推移,应用程序开发模式将出现,使得能够有效地捕获因果依赖关系,并且保持正确的衍生状态,而不会迫使所有事件经历全部命令广播的瓶颈。
|
||||
|
||||
### 批量处理与流处理
|
||||
|
||||
我会说数据集成的目标是确保数据在所有正确的地方以正确的形式结束。这样做需要消耗投入,转化,加入,过滤,汇总,培训模型,评估并最终写入适当的输出。批处理和流处理器是实现这一目标的工具。
|
||||
|
||||
批处理和流处理的输出是派生的数据集,例如搜索索引,实例化视图,向用户显示的建议,聚合度量等(请参阅“批处理工作流的输出”(第417页)和“流处理的用法”第465页)。
|
||||
批处理和流处理的输出是衍生的数据集,例如搜索索引,实例化视图,向用户显示的建议,聚合度量等(请参阅“批处理工作流的输出”(第417页)和“流处理的用法”第465页)。
|
||||
|
||||
正如我们在第10章和第11章中看到的,批处理和流处理有许多共同的原则,主要的根本区别在于流处理器在无界数据集上运行,而批处理输入是已知的有限大小。处理引擎的实现方式也有很多细节上的差异,但是这些区别开始模糊。
|
||||
|
||||
Spark在批处理引擎上执行流处理,将流分解为微格式,而Apache Flink则在流处理引擎上执行批处理[5]。原则上,一种类型的处理可以在另一种类型上仿真,但是性能特征会有所不同:例如,在跳跃或滑动窗口时,微博可能表现不佳[6]。
|
||||
|
||||
#### 保持派生状态
|
||||
#### 保持衍生状态
|
||||
|
||||
批处理具有非常强大的功能特性(即使代码不是用函数式编程语言编写的):它鼓励确定性的纯函数,其输出仅依赖于输入,除了显式输出外没有副作用,处理输入作为不可变的,并作为附加的输出。流处理类似,但它扩展了运算符以允许受管理的容错状态(请参阅第478页的“重建失败后的状态”)。
|
||||
|
||||
具有良好定义的输入和输出的确定性函数的原理不仅有利于容错(请参见第478页的“幂等性”),但也简化了有关组织中数据流的推理[7]。无论派生数据是搜索索引,统计模型还是缓存,从数据管道角度来看,从另一个派生出一件事情,通过功能应用程序代码推送一个系统中的状态更改和应用对衍生系统的影响。
|
||||
具有良好定义的输入和输出的确定性函数的原理不仅有利于容错(请参见第478页的“幂等性”),但也简化了有关组织中数据流的推理[7]。无论衍生数据是搜索索引,统计模型还是缓存,从数据管道角度来看,从另一个衍生出一件事情,通过功能应用程序代码推送一个系统中的状态更改和应用对衍生系统的影响。
|
||||
|
||||
原则上,派生数据系统可以同步维护,就像关系数据库在与被索引表写入操作相同的事务中同步更新辅助索引一样。然而,异步是基于事件日志的系统稳健的原因:它允许系统的一部分故障被本地包含,而如果任何一个参与者失败,分布式事务将中止,因此他们倾向于通过将故障扩展到系统的其余部分(请参阅第363页的“分布式事务的限制”)。
|
||||
原则上,衍生数据系统可以同步维护,就像关系数据库在与被索引表写入操作相同的事务中同步更新辅助索引一样。然而,异步是基于事件日志的系统稳健的原因:它允许系统的一部分故障被本地包含,而如果任何一个参与者失败,分布式事务将中止,因此他们倾向于通过将故障扩展到系统的其余部分(请参阅第363页的“分布式事务的限制”)。
|
||||
|
||||
我们在第206页的“分区和二级索引”中看到,二级索引经常跨越分区边界。具有二级索引的分区系统需要将写入发送到多个分区(如果索引是分词)或将读取发送到所有分区(如果索引是文档分区的话)。如果索引是异步维护的,这种交叉分区通信也是最可靠和可扩展的[8](另请参阅“多分区数据处理”第479页)。
|
||||
|
||||
#### 为应用程序演变重新处理数据
|
||||
|
||||
在维护派生数据时,批处理和流处理都是有用的。流处理允许将输入中的变化以低延迟反映在派生视图中,而批处理允许重新处理大量累积的历史数据以便将新视图导出到现有数据集上。
|
||||
在维护衍生数据时,批处理和流处理都是有用的。流处理允许将输入中的变化以低延迟反映在衍生视图中,而批处理允许重新处理大量累积的历史数据以便将新视图导出到现有数据集上。
|
||||
|
||||
特别是,重新处理现有数据为维护系统提供了一个良好的机制,并将其发展为支持新功能和变更需求(参见第4章)。如果不进行重新处理,模式演化就会局限于简单的变化,例如向记录中添加新的可选字段或添加新类型的记录。无论是在写模式还是在读模式中都是如此(请参阅第39页的“文档模型中的模式灵活性”)。另一方面,通过重新处理,可以将数据集重组为一个完全不同的模型,以便更好地满足新的要求。
|
||||
|
||||
@ -125,7 +125,7 @@ Spark在批处理引擎上执行流处理,将流分解为微格式,而Apache
|
||||
>
|
||||
> 以这种方式“再加工”现有的轨道,让新旧版本并存,可以在几年的时间内逐渐改变轨距。然而,这是一项昂贵的事业,这就是今天非标准仪表仍然存在的原因。例如,旧金山湾区的BART系统使用与美国大部分地区不同的仪表。
|
||||
|
||||
派生视图允许逐步演变。如果您想重新构建数据集,则不需要执行迁移作为突然切换。相反,您可以将旧架构和新架构并排维护为相同基础数据上的两个独立派生视图。然后,您可以开始将少量用户转移到新视图,以测试其性能并发现任何错误,而大多数用户仍然会被路由到旧视图。逐渐地,您可以增加访问新视图的用户比例,最终您可以删除旧视图[10]。
|
||||
衍生视图允许逐步演变。如果您想重新构建数据集,则不需要执行迁移作为突然切换。相反,您可以将旧架构和新架构并排维护为相同基础数据上的两个独立衍生视图。然后,您可以开始将少量用户转移到新视图,以测试其性能并发现任何错误,而大多数用户仍然会被路由到旧视图。逐渐地,您可以增加访问新视图的用户比例,最终您可以删除旧视图[10]。
|
||||
|
||||
这种逐渐迁移的美妙之处在于,如果出现问题,每个阶段的过程都很容易逆转:您始终有一个可以回到的工作系统。通过降低不可逆损害的风险,您可以更有信心继续前进,从而更快地改善您的系统[11]。
|
||||
|
||||
@ -135,9 +135,9 @@ Spark在批处理引擎上执行流处理,将流分解为微格式,而Apache
|
||||
|
||||
lambda体系结构的核心思想是通过将不可变事件附加到不断增长的数据集来记录传入数据,这类似于事件源(请参阅第457页上的“事件源”)。从这些事件中,推导出读取优化的视图。 lambda体系结构建议并行运行两个不同的系统:批处理系统(如Hadoop MapReduce)和独立的流处理系统(如Storm)。
|
||||
|
||||
在lambda方法中,流处理器消耗事件并快速生成对视图的近似更新;批处理器稍后将使用同一组事件并生成派生视图的更正版本。这个设计背后的原因是批处理更简单,因此不易出错,而流处理器被认为是不太可靠和难以容错的(请参阅“故障容错”)。而且,流处理可以使用快速近似算法,而批处理使用较慢的精确算法。
|
||||
在lambda方法中,流处理器消耗事件并快速生成对视图的近似更新;批处理器稍后将使用同一组事件并生成衍生视图的更正版本。这个设计背后的原因是批处理更简单,因此不易出错,而流处理器被认为是不太可靠和难以容错的(请参阅“故障容错”)。而且,流处理可以使用快速近似算法,而批处理使用较慢的精确算法。
|
||||
|
||||
拉姆达体系结构是一个有影响力的想法,它将数据系统的设计变得更好,尤其是通过推广将视图派生到不可变事件流和在需要时重新处理事件的原则。但是,我也认为它有一些实际问题:
|
||||
拉姆达体系结构是一个有影响力的想法,它将数据系统的设计变得更好,尤其是通过推广将视图衍生到不可变事件流和在需要时重新处理事件的原则。但是,我也认为它有一些实际问题:
|
||||
|
||||
* 必须保持相同的逻辑才能在批处理和流处理框架中运行,这是额外的工作。虽然像Summingbird [13]这样的库提供了一个抽象的计算,可以在一个批处理或流的上下文中运行,调试,调整和维护两个不同系统的操作复杂性仍然[14]。
|
||||
* 由于流管道和批处理管道产生单独的输出,因此需要合并它们以响应用户请求。如果计算是通过滚动窗口的简单聚合,则合并相当容易,但如果使用更复杂的操作(例如连接和会话化)导出视图,或者输出不是时间序列,则显得非常困难。
|
||||
@ -182,9 +182,9 @@ Unix和关系数据库以非常不同的哲学来处理信息管理问题。 Uni
|
||||
* 复制日志,保持其他节点上数据的副本最新(请参阅第158页中的“复制日志的实现”)
|
||||
* 全文搜索索引,允许在文本中进行关键字搜索(请参见第88页上的“全文搜索和模糊索引”)以及内置于某些关系数据库[1]
|
||||
|
||||
在第十章和第十一章中,出现了类似的主题。我们讨论了如何构建全文搜索索引(请参阅第357页上的“批处理工作流的输出”),了解有关实例化视图维护(请参阅“维护实例化视图”一节第437页)以及有关将更改从数据库复制到派生数据系统(请参阅第454页的“更改数据捕获”)。
|
||||
在第十章和第十一章中,出现了类似的主题。我们讨论了如何构建全文搜索索引(请参阅第357页上的“批处理工作流的输出”),了解有关实例化视图维护(请参阅“维护实例化视图”一节第437页)以及有关将更改从数据库复制到衍生数据系统(请参阅第454页的“更改数据捕获”)。
|
||||
|
||||
数据库中内置的功能与人们用批处理和流处理器构建的派生数据系统似乎有相似之处。
|
||||
数据库中内置的功能与人们用批处理和流处理器构建的衍生数据系统似乎有相似之处。
|
||||
|
||||
#### 创建一个索引
|
||||
|
||||
@ -198,7 +198,7 @@ Unix和关系数据库以非常不同的哲学来处理信息管理问题。 Uni
|
||||
|
||||
有鉴于此,我认为整个组织的数据流开始像一个巨大的数据库[7]。每当批处理,流或ETL过程将数据从一个地方传输到另一个地方并形成表单时,就像数据库子系统一样,使索引或物化视图保持最新。
|
||||
|
||||
像这样看,批处理和流处理器就像触发器,存储过程和物化视图维护例程的精细实现。他们维护的派生数据系统就像不同的索引类型。例如,关系数据库可能支持B树索引,散列索引,空间索引(请参阅第79页的“多列索引”)以及其他类型的索引。在新兴的派生数据系统体系结构中,不是将这些设施作为单个集成数据库产品的功能实现,而是由各种不同的软件提供,运行在不同的机器上,由不同的团队管理。
|
||||
像这样看,批处理和流处理器就像触发器,存储过程和物化视图维护例程的精细实现。他们维护的衍生数据系统就像不同的索引类型。例如,关系数据库可能支持B树索引,散列索引,空间索引(请参阅第79页的“多列索引”)以及其他类型的索引。在新兴的衍生数据系统体系结构中,不是将这些设施作为单个集成数据库产品的功能实现,而是由各种不同的软件提供,运行在不同的机器上,由不同的团队管理。
|
||||
|
||||
这些发展在未来将会把我们带到哪里?如果我们从没有适合所有访问模式的单一数据模型或存储格式的前提出发,我推测有两种途径可以将不同的存储和处理工具组合成一个有凝聚力的系统:
|
||||
|
||||
@ -257,18 +257,18 @@ unbundled方法遵循Unix传统的小型工具,它可以很好地完成一件
|
||||
|
||||
在本节中,我将详细介绍这些想法,并探讨一些围绕非捆绑数据库和数据流的想法构建应用程序的方法。
|
||||
|
||||
#### 应用程序代码作为派生函数
|
||||
#### 应用程序代码作为衍生函数
|
||||
|
||||
当一个数据集来自另一个数据集时,它会经历某种转换函数。例如:
|
||||
|
||||
* 辅助索引是一种具有直接转换函数的派生数据集:对于基表中的每一行或文档,它挑选被索引的列或字段中的值,并按这些值排序(假设B - 树或SSTable索引,按键排序,如第3章所述)。
|
||||
* 辅助索引是一种具有直接转换函数的衍生数据集:对于基表中的每一行或文档,它挑选被索引的列或字段中的值,并按这些值排序(假设B - 树或SSTable索引,按键排序,如第3章所述)。
|
||||
* 通过应用各种自然语言处理功能(如语言检测,分词,词干或词汇化,拼写纠正和同义词识别)创建全文搜索索引,然后构建用于高效查找的数据结构(例如作为倒排索引)。
|
||||
* 在机器学习系统中,我们可以将模型视为通过应用各种特征提取和统计分析功能从训练数据中导出。当模型应用于新的输入数据时,模型的输出是从输入和模型(因此间接地从训练数据)中导出的。
|
||||
* 缓存通常包含将以用户界面(UI)显示的形式的数据聚合。因此填充缓存需要知道UI中引用的字段; UI中的更改可能需要更新缓存填充方式的定义以及重建缓存。
|
||||
|
||||
辅助索引的派生函数通常是必需的,因此它作为核心特性被构建到许多数据库中,您可以仅通过说CREATE INDEX来调用它。对于全文索引,常见语言的基本语言特征可能内置到数据库中,但更复杂的特征通常需要特定于域的调整。在机器学习中,特征工程是众所周知的特定于应用程序的特征,并且通常必须包含关于应用程序的用户交互和部署的详细知识[35]。
|
||||
辅助索引的衍生函数通常是必需的,因此它作为核心特性被构建到许多数据库中,您可以仅通过说CREATE INDEX来调用它。对于全文索引,常见语言的基本语言特征可能内置到数据库中,但更复杂的特征通常需要特定于域的调整。在机器学习中,特征工程是众所周知的特定于应用程序的特征,并且通常必须包含关于应用程序的用户交互和部署的详细知识[35]。
|
||||
|
||||
当创建派生数据集的函数不是像创建二级索引那样的标准Cookie切割函数时,需要自定义代码来处理特定于应用程序的方面。而这个自定义代码是许多数据库难以抗争虽然关系数据库通常支持触发器,存储过程和用户定义的函数,它们可以用来在数据库中执行应用程序代码,但它们在数据库设计中已经有所反应了(请参阅“传输事件流”(第447页))。
|
||||
当创建衍生数据集的函数不是像创建二级索引那样的标准Cookie切割函数时,需要自定义代码来处理特定于应用程序的方面。而这个自定义代码是许多数据库难以抗争虽然关系数据库通常支持触发器,存储过程和用户定义的函数,它们可以用来在数据库中执行应用程序代码,但它们在数据库设计中已经有所反应了(请参阅“传输事件流”(第447页))。
|
||||
|
||||
#### 应用程序代码和状态的分离
|
||||
|
||||
@ -295,13 +295,13 @@ unbundled方法遵循Unix传统的小型工具,它可以很好地完成一件
|
||||
|
||||
如前所述,当触发器由于数据更改而触发时,或者次级索引更新以反映索引表中的更改时,数据库内部会发生类似的情况。分解数据库意味着将此想法应用于在主数据库之外创建衍生数据集:缓存,全文搜索索引,机器学习或分析系统。我们可以为此使用流处理和消息传递系统。
|
||||
|
||||
需要记住的重要一点是,维护派生数据与传统设计消息传递系统的异步作业执行不同(请参阅第448页上的“与传统消息传递相比的日志”):
|
||||
需要记住的重要一点是,维护衍生数据与传统设计消息传递系统的异步作业执行不同(请参阅第448页上的“与传统消息传递相比的日志”):
|
||||
|
||||
•在维护派生数据时,状态更改的顺序通常很重要(如果多个视图是从事件日志派生的,则需要按照相同的顺序处理事件,以便它们保持一致)。如第445页上的“确认和重新传递”中所述,许多消息代理在重新传送未确认消息时没有此属性。双重写入也被排除(请参阅第454页上的“保持系统同步”)。
|
||||
•在维护衍生数据时,状态更改的顺序通常很重要(如果多个视图是从事件日志衍生的,则需要按照相同的顺序处理事件,以便它们保持一致)。如第445页上的“确认和重新传递”中所述,许多消息代理在重新传送未确认消息时没有此属性。双重写入也被排除(请参阅第454页上的“保持系统同步”)。
|
||||
|
||||
•容错是导出数据的关键:仅丢失单个消息会导致派生数据集永远与其数据源不同步。消息传递和派生状态更新都必须可靠。例如,许多角色系统默认在内存中维护角色状态和消息,所以如果运行角色的机器崩溃,他们就会丢失。
|
||||
•容错是导出数据的关键:仅丢失单个消息会导致衍生数据集永远与其数据源不同步。消息传递和衍生状态更新都必须可靠。例如,许多角色系统默认在内存中维护角色状态和消息,所以如果运行角色的机器崩溃,他们就会丢失。
|
||||
|
||||
稳定的消息排序和容错消息处理是相当严格的要求,但与分布式事务相比,它们更便宜,操作更稳定。现代流处理器可以提供这些排序和可靠性保证,并允许应用程序代码作为流操作符运行。此应用程序代码可以执行数据库中内置的派生函数通常不提供的任意处理。就像管道链接的Unix工具一样,流操作符可以组成数据流周围的大型系统。每个运算符将状态变化流作为输入,并产生其他状态变化流作为输出。
|
||||
稳定的消息排序和容错消息处理是相当严格的要求,但与分布式事务相比,它们更便宜,操作更稳定。现代流处理器可以提供这些排序和可靠性保证,并允许应用程序代码作为流操作符运行。此应用程序代码可以执行数据库中内置的衍生函数通常不提供的任意处理。就像管道链接的Unix工具一样,流操作符可以组成数据流周围的大型系统。每个运算符将状态变化流作为输入,并产生其他状态变化流作为输出。
|
||||
|
||||
#### 流处理器和服务
|
||||
|
||||
@ -320,23 +320,23 @@ unbundled方法遵循Unix传统的小型工具,它可以很好地完成一件
|
||||
|
||||
加入是时间相关的:如果购买事件在稍后的时间点被重新处理,汇率将会改变。如果要重建原始输出,则需要获取原始购买时的历史汇率。无论您是查询服务还是订阅汇率更新流,您都需要处理这种时间依赖性(请参阅第475页的“连接的时间依赖性”)。
|
||||
|
||||
订阅一系列更改,而不是在需要时查询当前状态,使我们更接近类似电子表格的计算模型:当某些数据发生更改时,依赖于此的所有派生数据都可以快速更新。还有很多未解决的问题,例如围绕时间依赖连接等问题,但我认为围绕数据流想法构建应用程序是一个非常有希望的方向。
|
||||
订阅一系列更改,而不是在需要时查询当前状态,使我们更接近类似电子表格的计算模型:当某些数据发生更改时,依赖于此的所有衍生数据都可以快速更新。还有很多未解决的问题,例如围绕时间依赖连接等问题,但我认为围绕数据流想法构建应用程序是一个非常有希望的方向。
|
||||
|
||||
|
||||
|
||||
### 观察派生数据状态
|
||||
### 观察衍生数据状态
|
||||
|
||||
在抽象层面,上一节讨论的数据流系统为您提供了创建派生数据集(例如搜索索引,物化视图和预测模型)并使其保持最新的过程。让我们称这个过程为写入路径:只要某些信息被写入系统,它可能会经历批处理和流处理的多个阶段,并且最终每个派生数据集都会更新以合并写入的数据。图12-1显示了更新搜索索引的示例。
|
||||
在抽象层面,上一节讨论的数据流系统为您提供了创建衍生数据集(例如搜索索引,物化视图和预测模型)并使其保持最新的过程。让我们称这个过程为写入路径:只要某些信息被写入系统,它可能会经历批处理和流处理的多个阶段,并且最终每个衍生数据集都会更新以合并写入的数据。图12-1显示了更新搜索索引的示例。
|
||||
|
||||
![](img/fig12-1.png)
|
||||
|
||||
**图12-1 在搜索索引中,写入(文档更新)符合读取(查询)**
|
||||
|
||||
但为什么你首先创建派生数据集?很可能是因为你想在以后再次查询它。这是读取路径:在提供从派生数据集中读取的用户请求时,可能会对结果执行一些更多处理,然后构建对用户的响应。
|
||||
但为什么你首先创建衍生数据集?很可能是因为你想在以后再次查询它。这是读取路径:在提供从衍生数据集中读取的用户请求时,可能会对结果执行一些更多处理,然后构建对用户的响应。
|
||||
|
||||
总而言之,写入路径和读取路径涵盖了数据的整个旅程,从收集数据的地步到使用数据(可能是由另一个人)。写入路径是预先计算的行程的一部分 - 即,一旦数据进入,即刻完成,无论是否有人要求查看它。阅读路径是旅程中只有当有人要求时才会发生的部分。如果您熟悉函数式编程语言,则可能会注意到写入路径类似于急切的评估,读取路径类似于懒惰评估。
|
||||
|
||||
如图12-1所示,派生数据集是写入路径和读取路径相遇的地方。它代表了在写入时需要完成的工作量与在读取时需要完成的工作量之间的权衡。
|
||||
如图12-1所示,衍生数据集是写入路径和读取路径相遇的地方。它代表了在写入时需要完成的工作量与在读取时需要完成的工作量之间的权衡。
|
||||
|
||||
#### 物化视图和缓存
|
||||
|
||||
@ -382,7 +382,7 @@ unbundled方法遵循Unix传统的小型工具,它可以很好地完成一件
|
||||
|
||||
最近用于开发有状态客户端和用户界面的工具(如Elm语言[30]和Facebook的React,Flux和Redux工具链)已经通过订阅表示用户的事件流来管理内部客户端状态输入或来自服务器的响应,其结构与事件源相似(请参阅第457页的“事件源”)。
|
||||
|
||||
将这种编程模型扩展为允许服务器将状态改变事件推送到客户端事件管道中是非常自然的。因此,状态变化可以通过端到端的写入路径流动:从触发状态改变的一个设备上的交互,通过事件日志以及通过多个派生的数据系统和流处理器,一直到用户界面在另一台设备上观察状态的人。这些状态变化可以以相当低的延迟传播 - 比如说,在一秒内结束。
|
||||
将这种编程模型扩展为允许服务器将状态改变事件推送到客户端事件管道中是非常自然的。因此,状态变化可以通过端到端的写入路径流动:从触发状态改变的一个设备上的交互,通过事件日志以及通过多个衍生的数据系统和流处理器,一直到用户界面在另一台设备上观察状态的人。这些状态变化可以以相当低的延迟传播 - 比如说,在一秒内结束。
|
||||
|
||||
一些应用程序(如即时消息传递和在线游戏)已经具有这种“实时”体系结构(从低延迟的交互意义上说,不是“响应时间保证”在本页中的含义)。但为什么我们不用这种方式构建所有的应用程序?
|
||||
|
||||
@ -392,7 +392,7 @@ unbundled方法遵循Unix传统的小型工具,它可以很好地完成一件
|
||||
|
||||
#### 读也是事件
|
||||
|
||||
我们讨论过,当流处理器将派生数据写入存储(数据库,缓存或索引)时,以及当用户请求查询该存储时,存储将充当写入路径和读取路径之间的边界。该商店允许对数据进行随机访问读取查询,否则这些查询将需要扫描整个事件日志。
|
||||
我们讨论过,当流处理器将衍生数据写入存储(数据库,缓存或索引)时,以及当用户请求查询该存储时,存储将充当写入路径和读取路径之间的边界。该商店允许对数据进行随机访问读取查询,否则这些查询将需要扫描整个事件日志。
|
||||
|
||||
在很多情况下,数据存储与流式传输系统是分开的。但请记住,流处理器还需要维护状态以执行聚合和连接(请参阅第472页的“流连接”)。这种状态通常隐藏在流处理器内部,但是一些框架允许它也被外部客户端查询[45],将流处理器本身变成一种简单的数据库。
|
||||
|
||||
@ -473,7 +473,7 @@ COMMIT;
|
||||
#### 操作标识符
|
||||
|
||||
要通过几次网络通信使操作具有幂等性,仅仅依赖数据库提供的事务机制是不够的 - 您需要考虑请求的端到端流。
|
||||
例如,您可以为操作(例如UUID)生成唯一的标识符,并将其作为隐藏的表单字段包含在客户机应用程序中,或计算所有相关表单字段的散列以派生操作ID [3]。如果Web浏览器提交两次POST请求,这两个请求将具有相同的操作ID。然后,您可以将该操作ID传递到数据库,并检查您是否只使用给定的ID执行一个操作,如示例12-2中所示。
|
||||
例如,您可以为操作(例如UUID)生成唯一的标识符,并将其作为隐藏的表单字段包含在客户机应用程序中,或计算所有相关表单字段的散列以衍生操作ID [3]。如果Web浏览器提交两次POST请求,这两个请求将具有相同的操作ID。然后,您可以将该操作ID传递到数据库,并检查您是否只使用给定的ID执行一个操作,如示例12-2中所示。
|
||||
**例12-2 使用唯一的ID来抑制重复的请求**
|
||||
|
||||
```sql
|
||||
@ -489,7 +489,7 @@ COMMIT;
|
||||
|
||||
例12-2依赖于request_id列上的唯一性约束。如果一个事务尝试插入一个已经存在的ID,那么INSERT失败,事务被中止,使其无法生效两次。即使在较弱的隔离级别下,关系数据库也能正确地维护唯一性约束(而在第248页上的“编写偏斜和幻影”中讨论过,应用程序级别的check-then-insert可能会在不可序列化的隔离下失败)。
|
||||
|
||||
除了抑制重复的请求之外,示例12-2中的请求表充当事件日志的一种,暗示着事件源的方向(请参阅第457页的“事件源”)。账户余额的更新事实上不必与插入事件相同的事务发生,因为它们是多余的,并且可以从下游消费者中的请求事件派生出来 - 只要该事件只处理一次,这可以再次使用请求ID来执行。
|
||||
除了抑制重复的请求之外,示例12-2中的请求表充当事件日志的一种,暗示着事件源的方向(请参阅第457页的“事件源”)。账户余额的更新事实上不必与插入事件相同的事务发生,因为它们是多余的,并且可以从下游消费者中的请求事件衍生出来 - 只要该事件只处理一次,这可以再次使用请求ID来执行。
|
||||
|
||||
**端到端的论点**
|
||||
|
||||
@ -584,7 +584,7 @@ COMMIT;
|
||||
CAP定理(参见第359页的“线性化成本”)使用线性化的意义上的一致性,这是实现及时性的强有力的方法。像写后读一致性这样的时效性较弱的属性(请参阅第162页的“读取自己写的内容”)也很有用。
|
||||
|
||||
***完整性***
|
||||
诚信意味着没有腐败;即没有数据丢失,并且没有矛盾或错误的数据。尤其是,如果将某些派生数据集作为某些基础数据的视图进行维护(请参阅“从事件日志导出当前状态”(第458页)),派生必须正确。例如,数据库索引必须正确地反映数据库的内容 - 缺少某些记录的索引不是很有用。如果完整性受到侵犯,这种不一致是永久性的:在大多数情况下,等待并再次尝试不会修复数据库损坏。相反,需要明确的检查和修理。在ACID事务的上下文中(参见第223页上的“ACID的含义”),一致性通常被理解为某种特定于应用程序的完整性概念。原子性和耐久性是保持完整性的重要工具。
|
||||
诚信意味着没有腐败;即没有数据丢失,并且没有矛盾或错误的数据。尤其是,如果将某些衍生数据集作为某些基础数据的视图进行维护(请参阅“从事件日志导出当前状态”(第458页)),衍生必须正确。例如,数据库索引必须正确地反映数据库的内容 - 缺少某些记录的索引不是很有用。如果完整性受到侵犯,这种不一致是永久性的:在大多数情况下,等待并再次尝试不会修复数据库损坏。相反,需要明确的检查和修理。在ACID事务的上下文中(参见第223页上的“ACID的含义”),一致性通常被理解为某种特定于应用程序的完整性概念。原子性和耐久性是保持完整性的重要工具。
|
||||
|
||||
口号形式:违反及时性是“最终一致性”,而违反诚信则是“永久不一致”。
|
||||
|
||||
@ -605,9 +605,9 @@ ACID事务通常既提供时间性(例如线性化)又提供完整性(例
|
||||
性能和运行稳健性。我们通过以下机制的结合实现了这一完整性:
|
||||
|
||||
* 将写入操作的内容表示为单个消息,可以轻松地以原子方式编写 - 这种方法非常适合事件采购(请参阅第457页的“事件采购”)。
|
||||
* 使用确定性描述函数从该单个消息中获取所有其他状态更新,这与存储过程类似(请参见第252页的“实际的串行执行”和第479页的“作为派生函数的应用程序代码”
|
||||
* 使用确定性描述函数从该单个消息中获取所有其他状态更新,这与存储过程类似(请参见第252页的“实际的串行执行”和第479页的“作为衍生函数的应用程序代码”
|
||||
* 通过所有这些级别的处理传递客户端生成的请求ID,启用端到端重复抑制和幂等性
|
||||
* 使消息不可变并允许派生数据不时重新处理,这使得从错误中恢复变得更加容易(请参阅“不可变事件的优点”第367页)
|
||||
* 使消息不可变并允许衍生数据不时重新处理,这使得从错误中恢复变得更加容易(请参阅“不可变事件的优点”第367页)
|
||||
|
||||
这种机制的组合在我看来是未来构建容错应用程序的一个非常有前途的方向。
|
||||
|
||||
@ -632,14 +632,14 @@ ACID事务通常既提供时间性(例如线性化)又提供完整性(例
|
||||
|
||||
我们现在做了两个有趣的观察:
|
||||
|
||||
1. 数据流系统可以保持对派生数据的完整性保证,而无需原子提交,线性化或同步跨分区协调。
|
||||
1. 数据流系统可以保持对衍生数据的完整性保证,而无需原子提交,线性化或同步跨分区协调。
|
||||
2. 虽然严格的唯一性约束要求及时性和协调性,但许多应用程序实际上可以很好地处理宽松的约束,只要整个过程保持完整性,它们可能会被暂时违反并予以修复。
|
||||
|
||||
总之,这些观察意味着数据流系统可以为许多应用程序提供数据管理服务,而不需要协调,同时仍然提供强大的完整性保证。这种避免协调的数据系统具有很大的吸引力:它们可以比需要执行同步协调的系统获得更好的性能和容错能力[56]。
|
||||
|
||||
例如,这种系统可以在多引导器配置中跨多个数据中心进行分布式操作,在区域之间异步复制。任何一个数据中心都可以继续独立运行,因为不需要同步跨区域协调。这样一个系统的时效性保证会很弱 - 如果不引入协调,它就不可能线性化,但它仍然可以提供强有力的完整性保证。
|
||||
|
||||
在这种情况下,序列化事务作为维护派生状态的一部分仍然是有用的,但它们可以在小范围内运行,在那里它们工作得很好[8]。异构分布式事务(如XA事务)(请参阅“实践中的分布式事务”(第367页))不是必需的。同步协调仍然可以在需要的地方引入(例如,在无法恢复的操作之前执行严格的限制),但是如果只有一个小的协议,则不需要任何东西来支付协调费用应用程序的一部分需要它[43]。
|
||||
在这种情况下,序列化事务作为维护衍生状态的一部分仍然是有用的,但它们可以在小范围内运行,在那里它们工作得很好[8]。异构分布式事务(如XA事务)(请参阅“实践中的分布式事务”(第367页))不是必需的。同步协调仍然可以在需要的地方引入(例如,在无法恢复的操作之前执行严格的限制),但是如果只有一个小的协议,则不需要任何东西来支付协调费用应用程序的一部分需要它[43]。
|
||||
|
||||
查看协调和约束的另一种方法是:它们减少了由于不一致而必须做出的道歉数量,但也可能会降低系统的性能和可用性,从而可能会增加必须制定的道歉数量中断。您不能将道歉数量减少到零,但您可以根据自己的需求寻找最佳平衡点 - 这是既不存在太多不一致性又不存在太多可用性问题的最佳选择。
|
||||
|
||||
@ -687,9 +687,9 @@ ACID意义上的一致性(请参阅第224页的“一致性”)基于数据
|
||||
|
||||
如果一个事务在一个数据库中改变了多个对象,事实上很难说事实是什么意思。即使您捕获事务日志(请参阅第454页上的“更改数据捕获”),各种表中的插入,更新和删除操作并不一定清楚表明为何执行这些突变。决定这些突变的应用逻辑的调用是暂时的,不能被复制。
|
||||
|
||||
相比之下,基于事件的系统可以提供更好的可审计性。在事件源方法中,系统的用户输入被表示为一个单一的不可变事件,并且任何结果状态更新都来自该事件。派生可以做出确定性和可重复性,以便通过相同版本的派生代码运行相同的事件日志将导致相同的状态更新。
|
||||
相比之下,基于事件的系统可以提供更好的可审计性。在事件源方法中,系统的用户输入被表示为一个单一的不可变事件,并且任何结果状态更新都来自该事件。衍生可以做出确定性和可重复性,以便通过相同版本的衍生代码运行相同的事件日志将导致相同的状态更新。
|
||||
|
||||
清楚地看到数据流(请参阅第419页上的“批处理输出的原理”)可以使数据的来源更加清晰,从而使完整性检查更加可行。对于事件日志,我们可以使用散列来检查事件存储没有被破坏。对于任何派生状态,我们可以重新运行从事件日志中派生它的批处理和流处理器,以检查是否获得相同的结果,或者甚至并行运行冗余派生。
|
||||
清楚地看到数据流(请参阅第419页上的“批处理输出的原理”)可以使数据的来源更加清晰,从而使完整性检查更加可行。对于事件日志,我们可以使用散列来检查事件存储没有被破坏。对于任何衍生状态,我们可以重新运行从事件日志中衍生它的批处理和流处理器,以检查是否获得相同的结果,或者甚至并行运行冗余衍生。
|
||||
|
||||
确定性和定义良好的数据流也使调试和跟踪系统的执行变得容易,以确定它为什么做了某些事情[4,69]。如果出现意想不到的事情,那么具有诊断能力来重现导致意外事件的确切环境 - 一种时间行程调试功能是非常有价值的。
|
||||
|
||||
@ -697,7 +697,7 @@ ACID意义上的一致性(请参阅第224页的“一致性”)基于数据
|
||||
|
||||
如果我们不能完全相信系统的每个组件都不会发生腐败 - 每一个硬件都是无错的,并且每一个软件都没有缺陷 - 那么我们至少必须定期检查数据的完整性。如果我们不检查,我们就不会发现腐败,直到它太晚了,并且已经造成了一些下游损害,在这一点上追踪这个问题将变得更加困难和昂贵。
|
||||
|
||||
检查数据系统的完整性最好是以端到端的方式完成(请参阅“数据库的端到端争论”页码:51):我们可以在完整性检查中包含的系统越多,那里的机会就越少在这个过程的某个阶段,腐败是被忽视的。如果我们可以检查整个派生数据管道是端到端正确的,那么沿着路径的任何磁盘,网络,服务和算法都隐含在检查中。
|
||||
检查数据系统的完整性最好是以端到端的方式完成(请参阅“数据库的端到端争论”页码:51):我们可以在完整性检查中包含的系统越多,那里的机会就越少在这个过程的某个阶段,腐败是被忽视的。如果我们可以检查整个衍生数据管道是端到端正确的,那么沿着路径的任何磁盘,网络,服务和算法都隐含在检查中。
|
||||
|
||||
持续的端到端完整性检查可以提高您对系统正确性的信心,从而使您的移动速度更快[70]。与自动化测试一样,审计增加了发现错误的可能性,从而降低了系统更改或新存储技术造成损害的风险。如果您不害怕进行更改,您可以更好地开发应用程序以满足不断变化的需求。
|
||||
|
||||
@ -795,7 +795,7 @@ ACID意义上的一致性(请参阅第224页的“一致性”)基于数据
|
||||
|
||||
我们可能会断言用户自愿选择使用跟踪其活动的服务,并且他们已同意服务条款和隐私政策,因此他们同意收集数据。我们甚至可以声称,用户正在接受有价值的服务,以换取所提供的数据,并且为了提供服务,跟踪是必要的。毫无疑问,社交网络,搜索引擎以及其他各种免费的在线服务对于用户来说都是有价值的,但是这个说法存在问题。
|
||||
|
||||
用户几乎不知道他们提供给我们的数据库的数据,或者它们如何保留和处理 - 而大多数隐私政策的作用更多的是模糊而不是照亮。如果不了解他们的数据会发生什么,用户无法给出任何有意义的同意。通常,来自一个用户的数据还说明关于不是该服务的用户并且没有同意任何条款的其他人的事情。我们在本书的这一部分讨论的派生数据集(来自整个用户群的数据可能与行为跟踪和外部数据源相结合)恰恰是用户无法获得任何有意义理解的数据种类。
|
||||
用户几乎不知道他们提供给我们的数据库的数据,或者它们如何保留和处理 - 而大多数隐私政策的作用更多的是模糊而不是照亮。如果不了解他们的数据会发生什么,用户无法给出任何有意义的同意。通常,来自一个用户的数据还说明关于不是该服务的用户并且没有同意任何条款的其他人的事情。我们在本书的这一部分讨论的衍生数据集(来自整个用户群的数据可能与行为跟踪和外部数据源相结合)恰恰是用户无法获得任何有意义理解的数据种类。
|
||||
而且,数据是通过单向过程从用户中提取出来的,而不是真正的互惠关系,而不是公平的价值交换。没有对话,用户无法选择他们提供的数据量以及他们收到的服务回报:服务和用户之间的关系是非常不对称和片面的。这些条款是由服务设置,而不是由用户[99]。
|
||||
|
||||
对于不同意监视的用户,唯一真正的选择就是不使用服务。但是这个选择也不是免费的:如果一项服务如此受欢迎以至于“被大多数人认为是基本社会参与的必要条件”[99],那么指望人们选择退出这项服务是不合理的 - 使用它事实上是强制性的。例如,在大多数西方社会群体中,携带智能手机,使用Facebook进行社交以及使用Google查找信息已成为常态。特别是当一项服务具有网络效应时,人们选择不使用它会产生社会成本。
|
||||
@ -866,13 +866,13 @@ ACID意义上的一致性(请参阅第224页的“一致性”)基于数据
|
||||
|
||||
在本章中,我们讨论了设计数据系统的新方法,并且包括了我对未来的个人意见和猜测。我们从观察开始,即没有一种工具可以有效地服务于所有可能的用例,因此应用程序必须编写几个不同的软件才能实现其目标。我们讨论了如何使用批处理和事件流来解决这个数据集成问题,以便让数据变化在不同系统之间流动。
|
||||
|
||||
在这种方法中,某些系统被指定为记录系统,而其他数据则通过转换从中得出。通过这种方式,我们可以维护索引,物化视图,机器学习模型,统计摘要等等。通过使这些派生和转换异步和松散耦合,防止一个区域中的问题扩散到系统的不相关部分,从而增加整个系统的稳健性和容错性。
|
||||
在这种方法中,某些系统被指定为记录系统,而其他数据则通过转换从中得出。通过这种方式,我们可以维护索引,物化视图,机器学习模型,统计摘要等等。通过使这些衍生和转换异步和松散耦合,防止一个区域中的问题扩散到系统的不相关部分,从而增加整个系统的稳健性和容错性。
|
||||
|
||||
将数据流表示为从一个数据集到另一个数据集的转换也有助于演化应用程序:如果您想更改其中一个处理步骤,例如更改索引或缓存的结构,则可以在整个输入数据集上重新运行新的转换代码以便重新输出。同样,如果出现问题,您可以修复代码并重新处理数据以便恢复。
|
||||
|
||||
这些过程与内部数据库已经完成的过程非常相似,因此我们重新构思了数据流应用程序的概念,将数据库的组件分开,并通过组合这些松散耦合的组件来构建应用程序。
|
||||
|
||||
派生状态可以通过观察底层数据的变化来更新。此外,下游消费者可以进一步观察派生状态本身。我们甚至可以将此数据流一直传送到显示数据的最终用户设备,从而构建可动态更新以反映数据更改并继续脱机工作的用户界面。
|
||||
衍生状态可以通过观察底层数据的变化来更新。此外,下游消费者可以进一步观察衍生状态本身。我们甚至可以将此数据流一直传送到显示数据的最终用户设备,从而构建可动态更新以反映数据更改并继续脱机工作的用户界面。
|
||||
接下来,我们讨论了如何确保所有这些处理在出现故障时保持正确。我们看到强大的完整性保证可以通过异步事件处理可视化地实现,通过使用端到端操作标识符使操作幂等并异步检查约束。客户可以等到检查通过,或者不用等待就行,但是可能会有违反约束的道歉风险。这种方法比使用分布式事务的传统方法更具可扩展性和可靠性,并且适合于实践中有多少业务流程工作。
|
||||
|
||||
通过构建围绕数据流的应用程序并同步检查约束条件,我们可以避免大多数协调,并创建维护完整性但仍能很好地运行的系统,即使在地理分布的情况下和出现故障时也是如此。然后,我们谈了一些关于使用审计来验证数据的完整性并检测腐败的问题。
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
本书的最后一部分,会研究将多个不同数据系统(可能有着不同数据模型,并针对不同的访问模式进行优化)集成为一个协调一致的应用架构时,会遇到的问题。软件供应商经常会忽略这一方面的生态建设,并声称他们的产品能够满足你的所有需求。在现实世界中,集成不同的系统是实际应用中最重要的事情之一。
|
||||
|
||||
## 记录和派生数据系统
|
||||
## 记录和衍生数据系统
|
||||
|
||||
从高层次上看,存储和处理数据的系统可以分为两大类:
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
并不是所有的系统都在其架构中明确区分**记录系统**和**衍生数据系统**,但是这是一种有用的区分方式,因为它明确了系统中的数据流:系统的哪一部分具有哪些输入和哪些输出,以及它们如何相互依赖。
|
||||
|
||||
大多数数据库,存储引擎和查询语言,本质上既不是记录系统也不是派生系统。数据库只是一个工具:如何使用它取决于你自己。**记录系统和衍生数据系统之间的区别不在于工具,而在于应用程序中的使用方式。**
|
||||
大多数数据库,存储引擎和查询语言,本质上既不是记录系统也不是衍生系统。数据库只是一个工具:如何使用它取决于你自己。**记录系统和衍生数据系统之间的区别不在于工具,而在于应用程序中的使用方式。**
|
||||
|
||||
通过梳理数据的派衍生关系,可以清楚地理解一个令人困惑的系统架构。这将贯穿本书的这一部分。
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user