commit d05529b73cb625967d33d7c34834f39d8f0247b1 Author: Vonng Date: Thu Feb 8 14:07:06 2018 +0800 init commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..c7fe13a --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +# db + +> 不懂数据库的全栈工程师不是好架构师 +> +> —— Vonng + + + +## Data System + +数据系统"学习笔记":[DDIA](ddia/README.md) + +#### [I. 数据系统基础](ddia/part-i.md) + +1. [可靠性、可扩展性、可维护性](ddia/ch1.md) +2. [数据模型与查询语言](ddia/ch2.md) +3. [存储与检索](ddia/ch3.md) +4. [编码与演化](ddia/ch4.md) + +#### [II. 分布式数据](ddia/part-ii.md) + +5. [复制](ddia/ch5.md) +6. [分片](ddia/ch6.md) +7. [事务](ddia/ch7.md) +8. [分布式系统的麻烦](ddia/ch8.md) +9. [一致性与共识](ddia/ch9.md) + +#### [III. 派生数据](ddia/part-iii.md) + +10. [批处理](ddia/ch10.md) +11. [流处理](ddia/ch11.md) +12. [数据系统的未来](ddia/ch12.md) + + + + +## [PostgreSQL](pg/) + +PostgreSQL是世界上最先进的开源关系型数据库! + +### SQL + +* [PostgreSQL中的锁](note/pg-sql-lock.md) + +### GIS + +* PostGIS教程 + +### Adminstration + +- [Installation](pg/pg-admin-install.md) +- [Setup](pg/pg-admin-setup.md) +- [Configuration](pg/pg-admin-config.md) +- [Authentication](pg/pg-admin-auth.md) +- [PostgreSQL 监控](pg/pg-admin-monitor.md) +- [PostgreSQL备份与恢复](pg/pg-admin-backup.md) +- [PostgreSQL WAL与检查点](pg/pg-admin-wal.md) +- [PostgreSQL 高可用](pg/pg-admin-ha.md) + +### Tunning + +* [Memory Tunning](pg/pg-tune-memory.md) +* [Checkpoint Tuning Basic](pg/pg-tune-checkpoint) +* [Autovacuum Tuning Basics](pg/pg-tune-autovacuum.md) + +### Extension + +* [PostGIS Installation](pg/ext-postgis-install.md) +* [FileFDW Usage](pg/ext-file_fdw-intro.md) +* [RedisFDW Installation](pg/ext-redis_fdw-install.md) +* [MongoFDW Installation](pg/ext-mongo_fdw-install.md) +* [PgAdmin Installation](pg/ext-pgadmin-install.md) + + +### Reference + +* [PostgreSQL 9.6 中文文档](http://www.postgres.cn/docs/9.6/) +* [PostgreSQL 10.1 官方文档](https://www.postgresql.org/docs/10/static/index.html) + + +* [PostGIS 2.4 官方文档](https://postgis.net/docs/manual-2.4/) + + +* [Introduction to PostGIS](http://workshops.boundlessgeo.com/postgis-intro/index.html) + + + +## License + +闷声发大财才是坠吼地。 \ No newline at end of file diff --git a/ddia/README.md b/ddia/README.md new file mode 100644 index 0000000..b158e72 --- /dev/null +++ b/ddia/README.md @@ -0,0 +1,117 @@ +# 《设计数据密集应用》 + +* 作者: Martin Kleppmann +* 原书名称:《Designing Data-Intensive Application》 +* 译者:冯若航 (fengruohang@outlook.com , http://vonng.com/about) + + +## 前言 + +> 在我们的社会中,技术是一种强大的力量。数据、软件、通信可以用于坏的方面:不公平的阶级固化,损害公民权利,保护既得利益集团。但也可以用于好的方面:让底层人民发出自己的声音,让每个人都拥有机会,避免灾难。本书献给所有将技术用于善途的人们。 + + + +> 计算是一种流行文化,流行文化鄙视历史。 流行文化关乎个体身份和参与感,与合作无关。它活在当下,也与过去和未来无关。 我认为大部分(为钱)写代码的人就是这样, 他们不知道他们的文化来自哪里。 +> +> ​ ——阿兰·凯接受Dobb博士的杂志采访时(2012年) + + + +## 目录 + +### [序言](preface.md) [机翻] + +### 第一部分: [数据系统的基石](part-i.md) + +1. [可靠性、可扩展性、可维护性](ch1.md) [初翻30%] + * 关于数据系统的思考 + * 可靠性 + * 可扩展性 + * 可维护性 +2. [数据模型与查询语言](ch2.md) [初翻] + * 关系模型与文档模型 + * 数据查询语言 + * 图数据模型 +3. [存储与检索](ch3.md) [初翻] + * 支撑数据库的数据结构 + * 分析还是事务处理? + * 列存储 +4. [编码与演化](ch4.md) [初翻] + * 编码数据的格式 + * 数据流的模型 + +### 第二部分: [分布式数据](part-ii.md) + +5. [复制](ch5.md) [机翻] + * 主从 + * 复制延迟带来的问题 + * 多主复制 + * 无主复制 +6. [分片](ch6.md) [机翻] + * 分片与复制 + * 键值对数据的分片 + * 分片与次级索引 + * 分片再平衡 + * 请求路由 +7. [事务](ch7.md) [机翻] + * 事务的棘手概念 + * ACID的含义 + * 弱隔离级别 + * 序列化能力 +8. [分布式系统的麻烦](ch8.md) [待翻] + * 故障与部分失效 + * 不可靠的网络 + * 不可靠的时钟 + * 知识、真相与谎言 +9. [一致性与共识](ch9.md) [待翻] + * 一致性保证 + * 线性一致性 + * 顺序保证 + * 分布式事务与共识 + +### 第三部分:[衍生数据](part-iii.md) + +10. [批处理](ch10.md) [未翻] + * 使用Unix工具的批处理 + * MapReduce和分布式文件系统 + * 后MapReduce时代 +11. [流处理](ch11.md) [未翻] + * 传递事件流 + * 流与数据库 + * 流处理 +12. [数据系统的未来](ch12.md) [未翻] + * 数据集成 + * 拆分数据库 + * 目标是正确性 + * 做正确的事情 + + ​ + + + +## 翻译计划 + +机翻:程序翻译,基本保留原书的组织结构,也是阅读原文,学习本书的过程。 + +初翻:人工修复格式错误和显著翻译错误,重新组织语言,保障基本的可读性。 + +精翻:确定一些术语的最终译法,保证可以流畅阅读,着力信达雅。 + +翻译完全看心情,但通常每周至少会完成一章的初翻。 + +阅读建议使用Typora,可以获得最好的阅览体验。 + + + +## 译者声明 + +纯粹出于学习目的与个人兴趣翻译,仅供交流讨论与个人学习使用,闷声发大财自己看就行,严禁用于商业目的与公开传播发行。侵删。目前尚无中文译本,有能力阅读英文书籍者请购买原版支持。 + +``` +《中华人民共和国著作权法》 +第四节 权利的限制 +第二十二条 在下列情况下使用作品,可以不经著作权人许可,不向其支付报酬,但应当指明作者姓名、作品名称,并且不得侵犯著作权人依照本法享有的其他权利: +(六)为学校课堂教学或者科学研究,翻译或者少量复制已经发表的作品,供教学或者科研人员使用,但不得出版发行; +``` + +![](img/title.png) \ No newline at end of file diff --git a/ddia/ch1.md b/ddia/ch1.md new file mode 100644 index 0000000..18780ed --- /dev/null +++ b/ddia/ch1.md @@ -0,0 +1,519 @@ +# 1.可靠性,可扩展性,可维护性 + +![](img/ch1.png) + +> 互联网做得太棒了,以至于多数人将它看作像海洋这样的天然资源,而不是什么人工产物。 这种规模的技术没出问题,上一次是什么时候了? +> +> ——阿兰·凯在接受Dobb博士杂志采访时(2012年) + +----------------------- + +[TOC] + +现今很多应用程序都是**数据密集型(data-intensive)**,而非**计算密集型(compute-intensive)**的。CPU很少成为这类应用的瓶颈,更大的问题通常来自数据量,数据的复杂性、以及数据的变更速度。 + +数据密集型应用通常由标准组件构建而成,标准组件提供了很多通用的功能:例如,许多应用程序需要: + +* 存储数据,以便自己或其他应用程序之后能再次找到。(数据库 database) + + +* 记住开销昂贵操作的结果,加快读取速度。(缓存 cache) + + +* 允许用户按关键字搜索数据,或以各种方式对数据进行过滤。(搜索索引 search indexes) + + +* 发送消息至其他进程,进行异步处理(流处理 stream processing) + + +* 定期压缩积累的大批量数据(批处理 batch processing) + + +如果这些功能听上去平淡无奇,那真让人心酸……。因为这些*数据系统(data system)*是如此成功的抽象,我们一直用着它们,却没有想太多。绝大多数工程师不会想从零开始编写存储引擎,开发应用时,数据库已经是足够完美工具了。 + +但事实并没有这么简单。不同的应用有不同的需求,所以数据库系统也是百花齐放,有着各式各样的特性。有很多不同的手段可以实现缓存,也有好几种方法可以搞定搜索索引,诸如此类。所以开发应用时,我们仍然需要弄清楚什么样的工具和方法最适合手头的工作。而当单个工具解决不了你的问题时,你会发现组合使用这些工具还是有难度的。 + +本书将是一趟关于数据系统原理、实践与应用的旅途,并讲述了设计数据密集型应用的方法。我们将探索不同工具之间的共性与特性,以及各自的实现原理。 + +本章将从探索我们所要实现的基础目标开始:可靠,可扩展、可维护的数据系统。我们将澄清这些词语的含义,概述考量这些目标的方法。并回顾一些后续章节所需的基础知识。在接下来的章节中我们将抽丝剥茧,研究设计数据密集型应用时可能遇到的设计决策。 + + + +## 关于数据系统的思考 + +通常认为,数据库,消息队列,缓存这些,是差异显著的工具分类。虽然数据库和消息队列表面上有一些相似性—— 都会存一段时间的数据。——但它们有迥然的访问模式,这意味着迥然的性能特征和迥然的实现。 + +那么为什么要把它们混为一谈,放在一个 **数据系统(data system)**的总称之下呢? + +近些年来出现了许多新的数据存储工具与数据处理工具。它们针对各种不同应用场景进行优化,不再适用于传统的类别[【1】][1]。类别之间的界限越来越模糊,例如:数据存储也被当成消息队列用(Redis),消息队列带有类似数据库的持久保证(Apache Kafka)。 + +其次,越来越多的应用程序有各式各样苛刻广泛的要求,单个工具不足以满足所有的数据处理和存储需求。取而代之的是,工作被拆分成一系列能被单个工具能高效完成的任务。并通过应用代码将这些工具缝合起来。 + +例如,如果把缓存和全文搜索功能从主数据库分离出来,多了一个应用管理的缓存(Memcached或类似品)和一个全文搜索服务器(例如Elasticsearch或Solr),那么使缓存/索引与主数据库保证同步通常是应用代码的责任。[图1-1]() 给出了这种架构可能的样子(细节将在后面的章节中详细介绍)。 + +![](img/fig1-1.png) + +**图1-1 组合使用多个组件的数据系统,一种可能的架构** + +当你将多个工具组合在一起提供服务时,服务的接口,或*应用程序编程接口(API, Application Programming Interface)*通常向客户端隐藏这些实现细节。现在,你基本上已经使用较小的通用组件创建了一个全新的,专用的数据系统。这个新的复合数据系统可能会提供特定的保证:例如,缓存在写入时作废或更新,以便外部客户端获取一致的结果。现在你不仅是应用程序开发人员,还是数据系统设计人员了。 + + + +设计数据系统或服务时可能会遇到很多棘手的问题。当系统出问题时,如何确保数据的正确性和完整性?当部分系统退化降级时,如何为客户提供始终如一的良好性能?当负载增加时,如何扩容应对?什么样的API才是好的API? + +影响数据系统设计的因素很多,包括参与人员的技能和经验,历史遗留问题,系统路径依赖,交付时限,单位对各种风险的容忍度,监管约束等。这些因素都需要具体问题具体分析。 + +本书着重讨论三个在大多数软件系统中都很重要的问题: + +***可靠性(Reliability)*** + +系统在困境(硬件故障、软件故障、人为错误)中仍可正常工作(正确完成功能,能并达到期望性能水准)。 + +***可扩展性(Scalability)*** + +有合理的办法应对系统的增长(数据量,流量、复杂性)(参阅“可伸缩性”) + +***可维护性(Maintainability)*** + +许多不同的人(工程师、运维)在不同的生命周期,都能在高效地在系统上工作(使系统保持现有行为,适应新的应用场景)。(参阅”可维护性“) + +人们经常追求这些词汇,却并没有清楚理解它们到底意味着什么。为了工程的严谨性,本章的剩余部分将探讨可靠性、可扩展性、可维护性的含义。为实现这些目标而使用的各种技术,架构和算法,将在后续的章节中研究。 + + + + +## 可靠性 + +人们对于一个东西是否可靠,都有一个直观的想法。对于可靠的软件,典型的期望包括: + +* 应用程序表现出用户所期望的功能。 +* 它可以允许用户犯错或以出乎意料的方式使用软件。 +* 在预期的负载和数据量下,性能满足应用场景的要求。 +* 系统可以防止未经授权的访问和滥用。 + +如果所有这些在一起意味着“正确工作”,那么我们可以把可靠性粗略理解为”即使出现问题,也能继续正确工作”。 + +可能出问题的东西叫做***故障(fault)***,能预料并应对故障的系统特性可称为***容错(fault-tolerant)***或***韧性(resilient)***。第一个术语可能会产生误导:它暗示可以让系统容忍所有可能的错误,实际中这是不可能的。如果整个地球(及其上的所有服务器)都被黑洞吞噬,容忍这种错误需要将网络托管到宇宙中。能不能通过这种预算就祝你好运了。所以在讲容错时,只有谈论*特定类型*的错误才有意义。 + +注意***故障(fault)***不同于***失效(failure)***[【2】][2]。**故障**通常定义为系统的一部分偏离其标准,而**失效**则是系统作为一个整体停止向用户提供服务。故障的概率不可能降到零,因此最好设计容错机制,以防止**故障**导致**失效**。本书们将介绍几种用不可靠的部件构建可靠系统的技术。 + +反直觉的是,在这类容错系统中,通过故意触发来**提高**故障率是有意义的。例如在没有警告的情况下随机地杀死单个进程。许多高危漏洞实际上由糟糕的错误处理导致的[【3】][3];通过故意引发故障,来确保容错机制不断运行并接受考验,从而提高故障自然发生时系统能正确处理的信心。Netflix *Chaos Monkey*[【4】][]就是这种方法的一个例子。 + +尽管比起**阻止错误(Prevent error)**,我们更倾向于容忍错误。但也有**预防胜于治疗**的情况(比如不存在治疗方法时)。安全问题就属于这种情况。例如,如果攻击者破坏了系统,并获取了敏感数据,这种事是撤销不了的。但是本书主要讨论的是可以恢复的故障类型,如下所述。 + +### 硬件故障 + +当我们想到系统失效的原因时,硬件故障总是第一个进入脑海。硬盘崩溃,内存出错,机房断电,有人拔错网线。任何与大型数据中心打交道的人都会告诉你,当拥有很多机器时,这些事情**总是**会发生的。 + +硬盘的平均无故障时间(MTTF, mean time to failure)据报道称约为10到50年[【5】][] [【6】][]。因此,在一个有一万个磁盘的存储集群上,期望上每天平均会有一个磁盘挂掉。 + +为了减少系统的故障率,第一反应通常是增加单个硬件的冗余度。磁盘可以组RAID,服务器可能有双路电源和热插拔CPU,数据中心可能有电池和柴油发电机作为后备电源。当某个组件挂掉时,换下来并由冗余组件取而代之。这种方法并不能完全防止因为硬件问题导致的系统失效,但它简单易懂,通常也足以让机器不间断运行多年。 + +直到最近,硬件冗余对于大多数应用来说已经足够了,它使单台机器完全失效变得相当罕见。只要你能快速地把备份恢复到新机器上,故障停机时间对于大多数应用程序都不算灾难性的。少部分需要高可用的应用会采用多个冗余副本。 + +但是,随着数据量和应用程序的计算需求的增加,越来越多的应用程序开始使用大量的机器,这会相应地增加硬件故障率。此外,在一些云平台(如亚马逊网络服务(AWS))中,虚拟机实例在没有警告的情况下变得不可用[7],这是因为平台旨在优先考虑单机可靠性的灵活性和弹性。 + +因此,通过优先使用软件容错技术或除了硬件冗余之外,还有一种趋向于可以容忍整个机器损失的系统。这样的系统还具有操作优势:如果需要重启机器(例如应用操作系统安全补丁),则单服务器系统需要计划的停机时间,而可以容忍机器故障的系统可以一次修补一个节点没有整个系统的停机时间(滚动升级;参见第4章)。 + +### 软件错误 + +我们通常认为硬件故障是随机的,相互独立的:一台机器的磁盘失败并不意味着另一台机器的磁盘将会失败。可能存在较弱的相关性(例如,由于常见原因,例如服务器机架中的温度),否则大量硬件组件不可能同时发生故障。 +另一类错误是系统内部的系统错误[8]。这样的错误很难预测,而且由于它们在节点间相互关联,所以往往比不相关的硬件故障造成更多的系统故障[5]。例子包括: + +* 给定特定的错误输入时,导致应用程序服务器的每个实例崩溃的软件错误。例如,考虑到2012年6月30日的闰秒,由于Linux内核中的一个错误,导致许多应用程序同时挂起。 +* 失控进程会占用一些共享资源 - CPU时间,内存,磁盘空间或网络带宽。 +* 系统依赖的服务变慢,变为无响应,或者开始返回损坏的响应。 +* 级联故障,其中一个组件中的小故障触发另一个组件中的故障,进而触发进一步的故障[10]。 + +导致这类软件故障的错误通常会长时间处于休眠状态,直到被不寻常的情况触发为止。在这种情况下,显示出软件正在对其环境做出某种假设 - 虽然这种假设通常是正确的,但由于某种原因它最终会被否定[11]。 + +软件中的系统故障问题没有快速的解决方案。许多小事情可以帮助:仔细考虑系统中的假设和交互;彻底的测试;过程隔离;允许进程崩溃并重新启动;测量,监控和分析生产中的系统行为。如果一个系统需要提供一些保证(例如在一个消息队列中,输入消息的数量等于输出消息的数量),它可以在运行时不断检查自己,并在出现差异时发出警报被发现[12]。 + +### 人为错误 + +人类设计和建造软件系统,保持系统运行的操作人员也是人。即使他们有最好的意图,人类也是不可靠的。例如,一项关于大型互联网服务的研究发现,运营商的配置错误是导致中断的主要原因,而硬件故障(服务器或网络)仅在10-25%的中断中发挥作用[13]。 + +尽管人类不可靠,我们如何使我们的系统可靠?最好的系统结合了几种方法: + +* 以最大限度地减少错误机会的方式设计系统。例如,精心设计的抽象,API和管理界面使得“做正确的事情”变得容易,并且阻止“错误的事情”。然而,如果接口过于严格,人们会绕过它们,否定它们的好处。这是一个棘手的平衡得到正确的。 +* 将人们犯最多错误的地方与可能导致失败的地方分开。特别是提供功能齐全的非生产性沙箱环境,使用户可以在不影响真实用户的情况下使用真实数据安全地探索和实验。 +* 从单元测试到全系统集成测试和手动测试,在各个层面进行彻底测试[3]。自动化测试广泛使用,易于理解,特别适用于覆盖在正常运行中很少出现的角落案例。 +* 允许从人为错误中快速轻松地恢复,以最大限度地减少故障情况下的影响。 例如,快速回滚配置更改,逐渐推出新代码(以便任何意外的错误只影响一小部分用户),并提供重新计算数据的工具(万一事实证明旧计算 是不正确的)。 +* 设置详细和明确的监控,如性能指标和错误率。 在其他工程学科中,这被称为遥测。 (一旦火箭离开了地面,遥测技术对于追踪发生的事情和理解失败是必不可少的。)监测可以向我们展示预警信号,并让我们检查是否有任何假设或限制是违反的。迟来。 发生问题时,度量标准对于诊断问题是非常有价值的。 +* 实施良好的管理实践和培训 - 一个复杂而重要的方面,超出了本书的范围。 + +### 可靠性有多重要? + +可靠性不仅仅针对核电站和空中交通管制软件,更多的普通应用也预计可靠运行。商业应用程序中的错误会导致生产力的损失(如果数据报告不完整,则会面临法律风险),而且电子商务网站的中断可能会导致收入损失和声誉受损。 + +即使在“非关键”应用中,我们也对用户负有责任。考虑一个父母,他们的所有照片和他们的孩子的视频存储在您的照片应用程序[15]。如果数据库突然被破坏,他们会感觉如何?他们会知道如何从备份恢复它? + +在某些情况下,我们可能选择牺牲可靠性来降低开发成本(例如,为未经证实的市场开发原型产品)或运营成本(例如,利润率极低的服务),但是我们应该非常清楚我们什么时候偷工减料。 + + + +## 可扩展性 + +即使系统今天运行稳定,但这并不意味着未来一定能够可靠运行。降级的一个常见原因是负载增加:或许系统已经从10,000个并发用户增加到100000个并发用户,或者从100万增加到1000万。也许正在处理的数据量比以前大得多。 + +可伸缩性(Scalability)是我们用来描述系统应对负载增加的能力的术语。但是请注意,这不是我们可以附加到系统上的一维标签:说“X可伸缩”或“Y不能缩放”是没有意义的。相反,讨论可扩展性意味着考虑如“如果系统以特定方式增长,我们有什么选择来应对增长?”和“我们如何增加计算资源来处理额外的负载?”等问题。 + +### 描述负载 + +首先,我们需要简洁地描述系统的当前负载。只有这样我们才能讨论增长问题(如果我们的负荷加倍,会发生什么?)。负载可以用一些我们称之为负载参数的数字来描述。参数的最佳选择取决于系统的体系结构:它可能是每秒向Web服务器发出的请求,数据库中的读写比率,聊天室中同时活动的用户数量,缓存或其他东西。也许平均情况对你来说很重要,或者你的瓶颈主要是少数极端情况。 + +为了使这个想法更加具体,我们以2012年11月发布的数据[16]为例,以Twitter为例。 Twitter的两个主要业务是: + +* 发布推文:用户可以向其追随者发布新消息(平均每秒4.6k个请求/秒,峰值超过12k个请求/秒)。 + + +* 主页时间线:用户可以查看他们关注的人发布的推文(300k请求/秒)。 + +简单地处理每秒12,000次写入(发布推文的最高速率)将是相当容易的。然而,Twitter的扩张挑战并不是主要由于推特量,而是由扇出(fan-out:从电子工程中借用的一个术语,它描述了连接到另一个门输出的逻辑门输入的数量。 输出需要提供足够的电流来驱动所有连接的输入。 在事务处理系统中,我们使用它来描述为了服务一个传入请求而需要做的其他服务的请求数量。), 每个用户跟随很多人,每个用户跟着很多人。大致有两种方法来实现这两个操作: + +1. 发布推文只需将新推文插入推文的全局集合即可。当用户请求他们的主页时间线时,查找他们关注的所有人,为每个用户查找所有推文,并合并(按时间排序)。在如图1-2所示的关系数据库中,可以编写如下查询: + +```sql +SELECT tweets.*, users.* +FROM tweets +JOIN users ON tweets.sender_id = users.id +JOIN follows ON follows.followee_id = users.id +WHERE follows.follower_id = current_user +``` + +为每个用户的主页时间线维护一个缓存,例如每个收件人用户的推文信箱(见图1-3)。 当用户发布tweet时,查找所有关注该用户的人,并将新的tweet插入到每个主页时间线缓存中。 阅读主页时间表的请求便宜,因为其结果是提前计算的。 + +![](img/fig1-2.png) + +**图1-2 推特主页时间线的关系型模式简单实现** + +![](img/fig1-3.png) + +**图1-2 用于分发推特至关注者的数据流水线,带有负载参数(2012年11月)** + +Twitter的第一个版本使用了方法1,但系统努力跟上主页时间线查询的负载,所以公司转向了方法2.这更好地发挥作用,因为发布的推文的平均比率几乎比主页时间线查询频率低了两个数量级,所以在这种情况下,最好在写入时间做更多的工作,而在读取时间做更少的工作。 + +然而,方法2的缺点是发布推文现在需要大量的额外工作。平均来说,一条推特被发送到约75个追随者,所以每秒4.6k的推文变成主页时间线缓存每秒345k的写入。但是这个平均值隐藏了每个用户的关注者数量与一些用户差异很大的事实 + +有超过三千万的追随者。这意味着一个推特可能会导致超过3000万的写入时间表!及时做到这一点 - Twitter试图在5秒内向粉丝发送推文 - 是一个重大的挑战。 +在Twitter的例子中,每个用户的关注者分布(可能是这些用户发微博的频率)是讨论可伸缩性的关键负载参数,因为它决定了扇出负载。您的应用程序可能具有非常不同的特征,但您可以应用相似的原则来推理其负载。 + +推特轶事的最后一个转折:现在,方法2 健壮的实施了,Twitter正在转向两种方法的混合。大多数用户的推文在发布的时候仍然是在主页时间线上,但是很少有粉丝(即名人)的用户被排除在外。用户可能关注的任何名人的推文都会单独提取,并在阅读时与用户的家庭时间线合并,如方法1所示。这种混合方法能够持续提供良好的性能。我们将在第12章重新讨论这个例子,因为我们已经覆盖了更多的技术层面。 + +### 描述性能 + +一旦描述了系统的负载,就可以调查负载增加时发生的情况。你可以用两种方法来看它: + +* 增加负载参数并保持系统资源(CPU,内存,网络带宽等)不变时,系统性能如何受影响? +* 当您增加一个负载参数时,如果要保持性能不变,您需要增加多少资源? + +这两个问题都需要性能数字,所以让我们简单地看一下系统的性能。 + +在像Hadoop这样的批处理系统中,我们通常关心吞吐量 - 每秒处理的记录数量,或者在一定数量的数据集上运行作业的总时间.(理想情况下,批量作业的运行时间是数据集的大小除以吞吐量。 在实践中,由于歪斜(数据不是均匀分布在工作进程中),而且需要等待最慢的任务完成,所以运行时间往往更长)在在线系统中,通常是什么更重要的是服务的响应时间 - 也就是客户端发送请求和接收响应之间的时间。 + + + +#### 延迟和响应时间 + +延迟(Latency)和响应时间(Response Time)通常用作同义词,但它们并不相同。响应时间是**客户看到的**:除了处理请求的实际时间(服务时间)之外,还包括网络延迟和排队延迟。**延迟是一个请求等待处理的时间** - 在这个时间内,它是潜在的,等待服务[17]。 + +即使你只是一次又一次地提出相同的请求,每次尝试都会得到一个稍微不同的响应时间。实际上,在处理各种请求的系统中,响应时间可能会有很大差异。因此,我们需要将响应时间视为一个单一的数字,而不是一个可以衡量的价值分布。 + +在图1-4中,每个灰色条表示对服务的请求,其高度表示请求花了多长时间。大多数请求是相当快的,但是偶尔出现的异常值需要更长的时间。也许缓慢的请求本质上更昂贵,例如,因为它们处理更多的数据。但是即使在你认为所有的请求都要花费同样的时间的情况下,你也会得到一些变化:上下文切换到后台进程可能引入随机的附加延迟,网络数据包丢失和TCP重传,垃圾收集暂停,强制从磁盘读取的页面错误,服务器机架[18]中的机械振动或许多其他原因。 + +![](img/fig1-4.png) + +**图1-4 展示了一个服务100次请求响应时间的均值与百分位数** + +通常看到报告的服务的平均响应时间。 (严格地说,“平均”一词并不是指任何特定的公式,但实际上它通常被理解为算术平均值:给定n 个值,加起来所有的值,除以n)。然而,平均值如果你想知道你的“典型”响应时间,那么它不是一个很好的指标,因为它不能告诉你有多少用户实际上经历了这个延迟。 + +通常使用百分比更好。如果将响应时间列表从最快到最慢排序,那么中值是中间值:例如,如果您的中值响应时间是200毫秒,这意味着一半请求的返回时间少于200毫秒,一半你的要求比这个要长。 + +如果您想知道用户通常需要等待多长时间,那么这使中间值成为一个好的度量标准:用户请求的一半服务时间少于中间响应时间,另一半服务时间比中间值长。中位数也被称为第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所示。 + +响应时间的高百分比(也称为尾部延迟 Tail Percentil)非常重要,因为它们直接影响用户的服务体验。例如,亚马逊描述了内部服务的响应时间要求,**以百分之九十九为单位,即使只影响一千个请求中的一个**。这是因为要求最慢的客户往往是那些账户数据最多的客户,因为他们进行了大量的采购 - 也就是说,他们是最有价值的客户[19]。通过确保网站快速发展,让客户满意是非常重要的:亚马逊还观察到,响应时间增加了100毫秒,销售量减少了1%[20],而另一些人则报告说,1秒钟的减速会减少客户 - 收敛度为16%[21,22]。 + +另一方面,优化第99.99个百分点(10000个请求中最慢的1个)被认为太昂贵,并且不能为亚马逊的目的带来足够的好处。以非常高的百分比来减少响应时间是困难的,因为它们很容易受到您控制之外的随机事件的影响,并且好处正在减少。 + +例如,百分比通常用于服务级别目标(SLO)和服务级别协议(SLA),即定义服务的预期性能和可用性的合同。 SLA可能会声明,如果服务的响应时间中位数小于200毫秒,并且在1秒内响应时间较长(如果响应时间较长,则可能会下降),则认为该服务已启动。可能需要至少99.9%的时间。这些指标为服务客户设定了期望值,并允许客户在SLA未达到的情况下要求退款。 + +排队延迟通常占高百分比响应时间的很大一部分。由于服务器只能并行处理少量的事务(例如,受其CPU核数量的限制),所以只需要少量缓慢的请求来阻止后续请求的处理,这种效果有时被称为头部阻塞。即使这些后续请求在服务器上快速处理,由于等待事先请求完成的时间,客户端将看到总体响应时间缓慢。由于这种影响,测量客户端的响应时间非常重要。 + +当为了测试系统的可扩展性而人为地产生负载时,产生负载的客户端需要不受响应时间的影响而不断发送请求。如果客户端在发送下一个请求之前等待先前的请求完成,那么这种行为会在测试中人为地保持队列的长度,而不是在实际中保持队列的长度,这会影响测量结果[23]。 + +> #### 实践中的百分位点 +> +> 在多重调用的后端服务里,高百分位数变得特别重要。即使并行调用,最终用户请求仍然需要等待最慢的并行呼叫完成。如图1-5所示,只需要一个缓慢的呼叫就可以使整个最终用户请求变慢。即使只有一小部分后端呼叫速度较慢,如果最终用户请求需要多个后端调用,则获得较慢调用的机会也会增加,因此较高比例的最终用户请求速度会变慢(效果称为尾部延迟放大[24])。 +> +> 如果您想将响应时间百分点添加到您的服务的监视仪表板,则需要持续有效地计算它们。例如,您可能希望在最近10分钟内保持请求响应时间的滚动窗口。每一分钟,您都会计算出该窗口中的中值和各种百分数,并将这些度量值绘制在图上。 +> +> 简单的实现是在时间窗口内保存所有请求的响应时间列表,并且每分钟对列表进行排序。如果对你来说效率太低,那么有一些算法能够以最小的CPU和内存成本(如正向衰减[25],t-digest [26]或HdrHistogram [27])来计算百分位数的近似值。请注意,平均百分比(例如,减少时间分辨率或合并来自多台机器的数据)在数学上没有意义 - 聚合响应时间数据的正确方法是添加直方图[28]。 + +![](img/fig1-5.png) + +**图1-5 当一个请求需要多个后端请求时,单个慢后端请求就会拖慢整个终端用请求** + + + +### 应对负荷的方法 + +现在我们已经讨论了用于描述测量性能的负载和度量的参数,我们可以开始认真讨论可伸缩性:即使当我们的负载参数增加一些时,我们如何保持良好的性能呢? + +适合于一个级别的负载的体系结构不太可能应付10倍的负载。如果您正在开发一个快速增长的服务,那么您可能需要重新考虑每个数量级负载增长的架构 - 或者甚至更多。 + +人们经常谈到 scale-up(垂直扩展,转向更强大的机器)和scale-out(水平扩展,将负载分配到多个小型机器)之间的矛盾。在多台机器上分配负载也称为“无共享(shared-nothing)”体系结构。可以在一台机器上运行的系统通常更简单,但是高端机器可能变得非常昂贵,所以非常密集的工作量通常无法避免向外扩展。实际上,优秀的体系结构通常包含一些实用的方法:例如,使用几个功能相当强大的机器可能比大量的小型虚拟机更简单,更便宜。 + +有些系统是弹性的,这意味着他们可以在检测到负载增加时自动添加计算资源,而其他系统则是手动扩展(人工分析容量并决定向系统添加更多计算机)。如果负载高度不可预测,则弹性系统可能非常有用,但手动缩放系统更简单,并且可能具有更少的操作意外(请参阅“重新平衡分区”第195页)。 + +在多台机器上分发无状态服务非常简单,从单一节点到分布式设置的状态数据系统可能会带来很多额外的复杂性。出于这个原因,直到最近,人们普遍认为将数据库保持在单个节点上(扩展),直到缩放成本或高可用性要求迫使您将其改为分布式的。 + +随着分布式系统的工具和抽象变得越来越好,这种常识可能会改变,至少对于某些类型的应用来说。可以想象,分布式数据系统将成为未来的默认设置,即使对于不处理大量数据或流量的用例也是如此。在本书的其余部分中,我们将介绍多种分布式数据系统,并讨论它们不仅在可伸缩性方面的表现,还包括易用性和可维护性。 +大规模运行的系统体系结构通常对应用程序具有高度的特定性 - 没有像通用的,一刀切的可扩展体系结构(非正式地称为魔力缩放magic scaling sauce )这样的事物。问题可能是读取量,写入量,要存储的数据量,数据的复杂程度,响应时间要求,访问模式,或者(通常)所有这些的混合物以及更多的问题。 + +例如,设计用于处理每秒100,000个请求(每个大小为1 kB)的系统与为每分钟3个请求(每个大小为2 GB)设计的系统看起来非常不同,即使两个系统的大小相同数据吞吐量。 + +一个适合特定应用的体系结构是围绕着哪些操作是常见的,哪些是负载参数是罕见的。如果这些假设结果是错误的,那么缩放的工程努力至多是浪费的,最糟糕的是适得其反。在早期阶段的初创公司或非正式的产品中,能够快速迭代产品特征比扩展到假设的未来负载更重要。 + +尽管它们是特定于特定应用程序的,但可扩展架构通常是从通用构建模块构建而成,并以熟悉的模式排列。在本书中,我们将讨论这些构件和模式。 + + + +## 可维护性 + +众所周知,软件的大部分成本并没不在最初的开发阶段,而是在于持续的维护修复漏洞:保持系统正常运行,调查故障,适应新的平台,修改新的用例,偿还技术债务,增加新的功能。 + +然而不幸的是,许多从事软件系统工作的人不喜欢维护所谓的遗留系统 - 也许涉及修复其他人的错误或处理已经过时的平台,或者被迫做从未有意为之的系统。每一个遗留系统都是以自己的方式让人不爽,所以很难给出一个一般的建议来处理它们。 + +但是,我们可以也应该设计软件,以便在维护期间尽可能减少痛苦,从而避免自己创建传统软件。为此,我们将特别关注软件系统的三个设计原则: + +* 可操作性 + + 方便运营团队保持系统平稳运行。 + +* 简单 + + 通过从系统中消除尽可能多的复杂性,使新工程师能够轻松理解系统。 (注意这与用户界面的简单性不一样。) + + 可进化 + + 使工程师能够轻松地对将来的系统进行更改,并根据需求变化将其适用于意外的用例。也被称为可扩展性,可修改性或可塑性。 + + +正如以前的可靠性和可扩展性一样,实现这些目标也没有简单的解决方案。相反,我们会考虑可操作性,简单性和可演化性的系统。 + +### 可操作性:关爱运维 + +有人认为,“良好的运维经常可以解决不好的(或不完整的)软件的局限性,再好的系统也架不住垃圾运维。尽管运维的某些方面可以而且应该是自动化的,但首先要确保自动化的正确性,然后由人来完成。 + +运维团队对于保持软件系统顺利运行至关重要。一个优秀的运维团队通常负责以下内容,以及更多[29]: + +* 监控系统的运行状况,并在服务状况不佳时快速恢复服务 +* 追踪问题的原因,例如系统故障或性能下降 +* 保持软件和平台保持最新状态,包括安全补丁 +* 了解不同的系统如何相互影响,以便在造成损害之前避免有问题的更改 +* 预测未来的问题并在问题出现之前加以解决(例如扩容计划) +* 建立部署,配置管理等方面的良好实践和工具 +* 执行复杂的维护任务,例如将应用程序从一个平台移动到另一个平台 +* 随着配置更改,维护系统的安全性 +* 定义使操作可预测的流程,并帮助保持生产环境稳定 +* 保持组织对系统的了解,即使是个人来来去去 + +良好的可操作性意味着使日常工作变得简单,使运营团队能够专注于高价值的活动。数据系统可以做各种事情,使日常任务变得简单,包括: + +* 提供对系统的运行时行为和内部的可视性,并具有良好的监控能力 +* 为自动化和与标准工具的集成提供良好的支持 +* 避免依赖个别机器(在整个系统继续不间断运行的情况下允许机器停机维护) +* 提供良好的文档和易于理解的操作模型(“如果我做X,Y会发生”) +* 提供良好的默认行为,还可以让管理员在需要时自由覆盖默认值 +* 在适当的情况下进行自我修复,并在需要时让管理员手动控制系统状态 +* 展现可预见的行为,最大限度地减少惊喜 + + + +### 简单性:管理复杂性 + +小型软件项目可以有简单而富有表现力的代码,但随着项目越来越大,它们往往变得非常复杂,难以理解。这种复杂性拖慢了每个需要在系统上工作的人员,进一步增加了维护成本。一个陷入复杂的软件项目有时被描述为一个大泥潭[30]。 + +复杂性有各种可能的症状:状态空间的爆炸,模块的紧密耦合,纠结的依赖关系,不一致的命名和术语,旨在解决性能问题的黑客攻击,解决其他问题的特殊框架等等。已经有很多关于这个话题的说法[31,32,33]。 + +当复杂性使维护困难时,预算和时间安排通常会超支。在复杂的软件中,当发生变化时,引入错误的风险也更大:系统开发人员难以理解和推理时,隐藏的假设,意想不到的后果和意外的交互更容易被忽略。相反,降低复杂性大大提高了软件的可维护性,因此简单性应该是我们构建系统的关键目标。 + +简化系统并不一定意味着减少其功能;它也意味着消除意外的复杂性。 Moseley和Marks [32]把复杂性定义为偶然的,如果软件解决的问题不是固有的(用户看到的),而只是由实现产生的。 + +我们用来消除意外复杂性的最好工具之一是抽象。一个好的抽象可以隐藏大量的实现细节在一个干净,简单易懂的外观背后。一个好的抽象也可以用于各种不同的应用程序。这不仅是重复使用效率比多次重复实现类似的东西更高效,而且还会导致更高质量的软件,因为抽象组件中的质量改进将有利于所有使用它的应用程序。 + +例如,高级编程语言是隐藏机器代码,CPU寄存器和系统调用的抽象。 SQL是隐藏复杂的磁盘和内存数据结构, + +来自其他客户端的并发请求以及崩溃之后的不一致的抽象。当然,在用高级语言编程时,我们仍然使用机器码;我们只是不直接使用它,因为编程语言抽象使我们不必考虑它。 + +但是,找到好的抽象是非常困难的。在分布式系统领域,虽然有许多好的算法,但是我们应该如何将它们打包成抽象,这样就不那么清楚了,这些抽象可以帮助我们将系统的复杂性保持在可管理的水平。 + +在整本书中,我们将继续睁大眼睛来看好抽象,从而使我们能够将大型系统的一部分抽象成定义明确,可重用的组件。 + + + +### 可演化性:易于改变 + +你的系统的需求不会永远保持不变。他们更有可能处于不断变化的状态:您学习新的事实,之前出现意想不到的用例,业务优先级发生变化,用户请求新功能,新平台取代旧平台,法律或监管要求发生变化,系统增长强迫架构发生变化等 + +在组织流程方面,敏捷工作模式为适应变化提供了一个框架。敏捷社区还开发了技术工具和模式,这些工具和模式在频繁变化的环境中开发软件时很有帮助,如测试驱动开发(TDD)和重构。 + +这些敏捷技术的大部分讨论都集中在相当小的本地规模(同一个应用程序中的源代码文件)。在本书中,我们将探索在更大的数据系统层面上提高敏捷性的方法,可能由几个不同特性的应用程序或服务组成。例如,您将如何“重构”Twitter的架构来将Home Time从方法1重构为方法2? + +您可以轻松修改数据系统并使其适应不断变化的需求,这与其简单性和抽象性密切相关:简单易懂的系统通常比复杂系统更容易修改。但是由于这是一个非常重要的想法,我们将用一个不同的词来指代数据系统层面的敏捷性:可进化性[34]。 + + + +## 本章小结 + +在本章中,我们探讨了一些关于数据密集型应用程序的基本思路。这些原则将指导我们阅读本书的其余部分,在这里我们深入技术细节。 + +一个应用程序必须满足各种要求才能有用。有一些功能需求(它应该做什么,比如允许以各种方式存储,检索,搜索和处理数据)以及一些非功能性需求(一般属性如安全性,可靠性,合规性,可伸缩性,兼容性和可维护性)。在本章中,我们详细讨论了可靠性,可扩展性和可维护性。 + +**可靠性**意味着即使发生故障,也能使系统正常工作。故障可以是硬件(通常是随机的和不相关的),软件(缺陷通常是系统的,难以处理的),以及人类(不可避免地会不时出错)。容错技术可以隐藏最终用户的某些类型的故障。 + +**可扩展性**意味着即使在负载增加的情况下也有保持性能的策略。为了讨论可扩展性,我们首先需要定量描述负载和性能的方法。我们简单地将Twitter的家庭时间表作为描述负载的一个例子,并将响应时间百分比作为衡量每个时间段的一种方式。在可扩展的系统中,您可以添加处理能力以在高负载下保持可靠。 + +**可维护性**有许多方面,但实质上是为需要使用该系统的工程和运营团队提供更好的生活。良好的抽象可以帮助降低复杂性,并使系统更易于修改和适应新的用例。良好的可操作性意味着对系统的健康具有良好的可见性,并具有有效的管理方式。 + +不幸的是,为了使应用程序可靠,可扩展或可持续,并不容易。但是,某些模式和技术会不断出现在不同的应用程序中。在接下来的几章中,我们将看看数据系统的一些例子,并分析它们如何实现这些目标。 +在本书后面的第三部分中,我们将看看由几个组件组成的系统的模式,比如图1-1中的组件。 + + + + + +## 参考文献 + +[1]: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.68.9136&amp;amp;amp;amp;rep=rep1&amp;amp;amp;amp;type=pdf "Michael Stonebraker and Uğur Çetintemel: “'One Size Fits All': An Idea Whose Time Has Come and Gone,” at *21st International Conference on Data Engineering* (ICDE), April 2005." +[2]: http://www.sei.cmu.edu/reports/92tr033.pdf "Walter L. Heimerdinger and Charles B. Weinstock: “A Conceptual Framework for System Fault Tolerance,” Technical Report CMU/SEI-92-TR-033, Software Engineering Institute, Carnegie Mellon University, October 1992." +[3]: https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf "Ding Yuan, Yu Luo, Xin Zhuang, et al.: “Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems,” at 11th USENIX Symposium on Operating Systems Design and Implementation (OSDI), October 2014." +[4]: http://techblog.netflix.com/2011/07/netflix-simian-army.html "Yury Izrailevsky and Ariel Tseitlin: “The Netflix Simian Army,” techblog.netflix.com, July 19, 2011." + + + +1. Walter L. Heimerdinger and Charles B. Weinstock: + “[A Conceptual Framework for System Fault Tolerance](http://www.sei.cmu.edu/reports/92tr033.pdf),” Technical Report CMU/SEI-92-TR-033, Software Engineering Institute, Carnegie + Mellon University, October 1992. + +2. Ding Yuan, Yu Luo, Xin Zhuang, et al.: + “[Simple Testing Can Prevent Most Critical Failures: An Analysis of Production Failures in Distributed Data-Intensive Systems](https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-yuan.pdf),” at *11th USENIX Symposium on Operating Systems Design + and Implementation* (OSDI), October 2014. + +3. Yury Izrailevsky and Ariel Tseitlin: + “[The Netflix Simian Army](http://techblog.netflix.com/2011/07/netflix-simian-army.html),” + *techblog.netflix.com*, July 19, 2011. + +4. Daniel Ford, François Labelle, Florentina I. Popovici, et al.: + “[Availability in Globally Distributed Storage Systems](http://research.google.com/pubs/archive/36737.pdf),” + at *9th USENIX Symposium on Operating Systems Design and Implementation* (OSDI), + October 2010. + +5. Brian Beach: + “[Hard Drive Reliability Update – Sep 2014](https://www.backblaze.com/blog/hard-drive-reliability-update-september-2014/),” *backblaze.com*, September 23, 2014. + +6. Laurie Voss: + “[AWS: The Good, the Bad and the Ugly](https://web.archive.org/web/20160429075023/http://blog.awe.sm/2012/12/18/aws-the-good-the-bad-and-the-ugly/),” *blog.awe.sm*, December 18, 2012. + +7. Haryadi S. Gunawi, Mingzhe Hao, Tanakorn + Leesatapornwongsa, et al.: “[What Bugs Live in the Cloud?](http://ucare.cs.uchicago.edu/pdf/socc14-cbs.pdf),” at *5th ACM Symposium on Cloud Computing* (SoCC), November 2014. + [doi:10.1145/2670979.2670986](http://dx.doi.org/10.1145/2670979.2670986) + +8. Nelson Minar: + “[Leap Second Crashes Half the Internet](http://www.somebits.com/weblog/tech/bad/leap-second-2012.html),” *somebits.com*, July 3, 2012. + +9. Amazon Web Services: + “[Summary of the Amazon EC2 and Amazon RDS Service Disruption in the US East Region](http://aws.amazon.com/message/65648/),” *aws.amazon.com*, April 29, 2011. + +10. Richard I. Cook: + “[How Complex Systems Fail](http://web.mit.edu/2.75/resources/random/How%20Complex%20Systems%20Fail.pdf),” Cognitive Technologies Laboratory, April 2000. + +11. Jay Kreps: + “[Getting Real About Distributed System Reliability](http://blog.empathybox.com/post/19574936361/getting-real-about-distributed-system-reliability),” *blog.empathybox.com*, March 19, 2012. + +12. David Oppenheimer, Archana Ganapathi, and David A. Patterson: + “[Why Do Internet Services Fail, and What Can Be Done About It?](http://static.usenix.org/legacy/events/usits03/tech/full_papers/oppenheimer/oppenheimer.pdf),” at *4th USENIX Symposium on + Internet Technologies and Systems* (USITS), March 2003. + +13. Nathan Marz: + “[Principles of Software Engineering, Part 1](http://nathanmarz.com/blog/principles-of-software-engineering-part-1.html),” *nathanmarz.com*, April 2, 2013. + +14. Michael Jurewitz: + “[The Human Impact of Bugs](http://jury.me/blog/2013/3/14/the-human-impact-of-bugs),” + *jury.me*, March 15, 2013. + +15. Raffi Krikorian: + “[Timelines at Scale](http://www.infoq.com/presentations/Twitter-Timeline-Scalability),” + at *QCon San Francisco*, November 2012. + +16. Martin Fowler: + *Patterns of Enterprise Application Architecture*. Addison Wesley, 2002. + ISBN: 978-0-321-12742-6 + +17. Kelly Sommers: + “[After all that run around, what caused 500ms disk latency even when we replaced physical server?](https://twitter.com/kellabyte/status/532930540777635840)” *twitter.com*, November 13, 2014. + +18. Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, et al.: + “[Dynamo: Amazon's Highly Available Key-Value Store](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf),” at *21st ACM Symposium on Operating + Systems Principles* (SOSP), October 2007. + +19. Greg Linden: + “[Make Data Useful](http://glinden.blogspot.co.uk/2006/12/slides-from-my-talk-at-stanford.html),” slides from presentation at Stanford University Data Mining class (CS345), December 2006. + +20. Tammy Everts: + “[The Real Cost of Slow Time vs Downtime](http://www.webperformancetoday.com/2014/11/12/real-cost-slow-time-vs-downtime-slides/),” *webperformancetoday.com*, November 12, 2014. + +21. Jake Brutlag: + “[Speed Matters for Google Web Search](http://googleresearch.blogspot.co.uk/2009/06/speed-matters.html),” *googleresearch.blogspot.co.uk*, June 22, 2009. + +22. Tyler Treat: + “[Everything You Know About Latency Is Wrong](http://bravenewgeek.com/everything-you-know-about-latency-is-wrong/),” *bravenewgeek.com*, December 12, 2015. + +23. Jeffrey Dean and Luiz André Barroso: + “[The Tail at Scale](http://cacm.acm.org/magazines/2013/2/160173-the-tail-at-scale/fulltext),” + *Communications of the ACM*, volume 56, number 2, pages 74–80, February 2013. + [doi:10.1145/2408776.2408794](http://dx.doi.org/10.1145/2408776.2408794) + +24. Graham Cormode, Vladislav + Shkapenyuk, Divesh Srivastava, and Bojian Xu: + “[Forward Decay: A Practical Time Decay Model for Streaming Systems](http://dimacs.rutgers.edu/~graham/pubs/papers/fwddecay.pdf),” at *25th IEEE International Conference on Data + Engineering* (ICDE), March 2009. + +25. Ted Dunning and Otmar Ertl: + “[Computing Extremely Accurate Quantiles Using t-Digests](https://github.com/tdunning/t-digest),” *github.com*, March 2014. + +26. Gil Tene: + “[HdrHistogram](http://www.hdrhistogram.org/),” *hdrhistogram.org*. + +27. Baron Schwartz: + “[Why Percentiles Don’t Work the Way You Think](https://www.vividcortex.com/blog/why-percentiles-dont-work-the-way-you-think),” *vividcortex.com*, December 7, 2015. + +28. James Hamilton: + “[On Designing and Deploying Internet-Scale Services](https://www.usenix.org/legacy/events/lisa07/tech/full_papers/hamilton/hamilton.pdf),” at *21st Large Installation + System Administration Conference* (LISA), November 2007. + +29. Brian Foote and Joseph Yoder: + “[Big Ball of Mud](http://www.laputan.org/pub/foote/mud.pdf),” at + *4th Conference on Pattern Languages of Programs* (PLoP), + September 1997. + +30. Frederick P Brooks: “No Silver Bullet – Essence and + Accident in Software Engineering,” in *The Mythical Man-Month*, Anniversary + edition, Addison-Wesley, 1995. ISBN: 978-0-201-83595-3 + +31. Ben Moseley and Peter Marks: + “[Out of the Tar Pit](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.93.8928),” + at *BCS Software Practice Advancement* (SPA), 2006. + +32. Rich Hickey: + “[Simple Made Easy](http://www.infoq.com/presentations/Simple-Made-Easy),” + at *Strange Loop*, September 2011. + +33. Hongyu Pei Breivold, Ivica Crnkovic, and Peter J. Eriksson: + “[Analyzing Software Evolvability](http://www.mrtc.mdh.se/publications/1478.pdf),” + at *32nd Annual IEEE International Computer Software and Applications Conference* + (COMPSAC), July 2008. + [doi:10.1109/COMPSAC.2008.50](http://dx.doi.org/10.1109/COMPSAC.2008.50) + diff --git a/ddia/ch10.md b/ddia/ch10.md new file mode 100644 index 0000000..9002f0a --- /dev/null +++ b/ddia/ch10.md @@ -0,0 +1,362 @@ +# 10. 批处理 + +![](img/ch10.png) + +带有太强个人色彩的系统无法成功。当第一版健壮的设计完成时,真正的考验才开始:许多不同观点的人会以他们自己的方式来测试。 + +——高德纳 + +--------------- + +[TOC] + + + +## 使用Unix工具的批处理 + +### 分析简单日志 + +### Unix哲学 + + + +## MapReduce和分布式文件系统 + +### MapReduce任务执行 + +![](img/fig10-1.png) + +**图10-1 具有三个Mapper和三个Reducer的MapReduce任务** + +### Reduce端连接与分组 + +![](img/fig10-2.png) + +**图10-2 用户行为日志与用户档案的连接** + + + +### Map端连接 + +![](img/fig10-3.png) + +**图10-3 Reduce端在user ID上进行归并排序连接,如果输入数据集分片成多个文件,则每个都会被多个Mapper并行处理** + +### 工作流的输出 + + + +## 后MapReduce时代 + +### 内部状态表示 + +### 图与迭代处理 + +### 高级API和语言 + + + +## 本章小结 + + + +## 参考文献 + +1. Jeffrey Dean and Sanjay Ghemawat: + “[MapReduce: Simplified Data Processing on Large Clusters](http://research.google.com/archive/mapreduce.html),” at *6th USENIX Symposium on Operating System Design + and Implementation* (OSDI), December 2004. + +1. Joel Spolsky: + “[The Perils of JavaSchools](http://www.joelonsoftware.com/articles/ThePerilsofJavaSchools.html),” *joelonsoftware.com*, December 25, 2005. + +1. Shivnath Babu and Herodotos Herodotou: + “[Massively Parallel Databases and MapReduce Systems](http://research.microsoft.com/pubs/206464/db-mr-survey-final.pdf),” *Foundations and Trends in Databases*, + volume 5, number 1, pages 1–104, November 2013. + [doi:10.1561/1900000036](http://dx.doi.org/10.1561/1900000036) + +1. David J. DeWitt and Michael Stonebraker: + “[MapReduce: A Major Step Backwards](https://homes.cs.washington.edu/~billhowe/mapreduce_a_major_step_backwards.html),” originally published at *databasecolumn.vertica.com*, January 17, 2008. + +1. Henry Robinson: + “[The Elephant Was a Trojan Horse: On the Death of Map-Reduce at Google](http://the-paper-trail.org/blog/the-elephant-was-a-trojan-horse-on-the-death-of-map-reduce-at-google/),” + *the-paper-trail.org*, June 25, 2014. + +1. “[The Hollerith Machine](https://www.census.gov/history/www/innovations/technology/the_hollerith_tabulator.html),” United States Census Bureau, *census.gov*. + +1. “[IBM 82, 83, and 84 Sorters Reference Manual](http://www.textfiles.com/bitsavers/pdf/ibm/punchedCard/Sorter/A24-1034-1_82-83-84_sorters.pdf),” Edition A24-1034-1, International Business + Machines Corporation, July 1962. + +1. Adam Drake: + “[Command-Line Tools Can Be 235x Faster than Your Hadoop Cluster](http://aadrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html),” *aadrake.com*, January 25, 2014. + +1. “[GNU Coreutils 8.23 Documentation](http://www.gnu.org/software/coreutils/manual/html_node/index.html),” Free Software Foundation, Inc., 2014. + +1. Martin Kleppmann: + “[Kafka, Samza, and the Unix Philosophy of Distributed Data](http://martin.kleppmann.com/2015/08/05/kafka-samza-unix-philosophy-distributed-data.html),” *martin.kleppmann.com*, August 5, 2015. + +1. Doug McIlroy: + [Internal Bell Labs memo](http://cm.bell-labs.com/cm/cs/who/dmr/mdmpipe.pdf), + October 1964. Cited in: Dennis M. Richie: + “[Advice from Doug McIlroy](https://www.bell-labs.com/usr/dmr/www/mdmpipe.html),” + *cm.bell-labs.com*. + +1. M. D. McIlroy, E. N. Pinson, and B. A. Tague: + “[UNIX Time-Sharing System: Foreword](https://archive.org/details/bstj57-6-1899),” + *The Bell System Technical Journal*, volume 57, number 6, pages 1899–1904, + July 1978. + +1. Eric S. Raymond: + *The Art of UNIX Programming*. + Addison-Wesley, 2003. ISBN: 978-0-13-142901-7 + +1. Ronald Duncan: + “[Text File Formats – ASCII Delimited Text – Not CSV or TAB Delimited Text](https://ronaldduncan.wordpress.com/2009/10/31/text-file-formats-ascii-delimited-text-not-csv-or-tab-delimited-text/),” + *ronaldduncan.wordpress.com*, October 31, 2009. + +1. Alan Kay: + “[Is 'Software Engineering' an Oxymoron?](http://tinlizzie.org/~takashi/IsSoftwareEngineeringAnOxymoron.pdf),” *tinlizzie.org*. + +1. Martin Fowler: + “[InversionOfControl](http://martinfowler.com/bliki/InversionOfControl.html),” + *martinfowler.com*, June 26, 2005. + +1. Daniel J. Bernstein: + “[Two File Descriptors for Sockets](http://cr.yp.to/tcpip/twofd.html),” *cr.yp.to*. + +1. Rob Pike and Dennis M. Ritchie: + “[The Styx Architecture for Distributed Systems](http://doc.cat-v.org/inferno/4th_edition/styx),” *Bell Labs Technical Journal*, volume 4, number 2, pages +146–152, April 1999. + +1. Sanjay Ghemawat, Howard Gobioff, and Shun-Tak + Leung: “[The Google File System](http://research.google.com/archive/gfs-sosp2003.pdf),” + at *19th ACM Symposium on Operating Systems Principles* (SOSP), October 2003. + [doi:10.1145/945445.945450](http://dx.doi.org/10.1145/945445.945450) + +1. Michael Ovsiannikov, Silvius Rus, Damian Reeves, et al.: + “[The Quantcast File System](http://db.disi.unitn.eu/pages/VLDBProgram/pdf/industry/p808-ovsiannikov.pdf),” *Proceedings of the VLDB Endowment*, volume 6, number 11, pages 1092–1101, August 2013. + [doi:10.14778/2536222.2536234](http://dx.doi.org/10.14778/2536222.2536234) + +1. “[OpenStack Swift 2.6.1 Developer Documentation](http://docs.openstack.org/developer/swift/),” OpenStack Foundation, *docs.openstack.org*, March 2016. + +1. Zhe Zhang, Andrew Wang, Kai Zheng, et al.: + “[Introduction to HDFS Erasure Coding in Apache Hadoop](http://blog.cloudera.com/blog/2015/09/introduction-to-hdfs-erasure-coding-in-apache-hadoop/),” *blog.cloudera.com*, September 23, 2015. + +1. Peter Cnudde: + “[Hadoop Turns 10](http://yahoohadoop.tumblr.com/post/138739227316/hadoop-turns-10),” + *yahoohadoop.tumblr.com*, February 5, 2016. + +1. Eric Baldeschwieler: + “[Thinking About the HDFS vs. Other Storage Technologies](http://hortonworks.com/blog/thinking-about-the-hdfs-vs-other-storage-technologies/),” *hortonworks.com*, July 25, 2012. + +1. Brendan Gregg: + “[Manta: Unix Meets Map Reduce](http://dtrace.org/blogs/brendan/2013/06/25/manta-unix-meets-map-reduce/),” *dtrace.org*, June 25, 2013. + +1. Tom White: *Hadoop: The Definitive Guide*, + 4th edition. O'Reilly Media, 2015. ISBN: 978-1-491-90163-2 + +1. Jim N. Gray: + “[Distributed Computing Economics](http://arxiv.org/pdf/cs/0403019.pdf),” Microsoft + Research Tech Report MSR-TR-2003-24, March 2003. + +1. Márton Trencséni: + “[Luigi vs Airflow vs Pinball](http://bytepawn.com/luigi-airflow-pinball.html),” + *bytepawn.com*, February 6, 2016. + +1. Roshan Sumbaly, Jay Kreps, and Sam Shah: + “[The 'Big Data' Ecosystem at LinkedIn](http://www.slideshare.net/s_shah/the-big-data-ecosystem-at-linkedin-23512853),” at *ACM International Conference on Management of Data* + (SIGMOD), July 2013. + [doi:10.1145/2463676.2463707](http://dx.doi.org/10.1145/2463676.2463707) + +1. Alan F. Gates, Olga Natkovich, Shubham Chopra, et al.: + “[Building a High-Level Dataflow System on Top of Map-Reduce: The Pig Experience](http://www.vldb.org/pvldb/2/vldb09-1074.pdf),” at *35th International Conference on Very Large Data + Bases* (VLDB), August 2009. + +1. Ashish Thusoo, Joydeep Sen Sarma, Namit Jain, et al.: + “[Hive – A Petabyte Scale Data Warehouse Using Hadoop](http://i.stanford.edu/~ragho/hive-icde2010.pdf),” at *26th IEEE International Conference on Data Engineering* (ICDE), March 2010. + [doi:10.1109/ICDE.2010.5447738](http://dx.doi.org/10.1109/ICDE.2010.5447738) + +1. “[Cascading 3.0 User Guide](http://docs.cascading.org/cascading/3.0/userguide/),” Concurrent, Inc., *docs.cascading.org*, January 2016. + +1. “[Apache Crunch User Guide](https://crunch.apache.org/user-guide.html),” Apache Software Foundation, *crunch.apache.org*. + +1. Craig Chambers, Ashish Raniwala, Frances + Perry, et al.: “[FlumeJava: Easy, Efficient Data-Parallel Pipelines](https://research.google.com/pubs/archive/35650.pdf),” at *31st ACM SIGPLAN Conference on Programming Language + Design and Implementation* (PLDI), June 2010. + [doi:10.1145/1806596.1806638](http://dx.doi.org/10.1145/1806596.1806638) + +1. Jay Kreps: + “[Why Local State is a Fundamental Primitive in Stream Processing](https://www.oreilly.com/ideas/why-local-state-is-a-fundamental-primitive-in-stream-processing),” *oreilly.com*, July 31, 2014. + +1. Martin Kleppmann: + “[Rethinking Caching in Web Apps](http://martin.kleppmann.com/2012/10/01/rethinking-caching-in-web-apps.html),” *martin.kleppmann.com*, October 1, 2012. + +1. Mark Grover, Ted Malaska, Jonathan + Seidman, and Gwen Shapira: *[Hadoop Application Architectures](http://shop.oreilly.com/product/0636920033196.do)*. O'Reilly Media, 2015. ISBN: 978-1-491-90004-8 + +1. Philippe Ajoux, Nathan Bronson, + Sanjeev Kumar, et al.: + “[Challenges to Adopting Stronger Consistency at Scale](https://www.usenix.org/system/files/conference/hotos15/hotos15-paper-ajoux.pdf),” at *15th USENIX Workshop on Hot Topics in + Operating Systems* (HotOS), May 2015. + +1. Sriranjan Manjunath: + “[Skewed Join](https://wiki.apache.org/pig/PigSkewedJoinSpec),” *wiki.apache.org*, + 2009. + +1. David J. DeWitt, Jeffrey F. Naughton, Donovan A. + Schneider, and S. Seshadri: “[Practical Skew Handling in Parallel Joins](http://www.vldb.org/conf/1992/P027.PDF),” at *18th International Conference on Very Large Data Bases* (VLDB), August 1992. + +1. Marcel Kornacker, Alexander Behm, Victor + Bittorf, et al.: “[Impala: A Modern, Open-Source SQL Engine for Hadoop](http://pandis.net/resources/cidr15impala.pdf),” at *7th Biennial Conference on Innovative Data Systems + Research* (CIDR), January 2015. + +1. Matthieu Monsch: + “[Open-Sourcing PalDB, a Lightweight Companion for Storing Side Data](https://engineering.linkedin.com/blog/2015/10/open-sourcing-paldb--a-lightweight-companion-for-storing-side-da),” *engineering.linkedin.com*, October 26, 2015. + +1. Daniel Peng and Frank Dabek: + “[Large-Scale Incremental Processing Using Distributed Transactions and Notifications](https://www.usenix.org/legacy/event/osdi10/tech/full_papers/Peng.pdf),” at *9th USENIX + conference on Operating Systems Design and Implementation* (OSDI), October 2010. + +1. “["Cloudera Search User Guide,"](http://www.cloudera.com/documentation/cdh/5-1-x/Search/Cloudera-Search-User-Guide/Cloudera-Search-User-Guide.html) Cloudera, Inc., September 2015. + +1. Lili Wu, Sam Shah, Sean Choi, et al.: + “[The Browsemaps: Collaborative Filtering at LinkedIn](http://ls13-www.cs.uni-dortmund.de/homepage/rsweb2014/papers/rsweb2014_submission_3.pdf),” at *6th Workshop on Recommender Systems and + the Social Web* (RSWeb), October 2014. + +1. Roshan Sumbaly, Jay Kreps, Lei Gao, et al.: + “[Serving Large-Scale Batch Computed Data with Project Voldemort](http://static.usenix.org/events/fast12/tech/full_papers/Sumbaly.pdf),” at *10th USENIX Conference on File and Storage + Technologies* (FAST), February 2012. + +1. Varun Sharma: + “[Open-Sourcing Terrapin: A Serving System for Batch Generated Data](https://engineering.pinterest.com/blog/open-sourcing-terrapin-serving-system-batch-generated-data-0),” *engineering.pinterest.com*, September 14, 2015. + +1. Nathan Marz: + “[ElephantDB](http://www.slideshare.net/nathanmarz/elephantdb),” *slideshare.net*, May 30, 2011. + +1. Jean-Daniel (JD) Cryans: + “[How-to: Use HBase Bulk Loading, and Why](http://blog.cloudera.com/blog/2013/09/how-to-use-hbase-bulk-loading-and-why/),” *blog.cloudera.com*, September 27, 2013. + +1. Nathan Marz: + “[How to Beat the CAP Theorem](http://nathanmarz.com/blog/how-to-beat-the-cap-theorem.html),” *nathanmarz.com*, October 13, 2011. + +1. Molly Bartlett Dishman and Martin Fowler: + “[Agile Architecture](http://conferences.oreilly.com/software-architecture/sa2015/public/schedule/detail/40388),” at *O'Reilly Software Architecture Conference*, March 2015. + +1. David J. DeWitt and Jim N. Gray: + “[Parallel Database Systems: The Future of High Performance Database Systems](http://www.cs.cmu.edu/~pavlo/courses/fall2013/static/papers/dewittgray92.pdf),” + *Communications of the ACM*, volume 35, number 6, pages 85–98, June 1992. + [doi:10.1145/129888.129894](http://dx.doi.org/10.1145/129888.129894) + +1. Jay Kreps: + “[But the multi-tenancy thing is actually really really hard](https://twitter.com/jaykreps/status/528235702480142336),” tweetstorm, *twitter.com*, October 31, 2014. + +1. Jeffrey Cohen, Brian Dolan, Mark Dunlap, et al.: “[MAD Skills: New Analysis Practices for Big Data](http://www.vldb.org/pvldb/2/vldb09-219.pdf),” *Proceedings of the VLDB Endowment*, volume 2, number + 2, pages 1481–1492, August 2009. + [doi:10.14778/1687553.1687576](http://dx.doi.org/10.14778/1687553.1687576) + +1. Ignacio + Terrizzano, Peter Schwarz, Mary Roth, and John E. Colino: + “[Data Wrangling: The Challenging Journey from the Wild to the Lake](http://cidrdb.org/cidr2015/Papers/CIDR15_Paper2.pdf),” at *7th Biennial Conference on Innovative Data Systems + Research* (CIDR), January 2015. + +1. Paige Roberts: + “[To Schema on Read or to Schema on Write, That Is the Hadoop Data Lake Question](http://adaptivesystemsinc.com/blog/to-schema-on-read-or-to-schema-on-write-that-is-the-hadoop-data-lake-question/),” *adaptivesystemsinc.com*, July 2, 2015. + +1. Bobby Johnson and Joseph Adler: + “[The Sushi Principle: Raw Data Is Better](https://vimeo.com/123985284),” at + *Strata+Hadoop World*, February 2015. + +1. Vinod Kumar Vavilapalli, Arun C. Murthy, Chris Douglas, et al.: + “[Apache Hadoop YARN: Yet Another Resource Negotiator](http://www.socc2013.org/home/program/a5-vavilapalli.pdf),” at *4th ACM Symposium on Cloud Computing* (SoCC), October 2013. + [doi:10.1145/2523616.2523633](http://dx.doi.org/10.1145/2523616.2523633) + +1. Abhishek Verma, Luis Pedrosa, Madhukar Korupolu, et al.: + “[Large-Scale Cluster Management at Google with Borg](http://research.google.com/pubs/pub43438.html),” at *10th European Conference on Computer Systems* (EuroSys), April 2015. + [doi:10.1145/2741948.2741964](http://dx.doi.org/10.1145/2741948.2741964) + +1. Malte Schwarzkopf: + “[The Evolution of Cluster Scheduler Architectures](http://www.firmament.io/blog/scheduler-architectures.html),” *firmament.io*, March 9, 2016. + +1. Matei Zaharia, Mosharaf Chowdhury, Tathagata Das, et al.: + “[Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing](https://www.usenix.org/system/files/conference/nsdi12/nsdi12-final138.pdf),” at *9th + USENIX Symposium on Networked Systems Design and Implementation* (NSDI), April 2012. + +1. Holden Karau, Andy Konwinski, Patrick Wendell, and Matei + Zaharia: *Learning Spark*. O'Reilly Media, 2015. ISBN: 978-1-449-35904-1 + +1. Bikas Saha and Hitesh Shah: + “[Apache Tez: Accelerating Hadoop Query Processing](http://www.slideshare.net/Hadoop_Summit/w-1205phall1saha),” at *Hadoop Summit*, June 2014. + +1. Bikas Saha, Hitesh Shah, Siddharth Seth, et al.: + “[Apache Tez: A Unifying Framework for Modeling and Building Data Processing Applications](http://home.cse.ust.hk/~weiwa/teaching/Fall15-COMP6611B/reading_list/Tez.pdf),” at *ACM + International Conference on Management of Data* (SIGMOD), June 2015. + [doi:10.1145/2723372.2742790](http://dx.doi.org/10.1145/2723372.2742790) + +1. Kostas Tzoumas: + “[Apache Flink: API, Runtime, and Project Roadmap](http://www.slideshare.net/KostasTzoumas/apache-flink-api-runtime-and-project-roadmap),” *slideshare.net*, January 14, 2015. + +1. Alexander Alexandrov, Rico Bergmann, Stephan Ewen, et al.: + “[The Stratosphere Platform for Big Data Analytics](https://ssc.io/pdf/2014-VLDBJ_Stratosphere_Overview.pdf),” *The VLDB Journal*, volume 23, number 6, pages 939–964, May 2014. + [doi:10.1007/s00778-014-0357-y](http://dx.doi.org/10.1007/s00778-014-0357-y) + +1. Michael Isard, Mihai Budiu, Yuan Yu, et al.: + “[Dryad: Distributed Data-Parallel Programs from Sequential Building Blocks](http://research.microsoft.com/en-us/projects/dryad/eurosys07.pdf),” at *European Conference on Computer + Systems* (EuroSys), March 2007. + [doi:10.1145/1272996.1273005](http://dx.doi.org/10.1145/1272996.1273005) + +1. Daniel Warneke and Odej Kao: + “[Nephele: Efficient Parallel Data Processing in the Cloud](https://stratosphere2.dima.tu-berlin.de/assets/papers/Nephele_09.pdf),” at *2nd Workshop on Many-Task Computing on Grids and + Supercomputers* (MTAGS), November 2009. + [doi:10.1145/1646468.1646476](http://dx.doi.org/10.1145/1646468.1646476) + +1. Lawrence Page, Sergey Brin, Rajeev + Motwani, and Terry Winograd: “The PageRank + +1. Leslie G. Valiant: + “[A Bridging Model for Parallel Computation](http://dl.acm.org/citation.cfm?id=79181),” + *Communications of the ACM*, volume 33, number 8, pages 103–111, August 1990. + [doi:10.1145/79173.79181](http://dx.doi.org/10.1145/79173.79181) + +1. Stephan Ewen, Kostas Tzoumas, Moritz Kaufmann, and Volker Markl: + “[Spinning Fast Iterative Data Flows](http://vldb.org/pvldb/vol5/p1268_stephanewen_vldb2012.pdf),” *Proceedings of the VLDB Endowment*, volume 5, number 11, pages 1268-1279, July 2012. + [doi:10.14778/2350229.2350245](http://dx.doi.org/10.14778/2350229.2350245) + +1. Grzegorz Malewicz, Matthew H. + Austern, Aart J. C. Bik, et al.: “[Pregel: A System for Large-Scale Graph Processing](https://kowshik.github.io/JPregel/pregel_paper.pdf),” at *ACM International Conference on Management of + Data* (SIGMOD), June 2010. + [doi:10.1145/1807167.1807184](http://dx.doi.org/10.1145/1807167.1807184) + +1. Frank McSherry, Michael Isard, and Derek G. Murray: + “[Scalability! But at What COST?](http://www.frankmcsherry.org/assets/COST.pdf),” at + *15th USENIX Workshop on Hot Topics in Operating Systems* (HotOS), May 2015. + +1. Ionel Gog, Malte Schwarzkopf, Natacha Crooks, et al.: + “[Musketeer: All for One, One for All in Data Processing Systems](http://www.cl.cam.ac.uk/research/srg/netos/camsas/pubs/eurosys15-musketeer.pdf),” at *10th European Conference on + Computer Systems* (EuroSys), April 2015. + [doi:10.1145/2741948.2741968](http://dx.doi.org/10.1145/2741948.2741968) + +1. Aapo Kyrola, Guy Blelloch, and Carlos Guestrin: + “[GraphChi: Large-Scale Graph Computation on Just a PC](https://www.usenix.org/system/files/conference/osdi12/osdi12-final-126.pdf),” at *10th USENIX Symposium on Operating Systems + Design and Implementation* (OSDI), October 2012. + +1. Andrew Lenharth, Donald Nguyen, and Keshav Pingali: + “[Parallel Graph Analytics](http://cacm.acm.org/magazines/2016/5/201591-parallel-graph-analytics/fulltext),” *Communications of the ACM*, volume 59, number 5, pages 78–87, May + 2016. [doi:10.1145/2901919](http://dx.doi.org/10.1145/2901919) + +1. Fabian Hüske: + “[Peeking into Apache Flink's Engine Room](http://flink.apache.org/news/2015/03/13/peeking-into-Apache-Flinks-Engine-Room.html),” *flink.apache.org*, March 13, 2015. + +1. Mostafa Mokhtar: + “[Hive 0.14 Cost Based Optimizer (CBO) Technical Overview](http://hortonworks.com/blog/hive-0-14-cost-based-optimizer-cbo-technical-overview/),” *hortonworks.com*, March 2, 2015. + +1. Michael Armbrust, Reynold S Xin, Cheng Lian, et al.: + “[Spark SQL: Relational Data Processing in Spark](http://people.csail.mit.edu/matei/papers/2015/sigmod_spark_sql.pdf),” at *ACM International Conference on Management of Data* (SIGMOD), June 2015. + [doi:10.1145/2723372.2742797](http://dx.doi.org/10.1145/2723372.2742797) + +1. Daniel Blazevski: + “[Planting Quadtrees for Apache Flink](http://insightdataengineering.com/blog/flink-knn/),” *insightdataengineering.com*, March 25, 2016. + +1. Tom White: + “[Genome Analysis Toolkit: Now Using Apache Spark for Data Processing](http://blog.cloudera.com/blog/2016/04/genome-analysis-toolkit-now-using-apache-spark-for-data-processing/),” *blog.cloudera.com*, April 6, 2016. + + + diff --git a/ddia/ch11.md b/ddia/ch11.md new file mode 100644 index 0000000..3f62c8c --- /dev/null +++ b/ddia/ch11.md @@ -0,0 +1,407 @@ +# 11. 流处理 + +![](img/ch11.png) + +> 有效的复杂系统总是从简单的系统演化而来。 反之亦然:从零开始设计的复杂系统没一个能有效工作的。 +> +> ——约翰·加尔,Systemantics(1975) + +--------------- + +[TOC] + +## 传递事件流 + +### 消息系统 + +### 分区日志 + + + +## 流与数据库 + +### 保持系统同步 + +### 改变数据捕获 + +### 事件源 + +### 状态,流,不可变性 + + + +## 流处理 + +### 流处理的应用 + +### 与时间讲理 + +### 流式连接 + +### 容错 + + + +## 本章小结 + + + + +![](img/fig11-1.png) + +**图11-1** + +![](img/fig11-2.png) + +**图11-2** + +![](img/fig11-3.png) + +**图11-3** + + + +## 参考文献 + +1. Tyler Akidau, Robert Bradshaw, Craig Chambers, et al.: + “[The Dataflow Model: A Practical Approach to Balancing Correctness, Latency, and Cost in Massive-Scale, Unbounded, Out-of-Order Data Processing](http://www.vldb.org/pvldb/vol8/p1792-Akidau.pdf),” + *Proceedings of the VLDB Endowment*, volume 8, number 12, pages 1792–1803, August 2015. + [doi:10.14778/2824032.2824076](http://dx.doi.org/10.14778/2824032.2824076) + +1. Harold Abelson, Gerald Jay Sussman, and Julie Sussman: + *Structure and Interpretation of Computer Programs*, + 2nd edition. MIT Press, 1996. ISBN: 978-0-262-51087-5, available online at *mitpress.mit.edu* + +1. Patrick Th. Eugster, Pascal A. Felber, + Rachid Guerraoui, and Anne-Marie Kermarrec: + “[The Many Faces of Publish/Subscribe](http://www.cs.ru.nl/~pieter/oss/manyfaces.pdf),” + *ACM Computing Surveys*, volume 35, number 2, pages 114–131, June 2003. + [doi:10.1145/857076.857078](http://dx.doi.org/10.1145/857076.857078) + +1. Joseph M. Hellerstein and Michael Stonebraker: + *Readings in Database Systems*, 4th edition. + MIT Press, 2005. ISBN: 978-0-262-69314-1, available online at *redbook.cs.berkeley.edu* + +1. Don Carney, Uğur Çetintemel, Mitch Cherniack, et al.: + “[Monitoring Streams – A New Class of Data Management Applications](http://www.vldb.org/conf/2002/S07P02.pdf),” at *28th International Conference on Very Large Data Bases* + (VLDB), August 2002. + +1. Matthew Sackman: + “[Pushing Back](http://www.lshift.net/blog/2016/05/05/pushing-back/),” + *lshift.net*, May 5, 2016. + +1. Vicent Martí: + “[Brubeck, a statsd-Compatible Metrics Aggregator](http://githubengineering.com/brubeck/),” *githubengineering.com*, June 15, 2015. + +1. Seth Lowenberger: + “[MoldUDP64 Protocol Specification V 1.00](http://www.nasdaqtrader.com/content/technicalsupport/specifications/dataproducts/moldudp64.pdf),” *nasdaqtrader.com*, July 2009. + +1. Pieter Hintjens: + *ZeroMQ – The Guide*. O'Reilly Media, 2013. + ISBN: 978-1-449-33404-8 + +1. Ian Malpass: + “[Measure Anything, Measure Everything](https://codeascraft.com/2011/02/15/measure-anything-measure-everything/),” *codeascraft.com*, February 15, 2011. + +1. Dieter Plaetinck: + “[25 Graphite, Grafana and statsd Gotchas](https://blog.raintank.io/25-graphite-grafana-and-statsd-gotchas/),” *blog.raintank.io*, March 3, 2016. + +1. Jeff Lindsay: + “[Web Hooks to Revolutionize the Web](http://progrium.com/blog/2007/05/03/web-hooks-to-revolutionize-the-web/),” *progrium.com*, May 3, 2007. + +1. Jim N. Gray: + “[Queues Are Databases](http://research.microsoft.com/pubs/69641/tr-95-56.pdf),” + Microsoft Research Technical Report MSR-TR-95-56, December 1995. + +1. Mark Hapner, Rich Burridge, Rahul Sharma, et al.: + “[JSR-343 Java Message Service (JMS) 2.0 Specification](https://jcp.org/en/jsr/detail?id=343),” *jms-spec.java.net*, March 2013. + +1. Sanjay Aiyagari, Matthew Arrott, Mark Atwell, et al.: + “[AMQP: Advanced Message Queuing Protocol Specification](http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf),” Version 0-9-1, November 2008. + +1. “[Google Cloud Pub/Sub: A Google-Scale Messaging Service](https://cloud.google.com/pubsub/architecture),” *cloud.google.com*, 2016. + +1. “[Apache Kafka 0.9 Documentation](http://kafka.apache.org/documentation.html),” *kafka.apache.org*, November 2015. + +1. Jay Kreps, Neha Narkhede, and Jun Rao: + “[Kafka: A Distributed Messaging System for Log Processing](http://www.longyu23.com/doc/Kafka.pdf),” at *6th International Workshop on + Networking Meets Databases* (NetDB), June 2011. + +1. “[Amazon Kinesis Streams Developer Guide](http://docs.aws.amazon.com/streams/latest/dev/introduction.html),” *docs.aws.amazon.com*, April 2016. + +1. Leigh Stewart and Sijie Guo: + “[Building DistributedLog: Twitter’s High-Performance Replicated Log Service](https://blog.twitter.com/2015/building-distributedlog-twitter-s-high-performance-replicated-log-service),” *blog.twitter.com*, + September 16, 2015. + +1. “[DistributedLog Documentation](http://distributedlog.incubator.apache.org/docs/latest/),” Twitter, Inc., *distributedlog.io*, May 2016. + +1. Jay Kreps: + “[Benchmarking Apache Kafka: 2 Million Writes Per Second (On Three Cheap Machines)](https://engineering.linkedin.com/kafka/benchmarking-apache-kafka-2-million-writes-second-three-cheap-machines),” *engineering.linkedin.com*, + April 27, 2014. + +1. Kartik Paramasivam: + “[How We’re Improving and Advancing Kafka at LinkedIn](https://engineering.linkedin.com/apache-kafka/how-we_re-improving-and-advancing-kafka-linkedin),” *engineering.linkedin.com*, September 2, 2015. + +1. Jay Kreps: + “[The Log: What Every Software Engineer Should Know About Real-Time Data's Unifying Abstraction](http://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying),” + *engineering.linkedin.com*, December 16, 2013. + +1. Shirshanka Das, Chavdar Botev, Kapil Surlaker, + et al.: “[All Aboard the Databus!](http://www.socc2012.org/s18-das.pdf),” at *3rd ACM + Symposium on Cloud Computing* (SoCC), October 2012. + +1. Yogeshwer Sharma, Philippe Ajoux, Petchean Ang, et al.: + “[Wormhole: Reliable Pub-Sub to Support Geo-Replicated Internet Services](https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-sharma.pdf),” at *12th USENIX Symposium on + Networked Systems Design and Implementation* (NSDI), May 2015. + +1. P. P. S. Narayan: + “[Sherpa Update](http://web.archive.org/web/20160801221400/https://developer.yahoo.com/blogs/ydn/sherpa-7992.html),” + *developer.yahoo.com*, June 8, . + +1. Martin Kleppmann: + “[Bottled Water: Real-Time Integration of PostgreSQL and Kafka](http://martin.kleppmann.com/2015/04/23/bottled-water-real-time-postgresql-kafka.html),” *martin.kleppmann.com*, April 23, 2015. + +1. Ben Osheroff: + “[Introducing Maxwell, a mysql-to-kafka Binlog Processor](https://developer.zendesk.com/blog/introducing-maxwell-a-mysql-to-kafka-binlog-processor),” *developer.zendesk.com*, August 20, 2015. + +1. Randall Hauch: + “[Debezium 0.2.1 Released](http://debezium.io/blog/2016/06/10/Debezium-0/),” *debezium.io*, + June 10, 2016. + +1. Prem Santosh Udaya Shankar: + “[Streaming MySQL Tables in Real-Time to Kafka](https://engineeringblog.yelp.com/2016/08/streaming-mysql-tables-in-real-time-to-kafka.html),” *engineeringblog.yelp.com*, August 1, 2016. + +1. “[Mongoriver](https://github.com/stripe/mongoriver),” + Stripe, Inc., *github.com*, September 2014. + +1. Dan Harvey: + “[Change Data Capture with Mongo + Kafka](http://www.slideshare.net/danharvey/change-data-capture-with-mongodb-and-kafka),” at *Hadoop Users Group UK*, August 2015. + +1. “[Oracle GoldenGate 12c: Real-Time Access to Real-Time Information](http://www.oracle.com/us/products/middleware/data-integration/oracle-goldengate-realtime-access-2031152.pdf),” Oracle White Paper, March 2015. + +1. “[Oracle GoldenGate Fundamentals: How Oracle GoldenGate Works](https://www.youtube.com/watch?v=6H9NibIiPQE),” Oracle Corporation, *youtube.com*, November 2012. + +1. Slava Akhmechet: + “[Advancing the Realtime Web](http://rethinkdb.com/blog/realtime-web/),” *rethinkdb.com*, + January 27, 2015. + +1. “[Firebase Realtime Database Documentation](https://firebase.google.com/docs/database/),” Google, Inc., *firebase.google.com*, May 2016. + +1. “[Apache CouchDB 1.6 Documentation](http://docs.couchdb.org/en/latest/),” *docs.couchdb.org*, 2014. + +1. Matt DeBergalis: + “[Meteor 0.7.0: Scalable Database Queries Using MongoDB Oplog Instead of Poll-and-Diff](http://info.meteor.com/blog/meteor-070-scalable-database-queries-using-mongodb-oplog-instead-of-poll-and-diff),” *info.meteor.com*, + December 17, 2013. + +1. “[Chapter 15. Importing and Exporting Live Data](https://docs.voltdb.com/UsingVoltDB/ChapExport.php),” VoltDB 6.4 User Manual, *docs.voltdb.com*, June 2016. + +1. Neha Narkhede: + “[Announcing Kafka Connect: Building Large-Scale Low-Latency Data Pipelines](http://www.confluent.io/blog/announcing-kafka-connect-building-large-scale-low-latency-data-pipelines),” *confluent.io*, + February 18, 2016. + +1. Greg Young: + “[CQRS and Event Sourcing](https://www.youtube.com/watch?v=JHGkaShoyNs),” at *Code on + the Beach*, August 2014. + +1. Martin Fowler: + “[Event Sourcing](http://martinfowler.com/eaaDev/EventSourcing.html),” *martinfowler.com*, + December 12, 2005. + +1. Vaughn Vernon: + *Implementing Domain-Driven Design*. + Addison-Wesley Professional, 2013. ISBN: 978-0-321-83457-7 + +1. H. V. Jagadish, Inderpal Singh Mumick, and Abraham Silberschatz: + “[View Maintenance Issues for the Chronicle Data Model](http://www.mathcs.emory.edu/~cheung/papers/StreamDB/Histogram/1995-Jagadish-Histo.pdf),” at *14th ACM SIGACT-SIGMOD-SIGART Symposium + on Principles of Database Systems* (PODS), May 1995. + [doi:10.1145/212433.220201](http://dx.doi.org/10.1145/212433.220201) + +1. “[Event Store 3.5.0 Documentation](http://docs.geteventstore.com/),” Event Store LLP, *docs.geteventstore.com*, February 2016. + +1. Martin Kleppmann: + *Making Sense of Stream + Processing*. Report, O'Reilly Media, May 2016. + +1. Sander Mak: + “[Event-Sourced Architectures with Akka](http://www.slideshare.net/SanderMak/eventsourced-architectures-with-akka),” at *JavaOne*, September 2014. + +1. Julian Hyde: + [personal communication](https://twitter.com/julianhyde/status/743374145006641153), + June 2016. + +1. Ashish Gupta and Inderpal Singh Mumick: + *Materialized Views: Techniques, Implementations, and Applications*. MIT Press, 1999. + ISBN: 978-0-262-57122-7 + +1. Timothy Griffin and Leonid Libkin: + “[Incremental Maintenance of Views with Duplicates](http://homepages.inf.ed.ac.uk/libkin/papers/sigmod95.pdf),” at *ACM International Conference on Management of + Data* (SIGMOD), May 1995. + [doi:10.1145/223784.223849](http://dx.doi.org/10.1145/223784.223849) + +1. Pat Helland: + “[Immutability Changes Everything](http://www.cidrdb.org/cidr2015/Papers/CIDR15_Paper16.pdf),” at *7th Biennial Conference on Innovative Data Systems Research* (CIDR), + January 2015. + +1. Martin Kleppmann: + “[Accounting for Computer Scientists](http://martin.kleppmann.com/2011/03/07/accounting-for-computer-scientists.html),” *martin.kleppmann.com*, March 7, 2011. + +1. Pat Helland: + “[Accountants Don't Use Erasers](https://blogs.msdn.microsoft.com/pathelland/2007/06/14/accountants-dont-use-erasers/),” *blogs.msdn.com*, June 14, 2007. + +1. Fangjin Yang: + “[Dogfooding with Druid, Samza, and Kafka: Metametrics at Metamarkets](https://metamarkets.com/2015/dogfooding-with-druid-samza-and-kafka-metametrics-at-metamarkets/),” *metamarkets.com*, June 3, 2015. + +1. Gavin Li, Jianqiu Lv, and Hang Qi: + “[Pistachio: Co-Locate the Data and Compute for Fastest Cloud Compute](http://yahoohadoop.tumblr.com/post/116365275781/pistachio-co-locate-the-data-and-compute-for),” *yahoohadoop.tumblr.com*, April 13, 2015. + +1. Kartik Paramasivam: + “[Stream Processing Hard Problems – Part 1: Killing Lambda](https://engineering.linkedin.com/blog/2016/06/stream-processing-hard-problems-part-1-killing-lambda),” *engineering.linkedin.com*, June 27, 2016. + +1. Martin Fowler: + “[CQRS](http://martinfowler.com/bliki/CQRS.html),” *martinfowler.com*, July 14, 2011. + +1. Greg Young: + “[CQRS Documents](https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf),” + *cqrs.files.wordpress.com*, November 2010. + +1. Baron Schwartz: + “[Immutability, MVCC, and Garbage Collection](http://www.xaprb.com/blog/2013/12/28/immutability-mvcc-and-garbage-collection/),” *xaprb.com*, December 28, 2013. + +1. Daniel Eloff, Slava Akhmechet, Jay Kreps, et al.: + ["Re: Turning the Database Inside-out with Apache Samza](https://news.ycombinator.com/item?id=9145197)," Hacker News discussion, *news.ycombinator.com*, March 4, 2015. + +1. “[Datomic Development Resources: Excision](http://docs.datomic.com/excision.html),” Cognitect, Inc., *docs.datomic.com*. + +1. “[Fossil Documentation: Deleting Content from Fossil](http://fossil-scm.org/index.html/doc/trunk/www/shunning.wiki),” *fossil-scm.org*, 2016. + +1. Jay Kreps: + “[The irony of distributed systems is that data loss is really easy but deleting data is surprisingly hard,](https://twitter.com/jaykreps/status/582580836425330688)” *twitter.com*, March 30, + 2015. + +1. David C. Luckham: + “[What’s the Difference Between ESP and CEP?](http://www.complexevents.com/2006/08/01/what%E2%80%99s-the-difference-between-esp-and-cep/),” *complexevents.com*, August 1, 2006. + +1. Srinath Perera: + “[How Is Stream Processing and Complex Event Processing (CEP) Different?](https://www.quora.com/How-is-stream-processing-and-complex-event-processing-CEP-different),” *quora.com*, December 3, 2015. + +1. Arvind Arasu, Shivnath Babu, and Jennifer Widom: + “[The CQL Continuous Query Language: Semantic Foundations and Query Execution](http://research.microsoft.com/pubs/77607/cql.pdf),” *The VLDB Journal*, volume 15, number 2, pages +121–142, June 2006. + [doi:10.1007/s00778-004-0147-z](http://dx.doi.org/10.1007/s00778-004-0147-z) + +1. Julian Hyde: + “[Data in Flight: How Streaming SQL Technology Can Help Solve the Web 2.0 Data Crunch](http://queue.acm.org/detail.cfm?id=1667562),” *ACM Queue*, volume 7, number 11, December 2009. + [doi:10.1145/1661785.1667562](http://dx.doi.org/10.1145/1661785.1667562) + +1. “[Esper Reference, Version 5.4.0](http://www.espertech.com/esper/release-5.4.0/esper-reference/html_single/index.html),” EsperTech, Inc., *espertech.com*, April 2016. + +1. Zubair Nabi, Eric Bouillet, Andrew Bainbridge, and Chris Thomas: + “[Of Streams and Storms](https://developer.ibm.com/streamsdev/wp-content/uploads/sites/15/2014/04/Streams-and-Storm-April-2014-Final.pdf),” IBM technical report, *developer.ibm.com*, April 2014. + +1. Milinda Pathirage, Julian Hyde, Yi Pan, and Beth Plale: + “[SamzaSQL: Scalable Fast Data Management with Streaming SQL](https://github.com/milinda/samzasql-hpbdc2016/blob/master/samzasql-hpbdc2016.pdf),” at *IEEE International Workshop on + High-Performance Big Data Computing* (HPBDC), May 2016. + [doi:10.1109/IPDPSW.2016.141](http://dx.doi.org/10.1109/IPDPSW.2016.141) + +1. Philippe Flajolet, Éric Fusy, Olivier + Gandouet, and Frédéric Meunier: + “[HyperLo⁠g​Log: The Analysis of a Near-Optimal Cardinality Estimation Algorithm](http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf),” at *Conference on Analysis of + Algorithms* (AofA), June 2007. + +1. Jay Kreps: + “[Questioning the Lambda Architecture](https://www.oreilly.com/ideas/questioning-the-lambda-architecture),” *oreilly.com*, July 2, 2014. + +1. Ian Hellström: + “[An Overview of Apache Streaming Technologies](https://databaseline.wordpress.com/2016/03/12/an-overview-of-apache-streaming-technologies/),” *databaseline.wordpress.com*, March 12, 2016. + +1. Jay Kreps: + “[Why Local State Is a Fundamental Primitive in Stream Processing](https://www.oreilly.com/ideas/why-local-state-is-a-fundamental-primitive-in-stream-processing),” *oreilly.com*, July 31, 2014. + +1. Shay Banon: + “[Percolator](https://www.elastic.co/blog/percolator),” *elastic.co*, February 8, + 2011. + +1. Alan Woodward and Martin Kleppmann: + “[Real-Time Full-Text Search with Luwak and Samza](http://martin.kleppmann.com/2015/04/13/real-time-full-text-search-luwak-samza.html),” *martin.kleppmann.com*, April 13, 2015. + +1. “[Apache Storm 1.0.1 Documentation](https://storm.apache.org/releases/1.0.1/index.html),” *storm.apache.org*, May 2016. + +1. Tyler Akidau: + “[The World Beyond Batch: Streaming 102](https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-102),” *oreilly.com*, January 20, 2016. + +1. Stephan Ewen: + “[Streaming Analytics with Apache Flink](http://www.confluent.io/kafka-summit-2016-systems-advanced-streaming-analytics-with-apache-flink-and-apache-kafka),” at *Kafka Summit*, April + 2016. + +1. Tyler Akidau, Alex Balikov, Kaya Bekiroğlu, et al.: + “[MillWheel: Fault-Tolerant Stream Processing at Internet Scale](http://research.google.com/pubs/pub41378.html),” at *39th International Conference on Very Large Data Bases* (VLDB), + August 2013. + +1. Alex Dean: + “[Improving Snowplow's Understanding of Time](http://snowplowanalytics.com/blog/2015/09/15/improving-snowplows-understanding-of-time/),” *snowplowanalytics.com*, September 15, 2015. + +1. “[Windowing (Azure Stream Analytics)](https://msdn.microsoft.com/en-us/library/azure/dn835019.aspx),” Microsoft Azure Reference, *msdn.microsoft.com*, April 2016. + +1. “[State Management](http://samza.apache.org/learn/documentation/0.10/container/state-management.html),” Apache Samza 0.10 Documentation, *samza.apache.org*, December 2015. + +1. Rajagopal Ananthanarayanan, + Venkatesh Basker, Sumit Das, et al.: + “[Photon: Fault-Tolerant and Scalable Joining of Continuous Data Streams](http://research.google.com/pubs/pub41318.html),” at *ACM International Conference on Management of + Data* (SIGMOD), June 2013. + [doi:10.1145/2463676.2465272](http://dx.doi.org/10.1145/2463676.2465272) + +1. Martin Kleppmann: + “[Samza Newsfeed Demo](https://github.com/ept/newsfeed),” *github.com*, + September 2014. + +1. Ben Kirwin: + “[Doing the Impossible: Exactly-Once Messaging Patterns in Kafka](http://ben.kirw.in/2014/11/28/kafka-patterns/),” *ben.kirw.in*, November 28, 2014. + +1. Pat Helland: + “[Data on the Outside Versus Data on the Inside](http://cidrdb.org/cidr2005/papers/P12.pdf),” at *2nd Biennial Conference on Innovative Data Systems Research* (CIDR), January + 2005. + +1. Ralph Kimball and Margy Ross: + *The Data Warehouse Toolkit: The Definitive Guide to Dimensional Modeling*, + 3rd edition. John Wiley & Sons, 2013. ISBN: 978-1-118-53080-1 + +1. Viktor Klang: + “[I'm coining the phrase 'effectively-once' for message processing with at-least-once + idempotent operations](https://twitter.com/viktorklang/status/789036133434978304),” + *twitter.com*, October 20, 2016. + +1. Matei Zaharia, Tathagata Das, Haoyuan Li, et al.: + “[Discretized Streams: An Efficient and Fault-Tolerant Model for Stream Processing on Large Clusters](https://www.usenix.org/system/files/conference/hotcloud12/hotcloud12-final28.pdf),” at + *4th USENIX Conference in Hot Topics in Cloud Computing* (HotCloud), June 2012. + +1. Kostas Tzoumas, Stephan Ewen, and Robert Metzger: + “[High-Throughput, Low-Latency, and Exactly-Once Stream Processing with Apache Flink](http://data-artisans.com/high-throughput-low-latency-and-exactly-once-stream-processing-with-apache-flink/),” *data-artisans.com*, August 5, 2015. + +1. Paris Carbone, Gyula Fóra, Stephan Ewen, et al.: + “[Lightweight Asynchronous Snapshots for Distributed Dataflows](http://arxiv.org/abs/1506.08603),” arXiv:1506.08603 [cs.DC], June 29, 2015. + +1. Ryan Betts and John Hugg: + *Fast Data: Smart and + at Scale*. Report, O'Reilly Media, October 2015. + +1. Flavio Junqueira: + “[Making Sense of Exactly-Once Semantics](http://conferences.oreilly.com/strata/hadoop-big-data-eu/public/schedule/detail/49690),” at *Strata+Hadoop World London*, June 2016. + +1. Jason Gustafson, Flavio Junqueira, Apurva Mehta, Sriram Subramanian, and Guozhang Wang: “[KIP-98 – Exactly Once Delivery and Transactional Messaging](https://cwiki.apache.org/confluence/display/KAFKA/KIP-98+-+Exactly+Once+Delivery+and+Transactional+Messaging),” *cwiki.apache.org*, November 2016. + +1. Pat Helland: + “[Idempotence Is Not a Medical Condition](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.401.1539&rep=rep1&type=pdf),” *Communications of the ACM*, volume 55, number 5, page 56, May 2012. + [doi:10.1145/2160718.2160734](http://dx.doi.org/10.1145/2160718.2160734) + +1. Jay Kreps: + “[Re: Trying to Achieve Deterministic Behavior on Recovery/Rewind](http://mail-archives.apache.org/mod_mbox/samza-dev/201409.mbox/%3CCAOeJiJg%2Bc7Ei%3DgzCuOz30DD3G5Hm9yFY%3DUJ6SafdNUFbvRgorg%40mail.gmail.com%3E),” email to *samza-dev* mailing list, + September 9, 2014. + +1. E. N. (Mootaz) Elnozahy, + Lorenzo Alvisi, Yi-Min Wang, and David B. Johnson: + “[A Survey of Rollback-Recovery Protocols in Message-Passing Systems](http://www.cs.utexas.edu/~lorenzo/papers/SurveyFinal.pdf),” *ACM Computing Surveys*, volume 34, number 3, + pages 375–408, September 2002. + [doi:10.1145/568522.568525](http://dx.doi.org/10.1145/568522.568525) + +1. Adam Warski: + “[Kafka Streams – How Does It Fit the Stream Processing Landscape?](https://softwaremill.com/kafka-streams-how-does-it-fit-stream-landscape/),” *softwaremill.com*, June 1, 2016. + diff --git a/ddia/ch12.md b/ddia/ch12.md new file mode 100644 index 0000000..c396893 --- /dev/null +++ b/ddia/ch12.md @@ -0,0 +1,487 @@ +# 12. 数据系统的未来 + +![](img/ch12.png) + +> 如果船长的最高目标是保护他的船,他应该永远待在港口。 +> +> ——圣托马斯·阿奎那《神学大全》(1265-1274) + +--------------- + +[TOC] + +## 数据集成 + +### 组合使用派生数据的工具 + +### 批量处理与流处理 + + + +## 拆分数据库 + +### 组合使用数据存储技术 + +### 围绕数据流设计应用 + +### 观察派生数据状态 + + + +## 目标是正确性 + +### 数据库端到端的争论 + +### 强制实施约束 + +### 时间线与完整性 + +### 信任,但需要验证 + + + +## 做正确的事情 + +### 预测性的分析 + +### 隐私与跟踪 + + + +## 本章小结 + + + +## 参考文献 + + +1. Rachid Belaid: + “[Postgres Full-Text Search is Good Enough!](http://rachbelaid.com/postgres-full-text-search-is-good-enough/),” *rachbelaid.com*, July 13, 2015. + +1. Philippe Ajoux, Nathan Bronson, Sanjeev Kumar, et al.: + “[Challenges to Adopting Stronger Consistency at Scale](https://www.usenix.org/system/files/conference/hotos15/hotos15-paper-ajoux.pdf),” at *15th USENIX Workshop on Hot Topics + in Operating Systems* (HotOS), May 2015. + +1. Pat Helland and Dave Campbell: + “[Building on Quicksand](https://database.cs.wisc.edu/cidr/cidr2009/Paper_133.pdf),” at + *4th Biennial Conference on Innovative Data Systems Research* (CIDR), January 2009. + +1. Jessica Kerr: + “[Provenance and Causality in Distributed Systems](http://blog.jessitron.com/2016/09/provenance-and-causality-in-distributed.html),” *blog.jessitron.com*, September 25, 2016. + +1. Kostas Tzoumas: + “[Batch Is a Special Case of Streaming](http://data-artisans.com/batch-is-a-special-case-of-streaming/),” *data-artisans.com*, September 15, 2015. + +1. Shinji Kim and Robert Blafford: + “[Stream Windowing Performance Analysis: Concord and Spark Streaming](http://concord.io/posts/windowing_performance_analysis_w_spark_streaming),” *concord.io*, July 6, 2016. + +1. Jay Kreps: + “[The Log: What Every Software Engineer Should Know About Real-Time Data's Unifying Abstraction](http://engineering.linkedin.com/distributed-systems/log-what-every-software-engineer-should-know-about-real-time-datas-unifying),” + *engineering.linkedin.com*, December 16, 2013. + +1. Pat Helland: + “[Life Beyond Distributed Transactions: An Apostate’s Opinion](http://www-db.cs.wisc.edu/cidr/cidr2007/papers/cidr07p15.pdf),” at *3rd Biennial Conference on Innovative Data + Systems Research* (CIDR), January 2007. + +1. “[Great Western Railway (1835–1948)](https://www.networkrail.co.uk/VirtualArchive/great-western/),” Network Rail Virtual Archive, *networkrail.co.uk*. + +1. Jacqueline Xu: + “[Online Migrations at Scale](https://stripe.com/blog/online-migrations),” + *stripe.com*, February 2, 2017. + +1. Molly Bartlett Dishman and Martin Fowler: + “[Agile Architecture](http://conferences.oreilly.com/software-architecture/sa2015/public/schedule/detail/40388),” at *O'Reilly Software Architecture Conference*, March 2015. + +1. Nathan Marz and James Warren: + *Big Data: Principles and Best Practices of + Scalable Real-Time Data Systems*. Manning, 2015. ISBN: 978-1-617-29034-3 + +1. Oscar Boykin, Sam Ritchie, Ian O'Connell, and + Jimmy Lin: “[Summingbird: A Framework for Integrating Batch and Online MapReduce Computations](http://www.vldb.org/pvldb/vol7/p1441-boykin.pdf),” at *40th International Conference on + Very Large Data Bases* (VLDB), September 2014. + +1. Jay Kreps: + “[Questioning the Lambda Architecture](https://www.oreilly.com/ideas/questioning-the-lambda-architecture),” *oreilly.com*, July 2, 2014. + +1. Raul Castro Fernandez, Peter Pietzuch, + Jay Kreps, et al.: “[Liquid: Unifying Nearline and Offline Big Data Integration](http://www.cidrdb.org/cidr2015/Papers/CIDR15_Paper25u.pdf),” at *7th Biennial Conference on + Innovative Data Systems Research* (CIDR), January 2015. + +1. Dennis M. Ritchie and Ken Thompson: + “[The UNIX Time-Sharing System](http://www.cs.virginia.edu/~zaher/classes/CS656/p365-ritchie.pdf),” *Communications of the ACM*, volume 17, number 7, pages 365–375, July 1974. + [doi:10.1145/361011.361061](http://dx.doi.org/10.1145/361011.361061) + +1. Eric A. Brewer and Joseph M. Hellerstein: + “[CS262a: Advanced Topics in Computer Systems](http://people.eecs.berkeley.edu/~brewer/cs262/systemr.html),” lecture notes, University of California, Berkeley, *cs.berkeley.edu*, + August 2011. + +1. Michael Stonebraker: + “[The Case for Polystores](http://wp.sigmod.org/?p=1629),” *wp.sigmod.org*, + July 13, 2015. + +1. Jennie Duggan, + Aaron J. Elmore, Michael Stonebraker, et al.: + “[The BigDAWG Polystore System](http://dspace.mit.edu/openaccess-disseminate/1721.1/100936),” *ACM SIGMOD Record*, volume 44, number 2, pages 11–16, June 2015. + [doi:10.1145/2814710.2814713](http://dx.doi.org/10.1145/2814710.2814713) + +1. Patrycja Dybka: + “[Foreign Data Wrappers for PostgreSQL](http://www.vertabelo.com/blog/technical-articles/foreign-data-wrappers-for-postgresql),” *vertabelo.com*, March 24, 2015. + +1. David B. Lomet, Alan Fekete, Gerhard Weikum, and Mike Zwilling: + “[Unbundling Transaction Services in the Cloud](https://www.microsoft.com/en-us/research/publication/unbundling-transaction-services-in-the-cloud/),” at *4th Biennial Conference on Innovative Data Systems + Research* (CIDR), January 2009. + +1. Martin Kleppmann and Jay Kreps: + “[Kafka, Samza and the Unix Philosophy of Distributed Data](http://martin.kleppmann.com/papers/kafka-debull15.pdf),” *IEEE Data Engineering Bulletin*, volume 38, number 4, pages 4–14, + December 2015. + +1. John Hugg: + “[Winning Now and in the Future: Where VoltDB Shines](https://voltdb.com/blog/winning-now-and-future-where-voltdb-shines),” *voltdb.com*, March 23, 2016. + +1. Frank McSherry, Derek G. Murray, Rebecca Isaacs, and Michael Isard: + “[Differential Dataflow](http://cidrdb.org/cidr2013/Papers/CIDR13_Paper111.pdf),” + at *6th Biennial Conference on Innovative Data Systems Research* (CIDR), January 2013. + +1. Derek G Murray, Frank McSherry, Rebecca Isaacs, et al.: + “[Naiad: A Timely Dataflow System](http://research.microsoft.com/pubs/201100/naiad_sosp2013.pdf),” + at *24th ACM Symposium on Operating Systems Principles* (SOSP), pages 439–455, November 2013. + [doi:10.1145/2517349.2522738](http://dx.doi.org/10.1145/2517349.2522738) + +1. Gwen Shapira: + “[We have a bunch of customers who are implementing ‘database inside-out’ concept and they all ask ‘is anyone else doing it? are we crazy?’](https://twitter.com/gwenshap/status/758800071110430720)” *twitter.com*, July 28, 2016. + +1. Martin Kleppmann: + “[Turning the Database Inside-out with Apache Samza,](http://martin.kleppmann.com/2015/03/04/turning-the-database-inside-out.html)” at *Strange Loop*, September 2014. + +1. Peter Van Roy and Seif Haridi: + *Concepts, + Techniques, and Models of Computer Programming*. MIT Press, 2004. + ISBN: 978-0-262-22069-9 + +1. “[Juttle Documentation](http://juttle.github.io/juttle/),” *juttle.github.io*, 2016. + +1. Evan Czaplicki and Stephen Chong: + “[Asynchronous Functional Reactive Programming for GUIs](http://people.seas.harvard.edu/~chong/pubs/pldi13-elm.pdf),” at *34th ACM SIGPLAN Conference on Programming Language + Design and Implementation* (PLDI), June 2013. + [doi:10.1145/2491956.2462161](http://dx.doi.org/10.1145/2491956.2462161) + +1. Engineer Bainomugisha, Andoni Lombide Carreton, + Tom van Cutsem, Stijn Mostinckx, and Wolfgang de Meuter: + “[A Survey on Reactive Programming](http://soft.vub.ac.be/Publications/2012/vub-soft-tr-12-13.pdf),” *ACM Computing Surveys*, volume 45, number 4, pages 1–34, August 2013. + [doi:10.1145/2501654.2501666](http://dx.doi.org/10.1145/2501654.2501666) + +1. Peter + Alvaro, Neil Conway, Joseph M. Hellerstein, and William R. Marczak: + “[Consistency Analysis in Bloom: A CALM and Collected Approach](http://www.eecs.berkeley.edu/~palvaro/cidr11.pdf),” at *5th Biennial Conference on Innovative Data Systems Research* + (CIDR), January 2011. + +1. Felienne Hermans: + “[Spreadsheets Are Code](https://vimeo.com/145492419),” at *Code Mesh*, November + 2015. + +1. Dan Bricklin and Bob + Frankston: “[VisiCalc: Information from Its Creators](http://danbricklin.com/visicalc.htm),” *danbricklin.com*. + +1. D. Sculley, Gary Holt, Daniel Golovin, et al.: + “[Machine Learning: The High-Interest Credit Card of Technical Debt](http://research.google.com/pubs/pub43146.html),” at *NIPS Workshop on Software Engineering for Machine Learning* + (SE4ML), December 2014. + +1. Peter Bailis, Alan Fekete, Michael J Franklin, + et al.: “[Feral Concurrency Control: An Empirical Investigation of Modern Application Integrity](http://www.bailis.org/papers/feral-sigmod2015.pdf),” at *ACM International Conference on + Management of Data* (SIGMOD), June 2015. + [doi:10.1145/2723372.2737784](http://dx.doi.org/10.1145/2723372.2737784) + +1. Guy Steele: + “[Re: Need for Macros (Was Re: Icon)](https://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg01134.html),” email to *ll1-discuss* mailing list, *people.csail.mit.edu*, December 24, + 2001. + +1. David Gelernter: + “[Generative Communication in Linda](http://cseweb.ucsd.edu/groups/csag/html/teaching/cse291s03/Readings/p80-gelernter.pdf),” *ACM Transactions on Programming Languages and Systems* + (TOPLAS), volume 7, number 1, pages 80–112, January 1985. + [doi:10.1145/2363.2433](http://dx.doi.org/10.1145/2363.2433) + +1. Patrick Th. Eugster, Pascal A. Felber, + Rachid Guerraoui, and Anne-Marie Kermarrec: + “[The Many Faces of Publish/Subscribe](http://www.cs.ru.nl/~pieter/oss/manyfaces.pdf),” + *ACM Computing Surveys*, volume 35, number 2, pages 114–131, June 2003. + [doi:10.1145/857076.857078](http://dx.doi.org/10.1145/857076.857078) + +1. Ben Stopford: + “[Microservices in a Streaming World](https://www.infoq.com/presentations/microservices-streaming),” at *QCon London*, March 2016. + +1. Christian Posta: + “[Why Microservices Should Be Event Driven: Autonomy vs Authority](http://blog.christianposta.com/microservices/why-microservices-should-be-event-driven-autonomy-vs-authority/),” *blog.christianposta.com*, May 27, 2016. + +1. Alex Feyerke: + “[Say Hello to Offline First](http://hood.ie/blog/say-hello-to-offline-first.html),” + *hood.ie*, November 5, 2013. + +1. Sebastian Burckhardt, Daan Leijen, Jonathan + Protzenko, and Manuel Fähndrich: + “[Global Sequence Protocol: A Robust Abstraction for Replicated Shared State](http://drops.dagstuhl.de/opus/volltexte/2015/5238/),” at *29th European Conference on Object-Oriented + Programming* (ECOOP), July 2015. + [doi:10.4230/LIPIcs.ECOOP.2015.568](http://dx.doi.org/10.4230/LIPIcs.ECOOP.2015.568) + +1. Mark Soper: + “[Clearing Up React Data Management Confusion with Flux, Redux, and Relay](https://medium.com/@marksoper/clearing-up-react-data-management-confusion-with-flux-redux-and-relay-aad504e63cae),” *medium.com*, December 3, 2015. + +1. Eno Thereska, Damian Guy, Michael Noll, and Neha Narkhede: + “[Unifying Stream Processing and Interactive Queries in Apache Kafka](http://www.confluent.io/blog/unifying-stream-processing-and-interactive-queries-in-apache-kafka/),” *confluent.io*, October 26, 2016. + +1. Frank McSherry: + “[Dataflow as Database](https://github.com/frankmcsherry/blog/blob/master/posts/2016-07-17.md),” *github.com*, July 17, 2016. + +1. Peter Alvaro: + “[I See What You Mean](https://www.youtube.com/watch?v=R2Aa4PivG0g),” at *Strange + Loop*, September 2015. + +1. Nathan Marz: + “[Trident: A High-Level Abstraction for Realtime Computation](https://blog.twitter.com/2012/trident-a-high-level-abstraction-for-realtime-computation),” *blog.twitter.com*, August 2, 2012. + +1. Edi Bice: + “[Low Latency Web Scale Fraud Prevention with Apache Samza, Kafka and Friends](http://www.slideshare.net/edibice/extremely-low-latency-web-scale-fraud-prevention-with-apache-samza-kafka-and-friends),” at *Merchant Risk + Council MRC Vegas Conference*, March 2016. + +1. Charity Majors: + “[The Accidental DBA](https://charity.wtf/2016/10/02/the-accidental-dba/),” *charity.wtf*, + October 2, 2016. + +1. Arthur J. Bernstein, Philip M. Lewis, and Shiyong Lu: + “[Semantic Conditions for Correctness at Different Isolation Levels](http://db.cs.berkeley.edu/cs286/papers/isolation-icde2000.pdf),” at *16th International Conference on Data + Engineering* (ICDE), February 2000. + [doi:10.1109/ICDE.2000.839387](http://dx.doi.org/10.1109/ICDE.2000.839387) + +1. Sudhir Jorwekar, Alan Fekete, Krithi Ramamritham, and + S. Sudarshan: “[Automating the Detection of Snapshot Isolation Anomalies](http://www.vldb.org/conf/2007/papers/industrial/p1263-jorwekar.pdf),” at *33rd International Conference on Very + Large Data Bases* (VLDB), September 2007. + +1. Kyle Kingsbury: + [Jepsen blog post series](https://aphyr.com/tags/jepsen), *aphyr.com*, 2013–2016. + +1. Michael Jouravlev: + “[Redirect After Post](http://www.theserverside.com/news/1365146/Redirect-After-Post),” + *theserverside.com*, August 1, 2004. + +1. Jerome H. Saltzer, David P. Reed, and + David D. Clark: “[End-to-End Arguments in System Design](http://www.ece.drexel.edu/courses/ECE-C631-501/SalRee1984.pdf),” *ACM Transactions on Computer Systems*, volume 2, number 4, + pages 277–288, November 1984. + [doi:10.1145/357401.357402](http://dx.doi.org/10.1145/357401.357402) + +1. Peter Bailis, Alan Fekete, Michael J. Franklin, et al.: + “[Coordination-Avoiding Database Systems](http://arxiv.org/pdf/1402.2237.pdf),” + *Proceedings of the VLDB Endowment*, volume 8, number 3, pages 185–196, November 2014. + +1. Alex Yarmula: + “[Strong Consistency in Manhattan](https://blog.twitter.com/2016/strong-consistency-in-manhattan),” *blog.twitter.com*, March 17, 2016. + +1. Douglas B Terry, Marvin M Theimer, Karin Petersen, et al.: + “[Managing Update Conflicts in Bayou, a Weakly Connected Replicated Storage System](http://css.csail.mit.edu/6.824/2014/papers/bayou-conflicts.pdf),” at *15th ACM Symposium on Operating + Systems Principles* (SOSP), pages 172–182, December 1995. + [doi:10.1145/224056.224070](http://dx.doi.org/10.1145/224056.224070) + +1. Jim Gray: + “[The Transaction Concept: Virtues and Limitations](http://research.microsoft.com/en-us/um/people/gray/papers/theTransactionConcept.pdf),” at *7th International Conference on + Very Large Data Bases* (VLDB), September 1981. + +1. Hector Garcia-Molina and Kenneth Salem: + “[Sagas](http://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf),” at + *ACM International Conference on Management of Data* (SIGMOD), May 1987. + [doi:10.1145/38713.38742](http://dx.doi.org/10.1145/38713.38742) + +1. Pat Helland: + “[Memories, Guesses, and Apologies](http://blogs.msdn.com/b/pathelland/archive/2007/05/15/memories-guesses-and-apologies.aspx),” *blogs.msdn.com*, May 15, 2007. + +1. Yoongu Kim, Ross Daly, Jeremie Kim, et al.: + “[Flipping Bits in Memory Without Accessing Them: An Experimental Study of DRAM Disturbance Errors](https://users.ece.cmu.edu/~yoonguk/papers/kim-isca14.pdf),” at *41st Annual + International Symposium on Computer Architecture* (ISCA), June 2014. + [doi:10.1145/2678373.2665726](http://dx.doi.org/10.1145/2678373.2665726) + +1. Mark Seaborn and Thomas Dullien: + “[Exploiting the DRAM Rowhammer Bug to Gain Kernel Privileges](https://googleprojectzero.blogspot.co.uk/2015/03/exploiting-dram-rowhammer-bug-to-gain.html),” *googleprojectzero.blogspot.co.uk*, March 9, + 2015. + +1. Jim N. Gray and Catharine van Ingen: + “[Empirical Measurements of Disk Failure Rates and Error Rates](https://www.microsoft.com/en-us/research/publication/empirical-measurements-of-disk-failure-rates-and-error-rates/),” Microsoft Research, MSR-TR-2005-166, + December 2005. + +1. Annamalai Gurusami and Daniel Price: + “[Bug #73170: Duplicates in Unique Secondary Index Because of Fix of Bug#68021](http://bugs.mysql.com/bug.php?id=73170),” *bugs.mysql.com*, July 2014. + +1. Gary Fredericks: + “[Postgres Serializability Bug](https://github.com/gfredericks/pg-serializability-bug),” + *github.com*, September 2015. + +1. Xiao Chen: + “[HDFS DataNode Scanners and Disk Checker Explained](http://blog.cloudera.com/blog/2016/12/hdfs-datanode-scanners-and-disk-checker-explained/),” *blog.cloudera.com*, December 20, + 2016. + +1. Jay Kreps: + “[Getting Real About Distributed System Reliability](http://blog.empathybox.com/post/19574936361/getting-real-about-distributed-system-reliability),” *blog.empathybox.com*, March 19, 2012. + +1. Martin Fowler: + “[The LMAX Architecture](http://martinfowler.com/articles/lmax.html),” + *martinfowler.com*, July 12, 2011. + +1. Sam Stokes: + “[Move Fast with Confidence](http://blog.samstokes.co.uk/blog/2016/07/11/move-fast-with-confidence/),” *blog.samstokes.co.uk*, July 11, 2016. + +1. “[Sawtooth Lake Documentation](http://intelledger.github.io/introduction.html),” Intel Corporation, *intelledger.github.io*, 2016. + +1. Richard Gendal Brown: + “[Introducing R3 Corda™: A Distributed Ledger Designed for Financial Services](https://gendal.me/2016/04/05/introducing-r3-corda-a-distributed-ledger-designed-for-financial-services/),” *gendal.me*, April 5, 2016. + +1. Trent McConaghy, Rodolphe Marques, Andreas Müller, et al.: + “[BigchainDB: A Scalable Blockchain Database](https://www.bigchaindb.com/whitepaper/bigchaindb-whitepaper.pdf),” *bigchaindb.com*, June 8, 2016. + +1. Ralph C. Merkle: + “[A Digital Signature Based on a Conventional Encryption Function](https://people.eecs.berkeley.edu/~raluca/cs261-f15/readings/merkle.pdf),” at *CRYPTO '87*, August 1987. + [doi:10.1007/3-540-48184-2_32](http://dx.doi.org/10.1007/3-540-48184-2_32) + +1. Ben Laurie: + “[Certificate Transparency](http://queue.acm.org/detail.cfm?id=2668154),” *ACM + Queue*, volume 12, number 8, pages 10-19, August 2014. + [doi:10.1145/2668152.2668154](http://dx.doi.org/10.1145/2668152.2668154) + +1. Mark D. Ryan: + “[Enhanced Certificate Transparency and End-to-End Encrypted Mail](http://www.internetsociety.org/doc/enhanced-certificate-transparency-and-end-end-encrypted-mail),” at *Network and Distributed System + Security Symposium* (NDSS), February 2014. + [doi:10.14722/ndss.2014.23379](http://dx.doi.org/10.14722/ndss.2014.23379) + +1. “Software Engineering Code of Ethics and Professional + Practice,” Association for Computing Machinery, *acm.org*, 1999. + +1. François Chollet: + “[Software development is starting to involve important ethical choices](https://twitter.com/fchollet/status/792958695722201088),” *twitter.com*, October 30, 2016. + +1. Igor Perisic: + “[Making Hard Choices: The Quest for Ethics in Machine Learning](https://engineering.linkedin.com/blog/2016/11/making-hard-choices--the-quest-for-ethics-in-machine-learning),” *engineering.linkedin.com*, November + 2016. + +1. John Naughton: + “[Algorithm Writers Need a Code of Conduct](https://www.theguardian.com/commentisfree/2015/dec/06/algorithm-writers-should-have-code-of-conduct),” *theguardian.com*, December 6, 2015. + +1. Logan Kugler: + “[What Happens When Big Data Blunders?](http://cacm.acm.org/magazines/2016/6/202655-what-happens-when-big-data-blunders/fulltext),” *Communications of the ACM*, volume 59, number 6, pages +15–16, June 2016. [doi:10.1145/2911975](http://dx.doi.org/10.1145/2911975) + +1. Bill Davidow: + “[Welcome to Algorithmic Prison](http://www.theatlantic.com/technology/archive/2014/02/welcome-to-algorithmic-prison/283985/),” *theatlantic.com*, February 20, 2014. + +1. Don Peck: + “[They're Watching You at Work](http://www.theatlantic.com/magazine/archive/2013/12/theyre-watching-you-at-work/354681/),” *theatlantic.com*, December 2013. + +1. Leigh Alexander: + “[Is an Algorithm Any Less Racist Than a Human?](https://www.theguardian.com/technology/2016/aug/03/algorithm-racist-human-employers-work)” *theguardian.com*, August 3, 2016. + +1. Jesse Emspak: + “[How a Machine Learns Prejudice](https://www.scientificamerican.com/article/how-a-machine-learns-prejudice/),” *scientificamerican.com*, December 29, 2016. + +1. Maciej Cegłowski: + “[The Moral Economy of Tech](http://idlewords.com/talks/sase_panel.htm),” + *idlewords.com*, June 2016. + +1. Cathy O'Neil: + *Weapons of Math Destruction: How Big Data + Increases Inequality and Threatens Democracy*. Crown Publishing, 2016. + ISBN: 978-0-553-41881-1 + +1. Julia Angwin: + “[Make Algorithms Accountable](http://www.nytimes.com/2016/08/01/opinion/make-algorithms-accountable.html),” *nytimes.com*, August 1, 2016. + +1. Bryce Goodman and Seth Flaxman: + “[European Union Regulations on Algorithmic Decision-Making and a ‘Right to Explanation’](https://arxiv.org/abs/1606.08813),” *arXiv:1606.08813*, August 31, + 2016. + +1. “[A Review of the Data Broker Industry: Collection, Use, and Sale of Consumer Data for Marketing Purposes](https://www.commerce.senate.gov/public/index.cfm/reports?ID=57C428EC-8F20-44EE-BFB8-A570E9BE0CCC),” Staff Report, *United States Senate Committee on Commerce, Science, and + Transportation*, *commerce.senate.gov*, December 2013. + +1. Olivia Solon: + “[Facebook’s Failure: Did Fake News and Polarized Politics Get Trump Elected?](https://www.theguardian.com/technology/2016/nov/10/facebook-fake-news-election-conspiracy-theories)” *theguardian.com*, November 10, + 2016. + +1. Donella H. Meadows and Diana Wright: + *Thinking in Systems: A Primer*. Chelsea Green Publishing, 2008. ISBN: 978-1-603-58055-7 + +1. Daniel J. Bernstein: + “[Listening to a ‘big data’/‘data science’ talk](https://twitter.com/hashbreaker/status/598076230437568512),” *twitter.com*, May 12, 2015. + +1. Marc Andreessen: + “[Why Software Is Eating the World](http://genius.com/Marc-andreessen-why-software-is-eating-the-world-annotated),” *The Wall Street Journal*, 20 August 2011. + +1. J. M. Porup: + “[‘Internet of Things’ Security Is Hilariously Broken and Getting Worse](http://arstechnica.com/security/2016/01/how-to-search-the-internet-of-things-for-photos-of-sleeping-babies/),” *arstechnica.com*, January 23, 2016. + +1. Bruce Schneier: + *Data and Goliath: The Hidden Battles + to Collect Your Data and Control Your World*. W. W. Norton, 2015. + ISBN: 978-0-393-35217-7 + +1. The Grugq: + “[Nothing to Hide](https://grugq.tumblr.com/post/142799983558/nothing-to-hide),” + *grugq.tumblr.com*, April 15, 2016. + +1. Tony Beltramelli: + “[Deep-Spying: Spying Using Smartwatch and Deep Learning](https://arxiv.org/abs/1512.05616),” Masters Thesis, IT University of Copenhagen, December 2015. Available at + *arxiv.org/abs/1512.05616* + +1. Shoshana Zuboff: + “[Big Other: Surveillance Capitalism and the Prospects of an Information Civilization](http://papers.ssrn.com/sol3/papers.cfm?abstract_id=2594754),” *Journal of Information + Technology*, volume 30, number 1, pages 75–89, April 2015. + [doi:10.1057/jit.2015.5](http://dx.doi.org/10.1057/jit.2015.5) + +1. Carina C. Zona: + “[Consequences of an Insightful Algorithm](https://www.youtube.com/watch?v=YRI40A4tyWU),” + at *GOTO Berlin*, November 2016. + +1. Bruce Schneier: + “[Data Is a Toxic Asset, So Why Not Throw It Out?](https://www.schneier.com/essays/archives/2016/03/data_is_a_toxic_asse.html),” *schneier.com*, March 1, 2016. + +1. John E. Dunn: + “[The UK’s 15 Most Infamous Data Breaches](http://www.techworld.com/security/uks-most-infamous-data-breaches-2016-3604586/),” *techworld.com*, November 18, 2016. + +1. Cory Scott: + “[Data is not toxic - which implies no benefit - but rather hazardous material, where we must balance need vs. want](https://twitter.com/cory_scott/status/706586399483437056),” + *twitter.com*, March 6, 2016. + +1. Bruce Schneier: + “[Mission Creep: When Everything Is Terrorism](https://www.schneier.com/essays/archives/2013/07/mission_creep_when_e.html),” *schneier.com*, July 16, 2013. + +1. Lena Ulbricht and Maximilian von Grafenstein: + “[Big Data: Big Power Shifts?](http://policyreview.info/articles/analysis/big-data-big-power-shifts),” *Internet Policy Review*, volume 5, number 1, March 2016. + [doi:10.14763/2016.1.406](http://dx.doi.org/10.14763/2016.1.406) + +1. Ellen P. Goodman and Julia Powles: + “[Facebook and Google: Most Powerful and Secretive Empires We've Ever Known](https://www.theguardian.com/technology/2016/sep/28/google-facebook-powerful-secretive-empire-transparency),” *theguardian.com*, September 28, + 2016. + +1. [Directive 95/46/EC on the protection of individuals with regard to the processing of personal data and on the free movement of such data](http://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:31995L0046), Official Journal of the European Communities No. L 281/31, + *eur-lex.europa.eu*, November 1995. + +1. Brendan Van Alsenoy: + “[Regulating Data Protection: The Allocation of Responsibility and Risk Among Actors Involved in Personal Data Processing](https://lirias.kuleuven.be/handle/123456789/545027),” + Thesis, KU Leuven Centre for IT and IP Law, August 2016. + +1. Michiel Rhoen: + “[Beyond Consent: Improving Data Protection Through Consumer Protection Law](http://policyreview.info/articles/analysis/beyond-consent-improving-data-protection-through-consumer-protection-law),” *Internet Policy + Review*, volume 5, number 1, March 2016. + [doi:10.14763/2016.1.404](http://dx.doi.org/10.14763/2016.1.404) + +1. Jessica Leber: + “[Your Data Footprint Is Affecting Your Life in Ways You Can’t Even Imagine](https://www.fastcoexist.com/3057514/your-data-footprint-is-affecting-your-life-in-ways-you-cant-even-imagine),” *fastcoexist.com*, March 15, + 2016. + +1. Maciej Cegłowski: + “[Haunted by Data](http://idlewords.com/talks/haunted_by_data.htm),” *idlewords.com*, + October 2015. + +1. Sam Thielman: + “[You Are Not What You Read: Librarians Purge User Data to Protect Privacy](https://www.theguardian.com/us-news/2016/jan/13/us-library-records-purged-data-privacy),” *theguardian.com*, + January 13, 2016. + +1. Conor Friedersdorf: + “[Edward Snowden’s Other Motive for Leaking](http://www.theatlantic.com/politics/archive/2014/05/edward-snowdens-other-motive-for-leaking/370068/),” *theatlantic.com*, May 13, 2014. + +1. Phillip Rogaway: + “[The Moral Character of Cryptographic Work](http://web.cs.ucdavis.edu/~rogaway/papers/moral-fn.pdf),” Cryptology ePrint 2015/1162, December 2015. + diff --git a/ddia/ch2.md b/ddia/ch2.md new file mode 100644 index 0000000..9557e0e --- /dev/null +++ b/ddia/ch2.md @@ -0,0 +1,1083 @@ +# 2. 数据模型与查询语言 + +![](img/ch2.png) + +> 语言的极限即世界的极限 +> +> —— 路德维奇·维特根斯坦, 《逻辑哲学》(1922) +> + +------------------- + +[TOC] + + + +数据模型可能是开发软件最重要的部分,因为它们有着深远的影响:不仅在于软件的编写方式,而且在于它如何影响对问题解决方案的思考。 + +大多数应用程序是通过将一个数据模型叠加在另一个之上来构建的。对于每一层,关键问题是:它是如何用下一层来表示的?例如: + +1. 作为一名应用程序开发人员,您将看到现实世界(包括人员,组织,货物,行为,资金流向,传感器等),并根据对象或数据结构以及API进行建模,操纵这些数据结构。这些结构通常是应用程序特定的。 +2. 如果要存储这些数据结构,可以使用通用数据模型(如JSON或XML文档,关系数据库中的表、或图模型)来表示它们。 +3. 构建数据库软件的工程师决定以内存,磁盘或网络上的字节表示JSON / XML /关系/图形数据。该表示可以允许以各种方式查询,搜索,操纵和处理数据。 +4. 在更低的层面上,硬件工程师已经计算出如何用电流,光脉冲,磁场等来表示字节。 + +在一个复杂的应用程序中,可能会有更多的中间层次,比如基于API的API,但是基本思想仍然是一样的:每个层都通过提供一个干净的数据模型来隐藏下面层的复杂性。这些抽象允许不同的人群(例如数据库供应商的工程师和使用他们的数据库的应用程序开发人员)有效地协作。 + +有许多不同类型的数据模型,每个数据模型都体现了如何使用它的假设。某些用法很容易,有些不被支持;一些操作很快,一些操作不好;一些数据转换感觉自然,有些是尴尬的。 + +掌握一个数据模型可能需要很多努力(想想关系数据建模有多少本书)。即使只使用一种数据模型,而不用担心其内部工作,构建软件也是非常困难的。但是由于数据模型对软件的功能有很大的影响,因此选择适合应用程序的软件是非常重要的。 + +在本章中,我们将研究一系列用于数据存储和查询的通用数据模型(前面列表中的第2点)。特别是,我们将比较关系模型,文档模型和一些基于图形的数据模型。我们还将查看各种查询语言并比较它们的用例。在第3章中,我们将讨论存储引擎是如何工作的。也就是说,这些数据模型是如何实际实现的(列表中的第3点)。 + + + +## 关系模型与文档模型 + +现在最着名的数据模型可能是SQL,它基于Edgar Codd在1970年提出的关系模型[1]:数据被组织到关系中(称为SQL表),其中每个关系是元组的无序集合SQL中的行)。 + +关系模型是一个理论上的建议,当时很多人怀疑是否能够有效实施。然而,到了20世纪80年代中期,关系数据库管理系统(RDBMSes)和SQL已成为大多数需要存储和查询具有某种规模结构的数据的人们的首选工具。关系数据库的优势已经持续了大约25 - 30年 - 计算历史上的一个永恒。 + +关系数据库的根源在于商业数据处理,这是在20世纪60年代和70年代在大型计算机上进行的。从今天的角度来看,用例显得很平常:通常是交易处理(进入销售或银行交易,航空公司预订,仓库库存)和批处理(客户发票,工资单,报告)。 + +当时的其他数据库迫使应用程序开发人员考虑数据库内部的数据表示。关系模型的目标是将实现细节隐藏在更简洁的界面之后。 + +多年来,在数据存储和查询方面存在着许多相互竞争的方法。在20世纪70年代和80年代初,网络模型和分层模型是主要的选择,但关系模型占据了主导地位。对象数据库在二十世纪八十年代末和九十年代初再次出现。 XML数据库出现在二十一世纪初,但只有小众采用。关系模型的每个竞争者都在其时代产生了大量的炒作,但从来没有持续[2]。 + +随着电脑越来越强大和联网,它们开始被用于日益多样化的目的。值得注意的是,关系数据库在业务数据处理的原始范围之外被推广到很广泛的用例。您今天在网上看到的大部分内容仍然是由关系数据库提供支持,无论是在线发布,讨论,社交网络,电子商务,游戏,软件即服务生产力应用程序等等。 + +### NoSQL的诞生 + +现在,在2010年代,NoSQL是推翻关系模式主导地位的最新尝试。 “NoSQL”这个名字是不幸的,因为它实际上并没有涉及到任何特定的技术,它最初只是作为一个吸引人的Twitter标签在2009年的开源,分布式,非关系数据库上聚会。无论如何,这个术语触动了一个神经,并迅速通过网络启动社区和更远的地方传播开来。一些有趣的数据库系统现在与#NoSQL#标签相关联,并被追溯性地重新解释为不仅是SQL [4]。 + +采用NoSQL数据库有几个驱动力,其中包括: + +* 需要比关系数据库更好的可扩展性,包括非常大的数据集或非常高的写入吞吐量 +* 相比商业数据库产品,偏爱免费和开源软件 +* 关系模型不能很好地支持一些特殊的查询操作 +* 对关系模型限制性感到受挫,对更多动态性与表现力的渴望 + +不同的应用程序有不同的要求,一个用例的最佳技术选择可能不同于另一个用例的最佳选择。因此,在可预见的未来,关系数据库似乎可能会继续与各种非关系数据库一起使用 - 这种想法有时也被称为混合持久化(Polyglot Persistences) + +### 对象关系不匹配 + +现在大多数应用程序开发都是在面向对象的编程语言中完成的,这导致了对SQL数据模型的普遍批评:如果数据存储在关系表中,那么应用程序代码中的对象之间需要一个笨拙的转换层,表,行和列的数据库模型。模型之间的不连贯有时被称为阻抗不匹配(impedance mismatch 从电子学借用一个术语。每个电路的输入和输出都有一定的阻抗(交流电阻)。当您将一个电路的输出连接到另一个电路的输入时,如果两个电路的输出和输入阻抗匹配,则连接上的功率传输将被最大化。阻抗不匹配可能导致信号反射和其他问题。) + +像ActiveRecord和Hibernate这样的对象关系映射(ORM)框架减少了这个翻译层需要的样板代码的数量,但是它们不能完全隐藏这两个模型之间的差异。 + +![](img/fig2-1.png) + +**图2-1 使用关系型模式来表示领英简历** + +例如,图2-1展示了如何在关系模式中表达简历(一个LinkedIn简介)。整个配置文件可以通过一个唯一的标识符user_id来标识。像first_name和last_name这样的字段每个用户只出现一次,所以他们可以在用户表上建模为列。但是,大多数人的职业(职位)多于一份工作,人们可能有不同的教育期限和不同数量的联系信息。从用户到这些项目之间存在一对多的关系,可以用多种方式来表示: + +* 在传统SQL模型(SQL:1999之前)中,最常见的规范化表示形式是将职位,培训和联系信息放在单独的表中,对用户表提供外键引用,如图2-1所示。 +* 更高版本的SQL标准增加了对结构化数据类型和XML数据的支持;这允许将多值数据存储在单行内,支持在这些文档内查询和索引。这些功能在Oracle,IBM DB2,MS SQL Server和PostgreSQL中都有不同程度的支持[6,7]。 JSON数据类型也受到几个数据库的支持,包括IBM DB2,MySQL和PostgreSQL [8]。 +* 第三种选择是将作业,教育和联系信息编码为JSON或XML文档,将其存储在数据库的文本列中,并让应用程序解释其结构和内容。在此设置中,通常不能使用数据库查询该编码列中的值。 + +对于一个像简历这样的数据结构来说,JSON表示可以是非常合适的:参见例2-1。 JSON比XML更简单。 面向文档的数据库(如MongoDB [9],RethinkDB [10],CouchDB [11]和Espresso [12])支持这种数据模型。 + +```json +{ + "user_id": 251, + "first_name": "Bill", + "last_name": "Gates", + "summary": "Co-chair of the Bill & Melinda Gates... Active blogger.", + "region_id": "us:91", + "industry_id": 131, + "photo_url": "/p/7/000/253/05b/308dd6e.jpg", + "positions": [ + { + "job_title": "Co-chair", + "organization": "Bill & Melinda Gates Foundation" + }, + { + "job_title": "Co-founder, Chairman", + "organization": "Microsoft" + } + ], + "education": [ + { + "school_name": "Harvard University", + "start": 1973, + "end": 1975 + }, + { + "school_name": "Lakeside School, Seattle", + "start": null, + "end": null + } + ], + "contact_info": { + "blog": "http://thegatesnotes.com", + "twitter": "http://twitter.com/BillGates" + } +} +``` + +一些开发人员认为JSON模型减少了应用程序代码和存储层之间的阻抗不匹配。但是,正如我们将在第4章中看到的那样,JSON作为数据编码格式也存在问题。缺乏一个模式往往被认为是一个优势;我们将在第39页的“文档模型中的模式灵活性”中讨论这个问题。 + +JSON表示比图2-1中的多表模式具有更好的局部性。如果要在关系示例中获取配置文件,则需要执行多个查询(通过user_id查询每个表),或者在用户表与其下属表之间执行混乱的多路连接。在JSON表示中,所有的相关信息都在一个地方,一个查询就足够了。 + +从用户配置文件到用户位置,教育历史和联系信息的一对多关系意味着数据中的树状结构,而JSON表示使得这个树状结构变得明确(见图2-2)。 + +![](img/fig2-2.png) + +**图2-2 一对多关系构建了一个树结构** + +### 多对一和多对多的关系 + +在上一节的例2-1中,region_id和industry_id是以ID而不是纯字符串“Greater Seattle Area”和“Philanthropy”的形式给出的。为什么? + +如果用户界面具有用于输入区域和行业的自由文本字段,则将其存储为纯文本字符串是有意义的。但是,对地理区域和行业进行标准化,并让用户从下拉列表或自动填充器中进行选择是有好处的: + +* 统一的样式和拼写 +* 避免歧义(例如,如果有几个同名的城市) +* 易于更新 - 名称只存储在一个地方,所以如果需要更改(例如,由于政治事件而改变城市名称),便于全面更新。 +* 本地化支持 - 当网站翻译成其他语言时,标准化的名单可以被本地化,所以地区和行业可以用观众的语言表示 +* 更好的搜索 - 例如,搜索华盛顿州的慈善家可以匹配这个概况,因为地区列表可以编码西雅图在华盛顿的事实(从“大西雅图地区”这个字符串中看不出来) + +无论是存储一个ID还是一个文本字符串,都是一个关于**重复**的问题。当你使用一个ID时,对人类有意义的信息(比如慈善词)只存储在一个地方,引用它的所有信息都使用一个ID(ID只在数据库中有意义)。当你直接存储文本时,每个使用它的记录中,都存储的是有意义的信息。 + +使用ID的好处是,因为它对人类没有任何意义,所以永远不需要改变:ID可以保持不变,即使它标识的信息发生变化。任何对人类有意义的东西都可能需要在将来某个时候改变 - 如果这些信息被复制,所有的冗余副本都需要更新。这会导致写入开销,并且存在不一致的风险(信息的一些副本被更新,但其他信息的副本不被更新)。去除这种重复是数据库规范化的关键思想。(关系模型区分了几种不同的范式,但这些区别实际上并不重要。 作为一个经验法则,如果您重复只能存储在一个地方的值,那么架构不会被规范化(normalized)。) + +不幸的是,对这些数据进行规范化需要多对一的关系(许多人生活在一个特定的地区,许多人在一个特定的行业工作),这与文档模型不太吻合。在关系数据库中,通过ID来引用其他表中的行是正常的,因为连接很容易。在文档数据库中,一对多树结构不需要连接,对连接的支持通常很弱 + +如果数据库本身不支持连接,则必须通过对数据库进行多个查询来模拟应用程序代码中的连接。 (在这种情况下,地区和行业的名单可能很小,变化不大,应用程序可以简单地将它们留在内存中,但是,联接的工作从数据库转移到应用程序代码。 + +而且,即使应用程序的初始版本适合无连接的文档模型,随着功能添加到应用程序中,数据也会变得更加互联。例如,考虑一下我们可以对简历例子进行的一些修改: + +* 组织和学校作为实体 + + 在前面的描述中,组织(用户工作的公司)和school_name(他们学习的地方)只是字符串。也许他们应该是对实体的引用呢?然后,每个组织,学校或大学都可以拥有自己的网页(标识,新闻提要等)。每个简历可以链接到它所提到的组织和学校,并且包括他们的标识和其他信息(参见图2-3,来自LinkedIn的一个例子)。 + +* 推荐 + + 假设你想添加一个新的功能:一个用户可以为另一个用户写一个推荐。推荐在用户的简历上显示,并附上推荐用户的姓名和照片。如果推荐人更新他们的照片,他们写的任何建议都需要反映新的照片。因此,推荐应该引用作者的个人资料。 + +![](img/fig2-3.png) + +**图2-3 公司名不仅是字符串,还是一个指向公司实体的连接(领英截图)** + +图2-4 阐明了这些新功能怎样使用多对多关系。 每个虚线矩形内的数据可以分组成一个文档,但是对单位,学校和其他用户的引用需要表示为引用,并且在查询时需要连接。 + +![](img/fig2-4.png) + +**图2-4 使用多对多关系扩展简历** + +### 文档数据库是否在重蹈覆辙? + +虽然关系数据库中经常使用多对多的关系和连接,但文档数据库和NoSQL重新讨论了如何最好地在数据库中表示这种关系的争论。这个辩论比NoSQL早得多,事实上,它可以追溯到最早的计算机化数据库系统。 + +20世纪70年代最受欢迎的业务数据处理数据库是IBM的信息管理系统(IMS),最初是为了在阿波罗太空计划中进行存货而开发的,并于1968年首次商业发布[13]。目前它仍在使用和维护,在IBM大型机的OS / 390上运行[14]。 +IMS的设计使用了一个相当简单的数据模型,称为分层模型,它与文档数据库使用的JSON模型有一些显着的相似之处[2]。它将所有数据表示为嵌套在记录中的记录树,就像图2-2的JSON结构一样。 + +像文档数据库一样,IMS在一对多的关系中运行良好,但是它使多对多的关系变得困难,并且不支持连接。开发人员必须决定是否冗余(非规范化)数据或手动解决从一个记录到另一个记录的引用。这些二十世纪六七十年代的问题与开发人员今天遇到的文档数据库问题非常相似[15]。 + +提出了各种解决方案来解决层次模型的局限性。其中最突出的两个是关系模型(它变成了SQL,接管了世界)和网络模型(最初很受关注,但最终变得模糊)。这两个阵营之间的“大辩论”持续了70年代的大部分时间[2]。 + +由于这两个模式解决的问题今天仍然如此相关,今天的辩论值得简要回顾一下。 + +#### 网络模型 + +网络模型由一个称为数据系统语言会议(CODASYL)的委员会进行了标准化,并由几个不同的数据源进行实施;它也被称为CODASYL模型[16]。 + +CODASYL模型是层次模型的推广。在分层模型的树结构中,每条记录只有一个父节点,在网络模式中,一个记录可能有多个父母。例如,“大西雅图地区”地区可能有一条记录,而且每个居住在该地区的用户都可以与之相关联。这允许对多对一和多对多的关系进行建模。 + +网络模型中记录之间的链接不是外键,而更像编程语言中的指针(同时仍然存储在磁盘上)。访问记录的唯一方法是沿着这些链路链上的根记录进行路径。这被称为访问路径。 + +在最简单的情况下,访问路径可能类似于遍历链表:从列表头开始,一次查看一条记录,直到找到所需的记录。但在一个多对多关系的世界里,几条不同的路径可能会导致相同的记录,一个使用网络模型的程序员必须跟踪这些不同的访问路径。 + +CODASYL中的查询是通过遍历记录列表和访问路径后,通过在数据库中移动游标来执行的。如果记录有多个父母(即来自其他记录的多个传入指针),则应用程序代码必须跟踪所有的各种关系。甚至CODASYL委员会成员也承认,这就像在一个n维数据空间中进行导航[17]。 + +尽管手动访问路径选择能够最有效地利用20世纪70年代非常有限的硬件功能(如磁带驱动器,其搜索速度非常慢),但问题是他们使查询和更新数据库的代码变得复杂不灵活。无论是分层还是网络模型,如果你没有一个你想要的数据的路径,那么你就处于一个困难的境地。你可以改变访问路径,但是你必须经过大量的手写数据库查询代码,并重写它来处理新的访问路径。很难对应用程序的数据模型进行更改。 + +#### 关系模型 + +相比之下,关系模型做的就是将所有的数据放在open中:一个关系(table)只是一个元组(行)的集合,就是这样。没有迷宫似的嵌套结构,如果你想看看数据,没有复杂的访问路径。您可以读取表中的任何或所有行,选择符合任意条件的行。您可以通过指定某些列作为关键字并匹配这些关键字来读取特定行。您可以在任何表中插入一个新的行,而不必担心与其他表的外键关系(外键约束允许对修改做限制,对于关系模型这并不是必选项。 即使有约束,在查询时执行外键连接,而在CODASYL中,连接在插入时高效完成。) + +在关系数据库中,查询优化器自动决定查询的哪些部分以哪个顺序执行,以及使用哪些索引。这些选择实际上是“访问路径”,但最大的区别在于它们是由查询优化器自动生成的,而不是由程序员生成,所以我们很少需要考虑它们。 + +如果你想以新的方式查询你的数据,你可以声明一个新的索引,查询会自动使用哪个索引是最合适的。您不需要更改查询来利用新的索引。 (另请参阅第42页上的“用于数据的查询语言”。)关系模型因此使向应用程序添加新功能变得更加容易。 + +关系数据库的查询优化器是复杂的,他们已经耗费了多年的研究和开发工作[18]。但关系模型的一个关键洞察是:您只需构建一次查询优化器,然后使用该数据库的所有应用程序都可以从中受益。如果您没有查询优化器,那么为特定查询手动编写访问路径比编写通用优化器更容易 - 但通用解决方案长期获胜。 + +#### 与文档数据库相比 + +文档数据库在一个方面还原为层次模型:在其父记录中存储嵌套记录(图2-1中的一对多关系,如位置,教育和contact_info),而不是在单独的表中。 + +但是,在表示多对一和多对多的关系时,关系数据库和文档数据库并没有根本的不同:在这两种情况下,相关项目都被一个唯一的标识符引用,这个标识符在关系模型中被称为外键,在文档模型中称为文档引用[9]。该标识符在读取时通过使用加入或后续查询来解决。迄今为止,文档数据库没有遵循CODASYL的路径。 + +### 关系型数据库与文档数据库在今日的对比 + +将关系数据库与文档数据库进行比较时,需要考虑许多差异,包括它们的容错属性(请参阅第5章)和处理并发性(请参阅第7章)。在本章中,我们将只关注数据模型中的差异。 + +支持文档数据模型的主要论据是架构灵活性,由于局部性而导致的更好的性能,对于某些应用程序而言更接近于应用程序使用的数据结构。关系模型通过为连接提供更好的支持以及支持多对一和多对多的关系来反击。 + +#### 哪个数据模型更方便写代码? + +如果应用程序中的数据具有类似文档的结构(即,一对多关系树,通常整个树被一次加载),那么使用文档模型可能是一个好主意。将类似文档的结构分解成多个表(如图2-1中的位置,教育和contact_info)的关系技术可能导致繁琐的模式和不必要的复杂的应用程序代码。 + +文档模型有一定的局限性:例如,您不能直接引用文档中的需要的项目,而是需要说“用户251的位置列表中的第二项”(很像访问路径在分层模型中)。但是,只要文件嵌套不太深,通常不是问题。 + +应用程序对文档数据库连接的垃圾支持也许或也许不是一个问题。例如,在使用文档数据库记录 哪个事件发生在哪儿 的分析应用程序中,可能永远不需要多对多的关系[19]。 + +但是,如果您的应用程序确实使用多对多关系,则文档模型变得不太吸引人。通过反规范化可以减少对连接的需求,但是应用程序代码需要做额外的工作来保持数据的一致性。通过向数据库发出多个请求,可以在应用程序代码中模拟连接,但是这也将复杂性转移到应用程序中,并且通常比由数据库内的专用代码执行的连接慢。在这种情况下,使用文档模型会导致更复杂的应用程序代码和更差的性能[15]。 + +说哪个数据模型在一般情况下导致更简单的应用程序代码是不可能的;它取决于数据项之间存在的关系种类。对于高度相互关联的数据,文档模型很尴尬,关系模型是可接受的,而图形模型(参见第49页上的“类图形数据模型”)是最自然的。 + +#### 文档模型中的架构灵活性 + +大多数文档数据库以及关系数据库中的JSON支持都不会对文档中的数据执行任何模式。关系数据库中的XML支持通常带有可选的模式验证。没有模式意味着可以将任意的键和值添加到文档中,并且在读取时,客户端对于文档可能包含的字段没有保证。 + +文档数据库有时称为无模式,但这是误导性的,因为读取数据的代码通常采用某种结构 - 即存在隐式模式,但不由数据库强制执行[20]。一个更精确的术语是 读时模式(schema-on-read数据的结构是隐含的,只有在数据被读取时才被解释),相应的是写时模式(schema-on-write 传统的关系数据库方法,模式是明确,数据库确保所有的数据都符合它的形式)[21]。 + +读取模式类似于编程语言中的动态(运行时)类型检查,而模式写入类似于静态(编译时)类型检查。就像静态和动态类型检查的倡导者对于它们的相对优点有很大的争议[22],数据库中模式的执行是一个有争议的话题,一般来说没有正确或错误的答案。 + +在应用程序想要改变其数据格式的情况下,这些方法之间的区别特别明显。例如,假设你正在将每个用户的全名存储在一个字段中,而你想分别存储名字和姓氏[23]。在文档数据库中,只需开始使用新字段写入新文档,并在应用程序中使用代码来处理读取旧文档时的情况。例如: + +```go +if (user && user.name && !user.first_name) { +// Documents written before Dec 8, 2013 don't have first_name + user.first_name = user.name.split(" ")[0]; +} +``` + +另一方面,在“静态类型”数据库模式中,通常会执行以下操作: + +```sql +ALTER TABLE users ADD COLUMN first_name text; +UPDATE users SET first_name = split_part(name, ' ', 1); -- PostgreSQL +UPDATE users SET first_name = substring_index(name, ' ', 1); -- MySQL +``` + +模式变更的速度很慢,而且需要停机。这种声誉并不是完全应得的:大多数关系数据库系统在几毫秒内执行ALTER TABLE语句。 MySQL是一个值得注意的例外,它在ALTER TABLE上复制整个表,这可能意味着在更改一个大表时会花费几分钟甚至几个小时的停机时间,尽管存在各种工具可以解决这个限制[24,25,26]。 + +在大型表上运行UPDATE语句在任何数据库上都可能会很慢,因为每一行都需要重写。如果这是不可接受的,应用程序可以将first_name设置为默认值NULL,并在读取时填充它,就像使用文档数据库一样。 + +如果由于某种原因(例如,数据是异构的)集合中的项目并不都具有相同的结构,例如,因为: + +* 有许多不同类型的对象,将每种类型的对象放在自己的表中是不现实的。 +* 数据的结构由您无法控制,且随时可能更改的外部系统决定。 + +在这样的情况下,模式的伤害远大于它的帮助,无模式文档可能是一个更加自然的数据模型。但是,如果所有记录都有相同的结构,那么模式就是记录和强制这种结构的有用机制。我们将在第四章更详细地讨论模式和模式演化。 + +#### 查询的数据局部性 + +文档通常以单个连续字符串形式存储,编码为JSON,XML或其二进制变体(如MongoDB的BSON)。如果您的应用程序经常需要访问整个文档(例如,将其渲染至网页),则此存储局部性会带来性能优势。如果将数据分割到多个表中(如图2-1所示),则需要进行多次索引查找才能将其全部检索出来,这可能需要更多的磁盘查找并花费更多的时间。 + +局部性仅适用于需要文档绝大部分内容的情况。数据库通常需要加载整个文档,即使只访问其中的一小部分,这对于大型文档来说是很浪费的。更新文档时,通常需要整个重写。 只有不改变文档大小的修改才可以容易地原地执行。因此通常建议,保持相对小的文档,并避免增加文档的大小的写入[9]。这些性能限制大大减少了文档数据库的实用场景。 + +值得指出的是,将相关的数据分组在一起的想法并不局限于文档模型。例如,Google的Spanner数据库在关系数据模型中提供了相同的位置属性,允许模式声明一个表的行应该在父表内交织 interleaved(嵌套)[27]。 Oracle允许使用一个称为多表索引集群表(multi-table index cluster tables)的特性相同的表[28]。 Bigtable数据模型(在Cassandra和HBase中使用)中的列族(column-family)概念与管理局部性的目的类似[29]。 + +在第3章我们还会看到更多关于本地的内容。 + +#### 文档和关系数据库的融合 + +自2000年代以来,大多数关系数据库系统(MySQL除外)都支持XML。这包括对XML文档进行本地修改的功能,以及在XML文档中进行索引和查询的功能,这允许应用程序使用与使用文档数据库时所做的非常相似的数据模型。 + +从9.3版本开始的PostgreSQL [8],从5.7版本开始的MySQL以及从版本10.5开始的IBM DB2 [30]也对JSON文档提供了类似的支持级别。鉴于Web API的JSON流行,其他关系数据库很可能会跟随他们的脚步并添加JSON支持。 + +在文档数据库方面,RethinkDB支持其查询语言中的类似关系的连接,一些MongoDB驱动程序自动解决数据库引用(有效地执行客户端JOIN,尽管这可能比在数据库中执行的JOIN慢,需要额外的网络往返,并且没有优化)。 + +似乎随着时间的推移,关系数据库和文档数据库变得越来越相似,这是一件好事:数据模型相互补充,如果一个数据库能够处理类似文档的数据,并对其执行关系查询,那么应用可以使用最适合其需要的特征的组合。 +关系模型和文档模型的混合是数据库未来的一个很好的途径。 + +(Codd对关系模型[1]的原始描述实际上允许在关系模式中与JSON文档非常相似。 他称之为 非简单域(nonsimple domains)。 这个想法是,一行中的值不一定是一个像数字或字符串一样的原始数据类型,也可以是一个嵌套的关系(表格) - 所以你可以有一个任意嵌套的树结构作为一个值, 很像30年后添加到SQL中的JSON或XML支持。) + + + +## 数据查询语言 + +当引入关系模型时,它包含了一种查询数据的新方法:SQL是一个声明式查询语言,而IMS和CODASYL使用命令式的代码来查询数据库。那是什么意思? +许多常用的编程语言是命令式的。例如,如果你有一个动物物种的列表,返回列表中的鲨鱼可以这样写: + +```js +function getSharks() { + var sharks = []; + for (var i = 0; i < animals.length; i++) { + if (animals[i].family === "Sharks") { + sharks.push(animals[i]); + } + } + return sharks; +} +``` + +在关系代数中: +$$ +sharks = σ_{family = "sharks"}(animals) +$$ +σ(希腊字母西格玛)是选择操作符,只返回符合条件的动物,family=“shark”。 + +在定义SQL时,它紧密地遵循关系代数的结构: + +```sql +SELECT * FROM animals WHERE family ='Sharks'; +``` + +命令式语言告诉计算机以特定顺序执行某些操作。你可以想象一下,逐行地遍历代码,评估条件,更新变量,并决定是否再循环一遍。 + +在声明式查询语言(如SQL或关系代数)中,您只需指定所需数据的模式 - 结果必须符合哪些条件,以及如何将数据转换(例如,排序,分组和集合)但不是如何实现这一目标。数据库系统的查询优化器决定使用哪些索引和哪些连接方法,以及以何种顺序执行查询的各个部分。 + +声明式查询语言是有吸引力的,因为它通常比命令式API更加简洁和容易。但更重要的是,它还隐藏了数据库引擎的实现细节,这使得数据库系统可以在不需要对查询进行任何更改的情况下提高性能。 + +例如,在本节开头所示的命令代码中,动物列表以特定顺序出现。如果数据库想要在场景后面回收未使用的磁盘空间,则可能需要移动记录,改变动物出现的顺序。数据库能否安全地执行,而不会中断查询? +SQL示例不保证任何特定的顺序,所以它不介意顺序是否改变。但是如果查询被写为命令式的代码,那么数据库就永远不能确定代码是否依赖于排序。 SQL在功能上更加有限的事实为数据库提供了更多自动优化的空间。 + +最后,声明性语言往往适合于并行执行。现在,通过增加更多的内核,CPU的速度会更快,而不是以比以前更高的时钟速度运行[31]。命令代码很难在多个内核和多个机器之间并行化,因为它指定了必须以特定顺序执行的指令。声明性语言在并行执行中获得更快的机会,因为它们仅指定结果的模式,而不是用于确定结果的算法。如果合适,数据库可以自由使用查询语言的并行实现[32]。 + +### Web上的声明式查询 + +声明式查询语言的优点不仅限于数据库。 为了说明这一点,我们在一个完全不同的环境中比较声明式和命令式的方法:一个Web浏览器。 + +假设你有一个关于海洋动物的网站。 用户当前正在查看鲨鱼页面,因此您将当前所选的导航项目“鲨鱼”标记为当前选中项目。 + +```html + +``` + +现在想让当前所选页面的标题有一个蓝色的背景,以便在视觉上突出显示。 使用CSS实现起来非常简单: + +```css +li.selected > p { + background-color: blue; +} +``` + +在这里,CSS选择器`li.selected> p`声明了我们想要应用蓝色样式的元素的模式:即直接父元素是一个CSS元素的`
  • `元素的所有`

    `元素。 示例中的元素`

    Sharks `匹配此模式,但`

    Whales `不匹配,因为其`

  • `父类缺少`class =“selected”`。 + + + +如果你使用XSL而不是CSS,你可以做类似的事情: + +```xml + + + + + +``` + +在这里,XPath表达式`li[@class='selected']/p`相当于上例中的CSS选择器`li.selected> p`。 CSS和XSL的共同之处在于,它们都是用于指定文档样式的声明性语言。 + +想象一下,如果你必须使用一命令式的方法,生活会是什么样子。在Javascript中,使用文档对象模型(DOM)API,结果可能如下所示: + +```js +var liElements = document.getElementsByTagName("li"); +for (var i = 0; i < liElements.length; i++) { + if (liElements[i].className === "selected") { + var children = liElements[i].childNodes; + for (var j = 0; j < children.length; j++) { + var child = children[j]; + if (child.nodeType === Node.ELEMENT_NODE && child.tagName === "P") { + child.setAttribute("style", "background-color: blue"); + } + } + } +} +``` + +这个JavaScript强制性地将元素设置为蓝色背景,但是代码太糟糕了。不仅比CSS和XSL等价物更长,更难理解,而且还有一些严重的问题: + +* 如果选定的类被移除(例如,因为用户点击了不同的页面),即使代码重新运行,蓝色也不会被移除 - 因此该项目将保持突出显示,直到整个页面被重新加载。使用CSS,浏览器会自动检测`li.selected> p`规则何时不再适用,并在选定的类被移除后立即移除蓝色背景。 + + +* 如果您想要利用新的API(例如`document.getElementsBy ClassName(“selected”`)甚至`document.evaluate()`)来提高性能,则必须重写代码。另一方面,浏览器供应商可以在不破坏兼容性的情况下提高CSS和XPath的性能。 + +在Web浏览器中,使用声明式CSS样式比在JavaScript中强制操作样式好得多。类似地,在数据库中,像SQL这样的声明式查询语言比命令式查询API(IMS与CODASYL都是命令式的,应用程序通常通过COBOL代码来一次一行迭代数据库中的记录)要好得多 + +### MapReduce查询 + +MapReduce是一个编程模型,用于在许多机器上批量处理大量的数据,由Google推广[33]。一些NoSQL数据存储(包括MongoDB和CouchDB)支持有限形式的MapReduce,作为在多个文档中执行只读查询的机制。 +总的来说,MapReduce在第10章中有更详细的描述。现在我们将简要讨论一下MongoDB对这一模型的应用。 + +MapReduce既不是一个声明性的查询语言,也不是一个完全强制性的查询API,而是位于两者之间的地方:查询的逻辑用代码片断来表示,这些代码片段被处理框架重复地调用。它基于许多函数式编程语言中存在的map(也称为collect)和reduce(也称为fold或inject)函数。 + +举一个例子,假设你是一名海洋生物学家,每当你看到海洋中的动物时,你都会在数据库中添加观察记录。现在你想生成一个报告,说明你每月看到多少鲨鱼。 + +在PostgreSQL中,你可以像这样表达这个查询: + +```sql +SELECT + date_trunc('month', observation_timestamp) AS observation_month, + sum(num_animals) AS total_animals +FROM observations +WHERE family = 'Sharks' +GROUP BY observation_month; +``` + +`date_trunc('month',timestamp)`函数确定包含时间戳记的日历月份,并返回代表该月份开始的另一个时间戳记。换句话说,它将时间戳降到最近的月份。 + +这个查询首先过滤观察结果,只显示鲨鱼家族的物种,然后根据它们发生的日历月份对观察结果进行分组,最后将在该月的所有观察中看到的动物数加起来。 + +MongoDB的MapReduce功能也可以这样表示: + +```js +db.observations.mapReduce(function map() { + var year = this.observationTimestamp.getFullYear(); + var month = this.observationTimestamp.getMonth() + 1; + emit(year + "-" + month, this.numAnimals); + }, + function reduce(key, values) { + return Array.sum(values); + }, + { + query: { + family: "Sharks" + }, + out: "monthlySharkReport" + }); +``` + +* 可以声明性地指定只考虑鲨鱼种类的过滤器(这是对MapReduce的特定于MongoDB的扩展)。 +* 每个匹配查询的文档都会调用一次JavaScript函数`map`,将`this`设置为文档对象。 +* map函数发出一个键(包括年份和月份的字符串,如“2013-12”或“2014-1”)和一个值(该观察中的动物数量)。 +* map发出的键值对按键组合。 对于具有相同键(即,相同的月份和年份)的所有键值对,`reduce`函数被调用一次。 +* reduce函数将特定月份内所有观测值的动物数量相加。 +* 最终的输出被写入到`monthlySharkReport`集合中。 + +例如假设`observations`集合包含这两个文档: + +```json +{ + observationTimestamp: Date.parse( "Mon, 25 Dec 1995 12:34:56 GMT"), + family: "Sharks", + species: "Carcharodon carcharias", + numAnimals: 3 +{ +} + observationTimestamp: Date.parse("Tue, 12 Dec 1995 16:17:18 GMT"), + family: "Sharks", + species: "Carcharias taurus", + numAnimals: 4 +} +``` + +`map`函数会对这两个文档每个调用一次,导致 `emit("1995-12",3)`和`emit("1995-12",4)`,然后,`reduce`函数会执行`reduce("1995-12",[3,4])`,并返回`7`。 + +map和reduce函数在功能上有限制:它们必须是纯函数,这意味着它们只使用传递给它们的数据作为输入,它们不能执行额外的数据库查询,也不能有任何副作用。这些限制允许数据库以任何顺序运行任何功能,并在失败时重新运行它们。然而,它们仍然是强大的:它们可以解析字符串,调用库函数,执行计算等等。 + +MapReduce是一个相当低级的编程模型,用于在一组机器上进行分布式执行。像SQL这样的更高级的查询语言可以用一系列的MapReduce操作来实现(见第10章),但是也有很多不使用MapReduce的分布式SQL实现。请注意,SQL中没有任何内容限制它在单个机器上运行,而MapReduce在分布式查询执行方面没有垄断权。 + +能够在查询中使用JavaScript代码是高级查询的一个重要特性,但不限于MapReduce,一些SQL数据库也可以使用JavaScript函数进行扩展[34]。 + +MapReduce的可用性问题是,你必须编写两个协调的JavaScript函数,这通常比编写单个查询更困难。此外,声明性查询语言为查询优化器提供了更多机会来提高查询的性能。由于这些原因,MongoDB 2.2添加了对称为聚合管道的声明性查询语言的支持[9]。在这种语言中,相同的鲨鱼计数查询如下所示: + +```js +db.observations.aggregate([ + { $match: { family: "Sharks" } }, + { $group: { + _id: { + year: { $year: "$observationTimestamp" }, + month: { $month: "$observationTimestamp" } + }, + totalAnimals: { $sum: "$numAnimals" } }} +]); +``` + +聚合流水线语言与SQL的子集表现类似,但是它使用基于JSON的语法而不是SQL的英语句子式语法; 这种差异也许是品味的问题。 这个故事的寓意是NoSQL系统可能会发现自己意外地重新发明了SQL,尽管是伪装的。 + + + +## 图数据模型 + +如我们之前所见,多对多关系是不同数据模型之间的重要区别特征。如果您的应用程序大多数是一对多关系(树状结构化数据),或者记录之间没有关系,则文档模型是适当的。 + +但是,如果多对多的关系在您的数据中很常见呢?关系模型可以处理多对多关系的简单情况,但是随着数据之间的连接变得更加复杂,开始将数据建模为图形变得更加自然。 + +一个图由两种对象组成:顶点(vertices 也称为节点nodes 或实体 entities)和边(edges 也称为关系relationships或弧 arcs )。许多种数据可以被模拟为一个图形。典型的例子包括: + +***社交图表*** + +顶点是人,边指示哪些人彼此认识。 + +***网络图*** + +顶点是网页,边缘表示到其他页面的HTML链接。 + +***公路或铁路网络*** + +顶点是连接点,边线代表它们之间的道路或铁路线。 + +众所周知的算法可以在这些图上进行操作:例如,汽车导航系统搜索道路网络中两点之间的最短路径,PageRank可以用在网络图上来确定网页的流行程度,从而其在搜索结果中的排名。 + +在刚刚给出的例子中,图中的所有顶点代表了相同类型的东西(人,网页或交叉路口)。然而,图并不局限于这样的同类数据:图的同样强大的用途是提供一种在单个数据存储中存储完全不同类型的对象的一致方式。例如,Facebook维护一个包含许多不同类型的顶点和边的单个图:顶点表示用户所做的人,地点,事件,签到和评论;边缘表示哪些人是彼此的朋友,哪个位置发生了检查,谁评论了哪个职位,谁参加了哪个事件,等等[35]。 + +在本节中,我们将使用图2-5所示的示例。它可以从社交网络或系谱数据库中获得:它显示了两个人,来自爱达荷州的Lucy和来自法国Beaune的Alain。他们已婚,住在伦敦。 + +![](img/fig2-5.png) + +##### 图2-5 图数据结构示例(框代表顶点,箭头代表边) + +有几种不同但相关的方法来构建和查询图表中的数据。 在本节中,我们将讨论属性图模型(由Neo4j,Titan和InfiniteGraph实现)和三存储(triple-store)模型(由Datomic,AllegroGraph等实现)。 我们将看图的三种声明性查询语言:Cypher,SPARQL和Datalog。 除此之外,还有像Gremlin [36]这样的图形查询语言和像Pregel这样的图形处理框架(见第10章)。 + +### 属性图 + +在属性图模型中,每个顶点(vertex)包括: + +* 唯一的标识符 +* 出向边的集合(outgoing edges) +* 入向边的集合(ingoing edges) +* 一组属性(键值对) + +每条边(edge)包括: + +* 唯一标识符 +* 边的起点(tail vertex 箭头出发的点) +* 边的终点(head vertex 箭头指向的点) +* 描述两个顶点之间关系类型的标签 +* 一组属性(键值对) + +可以将图存储看作两个关系表:一个存储顶点,另一个存储边,如例2-2所示(该模式使用PostgreSQL json数据类型来存储每个顶点或边的属性)。头部和尾部顶点存储为每个边缘。如果您想要一组顶点的输入或输出边,您可以分别通过head_vertex或tail_vertex来查询边表。 +例2-2 使用关系模式来表示属性图 + +```sql +CREATE TABLE vertices ( + vertex_id INTEGER PRIMARY KEY, + properties JSON +); + +CREATE TABLE edges ( + edge_id INTEGER PRIMARY KEY, + tail_vertex INTEGER REFERENCES vertices (vertex_id), + head_vertex INTEGER REFERENCES vertices (vertex_id), + label TEXT, + properties JSON +); + +CREATE INDEX edges_tails ON edges (tail_vertex); +CREATE INDEX edges_heads ON edges (head_vertex); +``` + +这个模型的一些重要方面是: + +1. 任何顶点都可以有一个边连接到任何其他顶点。没有哪种事物可不可以关联的模式限制。 +2. 给定任何顶点,您可以高效地找到它的入边和出边,从而遍历图,即沿着一系列顶点的路径前后移动。 (这就是为什么示例2-2在tail_vertex和head_vertex列上都有索引的原因。) +3. 通过对不同类型的关系使用不同的标签,可以在一个图中存储几种不同的信息,同时仍然保持一个干净的数据模型。 + +这些特性为数据建模提供了很大的灵活性,如图2-5所示。图中显示了一些传统关系模式难以表达的东西,例如不同国家的不同地区结构(法国有省和州,美国有不同的州和州),国中国的怪事(先忽略主权国家和国家错综复杂的烂摊子),不同的数据粒度(露西现在的住所被指定为一个城市,而她的出生地点只是在一个州的级别)。 + +你可以想象延伸图还包括许多关于露西和阿兰,或其他人的其他事实。例如,您可以用它来表示食物过敏(通过为每个过敏原引入一个顶点,以及人与过敏原之间的边缘来指示过敏),并将过敏原与一组过敏原显示哪些食物含有哪些物质的顶点。然后,你可以写一个查询,找出每个人吃什么是安全的。图表对于可演化性是有利的:当您向应用程序添加功能时,可以轻松扩展图形以适应应用程序数据结构的变化。 + +### Cypher查询语言 + +Cypher是属性图的声明式查询语言,为Neo4j图形数据库发明[37]。 (它是以电影“黑客帝国”中的角色命名的,与密码术中的密码无关[38]。) + +例2-3显示了将图2-5的左边部分插入图形数据库的Cypher查询。图的其余部分可以类似地添加,为了便于阅读而省略。每个顶点都有一个像USA或Idaho这样的符号名称,查询的其他部分可以使用这些名称在顶点之间创建边,使用箭头符号:`(Idaho) - [:WITHIN] - >(USA)`创建一个带有标记`WITHIN`的边,爱达荷州为尾节点,美国为头节点。 + +例2-3 将图2-5中的数据子集表示为Cypher查询 + +```cypher +CREATE + (NAmerica:Location {name:'North America', type:'continent'}), + (USA:Location {name:'United States', type:'country' }), + (Idaho:Location {name:'Idaho', type:'state' }), + (Lucy:Person {name:'Lucy' }), + (Idaho) -[:WITHIN]-> (USA) -[:WITHIN]-> (NAmerica), + (Lucy) -[:BORN_IN]-> (Idaho) +``` + +当图2-5的所有顶点和边被添加到数据库时,我们可以开始提出有趣的问题:例如,找到所有从美国移民到欧洲的人的名字。更确切地说,在这里我们想要找到在美国有一个BORN_IN边缘的所有顶点,还有一个LIVING_IN边缘到欧洲的一个位置,并且返回每个这些顶点的名称属性。 + +例2-4展示了如何在Cypher中表达这个查询。在MATCH子句中使用相同的箭头符号来查找图中的模式: + +```cypher +MATCH + (person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (us:Location {name:'United States'}), + (person) -[:LIVES_IN]-> () -[:WITHIN*0..]-> (eu:Location {name:'Europe'}) +RETURN person.name +``` + +查询可以被读取如下: + +* 找到满足以下两者的所有顶点(称之为person): + * 有一条到某个顶点`BORN_IN`类型的出边。从那个顶点开始可以沿着一系列`WITHIN`出边最终到达类型为`Location`,`name=United States`的顶点 + * 也有一条到某个顶点`LIVES_IN`类型的出边。沿着这条边,可以通过一系列`WITHIN`出边最终到达类型为`Location`,`name=Europe`的顶点 + * 对于这样的`Person`类型顶点,返回其name属性。 + +这条查询有几种可行的查询路径。这里给出的描述建议你首先扫描数据库中的所有人,检查每个人的出生地和居住地,然后只返回符合条件的人。 + +等价地,也可以从两个位置顶点开始并向后查找。如果名称属性上有一个索引,则可以高效地找到代表美国和欧洲的两个顶点。然后,您可以继续查找所有在WITHIN边缘中的位置(美国和欧洲的所有位置(州,地区,城市等))。最后,您可以查找可以通过在某个位置顶点处传入的BORN_IN或LIVES_IN边缘找到的人员。 + +对于声明性查询语言来说,典型的情况是,在编写查询语句时,您不需要指定执行细节:查询优化程序会自动选择预测效率最高的策略,因此您可以继续编写其余的应用程序。 + +### SQL中的图表查询 + +例2-2建议可以在关系数据库中表示图形数据。但是,如果我们把图形数据放入关系结构中,我们是否也可以使用SQL查询它?答案是肯定的,但有些困难。在关系数据库中,您通常会事先知道在查询中需要哪些连接。在图表查询中,您可能需要在找到要查找的顶点之前,遍历可变数量的边。也就是说,JOIN的数量事先并不确定。 + +在我们的例子中,这发生在Cypher查询中的`() - [:WITHIN * 0 ..] - >()`规则中。一个人的LIVES_IN边缘可以指向任何类型的位置:街道,城市,地区,地区,国家等。城市可以在一个地区,在一个州内的一个地区,在一个国家内的一个州等等.LIVES_IN边缘可以直接指向你正在查找的位置,或者可以在位置层次结构中删除几个级别。 +在Cypher中,`WITHIN * 0`表示这个事实非常简洁:意思是“沿着一个WITHIN边,零次或多次”。它就像正则表达式中的*运算符。 +由于SQL:1999,查询中可变长度遍历路径的思想可以使用称为递归公用表表达式(`WITH RECURSIVE`语法)的东西来表示。例2-5显示了同样的查询 - 查找使用这种技术(PostgreSQL,IBM DB2,Oracle和SQL Server支持)中从美国移民到欧洲的人的姓名。但是,与Cypher相比,语法非常笨拙。 + +例2-5 与示例2-4相同的查询,使用递归公用表表达式在SQL中表示 + +```sql +WITH RECURSIVE + -- in_usa 包含所有的美国境内的地点ID + in_usa(vertex_id) AS ( + SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'United States' + UNION + SELECT edges.tail_vertex FROM edges + JOIN in_usa ON edges.head_vertex = in_usa.vertex_id + WHERE edges.label = 'within' + ), + -- in_europe 包含所有的欧洲境内的地点ID + in_europe(vertex_id) AS ( + SELECT vertex_id FROM vertices WHERE properties ->> 'name' = 'Europe' + UNION + SELECT edges.tail_vertex FROM edges + JOIN in_europe ON edges.head_vertex = in_europe.vertex_id + WHERE edges.label = 'within' ), + + -- born_in_usa 包含了所有类型为Person,且出生在美国的顶点 + born_in_usa(vertex_id) AS ( + SELECT edges.tail_vertex FROM edges + JOIN in_usa ON edges.head_vertex = in_usa.vertex_id + WHERE edges.label = 'born_in' ), + + -- lives_in_europe 包含了所有类型为Person,且居住在欧洲的顶点。 + lives_in_europe(vertex_id) AS ( + SELECT edges.tail_vertex FROM edges + JOIN in_europe ON edges.head_vertex = in_europe.vertex_id + WHERE edges.label = 'lives_in') + + SELECT vertices.properties ->> 'name' + FROM vertices + JOIN born_in_usa ON vertices.vertex_id = born_in_usa.vertex_id + JOIN lives_in_europe ON vertices.vertex_id = lives_in_europe.vertex_id; +``` + +* 集合in_usa以(Location name=United States)作为种子,沿着边表,从种子集作为起点,将所有具有`with_in`边的终点加入种子集,不断递归直到边表内的所有条目都被访问完毕。 +* 同理建立点集in_europe,获取欧洲的地点列表。 +* 对于in_usa集合中的每个顶点,按照传入的born_in边缘来查找出生在美国某个地方的人。 +* 同样,对于in_europe集合中的每个顶点,请按照传入的lives_in边缘来查找居住在欧洲的人。 +* 最后,把在美国出生的人与在欧洲居住的人相交,获取他们的名称。 + +同一个查询,可以用一个查询语言写成4行,而另一个查询需要29行,这说明了不同的数据模型是为不同的应用场景设计的。选择适合应用程序的数据模型非常重要。 + +### 三元组存储和SPARQL + +三元组存储模式大体上与属性图模型相同,用不同的词来描述相同的想法。不过值得讨论的是,因为三元组存储有很多现成的工具和语言,这些工具和语言对于构建应用程序的工具箱可能是有价值的补充。 + +在三元组存储中,所有信息都以非常简单的三部分表示形式存储(主体,谓词,客体)。例如,在三重(吉姆,喜欢,香蕉),吉姆是主语,喜欢是谓语(动词),香蕉是对象。 + +三元组的主体相当于图中的一个顶点。而客体是两件事情之一: + +1. 原始数据类型中的值,例如字符串或数字。在这种情况下,三元组的谓词和对象相当于主题顶点上的属性的键和值。例如,(lucy,age,33)就像属性{“age”:33}的顶点lucy。 +2. 图中的另一个顶点。在这种情况下,谓词是图中的边,主体是尾部顶点,而对象是顶点。例如,在(lucy,marriedTo,alain)主语和宾语lucy和alain都是顶点,并且谓词marriedTo是连接他们的边的标签。 + +示例2-6显示了与示例2-3相同的数据,以称为Turtle的格式(Notation3(N3)[39])的一个子集形式写成三元组。 +例2-6。图2-5中的数据子集,表示为Turtle三元组 + +```reStructuredText +@prefix : . +_:lucy a :Person. +_:lucy :name "Lucy". +_:lucy :bornIn _:idaho. +_:idaho a :Location. +_:idaho :name "Idaho". +_:idaho :type "state". +_:idaho :within _:usa. +_:usa a :Location +_:usa :name "United States" +_:usa :type "country". +_:usa :within _:namerica. +_:namerica a :Location +_:namerica :name "North America" +_:namerica :type :"continent" +``` + +在这个例子中,图的顶点被写为:`_:someName`。这个名字并不意味着这个文件以外的任何东西。它的存在只是帮助我们明确三元组之间的相互引用。当谓词表示边时,该对象是一个顶点,如_:idaho:在_:usa内。当谓词是一个属性时,该对象是一个字符串,如_:usa:name“United States”。 +一遍又一遍地重复相同的主题是相当重复的,但幸运的是,您可以使用分号来说明关于同一主题的多个事情。这使得Turtle格式相当不错,可读性强:参见例2-7。 +例2-7。在示例2-6 @prefix:中写入数据的更简洁的方法。 + +``` +@prefix : . +_:lucy a :Person; :name "Lucy"; :bornIn _:idaho. +_:idaho a :Location; :name "Idaho"; :type "state"; :within _:usa +_:usa a :Loaction; :name "United States"; :type "country"; :within _:namerica. +_:namerica a :Location; :name "North America"; :type "continent". +``` + +#### 语义网络 + +如果您阅读更多关于三元组存储的信息,您可能会被卷入关于语义网的文章中。三元组存储数据模型完全独立于语义网络,例如,Datomic [40]是三元组存储,并不声称与它有任何关系。但是,由于在很多人眼中这两者紧密相连,我们应该简要地讨论一下。 + +语义网从本质上讲是一个简单而合理的想法:网站已经将信息发布为文字和图片供人类阅读,为什么不把它们作为机器可读的数据发布给计算机呢?资源描述框架(RDF)[41]的目的是作为不同网站以一致的格式发布数据的一种机制,允许来自不同网站的数据自动合并成一个数据网络 - 一种互联网范围内的“一切的数据库“。 + +不幸的是,这个语义网在二十一世纪初被过度使用,但到目前为止还没有显示出在实践中有任何实现的迹象,这使得许多人愤世嫉俗。它也遭受了令人眼花缭乱的缩略词,过于复杂的标准提议和自大。 + +但是,如果你仔细观察这些失败,那么语义Web项目中也有很多好的工作。即使您没有兴趣在语义网上发布RDF数据,三元组也可以成为应用程序的良好内部数据模型。 + +#### RDF数据模型 + +例2-7中使用的Turtle语言是RDF数据的可读格式。有时候,RDF也是以XML格式编写的,它可以更详细地完成同样的事情,参见例2-8。龟Turtle N3是最好的,因为它更容易阅读,像Apache Jena [42]这样的工具可以根据需要在不同的RDF格式之间自动转换。 + +例2-8。 例2-7的数据,用RDF / XML语法表示 + +```xml + + + Idaho + state + + + United States + country + + + North America + continent + + + + + + + Lucy + + + +``` + +RDF有一些奇怪之处,因为它是为了在互联网上交换数据而设计的。三元组的主题,谓词和对象通常是URI。例如,谓词可能是一个URI,如 ``或``,而不仅仅是WITHIN或LIVES_IN。这个设计背后的原因是你应该能够把你的数据和其他人的数据结合起来,如果他们给这个单词或者lives_in附加不同的含义,你不会得到冲突,因为它们的谓词实际上是``和``。 +从RDF的角度来看,URL 不一定需要能解析成什么东西,它只是一个命名空间。为避免与`http://URL`混淆,本节中的示例使用不可解析的URI,如urn:example:within。幸运的是,您只需在文件顶部指定一个前缀,然后忘记它。 + +### SPARQL查询语言 + +SPARQL是 RDF数据模型三元组存储的查询语言[43]。 (它是SPARQL协议和RDF查询语言的缩写,发音为“sparkle”。)它早于Cypher,并且由于Cypher的模式匹配是从SPARQL中借用的,所以它们看起来非常相似[37]。 +与从前从美国转移到欧洲的人相同的查询 - 在SPARQL中比在Cypher中更加简洁(请参见示例2-9)。 +例2-9。与示例2-4相同的查询,用SPARQL表示 + +```sparql +PREFIX : +SELECT ?personName WHERE { + ?person :name ?personName. + ?person :bornIn / :within* / :name "United States". + ?person :livesIn / :within* / :name "Europe". +} +``` + +结构非常相似。以下两个表达式是等价的(变量以SPARQL中的问号开头): + +``` +(person) -[:BORN_IN]-> () -[:WITHIN*0..]-> (location) # Cypher +?person :bornIn / :within* ?location. # SPARQL +``` + +由于RDF不区分属性和边,而只是使用两个属性,所以可以使用相同的语法来匹配属性。在下面的表达式中,变量usa被绑定到任何具有值为字符串“United States”的name属性的顶点: + +``` +(usa {name:'United States'}) # Cypher +?usa :name "United States". # SPARQL +``` + +SPARQL是一种很好的查询语言 - 即使语义网从来没有出现,它也可以成为应用程序内部使用的强大工具。 + +#### 图形数据库与网络模型相比较 + +在第36页的“文档数据库是否重复历史?”中,我们讨论了CODASYL和关系模型如何竞争解决IMS中的多对多关系问题。乍一看,CODASYL的网络模型看起来与图模型相似。 CODASYL是否是图形数据库的第二个变种? +不,他们在几个重要方面有所不同: + +* 在CODASYL中,数据库有一个模式,指定哪种记录类型可以嵌套在其他记录类型中。在图形数据库中,不存在这样的限制:任何顶点都可以具有到其他任何顶点的边。这为应用程序适应不断变化的需求提供了更大的灵活性。 +* 在CODASYL中,达到特定记录的唯一方法是遍历其中的一个访问路径。在图形数据库中,可以通过其唯一ID直接引用任何顶点,也可以使用索引来查找具有特定值的顶点。 +* 在CODASYL,记录的孩子们的有序集合,所以数据库的人不得不维持排序(其中有用于存储布局的后果),并且插入新记录到数据库的应用程序不得不担心的新记录的位置在这些集合中。在图形数据库中,顶点和边不是有序的(您只能在查询时对结果进行排序)。 +* 在CODASYL中,所有查询都是必要的,难以编写,并且很容易被架构中的变化破坏。在图形数据库中,如果需要,可以在命令式代码中编写遍历,但大多数图形数据库也支持高级声明式查询语言,如Cypher或SPARQL。 + + + +### 基础:Datalog + +Datalog是比SPARQL或Cypher更古老的语言,在20世纪80年代被学者广泛研究[44,45,46]。它在软件工程师中是不太知名的,但是它是重要的,因为它为以后的查询语言提供了基础。 + +在实践中,Datalog在一些数据系统中被使用:例如,它是Datomic [40]的查询语言,而Cascalog [47]是用于查询Hadoop中的大数据集的Datalog实现(Datomic和Cascalog使用Datalog的Clojure S表达式语法。 在下面的例子中使用了一个更容易阅读的Prolog语法,没有任何功能差异。) + +Datalog的数据模型类似于三元组模式,但有一点泛化。我们把它写成谓词(主语,宾语),而不是写三元语(主语,宾语,宾语)。例2-10显示了如何在Datalog中写入我们的例子中的数据。 + +例2-10. 图2-5中的数据子集,表示为Datalog事实 + +```prolog +name(namerica, 'North America'). +type(namerica, continent). + +name(usa, 'United States'). +type(usa, country). +within(usa, namerica). + +name(idaho, 'Idaho'). +type(idaho, state). +within(idaho, usa). + +name(lucy, 'Lucy'). +born_in(lucy, idaho). +``` + +现在我们已经定义了数据,我们可以像之前一样编写相同的查询,如例2-11所示。它看起来有点不同于Cypher或SPARQL的等价物,但是不要让你失望。 Datalog是Prolog的一个子集,如果你已经学过计算机科学,你可能已经见过。 + +例2-11。与示例2-4相同的查询,在Datalog中表示 + +``` +within_recursive(Location, Name) :- name(Location, Name). /* Rule 1 */ + +within_recursive(Location, Name) :- within(Location, Via), /* Rule 2 */ + within_recursive(Via, Name). + +migrated(Name, BornIn, LivingIn) :- name(Person, Name), /* Rule 3 */ + born_in(Person, BornLoc), + within_recursive(BornLoc, BornIn), + lives_in(Person, LivingLoc), + within_recursive(LivingLoc, LivingIn). + +?- migrated(Who, 'United States', 'Europe'). /* Who = 'Lucy'. */ + +``` + +Cypher和SPARQL使用SELECT立即跳转,但是Datalog一次只需要一小步。我们定义告诉数据库有关新谓词的规则:在这里,我们定义了两个新的谓词,在_recursive和migrated内。这些谓词不是存储在数据库中的三元组,而是它们是从数据或其他规则派生而来的。规则可以引用其他规则,就像函数可以调用其他函数或者自发地调用自己一样。像这样,复杂的查询可以一次构建一小块。 + +在规则中,以大写字母开头的单词是变量,谓词匹配如Cypher和SPARQL。例如,name(Location,Name)与可变绑定Location = namerica和Name ='North America'的三重名称(namerica,'North America')匹配。 + +如果系统可以在 - 操作符的右侧找到与所有谓词相匹配的规则,则适用该规则。当规则适用时,就好像将 - 的左边添加到数据库(用变量替换它们的值)。 + +因此,应用规则的一种可能的方式是: + +1. 名称(namerica,'北美')存在于数据库中,故规则1适用。它生成within_recursive(namerica,'北美')。 +2. 数据库中存在(usa,namerica),并在之前的步骤中生成(namerica,'North America'),所以适用规则2。它会产生within_recursive(美国,“北美”)。 +3. 在(爱达荷州,美国)存在于数据库和上一步生成within_recursive(美国,“北美”),所以规则2适用。它产生within_recursive(爱达荷州,“北美”)。 + +通过重复应用规则1和2,within_recursive谓词可以告诉我们在我们的数据库中包含的北美(或任何其他位置名称)的所有位置。这个过程如图2-6所示。 + +![](img/fig2-6.png) + +图2-6. 使用示例2-11中的Datalog规则确定爱达荷州在北美。 + +现在规则3可以找到出生在某个地方BornIn的人,并住在某个地方LivingIn。通过查询BornIn ='United States'和LivingIn ='Europe',并将此人作为变量Who,我们要求Datalog系统找出变量Who可以出现哪些值。因此,最后我们得到了与早先的Cypher和SPARQL查询相同的答案。 + +Datalog方法需要对本章讨论的其他查询语言采取不同的思维方式,但这是一种非常强大的方法,因为规则可以在不同的查询中进行组合和重用。简单的一次性查询不太方便,但是如果数据很复杂,它可以更好地处理。 + + + +## 本章小结 + +数据模型是一个巨大的课题,在本章中,我们快速浏览了各种不同的模型。我们没有足够的空间来详细介绍每个模型的细节,但是希望这个概述足以激起您的兴趣,以更多地了解最适合您的应用需求的模型。 + +在历史上,数据开始被表示为一棵大树(层次数据模型),但是这不利于表示多对多的关系,所以发明了关系模型来解决这个问题。最近,开发人员发现一些应用程序也不适合在关系模型中使用。新的非关系型“NoSQL”数据存储在两个主要方向上有分歧: + +1. 文档数据库的应用场景是:数据通常是自我包含的,而且文档之间的关系非常罕见。 +2. 图形数据库用于相反的场景: 任何东西都可能和任何东西相关联。 + +所有这三种模型(文档,关系和图形)今天都被广泛使用,并且在各自的领域都是很好用的。一个模型可以用另一个模型来模拟 - 例如,图形数据可以在关系数据库中表示 - 但结果往往是尴尬的。这就是为什么我们有不同的系统用于不同的目的,而不是一个单一的万能解决方案。 + +文档数据库和图数据库有一个共同点,那就是它们通常不会为存储的数据强制实施一个模式,这可以使应用程序更容易适应不断变化的需求。但是应用程序很可能仍假定数据具有一定的结构:这只是模式是明确的(强制写入)还是隐含的(在读取时处理)的问题。 + +每个数据模型都带有自己的查询语言或框架,我们讨论了几个例子:SQL,MapReduce,MongoDB的聚合管道,Cypher,SPARQL和Datalog。我们也谈到了CSS和XSL / XPath,它们不是数据库查询语言,而包含有趣的相似之处。 + +虽然我们已经覆盖了很多地方,但仍然有许多数据模型没有提到。举几个简单的例子: + +* 研究人员使用基因组数据通常需要执行序列相似性搜索,这意味着需要一个很长的字符串(代表一个DNA分子),并将其与一个类似但不完全相同的大型字符串数据库进行匹配。这里所描述的数据库都不能处理这种用法,这就是为什么研究人员编写了像GenBank这样的专门的基因组数据库软件的原因[48]。 +* 粒子物理学家数十年来一直在进行大数据类型的大规模数据分析,像大型强子对撞机(LHC)这样的项目现在可以工作在数百亿兆字节的范围内!在这样的规模下,需要定制解决方案来阻止硬件成本从失控中解脱出来[49]。 +* 全文搜索可以说是一种经常与数据库一起使用的数据模型。信息检索是一个大的专业课题,在本书中我们不会详细介绍,但是我们将在第三章和第三章中介绍搜索指标。 + +在下一章中,我们将讨论在实现本章描述的数据模型时会发挥的一些权衡。 + + + + + +## 参考文献 + + +1. Edgar F. Codd: + “[A Relational Model of Data for Large Shared Data Banks](https://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf),” *Communications of the ACM*, volume 13, number + 6, pages 377–387, June 1970. + [doi:10.1145/362384.362685](http://dx.doi.org/10.1145/362384.362685) + +1. Michael Stonebraker and Joseph M. Hellerstein: + “[What Goes Around Comes Around](http://mitpress2.mit.edu/books/chapters/0262693143chapm1.pdf),” + in *Readings in Database Systems*, 4th edition, MIT Press, pages 2–41, 2005. + ISBN: 978-0-262-69314-1 + +1. Pramod J. Sadalage and + Martin Fowler: *NoSQL Distilled*. Addison-Wesley, August 2012. ISBN: + 978-0-321-82662-6 + +1. Eric Evans: + “[NoSQL: What's in a Name?](http://blog.sym-link.com/2009/10/30/nosql_whats_in_a_name.html),” *blog.sym-link.com*, October 30, 2009. + +1. James Phillips: + “[Surprises in Our NoSQL Adoption Survey](http://blog.couchbase.com/nosql-adoption-survey-surprises),” *blog.couchbase.com*, February 8, 2012. + +1. Michael Wagner: + *SQL/XML:2006 – Evaluierung der Standardkonformität ausgewählter Datenbanksysteme*. + Diplomica Verlag, Hamburg, 2010. ISBN: 978-3-836-64609-3 + +1. “[XML Data in SQL Server](http://technet.microsoft.com/en-us/library/bb522446.aspx),” SQL Server 2012 documentation, *technet.microsoft.com*, 2013. + +1. “[PostgreSQL 9.3.1 Documentation](http://www.postgresql.org/docs/9.3/static/index.html),” The PostgreSQL Global Development Group, 2013. + +1. “[The MongoDB 2.4 Manual](http://docs.mongodb.org/manual/),” MongoDB, Inc., 2013. + +1. “[RethinkDB 1.11 Documentation](http://www.rethinkdb.com/docs/),” *rethinkdb.com*, 2013. + +1. “[Apache CouchDB 1.6 Documentation](http://docs.couchdb.org/en/latest/),” *docs.couchdb.org*, 2014. + +1. Lin Qiao, Kapil Surlaker, Shirshanka Das, et al.: + “[On Brewing Fresh Espresso: LinkedIn’s Distributed Data Serving Platform](http://www.slideshare.net/amywtang/espresso-20952131),” at *ACM International Conference on Management + of Data* (SIGMOD), June 2013. + +1. Rick Long, Mark Harrington, Robert Hain, and Geoff Nicholls: + *IMS Primer*. + IBM Redbook SG24-5352-00, IBM International Technical Support Organization, January 2000. + +1. Stephen D. Bartlett: + “[IBM’s IMS—Myths, Realities, and Opportunities](ftp://public.dhe.ibm.com/software/data/ims/pdf/TCG2013015LI.pdf),” The Clipper Group Navigator, TCG2013015LI, July 2013. + +1. Sarah Mei: + “[Why You Should Never Use MongoDB](http://www.sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/),” + *sarahmei.com*, November 11, 2013. + +1. J. S. Knowles and D. M. R. Bell: + “The CODASYL Model,” in *Databases—Role and Structure: An Advanced Course*, edited by P. M. + Stocker, P. M. D. Gray, and M. P. Atkinson, pages 19–56, Cambridge University Press, 1984. ISBN: + 978-0-521-25430-4 + +1. Charles W. Bachman: + “[The Programmer as Navigator](http://dl.acm.org/citation.cfm?id=362534),” + *Communications of the ACM*, volume 16, number 11, pages 653–658, November 1973. + [doi:10.1145/355611.362534](http://dx.doi.org/10.1145/355611.362534) + +1. Joseph M. Hellerstein, Michael Stonebraker, and James Hamilton: + “[Architecture of a Database System](http://db.cs.berkeley.edu/papers/fntdb07-architecture.pdf),” + *Foundations and Trends in Databases*, volume 1, number 2, pages 141–259, November 2007. + [doi:10.1561/1900000002](http://dx.doi.org/10.1561/1900000002) + +1. Sandeep Parikh and Kelly Stirman: + “[Schema Design for Time Series Data in MongoDB](http://blog.mongodb.org/post/65517193370/schema-design-for-time-series-data-in-mongodb),” *blog.mongodb.org*, October 30, 2013. + +1. Martin Fowler: + “[Schemaless Data Structures](http://martinfowler.com/articles/schemaless/),” + *martinfowler.com*, January 7, 2013. + +1. Amr Awadallah: + “[Schema-on-Read vs. Schema-on-Write](http://www.slideshare.net/awadallah/schemaonread-vs-schemaonwrite),” at *Berkeley EECS RAD Lab Retreat*, Santa Cruz, CA, May 2009. + +1. Martin Odersky: + “[The Trouble with Types](http://www.infoq.com/presentations/data-types-issues),” + at *Strange Loop*, September 2013. + +1. Conrad Irwin: + “[MongoDB—Confessions of a PostgreSQL Lover](https://speakerdeck.com/conradirwin/mongodb-confessions-of-a-postgresql-lover),” at *HTML5DevConf*, October 2013. + +1. “[Percona Toolkit Documentation: pt-online-schema-change](http://www.percona.com/doc/percona-toolkit/2.2/pt-online-schema-change.html),” Percona Ireland Ltd., 2013. + +1. Rany Keddo, Tobias Bielohlawek, and Tobias Schmidt: + “[Large Hadron Migrator](https://github.com/soundcloud/lhm),” SoundCloud, 2013. + +1. Shlomi Noach: + “[gh-ost: GitHub's Online Schema Migration Tool for MySQL](http://githubengineering.com/gh-ost-github-s-online-migration-tool-for-mysql/),” *githubengineering.com*, August 1, 2016. + +1. James C. Corbett, Jeffrey Dean, Michael Epstein, et al.: + “[Spanner: Google’s Globally-Distributed Database](http://research.google.com/archive/spanner.html),” + at *10th USENIX Symposium on Operating System Design and Implementation* (OSDI), + October 2012. + +1. Donald K. Burleson: + “[Reduce I/O with Oracle Cluster Tables](http://www.dba-oracle.com/oracle_tip_hash_index_cluster_table.htm),” *dba-oracle.com*. + +1. Fay Chang, Jeffrey Dean, Sanjay Ghemawat, et al.: + “[Bigtable: A Distributed Storage System for Structured Data](http://research.google.com/archive/bigtable.html),” at *7th USENIX Symposium on Operating System Design and + Implementation* (OSDI), November 2006. + +1. Bobbie J. Cochrane and Kathy A. McKnight: + “[DB2 JSON Capabilities, Part 1: Introduction to DB2 JSON](http://www.ibm.com/developerworks/data/library/techarticle/dm-1306nosqlforjson1/),” IBM developerWorks, June 20, 2013. + +1. Herb Sutter: + “[The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software](http://www.gotw.ca/publications/concurrency-ddj.htm),” *Dr. Dobb's Journal*, + volume 30, number 3, pages 202-210, March 2005. + +1. Joseph M. Hellerstein: + “[The Declarative Imperative: Experiences and Conjectures in Distributed Logic](http://www.eecs.berkeley.edu/Pubs/TechRpts/2010/EECS-2010-90.pdf),” Electrical Engineering and + Computer Sciences, University of California at Berkeley, Tech report UCB/EECS-2010-90, June + 2010. + +1. Jeffrey Dean and Sanjay Ghemawat: + “[MapReduce: Simplified Data Processing on Large Clusters](http://research.google.com/archive/mapreduce.html),” at *6th USENIX Symposium on Operating System Design and + Implementation* (OSDI), December 2004. + +1. Craig Kerstiens: + “[JavaScript in Your Postgres](https://blog.heroku.com/javascript_in_your_postgres),” + *blog.heroku.com*, June 5, 2013. + +1. Nathan Bronson, Zach Amsden, George Cabrera, et al.: + “[TAO: Facebook’s Distributed Data Store for the Social Graph](https://www.usenix.org/conference/atc13/technical-sessions/presentation/bronson),” at + *USENIX Annual Technical Conference* (USENIX ATC), June 2013. + +1. “[Apache TinkerPop3.2.3 Documentation](http://tinkerpop.apache.org/docs/3.2.3/reference/),” *tinkerpop.apache.org*, October 2016. + +1. “[The Neo4j Manual v2.0.0](http://docs.neo4j.org/chunked/2.0.0/index.html),” + Neo Technology, 2013. + +1. Emil Eifrem: + [Twitter correspondence](https://twitter.com/emileifrem/status/419107961512804352), January 3, 2014. + +1. David Beckett and Tim Berners-Lee: + “[Turtle – Terse RDF Triple Language](http://www.w3.org/TeamSubmission/turtle/),” + W3C Team Submission, March 28, 2011. + +1. “[Datomic Development Resources](http://docs.datomic.com/),” Metadata Partners, LLC, 2013. + +1. W3C RDF Working Group: + “[Resource Description Framework (RDF)](http://www.w3.org/RDF/),” + *w3.org*, 10 February 2004. + +1. “[Apache Jena](http://jena.apache.org/),” + Apache Software Foundation. + +1. Steve Harris, Andy Seaborne, and Eric + Prud'hommeaux: “[SPARQL 1.1 Query Language](http://www.w3.org/TR/sparql11-query/),” + W3C Recommendation, March 2013. + +1. Todd J. Green, Shan Shan Huang, Boon Thau Loo, and Wenchao Zhou: + “[Datalog and Recursive Query Processing](http://blogs.evergreen.edu/sosw/files/2014/04/Green-Vol5-DBS-017.pdf),” *Foundations and Trends in Databases*, + volume 5, number 2, pages 105–195, November 2013. + [doi:10.1561/1900000017](http://dx.doi.org/10.1561/1900000017) + +1. Stefano Ceri, Georg Gottlob, and Letizia Tanca: + “[What You Always Wanted to Know About Datalog (And Never Dared to Ask)](https://www.researchgate.net/profile/Letizia_Tanca/publication/3296132_What_you_always_wanted_to_know_about_Datalog_and_never_dared_to_ask/links/0fcfd50ca2d20473ca000000.pdf),” *IEEE + Transactions on Knowledge and Data Engineering*, volume 1, number 1, pages 146–166, March 1989. + [doi:10.1109/69.43410](http://dx.doi.org/10.1109/69.43410) + +1. Serge Abiteboul, Richard Hull, and Victor Vianu: + *Foundations of Databases*. Addison-Wesley, 1995. + ISBN: 978-0-201-53771-0, available online at *webdam.inria.fr/Alice* + +1. Nathan Marz: + “[Cascalog](http://cascalog.org/)," *cascalog.org*. + +1. Dennis A. Benson, + Ilene Karsch-Mizrachi, David J. Lipman, et al.: + “[GenBank](http://nar.oxfordjournals.org/content/36/suppl_1/D25.full-text-lowres.pdf),” + *Nucleic Acids Research*, volume 36, Database issue, pages D25–D30, December 2007. + [doi:10.1093/nar/gkm929](http://dx.doi.org/10.1093/nar/gkm929) + +1. Fons Rademakers: + “[ROOT for Big Data Analysis](http://indico.cern.ch/getFile.py/access?contribId=13&resId=0&materialId=slides&confId=246453),” at *Workshop on the Future of Big Data Management*, + London, UK, June 2013. + diff --git a/ddia/ch3.md b/ddia/ch3.md new file mode 100644 index 0000000..583e66c --- /dev/null +++ b/ddia/ch3.md @@ -0,0 +1,856 @@ +# 3. 存储与检索 + +![](img/ch3.png) + +> 建立秩序,省却搜索 +> +> ——德国谚语 +> + +------------------- + +[TOC] + +在最基本的层次上,一个数据库需要完成两件事情:当你给它数据时,它应该存储起来,而当你提问时,它应该把数据返回给你。 + +在第二章中,我们讨论了数据模型和查询语言,即程序员录入数据库的数据格式,以及你可以再次获取它的机制。在本章中,我们讨论一样的问题,但是是从数据库的视角:我们如何存储我们提供的数据,以及如何在需要时重新找到数据。 + +作为程序员,为什么要关心数据库如何在内部处理存储和检索?您可能不会从头开始实现自己的存储引擎,但是您需要从可用的许多存储引擎中选择适合应用程序的存储引擎。为了调整存储引擎以适应应用的工作负载,你需要大致了解存储引擎在做什么。 + +特别需要注意,针对事务性工作负载优化的存储引擎,与针对分析优化的存储引擎之间存在着巨大差异。稍后我们将在第90页的 “事务处理或分析?” 和第91页的 “列存储”中探讨这个区别,那里将讨论针对分析优化的一系列存储引擎。 + +但是,我们将从您最可能熟悉的两大类数据库:传统关系型数据库与所谓的“NoSQL”数据库开始,通过介绍它们的存储引擎来开始本章的内容。 + +我们会研究两大类存储引擎:日志结构(log-structured)的存储引擎,以及面向页面(page-oriented)的存储引擎(如B树)。 + + + +### 数据库的底层数据结构 + +世界上最简单的数据库可以用两个Bash函数实现: + +```bash +#!/bin/bash +db_set () { + echo "$1,$2" >> database +} + +db_get () { + grep "^$1," database | sed -e "s/^$1,//" | tail -n 1 +} +``` + +这两个函数实现了键值存储的功能。执行`db_set key value`,会将`key`和`value`存储在数据库中。键和值可以是(几乎)任何你喜欢的东西,例如,值可以是JSON文档。然后调用`db_get key`,查找与该键关联的最新值并将其返回。麻雀虽小,五脏俱全: + +```bash +$ db_set 123456 '{"name":"London","attractions":["Big Ben","London Eye"]}' $ db_set 42 '{"name":"San Francisco","attractions":["Golden Gate Bridge"]}' +$ db_get 42 +{"name":"San Francisco","attractions":["Golden Gate Bridge"]} +``` + +底层的存储格式非常简单:一个文本文件,每行包含一条逗号分隔的键值对(忽略转义问题的话,大致类似于CSV文件)。每次对`db_set`的调用都会追加记录到文件末尾,所以更新键的时候旧版本的值不会被覆盖。需要查看文件中最后一次出现的键以查找最新值(因此`db_get`中使用了`tail -n 1 `。) + +```bash +$ db_set 42 '{"name":"San Francisco","attractions":["Exploratorium"]}' $ db_get 42 + {"name":"San Francisco","attractions":["Exploratorium"]} +$ cat database +123456,{"name":"London","attractions":["Big Ben","London Eye"]} 42,{"name":"San Francisco","attractions":["Golden Gate Bridge"]} 42,{"name":"San Francisco","attractions":["Exploratorium"]} +``` + +`db_set`函数对于极其简单的场景其实有非常好的性能,因为在文件尾部追加写入通常是非常高效的。与`db_set`做的事情类似,许多数据库内部使用日志(log),也就是一个Append-Only的数据文件。真正的数据库有更多的问题需要处理(如并发控制,回收磁盘空间以免日志无限增长,处理错误和部分写入记录),但基本原理是一样的。日志非常有用,我们还将在本书的其它部分见到它。 + +> 日志这个词通常用来指应用程序日志,应用程序输出描述发生事情的文本。本书中,日志用于更一般的含义上:一个只有追加记录的序列。它不一定是人类可读的记录,它可能是只能由其他程序读取的二进制记录。 + +另一方面,如果这个数据库中有大量记录,则我们的`db_get`函数的性能会非常糟糕。每次你想查找一个键时,`db_get`必须从头到尾扫描整个数据库文件来查找键的出现。在算法方面,查找的成本是`O(n)`:如果数据库的记录数量n增加了一倍,查找也需要一倍的时间。这就不好了。 + +为了高效地找到数据库中特定键的值,我们需要一个数据结构:索引。本章将介绍一系列的索引结构并对它们进行对比。索引的通用思路是保存一些额外的元数据作为路标,帮助你找到你想要的数据。如果您想以几种不同的方式在相同的数据中搜索,也许需要在数据的不同部分使用多个不同的索引。 + +索引是从主数据派生的附加结构。许多数据库允许添加和删除索引,这不会影响数据的内容,它只影响查询的性能。维护额外的结构会产生开销,特别是在写入时。写入性能很难超过简单地追加写入文件,因为这是最简单的写入操作。任何类型的索引通常都会减慢写入速度,因为每次写入数据时都需要更新索引。 + +这是存储系统中一个重要的权衡:精心选取的索引加快了读取查询速度,但是每个索引都会减慢写入速度。因此数据库通常不会索引所有内容,需要程序员或DBA通过对应用查询模式的了解来手动选择索引。你可以选择能为应用带来最大收益,同时又不会引入超必要开销的索引。 + + + +### 哈希索引 + +让我们从键值数据(key-value Data)的索引开始。这不是您可以索引的唯一一种数据类型,但键值数据是非常常见的。对于更复杂的索引来说,这是一个有用的构建模块。 + +键值存储与在大多数编程语言中可以找到的字典类型非常相似,通常字典都是用散列表(哈希表)实现的。哈希映射在许多算法教科书中都有描述[1,2],所以我们不会详细讨论它们工作方式。既然我们已经有内存数据结构——HashMap,为什么不使用它们来索引在磁盘上的数据呢? + +假设我们的数据存储只包含一个文件,就像前面的例子一样。然后,最简单的索引策略是:保留一个内存HashMap,其中每个键映射到数据文件中的一个字节偏移量,即该值可以被找到的位置。 + +如图3-1所示。无论何时将新的键值对添加到文件中,还要更新散列映射以反映刚刚写入的数据的偏移量(这适用于插入新键和更新现有键)。查找一个值时,使用哈希映射来查找数据文件中的偏移量,寻找该位置并读取该值。 + +![](img/fig3-1.png) + +**图3-1 以类CSV格式存储键值对的日志,并使用内存哈希映射进行索引。** + +听上去简单,但这是一个可行的方法。这实际上就是Bitcask做的事情(Riak中默认的存储引擎)[3]。 Bitcask提供高性能的读取和写入操作,但所有键必须能合适地放入在内存,因为哈希映射完全保留在内存中。这些值可以使用比可用内存更多的空间,因为可以从磁盘上通过一次`seek`加载所需部分,如果数据文件的那部分已经在文件系统缓存中,则读取根本不需要任何磁盘I/O。 + +像Bitcask这样的存储引擎非常适合每个键的值经常更新的情况。例如,键可能是猫视频的URL,值可能是它播放的次数(每次有人点击播放按钮时递增)。在这种类型的工作负载中,有很多写操作,但是没有太多不同的键 - 每个键有很多的写操作,但是将所有键保存在内存中是可行的。 + +直到现在,我们只是追加写一个文件 - 所以我们如何避免最终用完磁盘空间?一个好的解决方案是通过在达到一定大小时关闭一个段文件,然后将其写入一个新的段文件来将日志分割成特定大小的段。然后我们可以对这些段进行压缩,如图3-2所示。压缩意味着在日志中丢弃重复的键,只保留每个键的最新更新。 + +![](img/fig3-2.png) + +**图3-2 压缩键值更新日志(统计猫视频的播放次数),只保留每个键的最近值** + +而且,由于压缩经常会使得段更小(假设在一个段内键被平均重写了好几次),我们也可以在执行压缩的同时将多个段合并在一起,如图3-3所示。段被写入后永远不会被修改,所以合并的段被写入一个新的文件。冻结段的合并和压缩可以在后台线程中完成,在进行时,我们仍然可以继续使用旧的段文件来正常提供读写请求。合并过程完成后,我们将读取请求转换为使用新的合并段而不是旧段 - 然后可以简单地删除旧的段文件。 + +![](img/fig3-3.png) + +**图3-3 同时执行压缩和分段合并** + +每个段现在都有自己的内存散列表,将键映射到文件偏移量。为了找到一个键的值,我们首先检查最近段的哈希映射;如果键不存在,我们检查第二个最近的段,依此类推。合并过程保持细分的数量,所以查找不需要检查许多哈希映射。 +大量的细节进入实践这个简单的想法工作。简而言之,一些真正实施中重要的问题是: + +* 文件格式 + + CSV不是日志的最佳格式。使用二进制格式更快,更简单,首先以字节为单位对字符串的长度进行编码,然后使用原始字符串(不需要转义)。 + +* 删除记录 + + 如果要删除一个键及其关联的值,则必须在数据文件(有时称为逻辑删除)中附加一个特殊的删除记录。当日志段被合并时,逻辑删除告诉合并过程放弃删除键的任何以前的值。 + +* 崩溃恢复 + + 如果数据库重新启动,则内存散列映射将丢失。原则上,您可以通过从头到尾读取整个段文件并在每次按键时注意每个键的最近值的偏移量来恢复每个段的哈希映射。但是,如果段文件很大,这可能需要很长时间,这将使服务器重新启动痛苦。 Bitcask通过存储加速恢复磁盘上每个段的哈希映射的快照,可以更快地加载到内存中。 + +* 部分书面记录 + + 数据库可能随时崩溃,包括将记录附加到日志中途。 Bitcask文件包含校验和,允许检测和忽略日志的这些损坏部分。 + +* 并发控制 + + 由于写操作是以严格顺序的顺序附加到日志中的,所以常见的实现选择是只有一个写入器线程。数据文件段是附加的,否则是不可变的,所以它们可以被多个线程同时读取。 + +乍一看,只有追加日志看起来很浪费:为什么不更新文件,用新值覆盖旧值?但是只能追加设计的原因有几个: + +* 追加和分段合并是顺序写入操作,通常比随机写入快得多,尤其是在磁盘旋转硬盘上。在某种程度上,顺序写入在基于闪存的固态硬盘(SSD)上也是优选的[4]。我们将在第83页的“比较B-树和LSM-树”中进一步讨论这个问题。 +* 如果段文件是附加的或不可变的,并发和崩溃恢复就简单多了。例如,您不必担心在覆盖值时发生崩溃的情况,而将包含旧值和新值的一部分的文件保留在一起。 +* 合并旧段可以避免数据文件随着时间的推移而分散的问题。 + +但是,哈希表索引也有局限性: + +* 散列表必须能放进内存 + + 如果你有非常多的键,那真是倒霉。原则上可以在磁盘上保留一个哈希映射,不幸的是磁盘哈希映射很难表现优秀。它需要大量的随机访问I/O,当它变满时增长是很昂贵的,并且散列冲突需要很多的逻辑[5]。 + +* 范围查询效率不高。例如,您无法轻松扫描kitty00000和kitty99999之间的所有键 - 您必须在散列映射中单独查找每个键。 + +在下一节中,我们将看看一个没有这些限制的索引结构。 + + + +### SSTables和LSM-Trees + +在图3-3中,每个日志结构存储段都是一系列键值对。这些对按照它们写入的顺序出现,日志中稍后的值优先于日志中较早的相同键的值。除此之外,文件中键值对的顺序并不重要。 + +现在我们可以对段文件的格式做一个简单的改变:我们要求键值对的序列按键排序。乍一看,这个要求似乎打破了我们使用顺序写入的能力,但是我们马上就会明白这一点。 + +我们把这个格式称为Sorted String Table,简称SSTable。我们还要求每个键只在每个合并的段文件中出现一次(压缩过程已经确保)。与使用散列索引的日志段相比,SSTable有几个很大的优势: + +1. 合并段是简单而高效的,即使文件大于可用内存。这种方法就像mergesort算法中使用的方法一样,如图3-4所示:您开始并排读取输入文件,查看每个文件中的第一个键,复制最低键(根据排序顺序)到输出文件,并重复。这产生一个新的合并段文件,也按键排序。 + + ![](img/fig3-4.png) + + ##### 图3-4 合并几个SSTable段,只保留每个键的最新值 + + 如果在几个输入段中出现相同的键,该怎么办?请记住,每个段都包含在一段时间内写入数据库的所有值。这意味着一个输入段中的所有值必须比另一个段中的所有值更新(假设我们总是合并相邻的段)。当多个段包含相同的键时,我们可以保留最近段的值,并丢弃旧段中的值。 + +2. 为了在文件中找到一个特定的键,你不再需要保存内存中所有键的索引。以[图3-5]()为例:假设你正在内存中寻找键`handiwork`,但是你不知道段文件中该关键字的确切偏移量。然而,你知道`handbag`和`handsome`的偏移,而且由于排序特性,你知道`handiwork`必须出现在这两者之间。这意味着您可以跳到`handbag`的偏移位置并从那里扫描,直到您找到`handiwork`(或没找到,如果该文件中没有该键)。 + +![](img/fig3-5.png) + +**图3-5 具有内存索引的SSTable** + +您仍然需要一个内存中索引来告诉您一些键的偏移量,但它可能很稀疏:每几千字节的段文件就有一个键就足够了,因为几千字节可以很快被扫描。 + +3. 由于读取请求无论如何都需要扫描所请求范围内的多个键值对,因此可以将这些记录分组到块中,并在将其写入磁盘之前对其进行压缩(如图3-5中的阴影区域所示) 。稀疏内存中索引的每个条目都指向压缩块的开始处。除了节省磁盘空间之外,压缩还可以减少IO带宽的使用。 + + +#### 构建和维护SSTables + +到目前为止,但是如何让你的数据首先被按键排序呢?我们的传入写入可以以任何顺序发生。 + +在磁盘上维护有序结构是可能的(参阅“B-Tree”),但在内存保存则要容易得多。有许多可以使用的众所周知的树形数据结构,例如红黑树或AVL树[2]。使用这些数据结构,您可以按任何顺序插入键,并按排序顺序读取它们。 + +现在我们可以使我们的存储引擎工作如下: + +* 写入时,将其添加到内存中的平衡树数据结构(for例如,红黑树)。这个内存树有时被称为memtable。 +* 当memtable大于某个阈值(通常为几兆字节)时,将其作为SSTable文件写入磁盘。这可以高效地完成,因为树已经维护了按键排序的键值对。新的SSTable文件成为数据库的最新部分。当SSTable被写入磁盘时,写入可以继续到一个新的memtable实例。 +* 为了提供读取请求,首先尝试在memtable中找到关键字,然后在最近的磁盘段中,然后在下一个较旧的段中找到该关键字。 +* 有时会在后台运行合并和压缩过程以组合段文件并丢弃覆盖或删除的值。 + +这个方案效果很好。它只会遇到一个问题:如果数据库崩溃,则最近的写入(在memtable中,但尚未写入磁盘)将丢失。为了避免这个问题,我们可以在磁盘上保存一个单独的日志,每个写入都会立即被附加到磁盘上,就像在前一节中一样。该日志不是按排序顺序,但这并不重要,因为它的唯一目的是在崩溃后恢复memtable。每当Memtable写出到SSTable时,相应的日志都可以被丢弃。 + +#### 用SSTables制作LSM树 + +这里描述的算法本质上是LevelDB [6]和RocksDB [7]中使用的关键值存储引擎库,被设计嵌入到其他应用程序中。除此之外,LevelDB可以在Riak中用作Bitcask的替代品。在Cassandra和HBase中使用了类似的存储引擎[8],这两种引擎都受到了Google的Bigtable文档[9](引入了SSTable和memtable)的启发。 + +最初这种索引结构是由Patrick O'Neil等人描述的。在Log-Structured Merge-Tree(或LSM-Tree)[10]的基础上,建立在以前的工作上日志结构的文件系统[11]。基于这种合并和压缩排序文件原理的存储引擎通常被称为LSM存储引擎。 + +Lucene是Elasticsearch和Solr使用的一种全文搜索的索引引擎,它使用类似的方法来存储它的词典[12,13]。全文索引比键值索引复杂得多,但是基于类似的想法:在搜索查询中给出一个单词,找到提及单词的所有文档(网页,产品描述等)。这是通过键值结构实现的,其中键是单词(术语),值是包含单词(发布列表)的所有文档的ID的列表。在Lucene中,从术语到发布列表的这种映射保存在SSTable类的有序文件中,根据需要在后台合并[14]。 + +#### 性能优化 + +与往常一样,大量的细节使得存储引擎在实践中表现良好。例如,当查找数据库中不存在的键时,LSM树算法可能会很慢:您必须检查memtable,然后将这些段一直回到最老的(可能必须从磁盘读取每一个),然后才能确定键不存在。为了优化这种访问,存储引擎通常使用额外的Bloom过滤器[15]。 (布隆过滤器是用于近似集合内容的内存高效数据结构,它可以告诉您数据库中是否出现键,从而为不存在的键节省许多不必要的磁盘读取操作。 + +还有不同的策略来确定SSTables如何被压缩和合并的顺序和时间。最常见的选择是大小分层压实。 LevelDB和RocksDB使用平坦压缩(因此Lev-elDB的名称),HBase使用大小分层,Cassandra同时支持[16]。在规模级别的调整中,更新和更小的SSTables先后被合并到更老的和更大的SSTable中。在水平压实中,关键范围被拆分成更小的SSTables,而较旧的数据被移动到单独的“水平”,这使得压缩能够更加递增地进行,并且使用更少的磁盘空间。 + +即使有许多微妙的东西,LSM树的基本思想 - 保存一系列在后台合并的SSTables - 简单而有效。即使数据集比可用内存大得多,它仍能继续正常工作。由于数据按排序顺序存储,因此可以高效地执行范围查询(扫描所有高于某些最小值和最高值的所有键),并且因为磁盘写入是连续的,所以LSM-tree可以支持非常高的写入吞吐量。 + + + +### B树 + +我们迄今为止讨论的日志结构索引正在被接受,但它们并不是最常见的索引类型。使用最广泛的索引结构是1970年引入[17],不到10年后又被称为“无处不在”[18],B树经受了时间的考验。在几乎所有的关系数据库中,它们仍然是标准的索引实现,许多非关系数据库也使用它们。 + +像SSTables一样,B树保持按键排序的键值对,这允许高效的键值查找和范围查询。但这就是相似之处的结尾:B树有着非常不同的设计理念。 + +我们前面看到的日志结构索引将数据库分解为可变大小的段,通常是几兆字节或更大的大小,并且总是按顺序编写段。相比之下,B树将数据库分解成固定大小的块或页面,传统上大小为4 KB(有时会更大),并且一次只能读取或写入一个页面。这种设计更接近于底层硬件,因为磁盘也被安排在固定大小的块中。 + +每个页面都可以使用地址或位置来标识,这允许一个页面引用另一个页面 - 类似于指针,但在磁盘而不是在内存中。我们可以使用这些页面引用来构建一个页面树,如[图3-6]()所示。 + +![](img/fig3-6.png) + +**图3-6 使用B树索引查找一个键** + +一个页面会被指定为B树的根;在索引中查找一个键时,就从这里开始。该页面包含几个键和对子页面的引用。每个子页面负责一段连续范围的键,引用之间的键,指明了引用子页面的键范围。 + +在[图3-6]()的例子中,我们正在寻找关键字251,所以我们知道我们需要遵循边界200和300之间的页面引用。这将我们带到一个类似的页面,进一步打破了200 -300到子范围。 + +最后,我们可以看到包含单个键(叶页)的页面,该页面包含每个键的内联值,或者包含对可以找到值的页面的引用。 + +在B树的一个页面中对子页面的引用的数量称为分支因子。例如,在图3-6中,分支因子是六。在实践中,分支因子取决于存储页面参考和范围边界所需的空间量,但通常是几百个。 + +如果要更新B树中现有键的值,则搜索包含该键的叶页,更改该页中的值,并将该页写回到磁盘(对该页的任何引用保持有效) 。如果你想添加一个新的键,你需要找到其范围包含新键的页面,并将其添加到该页面。如果页面中没有足够的可用空间容纳新键,则将其分成两个半满页面,并更新父页面以解释键范围的新分区,如图3-7所示。 + +![](img/fig3-7.png) + +**图3-7 通过分割页面来生长B树** + +该算法确保树保持平衡:具有n个键的B树总是具有O(log n)的深度。大多数数据库可以放入一个三到四层的B树,所以你不需要遵循许多页面引用来找到你正在查找的页面。 (分支因子为500的4 KB页面的四级树可以存储多达256 TB。) + +#### 让B树更可靠 + +B树的基本底层写操作是用新数据覆盖磁盘上的页面。假定覆盖不改变页面的位置;即,当页面被覆盖时,对该页面的所有引用保持完整。这与日志结构索引(如LSM-trees)形成鲜明对比,后者只附加到文件(并最终删除过时的文件),但从不修改文件。 + +您可以考虑将硬盘上的页面覆盖为实际的硬件操作。在磁性硬盘驱动器上,这意味着将磁头移动到正确的位置,等待旋转盘上的正确位置出现,然后用新的数据覆盖适当的扇区。在固态硬盘上,由于SSD必须一次擦除和重写相当大的存储芯片块,所以会发生更复杂的事情[19]。 + +而且,一些操作需要覆盖几个不同的页面。例如,如果因为插入导致页面过度而拆分页面,则需要编写已拆分的两个页面,并覆盖其父页面以更新对两个子页面的引用。这是一个危险的操作,因为如果数据库在仅有一些页面被写入后崩溃,那么最终将导致一个损坏的索引(例如,可能有一个孤儿页面不是任何父项的子项) 。 + +为了使数据库对崩溃具有韧性,B树实现通常会带有一个额外的磁盘数据结构:预写式日志(WAL,也称为重做日志)。这是一个只能追加的文件,每个B树修改都可以应用到树本身的页面上。当数据库在崩溃后恢复时,这个日志被用来恢复B树回到一致的状态[5,20]。 + +更新页面的一个额外的复杂情况是,如果多个线程要同时访问B树,则需要仔细的并发控制 - 否则线程可能会看到树处于不一致的状态。这通常通过使用***锁存器latches***(轻量级锁)保护树的数据结构来完成。日志结构化的方法在这方面更简单,因为它们在后台进行所有的合并,而不会干扰传入的查询,并且不时地将旧的分段原子交换为新的分段。 + +#### B树优化 + +由于B树已经存在了这么久,许多优化已经发展了多年,这并不奇怪。仅举几例: + +* 一些数据库(如LMDB)使用写时复制方案[21],而不是覆盖页面并维护WAL进行崩溃恢复。修改的页面被写入到不同的位置,并且树中的父页面的新版本被创建,指向新的位置。这种方法对于并发控制也很有用,我们将在第237页的“快照隔离和可重复读取”中看到。 +* 我们可以通过不存储整个键来节省页面空间,但可以缩小它的大小。特别是在树内部的页面上,键只需要提供足够的信息来充当键范围之间的边界。在页面中包含更多的键允许树具有更高的分支因子,因此更少的层次 +* 通常,页面可以放置在磁盘上的任何位置;没有什么要求附近的键范围页面附近的磁盘上。如果查询需要按照排序顺序扫描大部分关键字范围,那么每个页面的布局可能会非常不方便,因为每个读取的页面都可能需要磁盘查找。因此,许多B-树实现尝试布局树,使得叶子页面按顺序出现在磁盘上。但是,随着树的增长,维持这个顺序是很困难的。相比之下,由于LSM树在合并过程中一次又一次地重写存储的大部分,所以它们更容易使顺序键在磁盘上彼此靠近。 +* 额外的指针已添加到树中。例如,每个叶子页面可以在左边和右边具有对其兄弟页面的引用,这允许不跳回父页面就能顺序扫描。 +* B树的变体如分形树[22]借用一些日志结构的思想来减少磁盘寻道(而且它们与分形无关)。 + +### 比较B-树和LSM-树 + +尽管B树实现通常比LSM树实现更成熟,但LSM树由于其性能特点也非常有趣。根据经验,LSM树通常写速度更快,而B树被认为读取速度更快[23]。 LSM树上的读取通常比较慢,因为他们必须在压缩的不同阶段检查几个不同的数据结构和SSTables。 + +然而,基准通常对工作量的细节不确定和敏感。 您需要测试具有特定工作负载的系统,以便进行有效的比较。 在本节中,我们将简要讨论一些在衡量存储引擎性能时值得考虑的事情。 + +#### LSM树的优点 + +B树索引必须至少两次写入每一段数据:一次写入预先写入日志,一次写入树页面本身(也许再次分页)。即使在该页面中只有几个字节发生了变化,也需要一次编写整个页面的开销。有些存储引擎甚至会覆盖同一个页面两次,以免在电源故障的情况下导致页面部分更新[24,25]。 + +由于反复压缩和合并SSTables,日志结构索引也会重写数据。这种影响 - 在数据库的生命周期中写入数据库导致对磁盘的多次写入 - 被称为**写放大(Write amplification)**。固态硬盘是特别值得关注的,固态硬盘在磨损之前只能覆盖一段时间。 + +在写入繁重的应用程序中,性能瓶颈可能是数据库可以写入磁盘的速度。在这种情况下,写入放大具有直接的性能成本:存储引擎写入磁盘的次数越多,可用磁盘带宽内的每秒写入次数越少。 + +而且,LSM树通常能够比B-树支持更高的写入吞吐量,部分原因是它们有时具有较低的写入放大(尽管这取决于存储引擎配置和工作负载),部分是因为它们顺序地写入紧凑的SSTable文件而不是必须覆盖树中的几个页面[26]。这种差异在磁性硬盘驱动器上尤其重要,顺序写入比随机写入快得多。 + +LSM树可以被压缩得更好,因此经常比B树在磁盘上产生更小的文件。 B树存储引擎会由于分割而留下一些未使用的磁盘空间:当页面被拆分或某行不能放入现有页面时,页面中的某些空间仍未被使用。由于LSM树不是面向页面的,并且定期重写SSTables以去除碎片,所以它们具有较低的存储开销,特别是当使用平坦压缩时[27]。 + +在许多固态硬盘上,固件内部使用日志结构化算法,将随机写入转变为顺序写入底层存储芯片,因此存储引擎写入模式的影响不太明显[19]。但是,较低的写入放大率和减少的碎片对SSD仍然有利:更紧凑地表示数据可在可用的I/O带宽内提供更多的读取和写入请求。 + +#### LSM树的缺点 + +日志结构存储的缺点是压缩过程有时会干扰正在进行的读写操作。尽管存储引擎尝试逐步执行压缩而不影响并发访问,但是磁盘资源有限,所以很容易发生请求需要等待而磁盘完成昂贵的压缩操作。对吞吐量和平均响应时间的影响通常很小,但是在更高百分比的情况下(请参阅第13页上的“描述性能”),对日志结构化存储引擎的查询响应时间有时会相当长,而B树的行为则相对更具可预测性[28]。 + +压缩的另一个问题出现在高写入吞吐量:磁盘的有限写入带宽需要在初始写入(记录和刷新memtable到磁盘)和在后台运行的压缩线程之间共享。写入空数据库时,可以使用全磁盘带宽进行初始写入,但数据库越大,压缩所需的磁盘带宽就越多。 + +如果写入吞吐量很高,并且压缩没有仔细配置,压缩跟不上写入速率。在这种情况下,磁盘上未合并段的数量不断增加,直到磁盘空间用完,读取速度也会减慢,因为它们需要检查更多段文件。通常情况下,即使压缩无法跟上,基于SSTable的存储引擎也不会限制传入写入的速率,所以您需要进行明确的监控来检测这种情况[29,30]。 + +B树的一个优点是每个键只存在于索引中的一个位置,而日志结构化的存储引擎可能在不同的段中有相同键的多个副本。这个方面使得B树在想要提供强大的事务语义的数据库中很有吸引力:在许多关系数据库中,事务隔离是通过在键范围上使用锁来实现的,在B树索引中,这些锁可以直接连接到树[5]。在第7章中,我们将更详细地讨论这一点。 + +B树在数据库体系结构中是非常根深蒂固的,为许多工作负载提供始终如一的良好性能,所以它们不可能很快就会消失。在新的数据存储中,日志结构化索引变得越来越流行。没有快速和容易的规则来确定哪种类型的存储引擎对你的场景更好,所以值得进行一些经验上的测试 + +### 其他索引结构 + +到目前为止,我们只讨论了关键值索引,它们就像关系模型中的**主键(primary key)**索引。主键唯一标识关系表中的一行,或文档数据库中的一个文档或图形数据库中的一个顶点。数据库中的其他记录可以通过其主键(或ID)引用该行/文档/顶点,并且索引用于解析这样的引用。 + +有二级索引也很常见。在关系数据库中,您可以使用CREATE INDEX命令在同一个表上创建多个二级索引,而且这些索引通常对于有效地执行联接而言至关重要。例如,在第2章中的[图2-1]()中,很可能在`user_id`列上有一个二级索引,以便您可以在每个表中找到属于同一用户的所有行。 + +一个二级索引可以很容易地从一个键值索引构建。主要的不同是Key不是唯一的。即可能有许多行(文档,顶点)具有相同的键。这可以通过两种方式来解决:或者通过使索引中的每个值,成为匹配行标识符的列表(如全文索引中的发布列表),或者通过向每个索引添加行标识符来使每个关键字唯一。无论哪种方式,B树和日志结构索引都可以用作辅助索引。 + +#### 将值存储在索引中 + +索引中的关键字是查询搜索的内容,但是该值可以是以下两种情况之一:它可以是所讨论的实际行(文档,顶点),也可以是对存储在别处的行的引用。在后一种情况下,行被存储的地方被称为**堆文件(heap file)**,并且存储的数据没有特定的顺序(它可以是仅附加的,或者可以跟踪被删除的行以便用新数据覆盖它们后来)。堆文件方法很常见,因为它避免了在存在多个二级索引时复制数据:每个索引只引用堆文件中的一个位置,实际的数据保存在一个地方。 +在不更改键的情况下更新值时,堆文件方法可以非常高效:只要新值不大于旧值,就可以覆盖该记录。如果新值更大,情况会更复杂,因为它可能需要移到堆中有足够空间的新位置。在这种情况下,要么所有的索引都需要更新,以指向记录的新堆位置,或者在旧堆位置留下一个转发指针[5]。 + +在某些情况下,从索引到堆文件的额外跳跃对读取来说性能损失太大,因此可能希望将索引行直接存储在索引中。这被称为聚集索引。例如,在MySQL的InnoDB存储引擎中,表的主键总是一个聚簇索引,二级索引是指主键(而不是堆文件的位置)[31]。在SQL Server中,您可以为每个表指定一个聚簇索引[32]。 + +在**聚集索引(clustered index)**(在索引中存储所有行数据)和**非聚集索引(nonclustered index)**(仅在索引中存储对数据的引用)之间的折衷被称为**包含列的索引(index with included columns)**或**覆盖索引(covering index)**,其存储表的一部分在索引内[33]。这允许通过单独使用索引来回答一些查询(这种情况叫做:索引**覆盖了(cover)**查询)[32]。 + +与任何类型的数据重复一样,聚簇和覆盖索引可以加快读取速度,但是它们需要额外的存储空间,并且会增加写入开销。数据库还需要额外的努力来执行事务保证,因为应用程序不应该因为重复而导致不一致。 + +#### 多列索引 + +目前讨论的索引只将一个键映射到一个值。这是不够的,如果我们需要同时查询一个表(或文档中的多个字段)的多个列。 + +最常见的多列索引被称为**连接索引(concatenated index)**,它通过将一列附加到另一列(索引定义指定字段以何种顺序连接)来简单地将多个字段组合成一个键。这就像一个老式的纸质电话簿,它提供了一个从(姓,名)到电话号码的索引。由于排序顺序,索引可以用来查找所有具有特定姓氏的人,或所有具有特定姓氏 - 姓氏组合的人。**然而,如果你想找到所有具有特定名字的人,这个索引是没有用的**。 + +多维索引是一种查询多个列的更一般的方法,这对于地理空间数据尤为重要。例如,餐馆搜索网站可能有一个数据库,其中包含每个餐厅的经度和纬度。当用户在地图上查看餐馆时,网站需要搜索用户正在查看的矩形地图区域内的所有餐馆。这需要一个二维范围查询,如下所示: + +```sql +SELECT * FROM restaurants WHERE latitude > 51.4946 AND latitude < 51.5079 + AND longitude > -0.1162 AND longitude < -0.1004; +``` + +一个标准的B树或者LSM树索引不能够有效地回答这种查询:它可以给你一个纬度范围内的所有餐馆(但是在任何长度上),或者所有餐馆一系列的经度(但在北极和南极之间的任何地方),但不能同时存在。 + +一种选择是使用空间填充曲线将二维位置转换为单个数字,然后使用常规B树索引[34]。更普遍的是,使用特殊化的空间索引,例如R树。例如,PostGIS使用PostgreSQL的Gist工具[35]将地理空间索引实现为R-树。这里我们没有足够的空间来描述R树,但是有大量的文献可供参考。 + +一个有趣的想法是,多维索引不仅仅是地理位置。例如,在电子商务网站上,您可以使用维度(红色,绿色,蓝色)上的三维索引来搜索特定颜色范围内的产品,也可以在天气观测数据库中搜索二维(日期,温度)的指数,以便有效地搜索2013年的温度在25至30°C之间的所有观测资料。使用一维索引,您将不得不扫描2013年的所有记录(不管温度如何),然后通过温度进行过滤,反之亦然。 2D索引可以同时通过时间戳和温度缩小。这个技术被HyperDex使用[36]。 + +#### 全文搜索和模糊索引 + +到目前为止所讨论的所有索引都假定您有确切的数据,并允许您查询键的确切值或具有排序顺序的键的值范围。他们不允许你做的是搜索类似的键,如拼写错误的单词。这种模糊的查询需要不同的技术。 + +例如,全文搜索引擎通常允许搜索一个单词以扩展为包括该单词的同义词,忽略单词的语法变体,并且搜索在相同文档中彼此靠近的单词的出现,并且支持各种其他功能取决于文本的语言分析。为了处理文档或查询中的拼写错误,Lucene能够在一定的编辑距离内搜索文本(编辑距离1意味着添加,删除或替换了一个字母)[37]。 + +正如第78页“在SSTables中创建一个LSM-tree”中所提到的,Lucene为其词典使用了一个类似于SSTable的结构。这个结构需要一个小的内存索引,告诉查询在排序文件中哪个偏移量需要查找关键字。在LevelDB中,这个内存中的索引是一些键的稀疏集合,但在Lucene中,内存中的索引是键中字符的有限状态自动机,类似于trie [38]。这个自动机可以转换成Levenshtein自动机,它支持在给定的编辑距离内有效地搜索单词[39]。 + +其他的模糊搜索技术正朝着文档分类和机器学习的方向发展。有关更多详细信息,请参阅信息检索教科书[例如,40]。 + +#### 在内存中存储一切 + +本章到目前为止讨论的数据结构都是对磁盘限制的回答。与主内存相比,磁盘处理起来很尴尬。对于磁盘和SSD,如果要在读取和写入时获得良好性能,则需要仔细地布置磁盘上的数据。但是,我们容忍这种尴尬,因为磁盘有两个显着的优点:它们是耐用的(它们的内容在电源关闭时不会丢失),并且每GB的成本比RAM低。 + +随着RAM变得更便宜,每GB的成本价格被侵蚀了。许多数据集不是那么大,所以将它们全部保存在内存中是非常可行的,可能分布在多个机器上。这导致了内存数据库的发展。 + +某些内存中的键值存储(如Memcached)仅用于缓存,在重新启动计算机时丢失的数据是可以接受的。但其他内存数据库的目标是持久性,可以通过特殊的硬件(例如电池供电的RAM),将更改日志写入磁盘,将定时快照写入磁盘或通过复制内存来实现,记忆状态到其他机器。 + +内存数据库重新启动时,需要从磁盘或通过网络从副本重新加载其状态(除非使用特殊的硬件)。尽管写入磁盘,它仍然是一个内存数据库,因为磁盘仅用作耐久性附加日志,读取完全由内存提供。写入磁盘也具有操作优势:磁盘上的文件可以很容易地由外部实用程序进行备份,检查和分析。 + +诸如VoltDB,MemSQL和Oracle TimesTen等产品是具有关系模型的内存数据库,供应商声称,通过消除与管理磁盘上的数据结构相关的所有开销,他们可以提供巨大的性能改进[41,42]。 RAMCloud是一个开源的内存键值存储器,具有持久性(对存储器中的数据以及磁盘上的数据使用日志结构化方法)[43]。 Redis和Couchbase通过异步写入磁盘提供了较弱的持久性。 + +**反直觉的是,内存数据库的性能优势并不是因为它们不需要从磁盘读取的事实。即使是基于磁盘的存储引擎也可能永远不需要从磁盘读取,因为操作系统缓存最近在内存中使用了磁盘块。相反,它们更快的原因在于省去了将内存数据结构编码为磁盘数据结构的开销**。[44]。 + +除了性能,内存数据库的另一个有趣的领域是提供难以用基于磁盘的索引实现的数据模型。例如,Redis为各种数据结构(如优先级队列和集合)提供了类似数据库的接口。因为它将所有数据保存在内存中,所以它的实现相对简单。 + +最近的研究表明,内存数据库体系结构可以扩展到支持比可用内存更大的数据集,而不必重新采用以磁盘为中心的体系结构[45]。所谓的**反缓存(anti-caching)**方法通过在内存不足的情况下将最近最少使用的数据从内存转移到磁盘,并在将来再次访问时将其重新加载到内存中。这与操作系统对虚拟内存和交换文件的操作类似,但数据库可以比操作系统更有效地管理内存,因为它可以按单个记录的粒度工作,而不是整个内存页面。尽管如此,这种方法仍然需要索引能完全放入内存中(就像本章开头的Bitcask例子)。 + +如果非易失性存储器(NVM)技术得到更广泛的应用,可能还需要进一步改变存储引擎设计[46]。目前这是一个新的研究领域,值得关注。 + + + +## 事务处理还是分析? + +在业务数据处理的早期,对数据库的写入通常对应于正在进行的商业交易:进行销售,向供应商下订单,支付员工工资等等。随着数据库扩展到那些没有不涉及钱易手,术语交易仍然卡住,指的是形成一个逻辑单元的一组读写。 +事务不一定具有ACID(原子性,一致性,隔离性和持久性)属性。事务处理只是意味着允许客户端进行低延迟读取和写入 - 而不是批量处理作业,而这些作业只能定期运行(例如每天一次)。我们在第7章中讨论ACID属性,在第10章中讨论批处理。 + +即使数据库开始被用于许多不同类型的博客文章,游戏中的动作,地址簿中的联系人等等,基本访问模式仍然类似于处理业务事务。应用程序通常使用索引通过某个键查找少量记录。根据用户的输入插入或更新记录。由于这些应用程序是交互式的,因此访问模式被称为在线事务处理(OLTP)。 +但是,数据库也开始越来越多地用于数据分析,这些数据分析具有非常不同的访问模式。通常,分析查询需要扫描大量记录,每个记录只读取几列,并计算汇总统计信息(如计数,总和或平均值),而不是将原始数据返回给用户。例如,如果您的数据是一个销售交易表,那么分析查询可能是: + +* 一月份我们每个商店的总收入是多少? +* 我们在最近的推广活动中销售多少香蕉? +* 哪种品牌的婴儿食品最常与X品牌的尿布一起购买? + +这些查询通常由业务分析师编写,并提供给帮助公司管理层做出更好决策(商业智能)的报告。为了区分这种使用数据库的事务处理模式,它被称为在线分析处理(OLAP)。[47]。OLTP和OLAP之间的区别并不总是清晰的,但是一些典型的特征在表3中列出-1。 + +**表3-1 比较交易处理和分析系统的特点** + +| 属性 | 分析系统 OLAP | 事务处理 OLTP | +| :----: | :------------: | :-----------: | +| 主要读取模式 | 查询少量记录,按键读取 | 在大批量记录上聚合 | +| 主要写入模式 | 随机访问,写入要求低延时 | 批量导入(ETL),事件流 | +| 主要用户 | 终端用户,通过Web应用 | 内部数据分析师,决策支持 | +| 处理的数据 | 数据的最新状态(当前时间点) | 随时间推移的历史事件 | +| 数据集尺寸 | TB ~ PB | GB ~ TB | + +起初,相同的数据库用于事务处理和分析查询。 SQL在这方面证明是非常灵活的:对于OLTP类型的查询以及OLAP类型的查询来说,效果很好。尽管如此,在二十世纪八十年代末和九十年代初期,公司有停止使用OLTP系统进行分析的趋势,而是在单独的数据库上运行分析。这个单独的数据库被称为数据仓库。 + +### 数据仓库 + +一个企业可能有几十个不同的交易处理系统:系统为面向客户的网站提供动力,控制实体商店的销售点(checkout)系统,跟踪仓库中的库存,规划车辆路线,管理供应商,管理员工等。这些系统中的每一个都是复杂的,需要一个人员去维护,所以系统最终都是自动运行的。 + +这些OLTP系统通常具有高度的可用性,并以低延迟处理事务,因为这些系统往往对业务运作至关重要。因此数据库管理员密切关注他们的OLTP数据库他们通常不愿意让业务分析人员在OLTP数据库上运行临时分析查询,因为这些查询通常很昂贵,扫描大部分数据集,这会损害同时执行的事务的性能。 + +相比之下,数据仓库是一个独立的数据库,分析人员可以查询他们心中的内容,而不影响OLTP操作[48]。数据仓库包含公司所有各种OLTP系统中的只读数据副本。从OLTP数据库中提取数据(使用定期的数据转储或连续的更新流),转换成适合分析的模式,清理并加载到数据仓库中。将数据存入仓库的过程称为“提取 - 转换 - 加载(ETL)”,如图3-8所示。 + +![](img/fig3-8.png) + +**图3-8 ETL至数据仓库的简化提纲** + +几乎所有的大型企业都有数据仓库,但在小型企业中几乎闻所未闻。这可能是因为大多数小公司没有这么多不同的OLTP系统,大多数小公司只有少量的数据 - 可以在传统的SQL数据库中查询,甚至可以在电子表格中分析。在一家大公司里,要做一些在一家小公司很简单的事情,需要很多繁重的工作。 + +使用单独的数据仓库,而不是直接查询OLTP系统进行分析的一大优势是数据仓库可针对分析访问模式进行优化。事实证明,本章前半部分讨论的索引算法对于OLTP来说工作得很好,但对于回答分析查询并不是很好。在本章的其余部分中,我们将看看为分析而优化的存储引擎。 + +### OLTP数据库和数据仓库之间的分歧 + +数据仓库的数据模型通常是关系型的,因为SQL通常很适合分析查询。有许多图形数据分析工具可以生成SQL查询,可视化结果,并允许分析人员(通过下钻,切片和切块等操作)探索数据。 + +表面上,一个数据仓库和一个关系OLTP数据库看起来很相似,因为它们都有一个SQL查询接口。然而,系统的内部看起来可能完全不同,因为它们针对非常不同的查询模式进行了优化。现在许多数据库供应商都将重点放在支持事务处理或分析工作负载上,而不是两者都支持。 + +一些数据库(例如Microsoft SQL Server和SAP HANA)支持同一产品中的事务处理和数据仓库。但是,它们正在日益成为两个独立的存储和查询引擎,这些引擎正好可以通过一个通用的SQL接口访问[49,50,51]。 + +Teradata,Vertica,SAP HANA和ParAccel等数据仓库供应商通常使用昂贵的商业许可证销售他们的系统。 Amazon RedShift是ParAccel的托管版本。最近,大量的开源SQL-on-Hadoop项目已经出现,他们还年轻,但是正在与商业数据仓库系统竞争。这些包括Apache Hive,Spark SQL,Cloudera Impala,Facebook Presto,Apache Tajo和Apache Drill [52,53]。其中一些是基于谷歌的Dremel [54]的想法。 + +### 星型和雪花型:分析的模式 + +正如第2章所探讨的,根据应用程序的需要,在事务处理领域中使用了大量不同的数据模型。另一方面,在分析中,数据模型的多样性则少得多。许多数据仓库都以相当公式化的方式使用,被称为星型模式(也称为维度建模[55])。 + +图3-9中的示例模式显示了可能在食品零售商处找到的数据仓库。在模式的中心是一个所谓的事实表(在这个例子中,它被称为fact_sales)。事实表的每一行代表在特定时间发生的事件(这里,每一行代表客户购买的产品)。如果我们分析的是网站流量而不是零售量,则每行可能代表一个用户的页面浏览量或点击量。 + +![](img/fig3-9.png) + +**图3-9 用于数据仓库的星型模式的示例** + +通常情况下,事实被视为单独的事件,因为这样可以在以后分析中获得最大的灵活性。但是,这意味着事实表可以变得非常大。像苹果,沃尔玛或eBay这样的大企业在其数据仓库中可能有几十PB的交易历史,其中大部分实际上是表[56]。 + +事实表中的一些列是属性,例如产品销售的价格和从供应商那里购买的成本(允许计算利润余额)。事实表中的其他列是对其他表(称为维表)的外键引用。由于事实表中的每一行都表示一个事件,因此这些维度代表事件的发生地点,时间,方式和原因。 + +例如,在图3-9中,其中一个维度是已售出的产品。 dim_product表中的每一行代表一种待售产品,包括库存单位(SKU),说明,品牌名称,类别,脂肪含量,包装尺寸等。fact_sales表中的每一行都使用外部表明在特定交易中销售了哪些产品。 (为了简单起见,如果客户一次购买几种不同的产品,则它们在事实表中被表示为单独的行)。 + +即使日期和时间通常使用维度表来表示,因为这允许对日期(诸如公共假期)的附加信息进行编码,从而允许查询区分假期和非假期的销售。 + +“星型模式”这个名字来源于这样一个事实,即当表关系可视化时,事实表在中间,由维表包围;与这些桌子的连接就像星星的光芒。 + +这个模板的变体被称为雪花模式,其中尺寸被进一步分解为子尺寸。例如,品牌和产品类别可能有单独的表格,并且dim_product表格中的每一行都可以将品牌和类别作为外键引用,而不是将它们作为字符串存储在dim_product表格中。雪花模式比星形模式更规范化,但是星形模式通常是首选,因为分析师使用它更简单[55]。 + +在典型的数据仓库中,表格通常非常宽泛:事实表格通常有100列以上,有时甚至有数百[51]列。维度表也可以是非常宽的,因为它们包括可能与分析相关的所有元数据 - 例如,dim_store表可以包括在每个商店提供哪些服务的细节,它是否具有店内面包房,方形镜头,商店第一次开幕的日期,最后一次改造的时间,离最近的高速公路的距离等等。 + + + +## 面向列的存储 + +如果事实表中有万亿行和数PB的数据,那么高效地存储和查询它们就成为一个具有挑战性的问题。维度表通常要小得多(数百万行),所以在本节中我们将主要关注事实的存储。 + +尽管事实表通常超过100列,但典型的数据仓库查询一次只能访问4个或5个查询(“SELECT *”查询很少用于分析)[51]。以例3-1中的查询为例:它访问了大量的行(在2013日历年中每次都有人购买水果或糖果),但只需访问fact_sales表的三列:date_key,product_sk和数量。查询忽略所有其他列。 + +**例3-1 分析人们是否更倾向于购买新鲜水果或糖果,这取决于一周中的哪一天** + +```sql +SELECT + dim_date.weekday, + dim_product.category, + SUM(fact_sales.quantity) AS quantity_sold +FROM fact_sales + JOIN dim_date ON fact_sales.date_key = dim_date.date_key + JOIN dim_product ON fact_sales.product_sk = dim_product.product_sk +WHERE + dim_date.year = 2013 AND + dim_product.category IN ('Fresh fruit', 'Candy') +GROUP BY + dim_date.weekday, dim_product.category; +``` + +我们如何有效地执行这个查询? + +在大多数OLTP数据库中,存储都是以面向行的方式进行布局的:表格的一行中的所有值都相邻存储。文档数据库是相似的:整个文档通常存储为一个连续的字节序列。你可以在图3-1的CSV例子中看到这个。 + +为了处理像例3-1这样的查询,您可能在`fact_sales.date_key` `fact_sales.product_sk`上有索引,它们告诉存储引擎在哪里查找特定日期或特定产品的所有销售情况。但是,面向行的存储引擎仍然需要将所有这些行(每个包含超过100个属性)从磁盘加载到内存中,解析它们,并过滤掉那些不符合要求的条件。这可能需要很长时间。 + +面向列的存储背后的想法很简单:不要将所有来自一行的值存储在一起,而是将来自每一列的所有值存储在一起。如果每个列存储在一个单独的文件中,查询只需要读取和解析查询中使用的那些列,这可以节省大量的工作。这个原理如[图3-10](img/fig3-10.png)所示。 + +![](img/fig3-10.png) + +**图3-10 使用列存储关系型数据,而不是行** + +列存储在关系数据模型中是最容易理解的,但它同样适用于非关系数据。例如,Parquet [57]是一种列式存储格式,支持基于Google的Dremel [54]的文档数据模型。 + +面向列的存储布局依赖于包含相同顺序行的每个列文件。 因此,如果您需要重新组装整行,您可以从每个单独的列文件中获取第23项,并将它们放在一起形成表的第23行。 + + + +### 列压缩 + +除了仅从磁盘加载查询所需的列以外,我们还可以通过压缩数据来进一步降低对磁盘吞吐量的需求。幸运的是,面向列的存储通常很适合压缩。 + +看看图3-10中每一列的值序列:它们通常看起来是相当重复的,这是压缩的好兆头。根据列中的数据,可以使用不同的压缩技术。在数据仓库中特别有效的一种技术是位图编码,如图3-11所示。 + +![](img/fig3-11.png) + +**图3-11 压缩位图索引存储布局** + +通常情况下,一列中不同值的数量与行数相比较小(例如,零售商可能有数十亿的销售交易,但只有100,000个不同的产品)。现在我们可以得到一个有n个不同值的列,并把它转换成n个独立的位图:每个不同值的一个位图,每行一位。如果该行具有该值,则该位为1,否则为0。 + +如果n非常小(例如,国家/地区列可能有大约200个不同的值),则这些位图可以每行存储一位。但是,如果n更大,大部分位图中将会有很多的零(我们说它们是稀疏的)。在这种情况下,位图可以另外进行游程编码,如图3-11底部所示。这可以使列的编码非常紧凑。 +这些位图索引非常适合数据仓库中常见的各种查询。例如: + +```sql +WHERE product_sk IN(30,68,69) +``` + +加载product_sk = 30,product_sk = 68和product_sk = 69的三个位图,并计算三个位图的按位或,这可以非常有效地完成。 + +```sql +WHERE product_sk = 31 AND store_sk = 3 +``` + +加载product_sk = 31和store_sk = 3的位图,并逐位计算AND。 这是因为列按照相同的顺序包含行,因此一列的位图中的第k位对应于与另一列的位图中的第k位相同的行。 + +对于不同种类的数据,也有各种不同的压缩方案,但我们不会详细讨论它们,参见[58]的概述。 + +> #### 面向列的存储和列族 +> +> Cassandra和HBase有一个列族的概念,他们从Bigtable继承[9]。然而,把它们称为面向列是非常具有误导性的:在每个列族中,它们将一行中的所有列与行键一起存储,并且不使用列压缩。因此,Bigtable模型仍然主要是面向行的。 +> + +#### 内存带宽和向量处理 + +对于需要扫描数百万行的数据仓库查询来说,一个巨大的瓶颈是从磁盘获取数据到内存的带宽。但是,这不是唯一的瓶颈。分析数据库的开发人员也担心有效利用主存储器带宽到CPU缓存中的带宽,避免CPU指令处理流水线中的分支错误预测和泡沫,以及在现代中使用单指令多数据(SIMD)指令CPU [59,60]。 + +除了减少需要从磁盘加载的数据量以外,面向列的存储布局也可以有效利用CPU周期。例如,查询引擎可以将大量压缩的列数据放在CPU的L1缓存中,然后在紧密的循环中循环(即没有函数调用)。一个CPU可以执行这样一个循环比代码要快得多,这个代码需要处理每个记录的大量函数调用和条件。列压缩允许列中的更多行适合相同数量的L1缓存。前面描述的按位“与”和“或”运算符可以被设计为直接在这样的压缩列数据块上操作。这种技术被称为矢量化处理[58,49]。 + + + +### 列存储中的排序顺序 + +在列存储中,存储行的顺序并不一定很重要。按插入顺序存储它们是最简单的,因为插入一个新行就意味着附加到每个列文件。但是,我们可以选择强制执行一个命令,就像我们之前对SSTables所做的那样,并将其用作索引机制。 + +注意,每列独自排序是没有意义的,因为那样我们就不会知道列中的哪些项属于同一行。我们只能重建一行,因为我们知道一列中的第k项与另一列中的第k项属于同一行。 + +相反,即使按列存储数据,也需要一次对整行进行排序。数据库的管理员可以使用他们对常见查询的知识来选择表格应该被排序的列。例如,如果查询通常以日期范围为目标,例如上个月,则可以将date_key作为第一个排序键。然后,查询优化器只能扫描上个月的行,这比扫描所有行要快得多。 + +第二列可以确定第一列中具有相同值的任何行的排序顺序。例如,如果date_key是图3-10中的第一个排序关键字,那么product_sk可能是第二个排序关键字,因此同一天的同一产品的所有销售都将在存储中组合在一起。这将有助于需要在特定日期范围内按产品对销售进行分组或过滤的查询。 + +排序顺序的另一个好处是它可以帮助压缩列。如果主要排序列没有多个不同的值,那么在排序之后,它将具有很长的序列,其中相同的值连续重复多次。一个简单的运行长度编码(就像我们用于图3-11中的位图一样)可以将该列压缩到几千字节 - 即使表中有数十亿行。 + +第一个排序键的压缩效果最强。第二和第三个排序键会更混乱,因此不会有这么长时间的重复值。排序优先级下面的列以基本上随机的顺序出现,所以它们可能不会被压缩。但前几列排序仍然是一个整体。 + +#### 几个不同的排序顺序 + +这个想法的巧妙扩展在C-Store中引入,并在商业数据仓库Vertica [61,62]中被采用。不同的查询受益于不同的排序顺序,为什么不以相同的方式存储相同的数据呢?无论如何,数据需要复制到多台机器,这样,如果一台机器发生故障,您不会丢失数据。您可能还需要存储以不同方式排序的冗余数据,以便在处理查询时,可以使用最适合查询模式的版本。 + +在一个面向列的存储中有多个排序顺序有点类似于在一个面向行的存储中有多个二级索引。但最大的区别在于面向行的存储将每一行保存在一个地方(在堆文件或聚簇索引中),二级索引只包含指向匹配行的指针。在列存储中,通常在其他地方没有任何指向数据的指针,只有包含值的列。 + +### 写入列存储 + +这些优化在数据仓库中是有意义的,因为大多数负载由分析人员运行的大型只读查询组成。面向列的存储,压缩和排序都有助于更快地读取这些查询。然而,他们有写更加困难的缺点。 + +使用B树的更新就地方法对于压缩的列是不可能的。如果你想在排序表的中间插入一行,你很可能不得不重写所有的列文件。由于行由列中的位置标识,因此插入必须始终更新所有列。 + +幸运的是,本章前面已经看到了一个很好的解决方案:LSM树。所有的写操作首先进入一个内存中的存储,在这里它们被添加到一个已排序的结构中,并准备写入磁盘。内存中的存储是面向行还是列的,这并不重要。当已经积累了足够的写入数据时,它们将与磁盘上的列文件合并,并批量写入新文件。这基本上是Vertica所做的[62]。 + +查询需要检查磁盘上的列数据和最近在内存中的写入,并将两者结合起来。但是,查询优化器隐藏了用户的这个区别。从分析师的角度来看,通过插入,更新或删除操作进行修改的数据会立即反映在后续查询中。 + +### 聚合:数据立方体和物化视图 + +并不是每个数据仓库都必定是一个列存储:传统的面向行的数据库和其他一些架构也被使用。然而,对于专门的分析查询,列式存储可以显着加快,所以它正在迅速普及[51,63]。 + +数据仓库的另一个值得一提的是物化汇总。如前所述,数据仓库查询通常涉及一个聚合函数,如SQL中的COUNT,SUM,AVG,MIN或MAX。如果相同的聚合被许多不同的查询使用,那么每次都可以通过原始数据来处理。为什么不缓存一些查询使用最频繁的计数或总和? + +创建这种缓存的一种方式是物化视图。在关系数据模型中,它通常被定义为一个标准(虚拟)视图:一个类似于表的对象,其内容是一些查询的结果。不同的是,物化视图是查询结果的实际副本,写入磁盘,而虚拟视图只是写入查询的捷径。从虚拟视图读取时,SQL引擎会将其展开到视图的底层查询中,然后处理展开的查询。 + +当底层数据发生变化时,物化视图需要更新,因为它是数据的非规范化副本。数据库可以自动完成,但是这样的更新使得写入成本更高,这就是在OLTP数据库中不经常使用物化视图的原因。在读取繁重的数据仓库中,它们可能更有意义(不管它们是否实际上改善了读取性能取决于个别情况)。 + +物化视图的常见特例称为数据立方体或OLAP立方体[64]。它是按不同维度分组的聚合网格。图3-12显示了一个例子。 + +![](img/fig3-12.png) + +**图3-12 数据立方的两个维度,通过求和聚合** + +想象一下,现在每个事实都只有两个维度表的外键 - 在图3-12中,这些是日期和产品。您现在可以绘制一个二维表格,一个轴线上的日期和另一个轴上的产品。每个单元包含具有该日期 - 产品组合的所有事实的属性(例如,net_price)的聚集(例如,SUM)。然后,您可以沿着每行或每列应用相同的汇总,并获得一个维度减少的汇总(按产品的销售额,无论日期,还是按日期销售,无论产品如何)。 + +一般来说,事实往往有两个以上的维度。在图3-9中有五个维度:日期,产品,商店,促销和客户。要想象一个五维超立方体是什么样子是很困难的,但是原理是一样的:每个单元格都包含特定日期(产品 - 商店 - 促销 - 客户组合)的销售。这些值可以在每个维度上重复概括。 + +物化数据立方体的优点是某些查询变得非常快,因为它们已经被有效地预先计算了。例如,如果您想知道每个商店的总销售额,则只需查看合适维度的总计,无需扫描数百万行。 + +缺点是数据立方体不具有查询原始数据的灵活性。例如,没有办法计算哪个销售比例来自成本超过100美元的项目,因为价格不是其中的一个维度。因此,大多数数据仓库试图保留尽可能多的原始数据,并将聚合数据(如数据立方体)仅用作某些查询的性能提升。 + + + +## 本章小结 + +在本章中,我们试图深入了解数据库如何处理存储和检索。将数据存储在数据库中会发生什么,以及稍后再次查询数据时数据库会做什么? + +在高层次上,我们看到存储引擎分为两大类:优化事务处理(OLTP)和优化分析(OLAP)的类别。这些用例的访问模式之间有很大的区别: + +* OLTP系统通常面向用户,这意味着他们可能会看到大量的请求。为了处理负载,应用程序通常只触及每个查询中的少量记录。应用程序使用某种键来请求记录,存储引擎使用索引来查找所请求的键的数据。磁盘寻道时间往往是这里的瓶颈。 +* 数据仓库和类似的分析系统不太知名,因为它们主要由业务分析人员使用,而不是由最终用户使用。它们处理比OLTP系统少得多的查询量,但是每个查询通常要求很高,需要在短时间内扫描数百万条记录。磁盘带宽(不是查找时间)往往是瓶颈,列式存储是这种工作负载越来越流行的解决方案。 + +在OLTP方面,我们看到了来自两大主流学派的存储引擎: + +* 日志结构学派,只允许附加到文件和删除过时的文件,但不会更新已经写入的文件。 Bitcask,SSTables,LSM-tree,LevelDB,Cassandra,HBase,Lucene等都属于这个组。 +* 就地更新学派,将磁盘视为一组可以覆盖的固定大小的页面。 B树是这种哲学的最大的例子,被用在所有主要的关系数据库中,还有许多非关系数据库。 + +日志结构的存储引擎是相对较新的发展。他们的主要想法是,他们系统地将随机访问写入顺序写入磁盘,由于硬盘驱动器和固态硬盘的性能特点,可以实现更高的写入吞吐量。在完成OLTP方面,我们通过一些更复杂的索引结构和为保留所有数据而优化的数据库做了一个简短的介绍。 + +然后,我们从存储引擎的内部绕开,看看典型数据仓库的高级架构。这一背景说明了为什么分析工作负载与OLTP差别很大:当您的查询需要在大量行中顺序扫描时,索引的相关性就会降低很多。相反,非常紧凑地编码数据变得非常重要,以最大限度地减少查询需要从磁盘读取的数据量。我们讨论了列式存储如何帮助实现这一目标。 + +作为一名应用程序开发人员,如果您掌握了有关存储引擎内部的知识,那么您就能更好地了解哪种工具最适合您的特定应用程序。如果您需要调整数据库的调整参数,这种理解可以让您设想一个更高或更低的值可能会产生什么效果。 + +尽管本章不能让你成为一个特定存储引擎的调参专家,但它至少有大概率使你有了足够的概念与词汇储备去读懂数据库的文档,从而选择合适的数据库。 + + + + + +## 参考文献 + + +1. Alfred V. Aho, John E. Hopcroft, and Jeffrey D. Ullman: + *Data Structures and Algorithms*. Addison-Wesley, 1983. ISBN: 978-0-201-00023-8 + +1. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and + Clifford Stein: *Introduction to Algorithms*, 3rd edition. MIT Press, 2009. + ISBN: 978-0-262-53305-8 + +1. Justin Sheehy and David Smith: + “[Bitcask: A Log-Structured Hash Table for Fast Key/Value Data](http://basho.com/wp-content/uploads/2015/05/bitcask-intro.pdf),” Basho Technologies, April 2010. + +1. Yinan Li, Bingsheng He, Robin Jun Yang, et al.: + “[Tree Indexing on Solid State Drives](http://www.vldb.org/pvldb/vldb2010/papers/R106.pdf),” + *Proceedings of the VLDB Endowment*, volume 3, number 1, pages 1195–1206, + September 2010. + +1. Goetz Graefe: + “[Modern B-Tree Techniques](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.219.7269&rep=rep1&type=pdf),” + *Foundations and Trends in Databases*, volume 3, number 4, pages 203–402, August 2011. + [doi:10.1561/1900000028](http://dx.doi.org/10.1561/1900000028) + +1. Jeffrey Dean and Sanjay Ghemawat: + “[LevelDB Implementation Notes](https://github.com/google/leveldb/blob/master/doc/impl.html),” + *leveldb.googlecode.com*. + +1. Dhruba Borthakur: + “[The History of RocksDB](http://rocksdb.blogspot.com/),” + *rocksdb.blogspot.com*, November 24, 2013. + +1. Matteo Bertozzi: + “[Apache HBase I/O – HFile](http://blog.cloudera.com/blog/2012/06/hbase-io-hfile-input-output/),” *blog.cloudera.com*, June, 29 2012. + +1. Fay Chang, Jeffrey Dean, Sanjay Ghemawat, et al.: + “[Bigtable: A Distributed Storage System for Structured Data](http://research.google.com/archive/bigtable.html),” at *7th USENIX Symposium on Operating System Design and + Implementation* (OSDI), November 2006. + +1. Patrick + O'Neil, Edward Cheng, Dieter Gawlick, and Elizabeth O'Neil: + “[The Log-Structured Merge-Tree (LSM-Tree)](http://www.cs.umb.edu/~poneil/lsmtree.pdf),” + *Acta Informatica*, volume 33, number 4, pages 351–385, June 1996. + [doi:10.1007/s002360050048](http://dx.doi.org/10.1007/s002360050048) + +1. Mendel Rosenblum and John K. Ousterhout: + “[The Design and Implementation of a Log-Structured File System](http://research.cs.wisc.edu/areas/os/Qual/papers/lfs.pdf),” + *ACM Transactions on Computer Systems*, volume 10, number 1, pages 26–52, February 1992. + [doi:10.1145/146941.146943](http://dx.doi.org/10.1145/146941.146943) + +1. Adrien Grand: + “[What Is in a Lucene Index?](http://www.slideshare.net/lucenerevolution/what-is-inaluceneagrandfinal),” at *Lucene/Solr Revolution*, November 14, 2013. + +1. Deepak Kandepet: + “[Hacking Lucene—The Index Format]( http://hackerlabs.github.io/blog/2011/10/01/hacking-lucene-the-index-format/index.html),” *hackerlabs.org*, October 1, 2011. + +1. Michael McCandless: + “[Visualizing Lucene's Segment Merges](http://blog.mikemccandless.com/2011/02/visualizing-lucenes-segment-merges.html),” *blog.mikemccandless.com*, February 11, 2011. + +1. Burton H. Bloom: + “[Space/Time Trade-offs in Hash Coding with Allowable Errors](http://www.cs.upc.edu/~diaz/p422-bloom.pdf),” *Communications of the ACM*, volume 13, number 7, pages 422–426, July 1970. + [doi:10.1145/362686.362692](http://dx.doi.org/10.1145/362686.362692) + +1. “[Operating Cassandra: Compaction](https://cassandra.apache.org/doc/latest/operating/compaction.html),” Apache Cassandra Documentation v4.0, 2016. + +1. Rudolf Bayer and Edward M. McCreight: + “[Organization and Maintenance of Large Ordered Indices](http://www.dtic.mil/cgi-bin/GetTRDoc?AD=AD0712079),” Boeing Scientific Research Laboratories, Mathematical and Information Sciences + Laboratory, report no. 20, July 1970. + +1. Douglas Comer: + “[The Ubiquitous B-Tree](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.96.6637&rep=rep1&type=pdf),” *ACM Computing Surveys*, volume 11, number 2, pages 121–137, June 1979. + [doi:10.1145/356770.356776](http://dx.doi.org/10.1145/356770.356776) + +1. Emmanuel Goossaert: + “[Coding for SSDs](http://codecapsule.com/2014/02/12/coding-for-ssds-part-1-introduction-and-table-of-contents/),” *codecapsule.com*, February 12, 2014. + +1. C. Mohan and Frank Levine: + “[ARIES/IM: An Efficient and High Concurrency Index Management Method Using Write-Ahead Logging](http://www.ics.uci.edu/~cs223/papers/p371-mohan.pdf),” at *ACM + International Conference on Management of Data* (SIGMOD), June 1992. + [doi:10.1145/130283.130338](http://dx.doi.org/10.1145/130283.130338) + +1. Howard Chu: + “[LDAP at Lightning Speed]( https://buildstuff14.sched.com/event/08a1a368e272eb599a52e08b4c3c779d),” + at *Build Stuff '14*, November 2014. + +1. Bradley C. Kuszmaul: + “[A Comparison of Fractal Trees to Log-Structured Merge (LSM) Trees](http://insideanalysis.com/wp-content/uploads/2014/08/Tokutek_lsm-vs-fractal.pdf),” *tokutek.com*, + April 22, 2014. + +1. Manos Athanassoulis, Michael S. Kester, + Lukas M. Maas, et al.: “[Designing Access Methods: The RUM Conjecture](http://openproceedings.org/2016/conf/edbt/paper-12.pdf),” at *19th International Conference on Extending Database + Technology* (EDBT), March 2016. + [doi:10.5441/002/edbt.2016.42](http://dx.doi.org/10.5441/002/edbt.2016.42) + +1. Peter Zaitsev: + “[Innodb Double Write](https://www.percona.com/blog/2006/08/04/innodb-double-write/),” + *percona.com*, August 4, 2006. + +1. Tomas Vondra: + “[On the Impact of Full-Page Writes](http://blog.2ndquadrant.com/on-the-impact-of-full-page-writes/),” *blog.2ndquadrant.com*, November 23, 2016. + +1. Mark Callaghan: + “[The Advantages of an LSM vs a B-Tree](http://smalldatum.blogspot.co.uk/2016/01/summary-of-advantages-of-lsm-vs-b-tree.html),” *smalldatum.blogspot.co.uk*, January 19, 2016. + +1. Mark Callaghan: + “[Choosing Between Efficiency and Performance with RocksDB](http://www.codemesh.io/codemesh/mark-callaghan),” at *Code Mesh*, November 4, 2016. + +1. Michi Mutsuzaki: + “[MySQL vs. LevelDB](https://github.com/m1ch1/mapkeeper/wiki/MySQL-vs.-LevelDB),” + *github.com*, August 2011. + +1. Benjamin Coverston, + Jonathan Ellis, et al.: “[CASSANDRA-1608: Redesigned Compaction](https://issues.apache.org/jira/browse/CASSANDRA-1608), *issues.apache.org*, July 2011. + +1. Igor Canadi, Siying Dong, and Mark Callaghan: + “[RocksDB Tuning Guide](https://github.com/facebook/rocksdb/wiki/RocksDB-Tuning-Guide),” + *github.com*, 2016. + +1. *MySQL + 5.7 Reference Manual*. Oracle, 2014. + +1. *Books + Online for SQL Server 2012*. Microsoft, 2012. + +1. Joe Webb: + “[Using Covering Indexes to Improve Query Performance](https://www.simple-talk.com/sql/learn-sql-server/using-covering-indexes-to-improve-query-performance/),” *simple-talk.com*, 29 September 2008. + +1. Frank Ramsak, Volker Markl, Robert Fenk, et al.: + “[Integrating the UB-Tree into a Database System Kernel](http://www.vldb.org/conf/2000/P263.pdf),” + at *26th International Conference on Very Large Data Bases* (VLDB), September 2000. + +1. The PostGIS Development Group: + “[PostGIS 2.1.2dev Manual](http://postgis.net/docs/manual-2.1/),” + *postgis.net*, 2014. + +1. Robert Escriva, Bernard Wong, and Emin Gün Sirer: + “[HyperDex: A Distributed, Searchable Key-Value Store](http://www.cs.princeton.edu/courses/archive/fall13/cos518/papers/hyperdex.pdf),” at *ACM SIGCOMM Conference*, August 2012. + [doi:10.1145/2377677.2377681](http://dx.doi.org/10.1145/2377677.2377681) + +1. Michael McCandless: + “[Lucene's FuzzyQuery Is 100 Times Faster in 4.0](http://blog.mikemccandless.com/2011/03/lucenes-fuzzyquery-is-100-times-faster.html),” *blog.mikemccandless.com*, March 24, 2011. + +1. Steffen Heinz, Justin Zobel, and Hugh E. Williams: + “[Burst Tries: A Fast, Efficient Data Structure for String Keys](http://citeseer.ist.psu.edu/viewdoc/summary?doi=10.1.1.18.3499),” + *ACM Transactions on Information Systems*, volume 20, number 2, pages 192–223, April 2002. + [doi:10.1145/506309.506312](http://dx.doi.org/10.1145/506309.506312) + +1. Klaus U. Schulz and Stoyan Mihov: + “[Fast String Correction with Levenshtein Automata](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.16.652),” + *International Journal on Document Analysis and Recognition*, + volume 5, number 1, pages 67–85, November 2002. + [doi:10.1007/s10032-002-0082-8](http://dx.doi.org/10.1007/s10032-002-0082-8) + +1. Christopher D. Manning, Prabhakar Raghavan, and Hinrich Schütze: + *Introduction to Information Retrieval*. + Cambridge University Press, 2008. ISBN: 978-0-521-86571-5, available online at *nlp.stanford.edu/IR-book* + +1. Michael Stonebraker, Samuel Madden, Daniel J. Abadi, et al.: + “[The End of an Architectural Era (It’s Time for a Complete Rewrite)](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.137.3697&rep=rep1&type=pdf),” at + *33rd International Conference on Very Large Data Bases* (VLDB), September 2007. + +1. “[VoltDB Technical Overview White Paper](https://www.voltdb.com/wptechnicaloverview),” VoltDB, 2014. + +1. Stephen M. Rumble, Ankita Kejriwal, and John K. Ousterhout: + “[Log-Structured Memory for DRAM-Based Storage](https://www.usenix.org/system/files/conference/fast14/fast14-paper_rumble.pdf),” at *12th USENIX Conference on File and Storage + Technologies* (FAST), February 2014. + +1. Stavros Harizopoulos, Daniel J. Abadi, + Samuel Madden, and Michael Stonebraker: + “[OLTP Through the Looking Glass, and What We Found There](http://hstore.cs.brown.edu/papers/hstore-lookingglass.pdf),” at *ACM International Conference on Management of Data* + (SIGMOD), June 2008. + [doi:10.1145/1376616.1376713](http://dx.doi.org/10.1145/1376616.1376713) + +1. Justin DeBrabant, Andrew Pavlo, Stephen Tu, et al.: + “[Anti-Caching: A New Approach to Database Management System Architecture](http://www.vldb.org/pvldb/vol6/p1942-debrabant.pdf),” *Proceedings of the VLDB Endowment*, volume 6, + number 14, pages 1942–1953, September 2013. + +1. Joy Arulraj, Andrew Pavlo, and Subramanya R. Dulloor: + “[Let's Talk About Storage & Recovery Methods for Non-Volatile Memory Database Systems](http://www.pdl.cmu.edu/PDL-FTP/NVM/storage.pdf),” at *ACM International Conference on + Management of Data* (SIGMOD), June 2015. + [doi:10.1145/2723372.2749441](http://dx.doi.org/10.1145/2723372.2749441) + +1. Edgar F. Codd, S. B. Codd, and C. T. Salley: + “[Providing OLAP to User-Analysts: An IT Mandate](http://www.minet.uni-jena.de/dbis/lehre/ss2005/sem_dwh/lit/Cod93.pdf),” E. F. Codd Associates, +1993. + +1. Surajit Chaudhuri and Umeshwar Dayal: + “[An Overview of Data Warehousing and OLAP Technology](https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/sigrecord.pdf),” *ACM SIGMOD Record*, volume 26, number 1, pages 65–74, + March 1997. [doi:10.1145/248603.248616](http://dx.doi.org/10.1145/248603.248616) + +1. Per-Åke Larson, Cipri Clinciu, Campbell Fraser, et al.: + “[Enhancements to SQL Server Column Stores](http://research.microsoft.com/pubs/193599/Apollo3%20-%20Sigmod%202013%20-%20final.pdf),” at *ACM International Conference on Management of Data* + (SIGMOD), June 2013. + +1. Franz Färber, Norman May, Wolfgang Lehner, et al.: + “[The SAP HANA Database – An Architecture Overview](http://sites.computer.org/debull/A12mar/hana.pdf),” + *IEEE Data Engineering Bulletin*, volume 35, number 1, pages 28–33, March 2012. + +1. Michael Stonebraker: + “[The Traditional RDBMS Wisdom Is (Almost Certainly) All Wrong](http://slideshot.epfl.ch/talks/166),” presentation at *EPFL*, May 2013. + +1. Daniel J. Abadi: + “[Classifying the SQL-on-Hadoop Solutions](https://web.archive.org/web/20150622074951/http://hadapt.com/blog/2013/10/02/classifying-the-sql-on-hadoop-solutions/),” *hadapt.com*, October 2, 2013. + +1. Marcel Kornacker, Alexander Behm, Victor Bittorf, et al.: + “[Impala: A Modern, Open-Source SQL Engine for Hadoop](http://pandis.net/resources/cidr15impala.pdf),” at *7th Biennial Conference on Innovative Data Systems + Research* (CIDR), January 2015. + +1. Sergey Melnik, Andrey Gubarev, Jing Jing Long, et al.: + “[Dremel: Interactive Analysis of Web-Scale Datasets](http://research.google.com/pubs/pub36632.html),” at *36th International Conference on Very Large Data Bases* (VLDB), pages + 330–339, September 2010. + +1. Ralph Kimball and Margy Ross: + *The Data Warehouse Toolkit: The Definitive Guide to Dimensional Modeling*, + 3rd edition. John Wiley & Sons, July 2013. ISBN: 978-1-118-53080-1 + +1. Derrick Harris: + “[Why Apple, eBay, and Walmart Have Some of the Biggest Data Warehouses You’ve Ever Seen](http://gigaom.com/2013/03/27/why-apple-ebay-and-walmart-have-some-of-the-biggest-data-warehouses-youve-ever-seen/),” + *gigaom.com*, March 27, 2013. + +1. Julien Le Dem: + “[Dremel Made Simple with Parquet](https://blog.twitter.com/2013/dremel-made-simple-with-parquet),” + *blog.twitter.com*, September 11, 2013. + +1. Daniel J. Abadi, Peter Boncz, Stavros + Harizopoulos, et al.: + “[The Design and Implementation of Modern Column-Oriented Database Systems](http://cs-www.cs.yale.edu/homes/dna/papers/abadi-column-stores.pdf),” *Foundations and Trends in + Databases*, volume 5, number 3, pages 197–280, December 2013. + [doi:10.1561/1900000024](http://dx.doi.org/10.1561/1900000024) + +1. Peter Boncz, Marcin Zukowski, and Niels Nes: + “[MonetDB/X100: Hyper-Pipelining Query Execution](http://www.cidrdb.org/cidr2005/papers/P19.pdf),” + at *2nd Biennial Conference on Innovative Data Systems Research* (CIDR), January 2005. + +1. Jingren Zhou and Kenneth A. Ross: + “[Implementing Database Operations Using SIMD Instructions](http://www1.cs.columbia.edu/~kar/pubsk/simd.pdf),” + at *ACM International Conference on Management of Data* (SIGMOD), pages 145–156, June 2002. + [doi:10.1145/564691.564709](http://dx.doi.org/10.1145/564691.564709) + +1. Michael Stonebraker, Daniel J. Abadi, Adam Batkin, et al.: + “[C-Store: A Column-oriented DBMS](http://www.vldb2005.org/program/paper/thu/p553-stonebraker.pdf),” + at *31st International Conference on Very Large Data Bases* (VLDB), pages 553–564, September 2005. + +1. Andrew Lamb, Matt Fuller, Ramakrishna Varadarajan, et al.: + “[The Vertica Analytic Database: C-Store 7 Years Later](http://vldb.org/pvldb/vol5/p1790_andrewlamb_vldb2012.pdf),” + *Proceedings of the VLDB Endowment*, volume 5, number 12, pages 1790–1801, August 2012. + +1. Julien Le Dem and Nong Li: + “[Efficient Data Storage for Analytics with Apache Parquet 2.0](http://www.slideshare.net/julienledem/th-210pledem),” at *Hadoop Summit*, San Jose, + June 2014. + +1. Jim Gray, Surajit Chaudhuri, Adam Bosworth, et al.: + “[Data Cube: A Relational Aggregation Operator Generalizing Group-By, Cross-Tab, and Sub-Totals](http://arxiv.org/pdf/cs/0701155.pdf),” *Data Mining and Knowledge + Discovery*, volume 1, number 1, pages 29–53, March 2007. + [doi:10.1023/A:1009726021843](http://dx.doi.org/10.1023/A:1009726021843) + diff --git a/ddia/ch4.md b/ddia/ch4.md new file mode 100644 index 0000000..f6c6988 --- /dev/null +++ b/ddia/ch4.md @@ -0,0 +1,700 @@ +# 4. 编码与演化 + +![](img/ch4.png) + +> 唯变所适 +> +> ——以弗所的赫拉克利特,为柏拉图所引(公元前360年) +> + +------------------- + +[TOC] + +应用程序不可避免地随时间而变化。新产品的推出,对需求的深入理解,或者商业环境的变化,总会伴随着*功能(feature)*的增增改改。[第一章](ch1.md)介绍了*可演化性(evolvability)*的概念:应该尽力构建能灵活适应变化的系统(参阅“可演化性:拥抱变化”)。 + +在大多数情况下,修改应用程序的功能也意味着需要更改其存储的数据:可能需要使用新的字段或记录类型,或者以新方式展示现有数据。 + +我们在[第二章](ch2.md)讨论的数据模型有不同的方法来应对这种变化。关系数据库通常假定数据库中的所有数据都遵循一个模式:尽管可以更改该模式(通过模式迁移,即`ALTER`语句),但是在任何时间点都有且仅有一个正确的模式。相比之下,*读时模式 schema-on-read*(或 “无模式” schemaless)数据库不会强制一个模式,因此数据库可以包含在不同时间写入的新老数据格式的混合(参阅 “文档模型中的模式灵活性” )。 + +当数据格式(format)或模式(schema)发生变化时,通常需要对应用程序代码进行相应的更改(例如,为记录添加新字段,然后修改程序开始读写该字段)。但在大型应用程序中,代码变更通常不会立即完成: + +* 对于服务端(server-side)应用程序,可能需要执行*滚动升级 (rolling upgrade)*(也称为*阶段发布 staged rollout*),一次将新版本部署到少数几个节点,检查新版本是否运行正常,然后逐渐部完所有的节点。这样无需中断服务即可部署新版本,为频繁发布提供了可行性,从而带来更好的可演化性。 +* 对于客户端(client-side)应用程序,升不升级就要看用户的心情了。用户可能相当长一段时间里都不会去升级软件。 + +这意味着,新旧版本的代码,以及新旧数据格式可能会在系统中同时共处。系统想要继续顺利运行,就需要保持*双向兼容性*: + +* **向后兼容** (backward compatibility) + + 新代码可以读旧数据。 + +* **向前兼容** (forward compatibility) + + 旧代码可以读新数据。 + +向后兼容性通常并不难实现:新代码的作者当然知道由旧代码使用的数据格式,因此可以显示地处理它(最简单的办法是,保留旧代码即可读取旧数据)。 + +向前兼容性可能会更棘手,因为旧版的程序需要忽略新版数据格式中新增的部分。 + +本章中将介绍几种编码数据的格式,包括 JSON,XML,Protocol Buffers,Thrift和Avro。尤其将关注这些格式如何应对模式变化,以及它们如何对新旧代码数据需要共存的系统提供支持。然后将讨论如何使用这些格式进行数据存储和通信:在Web服务中,具象状态传输(REST)和远程过程调用(RPC),以及消息传递系统(如Actor和消息队列)。 + +## 编码数据的格式 + +程序通常(至少)使用两种形式的数据: + +1. 在内存中,数据保存在对象,结构体,列表,数组,哈希表,树等中。 这些数据结构针对CPU的高效访问和操作进行了优化(通常使用指针)。 +2. 如果要将数据写入文件,或通过网络发送,则必须将其*编码(encode)*为某种自包含的字节序列(例如,JSON文档)。 由于每个进程都有自己独立的地址空间,一个进程中的指针对任何其他进程都没有意义,所以这个字节序列表示会与通常在内存中使用的数据结构完全不同。 + +所以,需要在两种表示之间进行某种类型的翻译。 从内存中表示到字节序列的转换称为**编码(Encoding)**(也称为**序列化 serialization**或**编组 marshalling**),反过来称为**解码(Decoding)**(**解析Parsing**,**反序列化 deserialization**,**反编组 unmarshalling**)。 + +> #### 术语冲突 +> 不幸的是,在第七章:事务(Transaction)的上下文里,序列化(Serialization)这个术语也出现了,而且具有完全不同的含义。尽管序列化可能是更常见的术语,为了避免术语重载,本书中坚持使用编码(Encoding)表达此含义。 +> +> 译者注:Marshal与Serialization的区别:Marshal不仅传输对象的状态,而且会一起传输对象的方法(相关代码)。 + +这是一个常见的问题,因而有许多库和编码格式可供选择。 首先让我们概览一下。 + +### 语言特定的格式 + +许多编程语言都内建了将内存对象编码为字节序列的支持。例如,Java有`java.io.Serializable [1]`,Ruby有`Marshal [2]`,Python有`pickle [3]`等等。许多第三方库也存在,例如`Kryo for Java [4]`。 + +这些编码库非常方便,可以用很少的额外代码实现内存对象的保存与恢复。但是它们也有一些深层次的问题: + +* 这类编码通常与特定的编程语言深度绑定,其他语言很难读取这种数据。如果以这类编码存储或传输数据,那你就和这门语言绑死在一起了。并且很难将系统与其他组织的系统(可能用的是不同的语言)进行集成。 +* 为了恢复相同对象类型的数据,解码过程需要**实例化任意类**的能力,这通常是安全问题的一个来源[5]:如果攻击者可以让应用程序解码任意的字节序列,他们就能实例化任意的类,这会允许他们做可怕的事情,如远程执行任意代码[6 ,7]。 +* 在这些库中,数据版本控制通常是事后才考虑的。因为它们旨在快速简便地对数据进行编码,所以往往忽略了前向后向兼容性带来的麻烦问题。 +* 效率(编码或解码所花费的CPU时间,以及编码结构的大小)往往也是事后才考虑的。 例如,Java的内置序列化由于其糟糕的性能和臃肿的编码而臭名昭着[8]。 + +因此,除非临时使用,采用语言内置编码通常是一个坏主意。 + +### JSON,XML和二进制变体 + +谈到可以被许多编程语言编写和读取的标准化编码,JSON和XML是显眼的竞争者。它们广为人知,广受支持,也“广受憎恶”。 XML经常被批评为过于冗长和不必要的复杂[9]。 JSON倍受欢迎,主要由于它在Web浏览器中的内置支持(通过成为JavaScript的一个子集)以及相对于XML的简单性。 CSV是另一种流行的与语言无关的格式,尽管功能较弱。 + +JSON,XML和CSV是文本格式,因此具有人类可读性(尽管语法是一个热门辩题)。除了表面的语法问题之外,它们也有一些微妙的问题: + +* 数字的编码多有歧义之处。XML和CSV不能区分数字和字符串(除非引用外部模式)。 JSON虽然区分字符串和数字,但不区分整数和浮点数,而且不能指定精度。 +* 当处理大量数据时,这个问题更严重了。例如,大于$2^{53}$的整数不能在IEEE 754双精度浮点数中精确表示,因此在使用浮点数(例如JavaScript)的语言进行分析时,这些数字会变得不准确。 Twitter上有一个大于$2^{53}$的数字的例子,它使用一个64位的数字来标识每条推文。 Twitter API返回的JSON包含了两次推特ID,一次是JSON数字,一次是十进制字符串,以此避免JavaScript程序无法正确解析数字的问题[10]。 +* JSON和XML对Unicode字符串(即人类可读的文本)有很好的支持,但是它们不支持二进制数据(不带字符编码(character encoding)的字节序列)。二进制串是很实用的功能,所以人们通过使用Base64将二进制数据编码为文本来绕开这个限制。模式然后用于表示该值应该被解释为Base64编码。这个工作,但它有点hacky,并增加了33%的数据大小。 XML [11]和JSON [12]都有可选的模式支持。这些模式语言相当强大,所以学习和实现起来相当复杂。 XML模式的使用相当普遍,但许多基于JSON的工具嫌麻烦才不会使用模式。由于数据的正确解释(例如数字和二进制字符串)取决于模式中的信息,因此不使用XML / JSON模式的应用程序可能需要对相应的编码/解码逻辑进行硬编码。 +* CSV没有任何模式,因此应用程序需要定义每行和每列的含义。如果应用程序更改添加新的行或列,则必须手动处理该变更。 CSV也是一个相当模糊的格式(如果一个值包含逗号或换行符,会发生什么?)。尽管其转义规则已经被正式指定[13],但并不是所有的解析器都正确的实现了标准。 + +尽管存在这些缺陷,但JSON,XML和CSV已经足够用于很多目的。特别是作为数据交换格式(即将数据从一个组织发送到另一个组织),它们很可能仍然很受欢迎。这种情况下,只要人们对格式是什么意见一致,格式多么美观或者高效就没有关系。让不同的组织达成一致的难度超过了其他大多数问题。 + +#### 二进制编码 + +对于仅在组织内部使用的数据,使用最小公分母编码格式的压力较小。例如,可以选择更紧凑或更快的解析格式。虽然对小数据集来说,收益可以忽略不计,但一旦达到TB级别,数据格式的选择就会产生巨大的影响。 + +JSON比XML简洁,但与二进制格式一比,还是太占地方。这一事实导致大量二进制编码版本JSON & XML的出现,JSON(MessagePack,BSON,BJSON,UBJSON,BISON和Smile等)(例如WBXML和Fast Infoset)。这些格式已经被各种各样的领域所采用,但是没有一个像JSON和XML的文本版本那样被广泛采用。 + +这些格式中的一些扩展了一组数据类型(例如,区分整数和浮点数,或者增加对二进制字符串的支持),另一方面,它们没有盖面JSON / XML的数据模型。特别是由于它们没有规定模式,所以它们需要在编码数据中包含所有的对象字段名称。也就是说,在**例4-1**中的JSON文档的二进制编码中,需要在某处包含字符串`userName`,`favoriteNumber`和`interest`。 + +**例4-1 本章中用于展示二进制编码的示例记录** + +```json +{ + "userName": "Martin", + "favoriteNumber": 1337, + "interests": ["daydreaming", "hacking"] +} +``` + +我们来看一个MessagePack的例子,它是一个JSON的二进制编码。图4-1显示了如果使用MessagePack [14]对例4-1中的JSON文档进行编码,则得到的字节序列。前几个字节如下: + +1. 第一个字节`0x83`表示接下来是**3**个字段(低四位= `0x03`)的*对象 object*(高四位= `0x80`)。 (如果想知道如果一个对象有15个以上的字段会发生什么情况,字段的数量塞不进4个bit里,那么它会用另一个不同的类型标识符,字段的数量被编码两个或四个字节)。 +2. 第二个字节`0xa8`表示接下来是**8**字节长的字符串(最高四位= 0x08)。 +3. 接下来八个字节是ASCII字符串形式的字段名称`userName`。由于之前已经指明长度,不需要任何标记来标识字符串的结束位置(或者任何转义)。 +4. 接下来的七个字节对前缀为`0xa6`的六个字母的字符串值`Martin`进行编码,依此类推。 + +二进制编码长度为66个字节,仅略小于文本JSON编码所取的81个字节(删除了空白)。所有的JSON的二进制编码在这方面是相似的。空间节省了一丁点(以及解析加速)是否能弥补可读性的损失,谁也说不准。 + +在下面的章节中,能达到比这好得多的结果,只用32个字节对相同的记录进行编码。 + + +![](img/fig4-1.png) + +**图4-1 使用MessagePack编码的记录(例4-1)** + + + +### Thrift与Protocol Buffers + +Apache Thrift [15]和Protocol Buffers(protobuf)[16]是基于相同原理的二进制编码库。 Protocol Buffers最初是在Google开发的,Thrift最初是在Facebook开发的,并且在2007 - 2008年都是开源的[17]。 +Thrift和Protocol Buffers都需要一个模式来编码任何数据。要在Thrift的例4-1中对数据进行编码,可以使用Thrift接口定义语言(IDL)来描述模式,如下所示: + +```c +struct Person { + 1: required string userName, + 2: optional i64 favoriteNumber, + 3: optional list interests +} +``` + +Protocol Buffers的等效模式定义看起来非常相似: + +```protobuf +message Person { + required string user_name = 1; + optional int64 favorite_number = 2; + repeated string interests = 3; +} +``` + +Thrift和Protocol Buffers每一个都带有一个代码生成工具,它采用了类似于这里所示的模式定义,并且生成了以各种编程语言实现模式的类[18]。您的应用程序代码可以调用此生成的代码来对模式的记录进行编码或解码。 +用这个模式编码的数据是什么样的?令人困惑的是,Thrift有两种不同的二进制编码格式[^iii],分别称为BinaryProtocol和CompactProtocol。先来看看BinaryProtocol。这种格式的编码示例4-1需要59个字节,如图4-2 [19]所示。 + +( ) + +![](img/fig4-2.png) + +**图4-2 使用Thrift二进制协议编码的记录** + +[^iii]: 实际上,Thrift有三种二进制协议:CompactProtocol和DenseProtocol,尽管DenseProtocol只支持C ++实现,所以不算作跨语言[18]。 除此之外,它还有两种不同的基于JSON的编码格式[19]。 真逗! + +与图4-1类似,每个字段都有一个类型注释(用于指示它是一个字符串,整数,列表等),还可以根据需要指定长度(字符串的长度,列表中的项目数) 。出现在数据中的字符串(“Martin”,“daydreaming”,“hacking”)也被编码为ASCII(或者说,UTF-8),与之前类似。 + +与图4-1相比,最大的区别是没有字段名(userName,favoriteNumber,interest)。相反,编码数据包含字段标签,它们是数字(1,2和3)。这些是模式定义中出现的数字。字段标记就像字段的别名 - 它们是说我们正在谈论的字段的一种紧凑的方式,而不必拼出字段名称。 + +Thrift CompactProtocol编码在语义上等同于BinaryProtocol,但是如图4-3所示,它只将相同的信息打包成只有34个字节。它通过将字段类型和标签号打包到单个字节中,并使用可变长度整数来实现。数字1337不是使用全部八个字节,而是用两个字节编码,每个字节的最高位用来指示是否还有更多的字节来。这意味着-64到63之间的数字被编码为一个字节,-8192和8191之间的数字以两个字节编码,等等。较大的数字使用更多的字节。 + +![](img/fig4-3.png) + +**图4-3 使用Thrift压缩协议编码的记录** + +最后,Protocol Buffers(只有一种二进制编码格式)对相同的数据进行编码,如图4-4所示。 它的打包方式稍有不同,但与Thrift的CompactProtocol非常相似。 Protobuf将同样的记录塞进了33个字节中。 + +![](img/fig4-4.png) + +**图4-4 使用Protobuf编码的记录** + +需要注意的一个细节:在前面所示的模式中,每个字段被标记为必需或可选,但是这对字段如何编码没有任何影响(二进制数据中没有任何字段指示是否需要字段)。所不同的是,如果未设置该字段,则所需的运行时检查将失败,这对于捕获错误非常有用。 + +#### 字段标签和模式演变 + +我们之前说过,模式不可避免地需要随着时间而改变。我们称之为模式演变。 Thrift和Protocol Buffers如何处理模式更改,同时保持向后兼容性? + +从示例中可以看出,编码的记录就是其编码字段的拼接。每个字段由其标签号码(样本模式中的数字1,2,3)标识,并用数据类型(例如字符串或整数)注释。如果没有设置字段值,则简单地从编码记录中省略。从中可以看到,字段标记对编码数据的含义至关重要。您可以更改架构中字段的名称,因为编码的数据永远不会引用字段名称,但不能更改字段的标记,因为这会使所有现有的编码数据无效。 + +您可以添加新的字段到架构,只要您给每个字段一个新的标签号码。如果旧的代码(不知道你添加的新的标签号码)试图读取新代码写入的数据,包括一个新的字段,其标签号码不能识别,它可以简单地忽略该字段。数据类型注释允许解析器确定需要跳过的字节数。这保持了前向兼容性:旧代码可以读取由新代码编写的记录。 + +向后兼容性呢?只要每个字段都有一个唯一的标签号码,新的代码总是可以读取旧的数据,因为标签号码仍然具有相同的含义。唯一的细节是,如果你添加一个新的领域,你不能要求。如果您要添加一个字段并将其设置为必需,那么如果新代码读取旧代码写入的数据,则该检查将失败,因为旧代码不会写入您添加的新字段。因此,为了保持向后兼容性,在模式的初始部署之后添加的每个字段必须是可选的或具有默认值。 + +删除一个字段就像添加一个字段,倒退和向前兼容性问题相反。这意味着您只能删除一个可选的字段(必填字段永远不能删除),而且您不能再次使用相同的标签号码(因为您可能仍然有数据写在包含旧标签号码的地方,而该字段必须被新代码忽略)。 + +#### 数据类型和模式演变 + +如何改变字段的数据类型?这可能是可能的 - 检查文件的细节 - 但是有一个风险,价值将失去精度或被扼杀。例如,假设你将一个32位的整数变成一个64位的整数。新代码可以轻松读取旧代码写入的数据,因为解析器可以用零填充任何缺失的位。但是,如果旧代码读取由新代码写入的数据,则旧代码仍使用32位变量来保存该值。如果解码的64位值不适合32位,则它将被截断。 + +Protobuf的一个奇怪的细节是,它没有列表或数组数据类型,而是有一个字段的重复标记(这是第三个选项旁边必要和可选)。如图4-4所示,重复字段的编码正如它所说的那样:同一个字段标记只是简单地出现在记录中。这具有很好的效果,可以将可选(单值)字段更改为重复(多值)字段。读取旧数据的新代码会看到一个包含零个或一个元素的列表(取决于该字段是否存在)。读取新数据的旧代码只能看到列表的最后一个元素。 + +Thrift有一个专用的列表数据类型,它使用列表元素的数据类型进行参数化。这不允许Protocol Buffers所做的从单值到多值的相同演变,但是它具有支持嵌套列表的优点。 + +### Avro + +Apache Avro [20]是另一种二进制编码格式,与Protocol Buffers和Thrift有趣的不同。 它是作为Hadoop的一个子项目在2009年开始的,因为Thrift不适合Hadoop的用例[21]。 + +Avro也使用模式来指定正在编码的数据的结构。 它有两种模式语言:一种(Avro IDL)用于人工编辑,一种(基于JSON),更易于机器读取。 + +我们用Avro IDL编写的示例模式可能如下所示: + +```c +record Person { + string userName; + union { null, long } favoriteNumber = null; + array interests; +} +``` + +等价的JSON表示: + +```json +{ + "type": "record", + "name": "Person", + "fields": [ + {"name": "userName", "type": "string"}, + {"name": "favoriteNumber", "type": ["null", "long"], "default": null}, + {"name": "interests", "type": {"type": "array", "items": "string"} + ] +} +``` + +首先,请注意架构中没有标签号码。 如果我们使用这个模式编码我们的例子记录(例4-1),Avro二进制编码只有32个字节长,这是我们所见过的所有编码中最紧凑的。 编码字节序列的分解如图4-5所示。 + +如果您检查字节序列,您可以看到没有什么可以识别字段或其数据类型。 编码只是由连在一起的值组成。 一个字符串只是一个长度前缀,后跟UTF-8字节,但是在被包含的数据中没有任何内容告诉你它是一个字符串。 它可以是一个整数,也可以是其他的整数。 整数使用可变长度编码(与Thrift的CompactProtocol相同)进行编码。 + +![](img/fig4-5.png) + +**图4-5 使用Avro编码的记录** + +为了解析二进制数据,您按照它们出现在架构中的顺序遍历这些字段,并使用架构来告诉您每个字段的数据类型。这意味着如果读取数据的代码使用与写入数据的代码完全相同的模式,则只能正确解码二进制数据。阅读器和作者之间的模式不匹配意味着错误地解码数据。 + +那么,Avro如何支持模式演变呢? + +#### 作者模式与读者模式 + +有了Avro,当应用程序想要编码一些数据(将其写入文件或数据库,通过网络发送等)时,它使用它知道的任何版本的模式编码数据,例如,架构可能被编译到应用程序中。这被称为作者的模式。 + +当一个应用程序想要解码一些数据(从一个文件或数据库读取数据,从网络接收数据等)时,它希望数据在某个模式中,这就是读者的模式。这是应用程序代码所依赖的模式,在应用程序的构建过程中,代码可能是从该模式生成的。 + +Avro的关键思想是作者的模式和读者的模式不必是相同的 - 他们只需要兼容。当数据解码(读取)时,Avro库通过并排查看作者的模式和读者的模式并将数据从作者的模式转换到读者的模式来解决差异。 Avro规范[20]确切地定义了这种解析的工作原理,如图4-6所示。 + +例如,如果作者的模式和读者的模式的字段顺序不同,这是没有问题的,因为模式解析通过字段名匹配字段。如果读取数据的代码遇到出现在作者模式中但不在读者模式中的字段,则忽略它。如果读取数据的代码需要某个字段,但是作者的模式不包含该名称的字段,则使用在读者模式中声明的默认值填充。 + +![](img/fig4-6.png) + +**图4-6 一个Avro Reader解决读写模式的差异** + + + +#### 模式演变规则 + +使用Avro,向前兼容性意味着您可以将新版本的架构作为编写器,并将旧版本的架构作为读者。相反,向后兼容意味着你可以有一个作为读者的新版本的模式和作为作者的旧版本。 + +为了保持兼容性,您只能添加或删除具有默认值的字段。 (我们的Avro模式中的字段favourNumber的默认值为null)。例如,假设您添加一个默认值的字段,所以这个新的字段存在于新的模式中,而不是旧的。当使用新模式的阅读器读取使用旧模式写入的记录时,将为缺少的字段填充默认值。 + +如果你要添加一个没有默认值的字段,新的阅读器将无法读取旧作者写的数据,所以你会破坏向后兼容性。如果您要删除没有默认值的字段,旧的阅读器将无法读取新作者写入的数据,因此您会打破兼容性。在一些编程语言中,null是任何变量可以接受的默认值,但在Avro中并不是这样:如果要允许一个字段为null,则必须使用联合类型。例如,`union {null,long,string}`字段;表示该字段可以是数字或字符串,也可以是`null`。如果它是union的分支之一,那么只能使用null作为默认值.iv这比默认情况下可以为null是更加冗长的,但是通过明确什么可以和不可以是什么,有助于防止错误null [22]。 + +因此,Avro没有像Protocol Buffers和Thrift那样的`optional`和`required`标记(它有联合类型和默认值)。 + +只要Avro可以转换类型,就可以改变字段的数据类型。更改字段的名称是可能的,但有点棘手:读者的模式可以包含字段名称的别名,所以它可以匹配旧作家的模式字段名称与别名。这意味着更改字段名称是向后兼容的,但不能向前兼容。同样,向联合类型添加分支也是向后兼容的,但不能向前兼容。 + +##### 但作者模式到底是什么? + +到目前为止,我们已经讨论了一个重要的问题:读者如何知道作者的模式是哪一部分数据被编码的?我们不能只将整个模式包括在每个记录中,因为模式可能比编码的数据大得多,从而使二进制编码节省的所有空间都是徒劳的。 +答案取决于Avro使用的上下文。举几个例子: + +* 有很多记录的大文件 + + Avro的一个常见用途 - 尤其是在Hadoop环境中 - 用于存储包含数百万条记录的大文件,所有记录都使用相同的模式进行编码。 (我们将在第10章讨论这种情况。)在这种情况下,该文件的作者可以在文件的开头只包含一次作者的模式。 Avro指定一个文件格式(对象容器文件)来做到这一点。 + +* 支持独立写入的记录的数据库 + + 在一个数据库中,不同的记录可能会在不同的时间点使用不同的作者的模式编写 - 你不能假定所有的记录都有相同的模式。最简单的解决方案是在每个编码记录的开始处包含一个版本号,并在数据库中保留一个模式版本列表。读者可以获取记录,提取版本号,然后从数据库中获取该版本号的作者模式。使用该作者的模式,它可以解码记录的其余部分。 (例如Espresso [23]就是这样工作的。) + +* 通过网络连接发送记录 + + 当两个进程通过双向网络连接进行通信时,他们可以在连接设置上协商模式版本,然后在连接的生命周期中使用该模式。 Avro RPC协议(请参阅第131页上的“通过服务的数据流:REST和RPC”)如此工作。 + +具有模式版本的数据库在任何情况下都是非常有用的,因为它充当文档并为您提供了检查模式兼容性的机会[24]。作为版本号,你可以使用一个简单的递增整数,或者你可以使用模式的散列。 + +#### 动态生成的模式 + +与Protocol Buffers和Thrift相比,Avro方法的一个优点是架构不包含任何标签号码。但为什么这很重要?在模式中保留一些数字有什么问题? + +不同之处在于Avro对动态生成的模式更友善。例如,假如你有一个关系数据库,你想要把它的内容转储到一个文件中,并且你想使用二进制格式来避免前面提到的文本格式(JSON,CSV,SQL)的问题。如果你使用Avro,你可以很容易地从关系模式生成一个Avro模式(在我们之前看到的JSON表示中),并使用该模式对数据库内容进行编码,并将其全部转储到Avro对象容器文件[25]中。您为每个数据库表生成一个记录模式,每个列成为该记录中的一个字段。数据库中的列名称映射到Avro中的字段名称。 + +现在,如果数据库模式发生变化(例如,一个表中添加了一列,删除了一列),则可以从更新的数据库模式生成新的Avro模式,并在新的Avro模式中导出数据。数据导出过程不需要注意模式的改变 - 每次运行时都可以简单地进行模式转换。任何读取新数据文件的人都会看到记录的字段已经改变,但是由于字段是通过名字来标识的,所以更新的作者的模式仍然可以与旧的读者模式匹配。 + +相比之下,如果您为此使用Thrift或Protocol Buffers,则字段标记可能必须手动分配:每次数据库模式更改时,管理员都必须手动更新从数据库列名到字段标签。 (这可能会自动化,但模式生成器必须非常小心,不要分配以前使用的字段标记。)这种动态生成的模式根本不是Thrift或Protocol Buffers的设计目标,而是为Avro。 + +#### 代码生成和动态类型的语言 + +Thrift和Protobuf依赖于代码生成:在定义了模式之后,可以使用您选择的编程语言生成实现此模式的代码。这在Java,C ++或C#等静态类型语言中很有用,因为它允许将高效的内存中结构用于解码的数据,并且在编写访问数据结构的程序时允许在IDE中进行类型检查和自动完成。 + +在动态类型编程语言(如JavaScript,Ruby或Python)中,生成代码没有太多意义,因为没有编译时类型检查器来满足。代码生成在这些语言中经常被忽视,因为它们避免了明确的编译步骤。而且,对于动态生成的模式(例如从数据库表生成的Avro模式),代码生成对获取数据是一个不必要的障碍。 + +Avro为静态类型编程语言提供了可选的代码生成功能,但是它也可以在不生成任何代码的情况下使用。如果你有一个对象容器文件(它嵌入了作者的模式),你可以简单地使用Avro库打开它,并以与查看JSON文件相同的方式查看数据。该文件是自描述的,因为它包含所有必要的元数据。 + +这个属性特别适用于动态类型的数据处理语言如Apache Pig [26]。在Pig中,您可以打开一些Avro文件,开始分析它们,并编写派生数据集以Avro格式输出文件,而无需考虑模式。 + +### 模式的优点 + +正如我们所看到的,Protocol Buffers,Thrift和Avro都使用模式来描述二进制编码格式。他们的模式语言比XML模式或者JSON模式简单得多,它支持更详细的验证规则(例如,“该字段的字符串值必须与该正则表达式匹配”或“该字段的整数值必须在0和100之间“)。由于Protocol Buffers,Thrift和Avro实现起来更简单,使用起来也更简单,所以它们已经发展到支持相当广泛的编程语言。 + +这些编码所基于的想法绝不是新的。例如,它们与ASN.1有很多相似之处,它是1984年首次被标准化的模式定义语言[27]。它被用来定义各种网络协议,其二进制编码(DER)仍然被用于编码SSL证书(X.509),例如[28]。 ASN.1支持使用标签号码的模式演进,类似于Protocol Buf-fers和Thrift [29]。然而,这也是非常复杂和严重的文件记录,所以ASN.1可能不是新应用程序的好选择。 + +许多数据系统也为其数据实现某种专有的二进制编码。例如,大多数关系数据库都有一个网络协议,您可以通过该协议向数据库发送查询并获取响应。这些协议通常特定于特定的数据库,并且数据库供应商提供将来自数据库的网络协议的响应解码为内存数据结构的驱动程序(例如使用ODBC或JDBC API)。 + +所以,我们可以看到,尽管JSON,XML和CSV等文本数据格式非常普遍,但基于模式的二进制编码也是一个可行的选择。他们有一些很好的属性: + +* 它们可以比各种“二进制JSON”变体更紧凑,因为它们可以省略编码数据中的字段名称。 +* 模式是一种有价值的文档形式,因为模式是解码所必需的,所以可以确定它是最新的(而手动维护的文档可能很容易偏离现实)。 +* 保留模式数据库允许您在部署任何内容之前检查模式更改的向前和向后兼容性。 +* 对于静态类型编程语言的用户来说,从模式生成代码的能力是有用的,因为它可以在编译时进行类型检查。 + +总而言之,模式进化允许与JSON数据库提供的无模式/模式读取相同的灵活性(请参阅第39页的“文档模型中的模式灵活性”),同时还可以更好地保证数据和更好的工具。 + + + +## 数据流的类型 + +在本章的开始部分,我们曾经说过,无论何时您想要将某些数据发送到不共享内存的另一个进程,例如,只要您想通过网络发送数据或将其写入文件,就需要将它编码为一个字节序列。然后我们讨论了做这个的各种不同的编码。 +我们讨论了向前和向后的兼容性,这对于可演化性来说非常重要(通过允许您独立升级系统的不同部分,而不必一次改变所有内容,可以轻松地进行更改)。兼容性是编码数据的一个进程和解码它的另一个进程之间的一种关系。 + +这是一个相当抽象的概念 - 数据可以通过多种方式从一个流程流向另一个流程。谁编码数据,谁解码?在本章的其余部分中,我们将探讨数据如何在流程之间流动的一些最常见的方式: + +* 通过数据库(请参阅第129页的“通过数据库的数据流” +* 通过服务调用(请参阅第131页的“通过服务传输数据流:REST和RPC” +* 通过异步消息传递(请参阅第136页的“消息传递数据流” + + + +### 数据库中的数据流 + +在数据库中,写入数据库的过程对数据进行编码,从数据库读取的过程对数据进行解码。可能只有一个进程访问数据库,在这种情况下,读者只是相同进程的后续版本 - 在这种情况下,您可以考虑将数据库中的内容存储为向未来的自我发送消息。 + +向后兼容性显然是必要的。否则你未来的自己将无法解码你以前写的东西。 + +一般来说,几个不同的进程同时访问数据库是很常见的。这些进程可能是几个不同的应用程序或服务,或者它们可能只是几个相同服务的实例(为了可扩展性或容错性而并行运行)。无论哪种方式,在应用程序发生变化的环境中,访问数据库的某些进程可能会运行较新的代码,有些进程可能会运行较旧的代码,例如,因为新版本当前正在部署在滚动升级,所以有些实例已经更新,而其他实例尚未更新。 + +这意味着数据库中的一个值可能会被更新版本的代码写入,然后被仍旧运行的旧版本的代码读取。因此,数据库也经常需要向前兼容。 + +但是,还有一个额外的障碍。假设您将一个字段添加到记录模式,并且较新的代码将该新字段的值写入数据库。随后,旧版本的代码(尚不知道新字段)将读取记录,更新记录并将其写回。在这种情况下,理想的行为通常是旧代码保持新的领域完整,即使它不能被解释。 + +前面讨论的编码格式支持未知域的保存,但是有时候需要在应用程序层面保持谨慎,如图4-7所示。例如,如果将数据库值解码为应用程序中的模型对象,稍后重新编码这些模型对象,那么未知字段可能会在该翻译过程中丢失。 + +解决这个问题不是一个难题,你只需要意识到它。 + + + +![](img/fig4-7.png) + +**图4-7 当较旧版本的应用程序更新以前由较新版本的应用程序编写的数据时,如果不小心,数据可能会丢失。** + +#### 在不同的时间写入不同的值 + +数据库通常允许任何时候更新任何值。这意味着在一个单一的数据库中,可能有一些价值是五毫秒前写的,而一些价值是五年前写的。 + +在部署应用程序的新版本(至少是服务器端应用程序)时,您可能会在几分钟内完全用新版本替换旧版本。数据库内容也是如此:五年前的数据仍然存在于原始编码中,除非您已经明确地重写了它。这种观察有时被总结为数据超出代码。 + +将数据重写(迁移)到一个新的模式当然是可能的,但是在一个大数据集上执行是一个昂贵的事情,所以大多数数据库如果可能的话就避免它。大多数关系数据库都允许简单的模式更改,例如添加一个默认值为空的新列,而不重写现有数据[^v]读取旧行时,数据库将填充编码数据中缺少的任何列的空值在磁盘上。 LinkedIn的文档数据库Espresso使用Avro存储,允许它使用Avro的模式演变规则[23]。 + +因此,架构演变允许整个数据库看起来好像是用单个模式编码的,即使底层存储可能包含用模式的各种历史版本编码的记录。 + +[^v]: 除了MySQL,即使并非真的必要,它也经常会重写整个表,正如第39页的“文档模型中的架构灵活性”中所提到的。 + + + +#### 归档存储 + +也许您不时为数据库创建一个快照,例如备份或加载到数据仓库(请参阅第91页的“数据仓库”)。在这种情况下,即使源数据库中的原始编码包含来自不同时代的模式版本的混合,数据转储通常也将使用最新模式进行编码。既然你正在复制数据,那么你可能会一直对数据的副本进行编码。 + +由于数据转储是一次写入的,而且以后是不可变的,所以Avro对象容器文件等格式非常适合。这也是一个很好的机会,可以将数据编码为面向分析的列式格式,例如Parquet(请参阅第97页的“列压缩”)。 + +在第10章中,我们将详细讨论在档案存储中使用数据。 + + + +### 服务中的数据流:REST 与 RPC + +当您需要通过网络进行通信的进程时,安排该通信的方式有几种。最常见的安排是有两个角色:客户端和服务器。服务器通过网络公开API,并且客户端可以连接到服务器以向该API发出请求。服务器公开的API被称为服务。 + +Web以这种方式工作:客户(Web浏览器)向Web服务器发出请求,使GET请求下载HTML,CSS,JavaScript,图像等,并向POST请求提交数据到服务器。 API包含一组标准的协议和数据格式(HTTP,URL,SSL / TLS,HTML等)。由于网络浏览器,网络服务器和网站作者大多同意这些标准,您可以使用任何网络浏览器访问任何网站(至少在理论上!)。 + +Web浏览器不是唯一的客户端类型。例如,在移动设备或桌面计算机上运行的本地应用程序也可以向服务器发出网络请求,并且在Web浏览器内运行的客户端JavaScript应用程序可以使用XMLHttpRequest成为HTTP客户端(该技术被称为Ajax [30])。在这种情况下,服务器的响应通常不是用于显示给人的HTML,而是用于便于客户端应用程序代码(如JSON)进一步处理的编码数据。尽管HTTP可能被用作传输协议,但顶层实现的API是特定于应用程序的,客户端和服务器需要就该API的细节达成一致。 + +此外,服务器本身可以是另一个服务的客户端(例如,典型的Web应用服务器充当数据库的客户端)。这种方法通常用于将大型应用程序按照功能区域分解为较小的服务,这样当一个服务需要来自另一个服务的某些功能或数据时,就会向另一个服务发出请求。这种构建应用程序的方式传统上被称为面向服务的体系结构(service-oriented architecture,SOA),最近被改进和更名为微服务体系结构[31,32]。 + +在某些方面,服务类似于数据库:它们通常允许客户端提交和查询数据。但是,虽然数据库允许使用我们在第2章 中讨论的查询语言进行任意查询,但是服务公开了一个特定于应用程序的API,它只允许由服务的业务逻辑(应用程序代码)预定的输入和输出[33 ]。这种限制提供了一定程度的封装:服务可以对客户可以做什么和不可以做什么施加细粒度的限制。 + +面向服务/微服务架构的一个关键设计目标是通过使服务独立部署和演化来使应用程序更易于更改和维护。例如,每个服务应该由一个团队拥有,并且该团队应该能够经常发布新版本的服务,而不必与其他团队协调。换句话说,我们应该期望服务器和客户端的旧版本和新版本同时运行,因此服务器和客户端使用的数据编码必须在不同版本的服务API之间兼容 - 正是我们所做的本章一直在谈论。 + +#### Web服务 + +当HTTP被用作与服务交谈的底层协议时,它被称为Web服务。这可能是一个小错误,因为Web服务不仅在Web上使用,而且在几个不同的环境中使用。例如: + +1.运行在用户设备上的客户端应用程序(例如,移动设备上的本地应用程序,或使用Ajax的JavaScript web应用程序)通过HTTP向服务发出请求。这些请求通常通过公共互联网进行。 + +2.一种服务向同一组织拥有的另一项服务提出请求,这些服务通常位于同一数据中心内,作为面向服务/微型架构的一部分。 (支持这种用例的软件有时被称为中间件。) + +3.一种服务通过互联网向不同组织所拥有的服务提出请求。这用于不同组织后端系统之间的数据交换。此类别包括由在线服务(如信用卡处理系统)提供的公共API,或用于共享访问用户数据的OAuth。 + +有两种流行的Web服务方法:REST和SOAP。他们在哲学方面几乎是截然相反的,往往是各自支持者之间的激烈辩论(即使在每个阵营内也有很多争论。 例如,HATEOAS(超媒体作为应用程序状态的引擎)经常引发讨论[35]。) + +REST不是一个协议,而是一个基于HTTP原则的设计哲学[34,35]。它强调简单的数据格式,使用URL来标识资源,并使用HTTP功能进行缓存控制,身份验证和内容类型协商。与SOAP相比,REST已经越来越受欢迎,至少在跨组织服务集成的背景下[36],并经常与微服务相关[31]。根据REST原则设计的API称为RESTful。 + +相比之下,SOAP是用于制作网络API请求的基于XML的协议( 尽管首字母缩写词相似,SOAP并不是SOA的要求。 SOAP是一种特殊的技术,而SOA是构建系统的一般方法。)。虽然它最常用于HTTP,但其目的是独立于HTTP,并避免使用大多数HTTP功能。相反,它带有庞大而复杂的多种相关标准(Web服务框架,称为WS- *),它们增加了各种功能[37]。 + +SOAP Web服务的API使用称为Web服务描述语言(WSDL)的基于XML的语言来描述。 WSDL支持代码生成,客户端可以使用本地类和方法调用(编码为XML消息并由框架再次解码)访问远程服务。这在静态类型编程语言中非常有用,但在动态类型编程语言中很少(请参阅“代码生成和动态类型化语言”第125页)。 + +由于WSDL的设计不是人类可读的,而且由于SOAP消息通常是手动构建的过于复杂,所以SOAP的用户在很大程度上依赖于工具支持,代码生成和IDE [38]。对于SOAP供应商不支持的编程语言的用户来说,与SOAP服务的集成是困难的。 + +尽管SOAP及其各种扩展表面上是标准化的,但是不同厂商的实现之间的互操作性往往会造成问题[39]。由于所有这些原因,尽管许多大型企业仍然使用SOAP,但在大多数小公司中已经不再受到青睐。 + +REST风格的API倾向于更简单的方法,通常涉及较少的代码生成和自动化工具。定义格式(如OpenAPI,也称为Swagger [40])可用于描述RESTful API并生成文档。 + +#### 远程过程调用(RPC)的问题 + +Web服务仅仅是通过网络进行API请求的一系列技术的最新版本,其中许多技术受到了大量的炒作,但是存在严重的问题。 Enterprise JavaBeans(EJB)和Java的远程方法调用(RMI)仅限于Java。分布式组件对象模型(DCOM)仅限于Microsoft平台。公共对象请求代理体系结构(CORBA)过于复杂,不提供前向或后向兼容性[41]。 + +所有这些都是基于远程过程调用(RPC)的思想,该过程调用自20世纪70年代以来一直存在[42]。 RPC模型试图向远程网络服务发出请求,看起来与在同一进程中调用编程语言中的函数或方法相同(这种抽象称为位置透明)。尽管RPC起初看起来很方便,但这种方法根本上是有缺陷的[43,44]。网络请求与本地函数调用非常不同: + +* 本地函数调用是可预测的,并且成功或失败,这仅取决于受您控制的参数。网络请求是不可预知的:由于网络问题,请求或响应可能会丢失,或者远程计算机可能很慢或不可用,这些问题完全不在您的控制范围之内。网络问题是常见的,所以你必须预测他们,例如通过重试失败的请求。 +* 本地函数调用要么返回结果,要么抛出异常,或者永远不返回(因为进入无限循环或进程崩溃)。网络请求有另一个可能的结果:由于超时,它可能会返回没有结果。在这种情况下,你根本不知道发生了什么:如果你没有得到来自远程服务的响应,你无法知道请求是否通过。 (我们将在第8章更详细地讨论这个问题。) +* 如果您重试失败的网络请求,可能会发生请求实际上正在通过,只有响应丢失。在这种情况下,重试将导致该操作被执行多次,除非您在协议中引入除重(*幂等 idempotence*)机制。本地函数调用没有这个问题。 (我们在第十一章更详细地讨论幂等性) +* 每次调用本地功能时,通常需要大致相同的时间来执行。网络请求比函数调用要慢得多,而且其延迟也是非常可变的:在不到一毫秒的时间内它可能会完成,但是当网络拥塞或者远程服务超载时,可能需要几秒钟的时间完全一样的东西。 +* 调用本地函数时,可以高效地将引用(指针)传递给本地内存中的对象。当你发出一个网络请求时,所有这些参数都需要被编码成可以通过网络发送的一系列字节。没关系,如果参数是像数字或字符串这样的基本类型,但是对于较大的对象很快就会变成问题。 + +客户端和服务可以用不同的编程语言实现,所以RPC框架必须将数据类型从一种语言翻译成另一种语言。这可能会捅出大篓子,因为不是所有的语言都具有相同的类型 - 例如回想一下JavaScript的数字大于$2^{53}$的问题(请参阅第114页上的“JSON,XML和二进制变体”)。用单一语言编写的单个进程中不存在此问题。 + +所有这些因素意味着尝试使远程服务看起来像编程语言中的本地对象一样毫无意义,因为这是一个根本不同的事情。 REST的部分吸引力在于,它并不试图隐藏它是一个网络协议的事实(尽管这似乎并没有阻止人们在REST之上构建RPC库)。 + +#### RPC的当前方向 + +尽管有这样那样的问题,RPC不会消失。在本章提到的所有编码的基础上构建了各种RPC框架:例如,Thrift和Avro带有RPC支持,gRPC是使用Protocol Buffers的RPC实现,Finagle也使用Thrift,Rest.li使用JSON over HTTP。 + +这种新一代的RPC框架更加明确的是,远程请求与本地函数调用不同。例如,Finagle和Rest.li使用futures(promises)来封装可能失败的异步操作。`Futures`还可以简化需要并行发出多项服务的情况,并将其结果合并[45]。 gRPC支持流,其中一个调用不仅包括一个请求和一个响应,还包括一系列的请求和响应[46]。 + +其中一些框架还提供服务发现,即允许客户端找出在哪个IP地址和端口号上可以找到特定的服务。我们将在第214页的“请求路由”中回到这个主题。 + +使用二进制编码格式的自定义RPC协议可以实现比通用的JSON over REST更好的性能。但是,RESTful API还有其他一些显着的优点:对于实验和调试(只需使用Web浏览器或命令行工具curl,无需任何代码生成或软件安装即可向其请求),它是受支持的所有的主流编程语言和平台,还有大量可用的工具(服务器,缓存,负载平衡器,代理,防火墙,监控,调试工具,测试工具等)的生态系统。由于这些原因,REST似乎是公共API的主要风格。 RPC框架的主要重点在于同一组织拥有的服务之间的请求,通常在同一数据中心内。 + +#### 数据编码与RPC的演化 + +对于可演化性,重要的是可以独立更改和部署RPC客户端和服务器。与通过数据库流动的数据相比(如上一节所述),我们可以在通过服务进行数据流的情况下做一个简化的假设:假定所有的服务器都会先更新,其次是所有的客户端。因此,您只需要在请求上具有向后兼容性,并且对响应具有前向兼容性。 + +RPC方案的前后向兼容性属性从它使用的编码方式中继承 + +* Thrift,gRPC(协议缓冲区)和Avro RPC可以根据相应编码格式的兼容性规则进行演变。 +* 在SOAP中,请求和响应是使用XML模式指定的。这些可以演变,但有一些微妙的陷阱[47]。 +* RESTful API通常使用JSON(没有正式指定的模式)用于响应,以及用于请求的JSON或URI编码/表单编码的请求参数。添加可选的请求参数并向响应对象添加新的字段通常被认为是保持兼容性的改变。 + +由于RPC经常被用于跨越组织边界的通信,所以服务的兼容性变得更加困难,因此服务的提供者经常无法控制其客户,也不能强迫他们升级。因此,需要长期保持兼容性,也许是无限期的。如果需要进行兼容性更改,则服务提供商通常会并排维护多个版本的服务API。 + +关于API版本化应该如何工作(即,客户端如何指示它想要使用哪个版本的API)没有一致意见[48])。对于RESTful API,常用的方法是在URL或HTTP Accept头中使用版本号。对于使用API密钥来标识特定客户端的服务,另一种选择是将客户端请求的API版本存储在服务器上,并允许通过单独的管理界面更新该版本选项[49]。 + +### 消息传递中的数据流 + +我们一直在研究从一个过程到另一个过程的编码数据流的不同方式。到目前为止,我们已经讨论了REST和RPC(其中一个进程通过网络向另一个进程发送请求并期望尽可能快的响应)以及数据库(一个进程写入编码数据,另一个进程在将来再次读取)。 + +在最后一节中,我们将简要介绍一下RPC和数据库之间的异步消息传递系统。它们与RPC类似,因为客户端的请求(通常称为消息)以低延迟传送到另一个进程。它们与数据库类似,不是通过直接的网络连接发送消息,而是通过称为消息代理(也称为消息队列或面向消息的中间件)的中介来临时存储消息。 + +与直接RPC相比,使用消息代理有几个优点: + +* 如果收件人不可用或过载,可以充当缓冲区,从而提高系统的可靠性。 +* 它可以自动将消息重新发送到已经崩溃的进程,从而防止消息丢失。 +* 避免发件人需要知道收件人的IP地址和端口号(这在虚拟机经常出入的云部署中特别有用)。 +* 它允许将一条消息发送给多个收件人。 +* 将发件人与收件人逻辑分离(发件人只是发布邮件,不关心使用者)。 + +然而,与RPC相比,差异在于消息传递通信通常是单向的:发送者通常不期望收到其消息的回复。一个进程可能发送一个响应,但这通常是在一个单独的通道上完成的。这种通信模式是异步的:发送者不会等待消息被传递,而只是发送它,然后忘记它。 + +#### 消息掮客 + +过去,信息掮客主要是TIBCO,IBM WebSphere和webMethods等公司的商业软件的秀场。最近像RabbitMQ,ActiveMQ,HornetQ,NATS和Apache Kafka这样的开源实现已经流行起来。我们将在第11章中对它们进行更详细的比较。 + +详细的交付语义因实现和配置而异,但通常情况下,消息代理的使用方式如下:一个进程将消息发送到指定的队列或主题,代理确保将消息传递给一个或多个消费者或订阅者到那个队列或主题。在同一主题上可以有许多生产者和许多消费者。 + +一个主题只提供单向数据流。但是,消费者本身可能会将消息发布到另一个主题上(因此,可以将它们链接在一起,就像我们将在第11章中看到的那样),或者发送给原始消息的发送者使用的回复队列(允许请求/响应数据流,类似于RPC)。 + +消息代理通常不会执行任何特定的数据模型 - 消息只是包含一些元数据的字节序列,因此您可以使用任何编码格式。如果编码是向后兼容的,则您可以灵活地更改发行商和消费者的独立编码,并以任意顺序进行部署。 + +如果消费者重新发布消息到另一个主题,则可能需要小心保留未知字段,以防止前面在数据库环境中描述的问题(图4-7)。 + +#### 分布式的Actor框架 + +actor模型是单个进程中并发的编程模型。逻辑被封装在角色中,而不是直接处理线程(以及竞争条件,锁定和死锁的相关问题)。每个角色通常代表一个客户或实体,它可能有一些本地状态(不与其他任何角色共享),它通过发送和接收异步消息与其他角色通信。消息传送不保证:在某些错误情况下,消息将丢失。由于每个角色一次只能处理一条消息,因此不需要担心线程,每个角色可以由框架独立调度。 + +在分布式的行为者框架中,这个编程模型被用来跨越多个节点来扩展应用程序。不管发送方和接收方是在同一个节点上还是在不同的节点上,都使用相同的消息传递机制。如果它们在不同的节点上,则该消息被透明地编码成字节序列,通过网络发送,并在另一侧解码。 + +位置透明在actor模型中比在RPC中效果更好,因为actor模型已经假定消息可能会丢失,即使在单个进程中也是如此。尽管网络上的延迟可能比同一个进程中的延迟更高,但是在使用参与者模型时,本地和远程通信之间的基本不匹配是较少的。 + +分布式的Actor框架实质上是将消息代理和角色编程模型集成到一个框架中。但是,如果要执行基于角色的应用程序的滚动升级,则仍然需要担心向前和向后兼容性问题,因为消息可能会从运行新版本的节点发送到运行旧版本的节点,反之亦然。 + +三个流行的分布式actor框架处理消息编码如下: + +* 默认情况下,Akka使用Java的内置序列化,不提供前向或后向兼容性。 但是,你可以用类似缓冲区的东西替代它,从而获得滚动升级的能力[50]。 +* `Orleans`默认使用不支持滚动升级部署的自定义数据编码格式; 要部署新版本的应用程序,您需要设置一个新的群集,将流量从旧群集迁移到新群集,然后关闭旧群集[51,52]。 像Akka一样,可以使用自定义序列化插件。 +* 在Erlang OTP中,对记录模式进行更改是非常困难的(尽管系统具有许多为高可用性设计的功能)。 滚动升级是可能的,但需要仔细计划[53]。 一个新的实验性的`maps`数据类型(2014年在Erlang R17中引入的类似于JSON的结构)可能使得这个数据类型在未来更容易[54]。 + + + + +## 本章小结 + +在本章中,我们研究了将数据结构转换为网络中的字节或磁盘上的字节的几种方法。我们看到了这些编码的细节不仅影响其效率,更重要的是应用程序的体系结构和部署它们的选项。 + +特别是,许多服务需要支持滚动升级,其中新版本的服务逐步部署到少数节点,而不是同时部署到所有节点。滚动升级允许在不停机的情况下发布新版本的服务(从而鼓励在罕见的大型版本上频繁发布小型版本),并使部署风险降低(允许在影响大量用户之前检测并回滚有故障的版本)。这些属性对于可演化性,以及对应用程序进行更改的容易性都是非常有利的。 + +在滚动升级期间,或出于各种其他原因,我们必须假设不同的节点正在运行我们的应用程序代码的不同版本。因此,在系统周围流动的所有数据都是以提供向后兼容性(新代码可以读取旧数据)和向前兼容性(旧代码可以读取新数据)的方式进行编码是重要的。 + +我们讨论了几种数据编码格式及其兼容性属性: + +* 编程语言特定的编码仅限于单一编程语言,并且往往无法提供前向和后向兼容性。 +* JSON,XML和CSV等文本格式非常普遍,其兼容性取决于您如何使用它们。他们有可选的模式语言,这有时是有用的,有时是一个障碍。这些格式对于数据类型有些模糊,所以你必须小心数字和二进制字符串。 +* 像Thrift,Protocol Buffers和Avro这样的二进制模式驱动格式允许使用清晰定义的前向和后向兼容性语义进行紧凑,高效的编码。这些模式可以用于静态类型语言的文档和代码生成。但是,他们有一个缺点,就是在数据可读之前需要对数据进行解码。 + +我们还讨论了数据流的几种模式,说明了数据编码是重要的不同场景: + +* 数据库,写入数据库的进程对数据进行编码,并从数据库读取进程对其进行解码 +* RPC和REST API,客户端对请求进行编码,服务器对请求进行解码并对响应进行编码,客户端最终对响应进行解码 +* 异步消息传递(使用消息代理或参与者),其中节点之间通过发送消息进行通信,消息由发送者编码并由接收者解码 + +我们可以小心地得出这样的结论:前向兼容性和滚动升级在某种程度上是可以实现的。愿您的应用程序的演变迅速、敏捷部署。 + + + +## 参考文献 + + +1. “[Java Object Serialization Specification](http://docs.oracle.com/javase/7/docs/platform/serialization/spec/serialTOC.html),” *docs.oracle.com*, 2010. + +1. “[Ruby 2.2.0 API Documentation](http://ruby-doc.org/core-2.2.0/),” *ruby-doc.org*, Dec 2014. + +1. “[The Python 3.4.3 Standard Library Reference Manual](https://docs.python.org/3/library/pickle.html),” *docs.python.org*, February 2015. + +1. “[EsotericSoftware/kryo](https://github.com/EsotericSoftware/kryo),” + *github.com*, October 2014. + +1. “[CWE-502: Deserialization of Untrusted Data](http://cwe.mitre.org/data/definitions/502.html),” Common Weakness Enumeration, *cwe.mitre.org*, + July 30, 2014. + +1. Steve Breen: + “[What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common? This Vulnerability](http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/),” *foxglovesecurity.com*, November 6, 2015. + +1. Patrick McKenzie: + “[What the Rails Security Issue Means for Your Startup](http://www.kalzumeus.com/2013/01/31/what-the-rails-security-issue-means-for-your-startup/),” *kalzumeus.com*, January 31, 2013. + +1. Eishay Smith: + “[jvm-serializers wiki](https://github.com/eishay/jvm-serializers/wiki),” + *github.com*, November 2014. + +1. “[XML Is a Poor Copy of S-Expressions](http://c2.com/cgi/wiki?XmlIsaPoorCopyOfEssExpressions),” *c2.com* wiki. + +1. Matt Harris: + “[Snowflake: An Update and Some Very Important Information](https://groups.google.com/forum/#!topic/twitter-development-talk/ahbvo3VTIYI),” email to *Twitter Development + Talk* mailing list, October 19, 2010. + +1. Shudi (Sandy) Gao, C. M. Sperberg-McQueen, and + Henry S. Thompson: “[XML Schema 1.1](http://www.w3.org/XML/Schema),” W3C Recommendation, + May 2001. + +1. Francis Galiegue, Kris Zyp, and Gary Court: + “[JSON Schema](http://json-schema.org/),” IETF Internet-Draft, February 2013. + +1. Yakov Shafranovich: + “[RFC 4180: Common Format and MIME Type for Comma-Separated Values (CSV) Files](https://tools.ietf.org/html/rfc4180),” October 2005. + +1. “[MessagePack Specification](http://msgpack.org/),” *msgpack.org*. + +1. Mark Slee, Aditya Agarwal, and Marc Kwiatkowski: + “[Thrift: Scalable Cross-Language Services Implementation](http://thrift.apache.org/static/files/thrift-20070401.pdf),” Facebook technical report, April 2007. + +1. “[Protocol Buffers Developer Guide](https://developers.google.com/protocol-buffers/docs/overview),” Google, Inc., *developers.google.com*. + +1. Igor Anishchenko: + “[Thrift vs Protocol Buffers vs Avro - Biased Comparison](http://www.slideshare.net/IgorAnishchenko/pb-vs-thrift-vs-avro),” *slideshare.net*, September 17, 2012. + +1. “[A Matrix of the Features Each Individual Language Library Supports](http://wiki.apache.org/thrift/LibraryFeatures),” + *wiki.apache.org*. + +1. Martin Kleppmann: + “[Schema Evolution in Avro, Protocol Buffers and Thrift](http://martin.kleppmann.com/2012/12/05/schema-evolution-in-avro-protocol-buffers-thrift.html),” *martin.kleppmann.com*, December 5, 2012. + +1. “[Apache Avro 1.7.7 Documentation](http://avro.apache.org/docs/1.7.7/),” *avro.apache.org*, July 2014. + +1. Doug Cutting, Chad Walters, Jim Kellerman, et al.: + “[[PROPOSAL] New Subproject: Avro](http://mail-archives.apache.org/mod_mbox/hadoop-general/200904.mbox/%3C49D53694.1050906@apache.org%3E),” email thread on *hadoop-general* mailing list, + *mail-archives.apache.org*, April 2009. + +1. Tony Hoare: + “[Null References: The Billion Dollar Mistake](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare),” at *QCon London*, + March 2009. + +1. Aditya Auradkar and Tom Quiggle: + “[Introducing Espresso—LinkedIn's Hot New Distributed Document Store](https://engineering.linkedin.com/espresso/introducing-espresso-linkedins-hot-new-distributed-document-store),” *engineering.linkedin.com*, January 21, 2015. + +1. Jay Kreps: + “[Putting Apache Kafka to Use: A Practical Guide to Building a Stream Data Platform (Part 2)](http://blog.confluent.io/2015/02/25/stream-data-platform-2/),” *blog.confluent.io*, + February 25, 2015. + +1. Gwen Shapira: + “[The Problem of Managing Schemas](http://radar.oreilly.com/2014/11/the-problem-of-managing-schemas.html),” *radar.oreilly.com*, November 4, 2014. + +1. “[Apache Pig 0.14.0 Documentation](http://pig.apache.org/docs/r0.14.0/),” *pig.apache.org*, November 2014. + +1. John Larmouth: + *ASN.1 + Complete*. Morgan Kaufmann, 1999. ISBN: 978-0-122-33435-1 + +1. Russell Housley, Warwick Ford, Tim Polk, and David Solo: + “[RFC 2459: Internet X.509 Public Key Infrastructure: Certificate and CRL Profile](https://www.ietf.org/rfc/rfc2459.txt),” IETF Network Working Group, Standards Track, + January 1999. + +1. Lev Walkin: + “[Question: Extensibility and Dropping Fields](http://lionet.info/asn1c/blog/2010/09/21/question-extensibility-removing-fields/),” *lionet.info*, September 21, 2010. + +1. Jesse James Garrett: + “[Ajax: A New Approach to Web Applications](http://www.adaptivepath.com/ideas/ajax-new-approach-web-applications/),” *adaptivepath.com*, February 18, 2005. + +1. Sam Newman: *Building Microservices*. + O'Reilly Media, 2015. ISBN: 978-1-491-95035-7 + +1. Chris Richardson: + “[Microservices: Decomposing Applications for Deployability and Scalability](http://www.infoq.com/articles/microservices-intro),” *infoq.com*, May 25, 2014. + +1. Pat Helland: + “[Data on the Outside Versus Data on the Inside](http://cidrdb.org/cidr2005/papers/P12.pdf),” at *2nd Biennial Conference on Innovative Data Systems Research* (CIDR), + January 2005. + +1. Roy Thomas Fielding: + “[Architectural Styles and the Design of Network-Based Software Architectures](https://www.ics.uci.edu/~fielding/pubs/dissertation/fielding_dissertation.pdf),” PhD Thesis, University of + California, Irvine, 2000. + +1. Roy Thomas Fielding: + “[REST APIs Must Be Hypertext-Driven](http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven),” *roy.gbiv.com*, October 20 2008. + +1. “[REST in Peace, SOAP](http://royal.pingdom.com/2010/10/15/rest-in-peace-soap/),” *royal.pingdom.com*, October 15, 2010. + +1. “[Web Services Standards as of Q1 2007](https://www.innoq.com/resources/ws-standards-poster/),” *innoq.com*, February 2007. + +1. Pete Lacey: + “[The S Stands for Simple](http://harmful.cat-v.org/software/xml/soap/simple),” *harmful.cat-v.org*, November 15, 2006. + +1. Stefan Tilkov: + “[Interview: Pete Lacey Criticizes Web Services](http://www.infoq.com/articles/pete-lacey-ws-criticism),” *infoq.com*, December 12, 2006. + +1. “[OpenAPI Specification (fka Swagger RESTful API Documentation Specification) Version 2.0](http://swagger.io/specification/),” + *swagger.io*, September 8, 2014. + +1. Michi Henning: + “[The Rise and Fall of CORBA](http://queue.acm.org/detail.cfm?id=1142044),” + *ACM Queue*, volume 4, number 5, pages 28–34, June 2006. + [doi:10.1145/1142031.1142044](http://dx.doi.org/10.1145/1142031.1142044) + +1. Andrew D. Birrell and Bruce Jay Nelson: + “[Implementing Remote Procedure Calls](http://www.cs.princeton.edu/courses/archive/fall03/cs518/papers/rpc.pdf),” *ACM Transactions on Computer Systems* (TOCS), + volume 2, number 1, pages 39–59, February 1984. + [doi:10.1145/2080.357392](http://dx.doi.org/10.1145/2080.357392) + +1. Jim Waldo, Geoff Wyant, Ann Wollrath, and Sam Kendall: + “[A Note on Distributed Computing](http://m.mirror.facebook.net/kde/devel/smli_tr-94-29.pdf),” + Sun Microsystems Laboratories, Inc., Technical Report TR-94-29, November 1994. + +1. Steve Vinoski: + “[Convenience over Correctness](http://steve.vinoski.net/pdf/IEEE-Convenience_Over_Correctness.pdf),” *IEEE Internet Computing*, volume 12, number 4, pages 89–92, July 2008. + [doi:10.1109/MIC.2008.75](http://dx.doi.org/10.1109/MIC.2008.75) + +1. Marius Eriksen: + “[Your Server as a Function](http://monkey.org/~marius/funsrv.pdf),” at + *7th Workshop on Programming Languages and Operating Systems* (PLOS), November 2013. + [doi:10.1145/2525528.2525538](http://dx.doi.org/10.1145/2525528.2525538) + +1. “[grpc-common Documentation](https://github.com/grpc/grpc-common),” Google, Inc., *github.com*, February 2015. + +1. Aditya Narayan and Irina Singh: + “[Designing and Versioning Compatible Web Services](http://www.ibm.com/developerworks/websphere/library/techarticles/0705_narayan/0705_narayan.html),” *ibm.com*, March 28, 2007. + +1. Troy Hunt: + “[Your API Versioning Is Wrong, Which Is Why I Decided to Do It 3 Different Wrong Ways](http://www.troyhunt.com/2014/02/your-api-versioning-is-wrong-which-is.html),” *troyhunt.com*, + February 10, 2014. + +1. “[API Upgrades](https://stripe.com/docs/upgrades),” Stripe, Inc., April 2015. + +1. Jonas Bonér: + “[Upgrade in an Akka Cluster](http://grokbase.com/t/gg/akka-user/138wd8j9e3/upgrade-in-an-akka-cluster),” email to *akka-user* mailing list, *grokbase.com*, August 28, 2013. + +1. Philip A. Bernstein, Sergey Bykov, Alan Geller, et al.: + “[Orleans: Distributed Virtual Actors for Programmability and Scalability](http://research.microsoft.com/pubs/210931/Orleans-MSR-TR-2014-41.pdf),” Microsoft Research + Technical Report MSR-TR-2014-41, March 2014. + +1. “[Microsoft Project Orleans Documentation](http://dotnet.github.io/orleans/),” Microsoft Research, *dotnet.github.io*, 2015. + +1. David Mercer, Sean Hinde, Yinso Chen, and Richard A O'Keefe: + “[beginner: Updating Data Structures](http://erlang.org/pipermail/erlang-questions/2007-October/030318.html),” email thread on *erlang-questions* mailing list, *erlang.com*, + October 29, 2007. + +1. Fred Hebert: + “[Postscript: Maps](http://learnyousomeerlang.com/maps),” *learnyousomeerlang.com*, + April 9, 2014. + diff --git a/ddia/ch5.md b/ddia/ch5.md new file mode 100644 index 0000000..44e1995 --- /dev/null +++ b/ddia/ch5.md @@ -0,0 +1,979 @@ +# 5. 复制 + +![](img/ch5.png) + +> 天作孽,尤可治,自作孽,不可活。 +> +> ——道格拉斯·亚当斯(1992) + +------ + +[TOC] + + + +复制意味着在通过网络连接的多台机器上保留相同数据的副本。正如在第二部分的介绍中所讨论的那样,您可能想要复制数据的原因有几个: + +* 为了保持数据在地理位置上接近用户(从而减少延迟) +* 即使部分零件出现故障,系统也能继续工作(从而提高可用性) +* 为了扩大可以为读取查询服务的机器数量(从而提高读取吞吐量) + +在本章中,我们将假设您的数据集非常小,以至于每台机器都可以保存整个数据集的副本。在第6章中,我们将放宽这个假设,讨论对单个机器来说太大的数据集的分割(分片)。在后面的章节中,我们将讨论复制数据系统中可能发生的各种故障,以及如何处理这些故障。 + +如果您正在复制的数据不会随时间而改变,那么复制很容易:您只需将数据复制到每个节点一次,即可完成。复制中的所有困难都在于处理复制数据的变化,这就是本章的内容。我们将讨论三种流行的复制节点间变化的算法:单主,多主和无主。几乎所有分布式数据库都使用这三种方法之一。 + +他们都有各种各样的优点和缺点,我们将详细审查。在复制时需要考虑许多折衷:例如,是否使用同步或异步复制,以及如何处理失败的副本。这些通常是数据库中的配置选项,虽然细节因数据库而异,但一般原则在许多不同的实现中是相似的。我们将在本章讨论这种选择的后果。 + +数据库的复制是一个老话题 - 自1970年代研究以来,这些原则并没有太大的改变[1],因为网络的基本约束保持不变。然而,除了研究之外,许多开发人员仍然假定一个数据库只有一个节点。主流使用分布式数据库是最近的事情。由于许多应用程序开发人员都是这方面的新手,对最终一致性等问题存在诸多误解。在第161页的“复制滞后问题”中,我们将更加精确地了解最终的一致性,并讨论诸如读写和单调读取保证等内容。 + +## 主从 + +存储数据库副本的每个节点称为*副本(replica)*。有了多个副本,一个问题不可避免地会出现:我们如何确保所有数据在所有副本上都结束? + +每次写入数据库都需要处理每个副本;否则,副本将不再包含相同的数据。最常见的解决方案称为基于领导者的复制(也称为主动/被动或主从复制),如图5-1所示。它的工作原理如下: + +1. 副本之一被指定为领导者(也称为 **主master** 或**首要的primary**)。当客户想要写入数据库时,他们必须将他们的请求发送给领导,首先将新数据写入其本地存储。 +2. 其他副本被称为追随者(只读副本,从属副本,热备份副本)(不同的人对热hot,温warn,冷cold 备份服务器有不同的定义。 例如,在PostgreSQL中,热备用来引用从客户端接受读取的副本,而温备紧跟领袖,但不处理来自客户端的任何查询。 就本书而言,差异并不重要。)每当领导者将新数据写入本地存储时,它也会将数据更改发送给其所有追随者作为复制的一部分记录或更改流。每个跟随者从领导者处获取日志,并相应地更新其本地数据库副本,方法是按照领导者处理的相同顺序应用所有写入。 +3. 当客户想要从数据库中读取数据时,它可以查询领导或任何追随者。 然而,只有领导才能接受写操作(从客户端的角度来看,从库都是只读的)。 + +![](img/fig5-1.png) +**图5-1 基于领袖(主从)的复制** + +### 同步复制与异步复制 + +复制系统的一个重要细节是复制是同步发生还是异步发生。 (在关系型数据库中,这通常是一个可配置的选项;其他系统通常被硬编码为一个或另一个)。 + +想想图5-1中发生的情况,网站的用户更新他们的个人资料图像。在某个时间点,客户发送更新请求给领导;不久之后,领导就收到了。在某个时候,领导者将数据转换转发给追随者。最后,领导通知客户更新成功。 + +图5-2显示了系统各个组件之间的通信:用户的客户端,领导者和两个关注者。时间从左到右流动。请求或响应消息显示为粗箭头。 + +![](img/fig5-2.png) +**图5-2 基于领导者的复制有一个同步和一个异步的从库。** + +在图5-2的示例中,跟随者1的复制是同步的:领导者等待直到跟随者1确认在向用户报告成功之前以及在使写入对其他客户端可见之前接收到写入。跟随者2的复制是异步的:领导者发送消息,但不等待跟随者的响应。 + +该图表明,在跟随者2处理消息之前存在显着的延迟。通常情况下,复制速度非常快:大多数数据库系统在不到一秒的时间内对关注者进行更改。但是,不能保证需要多长时间。有些情况下,追随者可能落后于领导者几分钟或更长时间;例如,如果跟随者正在从故障中恢复,如果系统正在接近最大容量,或者如果节点之间存在网络问题。 + +同步复制的优点是,跟随者保证有与领导者一致的最新数据副本。如果领导者突然失败,我们可以肯定的是,这些数据仍然可以在追随者身上找到。缺点是如果同步跟随器没有响应(因为它已经崩溃,或者出现网络故障,或者出于任何其他原因),写入不能被处理。领导者必须阻止所有写入,并等待同步副本再次可用。 + +因此,所有追随者都是同步的是不切实际的:任何一个节点的中断都会导致整个系统停滞不前。实际上,如果在数据库上启用同步复制,通常意味着其中一个跟随者是同步的,而其他的则是异步的。如果同步跟随器变得不可用或缓慢,则使一个异步跟随器同步。这保证您至少在两个节点上拥有最新的数据副本:领导者和同步追随者。 这种配置有时也被称为半同步[7]。 + +通常,基于领导者的复制配置为完全异步。 在这种情况下,如果领导失败并且不可恢复,则任何尚未复制给追随者的写入都将丢失。 这意味着写入不能保证持久,即使已经被客户确认。 然而,一个完全异步的配置具有领导者可以继续处理写入的优点,即使其所有追随者都落后了。 + +弱化的耐久性可能听起来像是一个坏的折衷,但异步复制却被广泛使用,特别是如果有很多追随者或者是地理分布的话。 我们将在第161页的“复制滞后问题”中回到这个问题。 + +#### *关于复制的研究* + +如果主库故障,那么异步复制系统会丢失数据可能是一个严重的问题,因此研究人员继续研究不丢失数据但仍能提供良好性能和可用性的复制方法。 例如,**链式复制**[8,9]是同步复制的一种变体,已经在一些系统(如Microsoft Azure存储[10,11])中成功实现。 + +复制的一致性与共识(得到几个节点以达成一个价值)之间有着密切的联系,我们将在第9章更详细地探讨这一领域的理论。在本章中,我们将集中讨论更简单的形式 在实践中最常用于数据库的复制。 + + + +### 设置新从 + +有时候,你需要建立新的追随者 - 也许增加副本的数量,或者替换失败的节点。你如何确保新的追随者有一个领导者的数据的准确副本? + +简单地将数据文件从一个节点复制到另一个节点通常是不够的:客户端不断向数据库写入数据,数据总是处于不断变化的状态,因此标准文件副本会在不同的时间点看到数据库的不同部分。结果可能没有任何意义。 + +您可以通过锁定数据库(使其不可用于写入)来使磁盘上的文件保持一致,但是这会违反我们的高可用性目标。幸运的是,建立一个跟随者通常可以在没有停机的情况下完成。从概念上讲,过程如下所示: + +1. 在某个时间点对领导者的数据库进行一致的快照(如果可能的话),而不必锁定整个数据库。大多数数据库都具有这种功能,因为它也是备份所必需的。在某些情况下,需要第三方工具,如MySQL的innobackupex [12]。 +2. 将快照复制到新的跟随者节点。 +3. 跟随者连接到领导并请求拍摄快照后发生的所有数据更改。这要求快照与领导者的复制日志中的确切位置相关联。该位置有不同的名称:例如,PostgreSQL将其称为*日志序列号(log sequence number, lsn)*,MySQL将其称为*二进制日志坐标(binlog coordinates)*。 +4. 从追随者处理了自快照以来的数据变化积压,我们说它已经赶上了。现在,它可以继续处理领导者发生的数据变化。 + +建立跟随者的实际步骤因数据库而异。在某些系统中,这个过程是完全自动化的,而在另外一些系统中,它可能是一个有点神秘的多步骤工作流程,需要由管理员手动执行。 + + + +### 处理节点失控 + +系统中的任何节点都可能因故障而意外停机,但可能由于计划内的维护(例如,重新引导机器以安装内核安全修补程序)。能够在不停机的情况下重新启动单个节点是操作和维护的一大优势。因此,我们的目标是保持整个系统的运行,尽管个别节点失效,并尽可能保持节点外部的影响。 + +如何通过基于领导者的复制实现高可用性? + +#### 从库故障:追赶恢复 + +在其本地磁盘上,每个追随者记录从领导者收到的数据变更。如果跟随者崩溃并重新启动,或者如果领导者和跟随者之间的网络暂时中断,则跟随者可以很容易地恢复:从日志中知道在发生故障之前处理的最后一个事务。因此,跟随者可以连接到领导者并请求在跟随者断开连接时发生的所有数据改变。当它应用了这些变化后,它已经赶上了领导者,并可以像以前一样继续接收数据流的变化。 + +#### 领导失败:故障转移 + +处理领导者的失败是棘手的:其中一个追随者需要被提升为新的领导者,客户端需要重新配置以将他们的写入发送给新的领导者,其他追随者需要开始消费来自新的领导。这个过程被称为故障转移。 + +故障转移可以手动进行(通知管理员领导已经失败,并采取必要的步骤来创建新的领导者)或自动进行。自动故障转移过程通常由以下步骤组成: + +1. 确定领导失败。有很多事情可能会出错:崩溃,停电,网络问题等等。没有万无一失的方法来检测出现了什么问题,所以大多数系统只是使用一个超时:节点频繁地在相互之间来回弹跳消息,并且如果一个节点在一段时间内(例如30秒)没有响应,它被认为是死的。 (如果领导被故意拆除进行计划维护,这不适用。) +2. 选择一个新的领导者。这可以通过选举过程(其中领导者由大多数剩余复制品选择)来完成,或者可以由之前选定的控制器节点来指定新的领导者。领导者的最佳人选通常是来自旧领导者的最新数据变更的副本(以最小化任何数据丢失)。让所有的节点同意一个新的领导是一个共识问题,在第9章详细讨论。 +3. 重新配置系统以使用新的领导者。客户端现在需要将他们的写请求发送给新领导(我们将在“请求路由”在本页中讨论这个问题)。如果老领导人回来了,可能仍然认为这是领导者,没有意识到其他副本迫使他下台。系统需要确保老领导成为追随者,并认可新领导。 + +故障转移会出现很多大麻烦: + +* 如果使用异步复制,则新主库可能没有收到老主库宕机前最后的写入操作。在选出新主库后,如果老主库重新加入集群,新主库在此期间可能会收到冲突的写入,那这些写入该如何处理?最常见的解决方案是简单丢弃老主库未复制的写入,这很可能打破客户对于数据持久性的期望。 +* 如果数据库需要和其他外部存储相协调,那么丢弃写入内容是极其危险的操作。例如在GitHub [13]的一场事故中,一个过时的MySQL从库被提升为主库。数据库使用自增ID作为主键,因为新主库的计数器落后于老主库的计数器,所以新主库重新分配了一些已经被老主库分配掉的ID作为主键。这些主键也在Redis中使用,主键重用使得MySQL和Redis中数据产生不一致,最后导致一些私有数据泄漏到错误的用户手中。 +* 发生某些故障时(见第8章)可能会出现两个节点都以为自己是领导者的情况。这种情况称为**脑裂(split brain)**,非常危险:如果两个领导者都可以接受写操作,却没有冲突解决机制(参见第168页的“多主复制”),那么数据就可能丢失或损坏。一些系统采取了安全防范措施:当检测到两个领导节点同时存在时会关闭其中一个节点。这种机制称为**击剑(fencing)**,更夸张的术语是:***爆其他节点的头(Shoot The Other Node In The Head , STONITH)***。但设计粗糙的机制可能最后会导致两个节点都被关闭[14]。 +* 领导被宣布死亡之前的正确超时是什么?在领导失败的情况下,超时时间越长意味着恢复的时间越长。但是,如果超时太短,可能会出现不必要的故障转移。例如,临时负载峰值可能导致节点的响应时间超过超时,或者网络故障可能导致延迟的数据包。如果系统已经处于高负载或网络问题的困扰之中,那么不必要的故障切换可能会使情况变得更糟,而不是更好。 + +这些问题没有简单的解决方案。因此,有些操作团队更愿意手动执行故障切换,即使软件支持自动故障切换。 + +这些问题 - 节点故障;不可靠的网络;并且在副本一致性,耐用性,可用性和延迟方面进行权衡 - 实际上是分布式系统中的基本问题。在第8章和第9章中,我们将更深入地讨论它们。 + +### 复制日志的实现 + +基于领导者的复制如何在底层工作?在实践中使用了几种不同的复制方法,所以让我们简单地看一下。 + +#### 基于语句的复制 + +在最简单的情况下,领导者记录它执行的每个写入请求(语句)并将该语句日志发送给其追随者。对于关系数据库来说,这意味着每个INSERT,UPDATE或DELETE语句都被转发给关注者和每个关注者。跟随者解析并执行该SQL语句就像从客户端收到一样。 + +虽然这听起来很合理,但这种复制方式可能有多种方式可以打破: + +* 任何调用非确定性函数的语句(如`NOW()`获取当前日期和时间或`RAND()`获取一个随机数)可能会在每个副本上生成不同的值。 +* 如果语句使用自动增量列,或者它们依赖于数据库中的现有数据(例如,UPDATE ... WHERE <某些条件>),则必须按照每个副本上的完全相同的顺序执行它们,否则它们可能会有不同的效果。当有多个同时执行的事务时,这可能是限制性的。 +* 有副作用的语句(例如,触发器,存储过程,用户定义的函数)可能会在每个副本上产生不同的副作用,除非副作用是绝对确定的。 + +有可能解决这些问题 - 例如,当语句被记录时,领导者可以用固定的返回值替换任何不确定的函数调用,以便追随者获得相同的值。但是,由于存在如此多的边缘情况,所以现在通常优选其他复制方法。 + +基于语句的复制在5.1版本之前在MySQL中使用。今天仍然有一些使用,因为它非常紧凑,但是默认情况下,如果在语句中有任何不确定性,MySQL现在会切换到基于行的复制(稍后讨论)。 VoltDB使用基于语句的复制,并通过要求事务是确定性来保证安全[15]。 + +#### 传输预写式日志(WAL) + +在第3章中,我们讨论了存储引擎如何在磁盘上表示数据,并且我们发现通常每个写操作都附加到日志中: + +* 对于日志结构存储引擎(请参阅第70页的“SSTables和LSM-tree”),此日志是存储的主要位置。日志段在后台压缩并垃圾回收。 +* 对于覆盖单个磁盘块的B树(请参阅第79页的“B树”),每次修改都会先写入预写日志,以便索引可以恢复到一致状态一个崩溃。 + +在任何一种情况下,日志都是包含对数据库所有写入的字节序列。我们可以使用完全相同的日志在另一个节点上构建副本:除了将日志写入磁盘之外,领导者还可以通过网络将其发送给其追随者。 + +当追随者处理这个日志时,它会建立一个和领导者一模一样的数据结构的副本。 + +PostgreSQL和Oracle等使用这种复制方法[16]。主要缺点是日志描述的数据很低:WAL包含哪些磁盘块中的哪些字节发生了更改。这使复制紧密地耦合到存储引擎。如果数据库将其存储格式从一个版本更改为另一个版本,则通常不可能在领导者和追随者上运行不同版本的数据库软件。 + +这看起来可能只是一个小实施细节,但可能会产生巨大的运营影响。如果复制协议允许跟随者使用比领导者更新的软件版本,则可以先执行升级跟随者,然后执行故障转移,使升级后的节点之一成为新的领导者,从而执行数据库软件的零停机升级。如果复制协议不允许此版本不匹配(WAL运输经常出现这种情况),则此类升级需要停机。 + +#### 逻辑(基于行)日志复制 + +另一种方法是对复制和存储引擎使用不同的日志格式,这样可以使复制日志从存储引擎内部分离出来。这种复制日志被称为逻辑日志,以将其与存储引擎(物理)数据表示区分开来。 + +关系数据库的逻辑日志通常是以行的粒度描述对数据库表的写入的记录序列: + +* 对于插入的行,日志包含所有列的新值。 +* 对于已删除的行,日志包含足够的信息来唯一标识已删除的行。通常这将是主键,但是如果表上没有主键,则需要记录所有列的旧值。 +* 对于更新的行,日志包含足够的信息来唯一标识更新的行,以及所有列的新值(或至少所有已更改的列的新值)。 + +修改多行的事务会生成多个这样的日志记录,后面跟着一条记录,指出事务已经提交。 MySQL的二进制日志(当配置为使用基于行的复制时)使用这种方法[17]。 + +由于逻辑日志与存储引擎内部分离,因此可以更容易地保持向后兼容,从而使领导者和跟随者能够运行不同版本的数据库软件甚至不同的存储引擎。 + +对于外部应用程序来说,逻辑日志格式也更容易解析。如果要将数据库的内容发送到外部系统(如数据),这一点很有用,例如复制到数据仓库进行离线分析,或建立自定义索引和缓存[18]。 这种技术被称为更改数据捕获,我们将在第11章中回到它。 + +#### 基于触发器的复制 + +到目前为止描述的复制方法是由数据库系统实现的,不涉及任何应用程序代码。在很多情况下,这就是你想要的 - 但是在某些情况下需要更多的灵活性。例如,如果您只想复制数据的一个子集,或者想从一种数据库复制到另一种数据库,或者如果您需要冲突解决逻辑(请参阅第171页的“处理写入冲突”),则可能需要将复制移动到应用程序层。 + +一些工具,如Oracle GoldenGate [19],可以通过读取数据库日志来使应用程序可用数据。另一种方法是使用许多关系数据库中可用的功能:触发器和存储过程。 + +触发器允许您注册在数据库系统中发生数据更改(写入事务)时自动执行的自定义应用程序代码。触发器有机会将此更改记录到一个单独的表中,通过这个表可以通过外部过程读取它。然后,外部进程可以应用任何必要的应用程序逻辑,并将数据更改复制到另一个系统。例如,Databus for Oracle [20]和Bucardo for Postgres [21]就是这样工作的。 + +基于触发器的复制通常比其他复制方法具有更高的开销,并且比数据库的内置复制更容易出现错误和限制。然而,由于其灵活性,它仍然是有用的。 + + + +## 复制延迟问题 + +能够容忍节点故障只是需要复制的一个原因。正如在第二部分的介绍中提到的,其他原因是可扩展性(处理比单个机器可处理更多的请求)和延迟(将副本地理位置更靠近用户)。 + +基于领导者的复制需要所有写入都通过单个节点,但只读查询可以转到任何副本。对于由大部分读取组成的工作负载以及只有很小比例的写入(Web上的常见模式),有一个有吸引力的选择:创建许多追随者,并将读取请求分布到以下各个位置。这将从领导者中移除负载,并允许读取请求由附近的副本服务。 + +在这种扩展体系结构中,只需添加更多的追随者,就可以提高只读请求的服务容量。但是,这种方法实际上只适用于异步复制 - 如果您尝试同步复制到所有追随者,则单个节点故障或网络中断将使整个系统无法写入。而且越多的节点越有可能会被关闭,所以完全同步的配置将是非常不可靠的。 + +不幸的是,如果应用程序从异步跟随者读取,如果跟随者落后,它可能会看到过时的信息。这会导致数据库中出现明显的不一致:如果同时对领导者和跟随者执行相同的查询,则可能得到不同的结果,因为并非所有的写入都反映在跟随者中。这种不一致只是一个暂时的状态 - 如果你不再写数据库并等待一段时间,追随者最终会赶上并与领导者保持一致。出于这个原因,这种效应被称为最终一致性[22,23] (道格拉斯·特里(Douglas Terry)等人创造了最终的一致性。 [24],由Werner Vogels [22]推广,成为许多NoSQL项目的战斗口号。 但是,不仅NoSQL数据库最终是一致的:异步复制关系数据库中的追随者具有相同的特征。) + +“最终”一词故意含糊不清:总的来说,复制品落后的程度是没有限制的。在正常的操作中,在领导者身上发生的写作和在追随者身上反映的延迟 - 复制滞后 - 可能仅仅是一秒的一部分,在实践中并不明显。但是,如果系统在接近容量的情况下运行,或者如果网络中存在问题,则滞后可以容易地增加到几秒甚至几分钟。 + +当滞后时间太长时,引入的不一致性不仅仅是一个理论问题,而且是一个真正的应用问题。在本节中,我们将重点介绍三个复制滞后时可能出现的问题,并概述解决这些问题的一些方法。 + +### 读已之写 + +许多应用程序让用户提交一些数据,然后查看他们提交的内容。这可能是客户数据库中的记录,也可能是对讨论主题的评论,或其他类似的内容。提交新数据时,必须将其发送给领导者,但是当用户查看数据时,可以从追随者读取数据。如果数据经常被查看,但只是偶尔写入,这是特别适合的。 + +对于异步复制,存在一个问题,如图5-3所示:如果用户在写入后不久查看数据,则新数据可能尚未到达副本。对用户来说,他们看起来好像是提交的数据丢失了,所以他们可以理解的不高兴 + +![](img/fig5-3.png) + +**图5-3 用户进行写入,然后从旧副本中读取数据。 为了防止这种异常,我们需要写后读的一致性** + +在这种情况下,我们需要读后写一致性,也称为读己之写一致性[24]。这是一个保证,如果用户重新加载页面,他们总是会看到他们自己提交的任何更新。它不会对其他用户做出承诺:其他用户的更新可能会在稍后才能看到。但是,它保证用户自己的输入已被正确保存。 + +我们如何在基于领导者的复制的系统中实现读后一致性?有各种可能的技术。提一些: + +* 阅读用户可能已经修改过的内容时,请阅读领导者;否则,从追随者读取它。这就要求你有一些方法可以知道是否修改了某些东西,而不需要实际查询它。例如,社交网络上的用户个人资料信息通常只能由个人资料的所有者编辑,而不能由其他人编辑。因此,一个简单的规则是:总是从领导读取用户自己的个人资料,从跟随者读取其他用户的个人资料。 +* 如果应用程序中的大部分内容都可能被用户编辑,那么这种方法将不会有效,因为大部分内容都必须从领导读取(否定读取缩放的好处)。在这种情况下,可以使用其他标准来决定是否从领导读取。例如,您可以跟踪上次更新的时间,并且在上次更新后的一分钟内,从领导者进行所有读取。您还可以监控追随者的复制滞后,并防止任何超过领导者一分多钟的追随者查询。 +* 客户端可以记住最近一次写入的时间戳,然后系统可以确保为该用户提供任何读取的副本反映更新,至少在该时间戳之前。如果复制副本不够充足,则可以由另一副本处理读取,或者查询可以等到复制副本赶上。时间戳可以是逻辑时间戳(指示写入顺序的东西,例如日志序列号)或实际系统时钟(在这种情况下,时钟同步变得至关重要;请参阅“不可靠的时钟”(第269页))。 +* 如果您的副本分布在多个数据中心(与用户的地理接近度或可用性),则会增加复杂性。任何需要由领导者提供服务的请求都必须路由到包含领导者的数据中心。 + +如果同一用户从多个设备访问您的服务,例如桌面Web浏览器和移动应用程序,则会出现另一个复杂情况。在这种情况下,您可能希望提供跨设备读写后一致性:如果用户在某个设备上输入了一些信息,然后在另一个设备上查看,则应该看到他们刚输入的信息。 + +在这种情况下,还有一些需要考虑的问题: + +* 需要记住用户上次更新时间戳的方法变得更加困难,因为在一台设备上运行的代码不知道在另一台设备上发生了什么更新。这个元数据将需要集中。 +* 如果副本分布在不同的数据中心,则不能保证来自不同设备的连接将路由到同一数据中心。 (例如,如果用户的台式计算机使用家庭宽带连接,并且他们的移动设备使用蜂窝数据网络,则设备的网络路线可能完全不同)。如果您的方法需要领导者阅读,您可能首先需要将来自所有用户设备的请求路由到同一个数据中心。 + + + +### 单调读 + +我们从异步从库读取异常的第二个例子是,用户可能会感受到时光倒流。 + +如果用户从不同副本进行多次读取,则可能发生这种情况。例如,图5-4显示了用户2345两次进行相同的查询,首先是一个滞后很少的追随者,然后是一个滞后较大的追随者。 (如果用户刷新一个网页,并且每个请求被路由到一个随机的服务器,这种情况是相当可能的。)第一个查询返回最近由用户1234添加的评论,但是第二个查询不返回任何东西,因为滞后的追随者还没有拿起写。实际上,第二个查询是在比第一个查询更早的时间点观察系统。如果第一个查询没有返回任何内容,这将不会那么糟糕,因为用户2345可能不知道用户1234最近添加了评论。然而,如果用户2345第一次看见用户1234的评论,然后看到它再次消失,那么对于用户2345来说,这是非常混乱的。 + +![](img/fig5-4.png) + +**图5-4 用户首先从新副本读取,然后从旧副本读取。时光倒流。为了防止这种异常,我们需要单调的读取。** + +单调读取[23]是这种异常不会发生的保证。这是一个比强有力的一致性更小的保证,但比最终的一致性更强有力的保证。当您读取数据时,您可能会看到一个旧值;单调读取仅意味着如果一个用户顺序地进行多次读取,则他们不会看到时间后退,即,在先前读取较新的数据之后,他们将不读取较旧的数据。 + +实现单调读取的一种方式是确保每个用户总是从同一个副本进行读取(不同的用户可以从不同的副本读取)。例如,可以基于用户ID的散列来选择副本,而不是随机选择副本。但是,如果该副本失败,用户的查询将需要重新路由到另一个副本。 + +### 一致前缀读 + +我们的第三个复制例子滞后于反常因果关系。 想象一下Poons先生和Cake夫人之间的以下简短对话: + +*Poons先生* +​ 蛋糕夫人,你能看到未来有多远? +*蛋糕夫人* +​ 通常约十秒钟,庞斯先生。 +这两句话之间有因果关系:蛋糕夫人听到了庞斯先生的问题并回答了这个问题。 +现在,想象第三个人正在通过追随者来听这个对话。 Cake夫人所说的事情经历了一个很少滞后的追随者,但Poons先生所说的事情有更长的复制滞后时间(见图5-5)。 这个观察者会听到以下内容: +蛋糕夫人 +通常约十秒钟,庞斯先生。 +Poons先生 +蛋糕夫人,你能看到未来有多远? +对于观察者来说,看起来好像克莱尔太太正在回答这个问题。 +Poons甚至问过它。 这种精神力量是令人印象深刻的,但非常混乱[25]。 + +![](img/fig5-5.png) + +**图5-5 如果某些分区的复制速度慢于其他分区,那么观察者在看到问题之前可能会看到答案。** + +防止这种异常需要另一种类型的保证:一致的前缀读取[23]。 这个保证说,如果一系列的写入按照某个顺序发生,那么读取这些写入的任何人都会看到它们以相同的顺序出现。 + +这是分区(分片)数据库中的一个特殊问题,我们将在第6章中讨论。如果数据库总是以相同的顺序应用写入,则读取总是会看到一致的前缀,所以这种异常不会发生。但是,在许多分布式数据库中,不同的分区独立运行,因此不存在全局写入顺序:当用户从数据库中读取数据时,可能会看到数据库的某些部分处于较旧的状态,而某些处于较新的状态。 +一种解决方案是确保任何因果关系的写入都写入相同的分区,但在某些无法高效完成的应用程序中。还有一些算法明确地跟踪因果依赖关系,我们将在“关系和并发”一节第186页中的“发生”中返回一个主题。 + + + +### 复制延迟的解决方案 + +在使用最终一致的系统时,如果复制延迟增加到几分钟甚至几小时,则应该考虑应用程序的行为。如果答案是“没问题”,那很好。但是,如果结果对于用户来说是一个不好的经验,那么设计系统来提供更强的保证是很重要的,例如写后读。假设事实上它是异步的复制是同步的,这是一个问题。 + +如前所述,应用程序可以提供比底层数据库更强有力的保证,例如通过对领导者进行某种读取。但是,在应用程序代码中处理这些问题是复杂的,容易出错。 + +如果应用程序开发人员不必担心细微的复制问题,并且可以相信他们的数据库“做正确的事情”,那将会更好。这就是事务存在的原因:它们是数据库提供更强保障的一种方式这样应用程序可以更简单。 + +单节点事务已经存在很长时间了。然而,在向分布式(复制和分区)数据库转移时,许多系统放弃了这些数据库,声称交易在性能和可用性方面过于昂贵,并断言在可扩展系统中最终的一致性是不可避免的。这个陈述中有一些事实,但它过于简单,我们将在本书其余部分的过程中形成一个更细致的观点。我们将回到第七章和第九章的交易话题,我们将在第三部分讨论一些替代机制。 + + + +## 多主复制 + +本章到目前为止,我们只考虑使用单个领导的复制架构。 虽然这是一种常见的方法,但也有一些有趣的选择。 + +基于领导者的复制有一个主要的缺点:只有一个领导者,所有的写作都必须通过它.(如果数据库被分区(见第6章),每个分区都有一个领导。 不同的分区可能在不同的节点上有其领导者,但是每个分区必须有一个领导者节点。)如果由于任何原因(例如由于你和领导之间的网络中断)而无法连接到领导者, 你不能写入数据库。 + +基于领导者的复制模型的自然延伸是允许多个节点接受写入。 复制仍然以同样的方式发生:处理写入的每个节点都必须将该数据更改转发给所有其他节点。 我们称之为多领导配置(也称为主 - 主或主动/主动复制)。 在这种情况下,每个领导者同时扮演其他领导者的追随者。 + +### 多主复制的应用场景 + +在单个数据中心内部使用多领导者设置很少有意义,因为这些好处很少超过复杂性。 但是,在某些情况下,这种配置是合理的。 + +#### 运维多数据中心 + +想象一下,你有一个数据库在几个不同的数据中心(也许这样你可以容忍整个数据中心的故障,或者为了更接近你的用户)复制品。 使用常规的基于领导者的复制设置,领导者必须位于其中一个数据中心,并且所有写入都必须经过该数据中心。 +在多领导配置中,您可以在每个数据中心都有领导。 图5-6显示了这个架构的外观。 在每个数据中心内,使用常规的领导者跟随者复制; 在数据中心之间,每个数据中心的负责人都会将其更改复制到其他数据中心的领导。 + +##### + +![](img/fig5-6.png) + +**图5-6 跨多个数据中心的多领导复制** + +我们来比较一下多数据中心部署中的单引擎和多引导器配置: + +* *性能* + + 在单领导配置中,每个写作都必须通过互联网与领导者一起进入数据中心。这可能会增加写入时间,并可能违反了首先有多个数据中心的目的。在多领导配置中,每个写操作都可以在本地数据中心进行处理,并与其他数据中心异步复制。因此,数据中心之间的网络延迟对用户来说是隐藏的,这意味着感知的性能可能会更好。 + +* *数据中心的中断容忍* + + 在单引导者配置中,如果引导者的数据中心发生故障,故障转移可以促使另一个数据中心的追随者成为领导者。在多领导者配置中,每个数据中心可以独立于其他数据中心继续运行,并且当发生故障的数据中心恢复联机时,复制将迎头赶上。 + +* *容忍网络问题* + + 数据中心之间的通信通常通过公共互联网,这可能不如数据中心内的本地网络可靠。单引号配置对这个数据中心链接中的问题非常敏感,因为通过这个链接进行写操作是同步的。具有异步复制功能的多领导者配置通常可以更好地承受网络问题:暂时的网络中断不会妨碍正在处理的写入。 + +有些数据库默认情况下支持多领导配置,但通常也使用外部工具实现,例如用于MySQL的Tungsten Replicator [26],用于PostgreSQL的BDR [27]以及用于Oracle的GoldenGate [19]。 + +尽管多领导者复制具有优势,但也有一个很大的缺点:在两个不同的数据中心可能会同时修改相同的数据,并且必须解决这些写冲突(如图5-6中的“冲突解决”所示)。我们将在第171页的“处理写入冲突”中讨论这个问题。 +由于多引导程序复制在许多数据库中都有所改进,所以常常存在微妙的配置缺陷,并且与其他数据库功能之间出现意外的交互。例如,自动增量键,触发器和完整性约束可能是有问题的。出于这个原因,多领导者复制往往被认为是危险的领域,应尽可能避免[28]。 + +#### 需要离线操作的客户端 + +多领导者复制的另一种情况是,如果您的应用程序在与Internet断开连接时需要继续工作, + +例如,考虑手机,笔记本电脑和其他设备上的日历应用程序。无论您的设备目前是否具有互联网连接,您都需要能够随时查看您的会议(发出读取请求)并输入新的会议(发出写入请求)。如果您在离线状态下进行任何更改,则当设备下次上线时,需要与服务器和其他设备同步。 + +在这种情况下,每个设备都有一个充当领导者的本地数据库(它接受写请求),并且在您所有设备上的日历副本之间存在异步多领导者复制过程(同步)。复制延迟可能是几小时甚至几天,具体取决于您何时可以访问互联网。 + +从架构的角度来看,这种设置基本上与数据中心之间的多领导者复制相同,极端:每个设备都是一个“数据中心”,它们之间的网络连接是非常不可靠的。正如破碎的日历同步实现的丰富历史所表明的,多领导者复制是一件棘手的事情。 + +有一些工具旨在使这种多领导者配置更容易。例如,CouchDB就是为这种操作模式而设计的[29]。 + +#### 协同编辑 + +实时协作编辑应用程序允许多个人同时编辑文档。例如,Etherpad [30]和Google Docs [31]允许多人同时编辑文本文档或电子表格(该算法在第174页的“自动冲突解决”中简要讨论)。我们通常不会将协作式编辑视为数据库复制问题,但与前面提到的离线编辑用例有许多相似之处。当一个用户编辑文档时,所做的更改将立即应用到其本地副本(Web浏览器或客户端应用程序中的文档状态),并异步复制到服务器和编辑同一文档的任何其他用户。 + +如果要保证不会发生编辑冲突,则应用程序必须先取得文档的锁定,然后用户才能对其进行编辑。如果另一个用户想要编辑同一个文档,他们首先必须等到第一个用户提交修改并释放锁定。这种协作模式相当于在领导者上进行交易的单领导者复制。 + +但是,为了加速协作,您可能希望将更改的单位设置得非常小(例如,一个按键),并避免锁定。这种方法允许多个用户同时进行编辑,但同时也带来了多领导者复制的所有挑战,包括需要解决冲突[32]。 + + + +### 处理写冲突 + +多领导者复制的最大问题是可能发生写冲突,这意味着需要解决冲突。 +例如,考虑一个由两个用户同时编辑的维基页面,如图5-7所示。用户1将页面的标题从A更改为B,并且用户2同时将标题从A更改为C.每个用户的更改已成功应用到其本地领导。但是,当异步复制时,会发现冲突[33]。单引导数据库中不会出现此问题。 + +![](img/fig5-7.png) + +**图5-7 由两位领导同时更新同一记录引起的写入冲突** + +#### 同步与异步冲突检测 + +在单主数据库中,第二个写入器将阻塞并等待第一个写入完成,或中止第二个写入事务,强制用户重试写入。另一方面,在多领导者设置中,两个写入都是成功的,并且在稍后的时间点仅仅异步地检测到冲突。那时要求用户解决冲突可能为时已晚。 + +原则上,可以使冲突检测同步 - 即等待写入被复制到所有副本,然后再告诉用户写入成功。但是,通过这样做,您将失去多领导者复制的主要优点:允许每个副本独立接受写入。如果您想要同步冲突检测,那么您可以使用单引导程序复制。 + +#### 避免冲突 + +处理冲突的最简单的策略就是避免它们:如果应用程序可以确保特定记录的所有写入都通过同一个领导者,那么冲突就不会发生。由于多领导者复制处理的许多实现冲突相当不好,避免冲突是一个经常推荐的方法[34]。 + +例如,在用户可以编辑自己的数据的应用程序中,可以确保来自特定用户的请求始终路由到同一数据中心,并使用该数据中心的领导者进行读写。不同的用户可能有不同的“家庭”数据中心(可能根据用户的地理位置选择),但从任何用户的角度来看,配置基本上都是单一的领导者。 + +但是,有时您可能需要更改指定的记录的领导 - 可能是因为一个数据中心出现故障,您需要将流量重新路由到另一个数据中心,或者可能是因为用户已经迁移到另一个位置,现在更接近不同的数据中心。在这种情况下,冲突避免会中断,你必须处理不同领导人同时写入的可能性。 + +#### 趋于一致的状态 + +单主程序数据库按顺序应用写操作:如果同一个字段有多个更新,则最后一个写操作将确定该字段的最终值。 + +在多领导者配置中,没有定义的写入顺序,所以不清楚最终值应该是什么。在图5-7中,标题1首先更新为B,然后更新为C;在领导者2中,首先更新为C,然后更新为B.两个订单都不是“更正确”的。 + +如果每个副本只是按照它看到写入的顺序写入,那么数据库最终将处于不一致的状态:最终值将是在领导者1处的C和在领导者2处的B.这是不可接受的 - 每个复制方案都必须确保数据在所有副本中最终都是相同的。因此,数据库必须以一种趋同的方式解决冲突,这意味着所有副本必须在所有更改都被复制时达到相同的最终值。 + +实现融合冲突解决有多种途径: + +* 给每个写入一个唯一的ID(例如,一个时间戳,一个长的随机数,一个UUID或者一个键和值的哈希),挑选最高ID的写入作为胜利者,并丢弃其他写入。如果使用时间戳,这种技术被称为最后一次写入胜利(LWW)。虽然这种方法很流行,但是很容易造成数据丢失[35]。我们将在本章末尾更详细地讨论LWW(第184页的“检测并发写入”)。 +* 为每个副本分配一个唯一的ID,并让始发于较高编号副本的写入始终优先于源自较低编号副本的写入。这种方法也意味着数据丢失。 +* 以某种方式将这些值合并在一起 - 例如,按字母顺序排序,然后连接它们(在图5-7中,合并的标题可能类似于“B / C”)。 +* 在保留所有信息的显式数据结构中记录冲突,并编写解决冲突的应用程序代码(可能通过提示用户)。 + + + +#### 自定义冲突解决逻辑 + +作为解决冲突最合适的方法可能取决于应用程序,大多数领导者复制工具允许您使用应用程序代码编写冲突解决逻辑。该代码可以在写入或读取时执行: + +* *写时执行* + +只要数据库系统检测到复制更改日志中存在冲突,就会调用冲突处理程序。例如,Bucardo允许您为此编写一段Perl代码。这个处理程序通常不能提示用户 - 它在后台进程中运行,并且必须快速执行。 + +* *读时执行* + +当检测到冲突时,所有冲突写入被存储。下一次读取数据时,会将这些多个版本的数据返回给应用程序。应用程序可能会提示用户或自动解决冲突,并将结果写回数据库。例如,CouchDB以这种方式工作。 + +请注意,冲突解决通常适用于单个行或文档层面,而不是整个事务[36]。因此,如果您有一笔交易,原本会进行几次不同的写入(请参阅第7章),则为了冲突解决的目的,每个写入仍被分开考虑。 + + + +#### *题外话:自动冲突解决* + +冲突解决规则可能很快变得复杂,并且自定义代码可能容易出错。亚马逊是一个经常被引用的例子,由于冲突解决处理程序令人惊讶的效果:一段时间以来,购物车上的冲突解决逻辑将保留添加到购物车的物品,但不包括从购物车中移除的物品。因此,顾客有时会看到物品重新出现在他们的购物车中,即使他们之前已经被移走[37]。 + +已经有一些有趣的研究来自动解决由于数据修改引起的冲突。有几行研究值得一提: + +* 无冲突的复制数据类型(CRDT)[32,38]是可以由多个用户同时编辑的集合,映射,有序列表,计数器等的一系列数据结构,它们以合理的方式自动解决冲突。一些CRDT已经在Riak 2.0中实现[39,40]。 +* 可合并的持久数据结构[41]显式跟踪历史记录,类似于Git版本控制系统,并使用三向合并功能(而CRDT使用双向合并)。 +* 可进行的转换[42]是Etherpad [30]和Google Docs [31]等合作编辑应用背后的冲突解决算法。它是专为同时编辑项目的有序列表而设计的,例如构成文本文档的字符列表。 + +这些算法在数据库中的实现还很年轻,但很可能将来它们将被集成到更多的复制数据系统中。自动冲突解决方案可以使应用程序处理多领导者数据同步更为简单。 + + + +#### 什么是冲突? + +有些冲突是显而易见的。在图5-7的例子中,两个写操作并发地修改了同一个记录中的同一个字段,并将其设置为两个不同的值。毫无疑问,这是一个冲突。 + +其他类型的冲突可能更加微妙地被发现。例如,考虑一个会议室预订系统:它跟踪哪个房间是哪个人在哪个时间预订的。这个应用程序需要确保每个房间只有一组人同时预定(即不得有相同房间的重叠预订)。在这种情况下,如果同时为同一个房间创建两个不同的预订,则可能会发生冲突。即使应用程序在允许用户进行预订之前检查可用性,如果两次预订是由两个不同的领导者进行的,则可能会有冲突。 + +现在还没有一个现成的答案,但在接下来的章节中,我们将追溯到对这个问题有很好的理解。我们将在第7章中看到更多的冲突示例,在第12章中我们将讨论用于检测和解决复制系统中冲突的可扩展方法。 + + + +### 多主复制拓扑 + +复制拓扑描述写入从一个节点传播到另一个节点的通信路径。如果你有两个领导者,如图5-7所示,只有一个合理的拓扑结构:领导者1必须把他所有的写到领导者2,反之亦然。有两个以上的领导,各种不同的拓扑是可能的。图5-8举例说明了一些例子。 + +![](img/fig5-8.png) + +**图5-8 三个可以设置多领导者复制的示例拓扑。** + +最普遍的拓扑是全部到全部(图5-8 [c]),其中每个领导者将其写入每个其他领导。但是,也会使用更多受限制的拓扑:例如,默认情况下,MySQL仅支持圆形拓扑[34],其中每个节点接收来自一个节点的写入,并将这些写入(加上自己的任何写入)转发给另一个节点。另一种流行的拓扑结构具有星形的形状:v一个指定的根节点将写入转发给所有其他节点。星型拓扑可以推广到树。 + +在圆形和星形拓扑中,写入可能需要在到达所有副本之前通过多个节点。因此,节点需要转发从其他节点收到的数据更改。为了防止无限复制循环,每个节点被赋予一个唯一的标识符,并且在复制日志中,每个写入都被标记了所有已经通过的节点的标识符[43]。当一个节点收到用自己的标识符标记的数据更改时,该数据更改将被忽略,因为节点知道它已经被处理。 + +循环和星型拓扑的问题是,如果只有一个节点发生故障,则可能会中断其他节点之间的复制消息流,导致它们无法通信,直到节点修复。拓扑结构可以重新配置为在发生故障的节点上工作,但在大多数部署中,这种重新配置必须手动完成。更密集连接的拓扑结构(例如全部到全部)的容错性更好,因为它允许消息沿着不同的路径传播,避免单点故障。 + +另一方面,全能拓扑也可能有问题。特别是,一些网络链接可能比其他网络链接更快(例如,由于网络拥塞),结果是一些复制消息可能“超过”其他复制消息,如图5-9所示。 + +##### + +![](img/fig5-9.png) + +**图5-9 使用多主程序复制时,可能会在某些副本中写入错误的顺序。** + +在图5-9中,客户端A向领导者1的表中插入一行,客户端B在领导者3上更新该行。然而,领导者2可以以不同的顺序接收写入:它可以首先接收更新(其中,从它的角度来看,是对数据库中不存在的行的更新),并且仅在稍后接收到相应的插入(其应该在更新之前)。 + +这是一个因果关系的问题,类似于我们在第165页上的“一致前缀读取”中看到的:更新取决于先前的插入,所以我们需要确保所有节点先处理插入,然后再处理更新。仅仅在每一次写入时添加一个时间戳是不够的,因为时钟不可能被充分地同步,以便在引导2处正确地排序这些事件(见第8章)。 + +要正确命令这些事件,可以使用一种称为**版本向量(version vectors)**的技术,本章稍后将讨论这种技术(请参阅第174页的“检测并发写入”)。然而,冲突检测技术在许多多领导者复制系统中执行得不好。例如,在撰写本文时,PostgreSQL BDR不提供写操作的因果排序[27],而Tungsten Replicator for MySQL甚至不尝试检测冲突[34]。 + +如果您正在使用具有多领导者复制功能的系统,那么应该了解这些问题,仔细阅读文档,并彻底测试您的数据库,以确保它确实提供了您认为具有的保证。 + +##### + +## 无主复制 + +我们在本章到目前为止所讨论的复制方法 - 单引导者和多引导者复制 - 是基于客户端向一个节点(领导者)发送写请求的想法,数据库系统负责复制写入其他副本。领导决定了写入的顺序,而跟随者按相同的顺序应用领导的写入。 + +一些数据存储系统采用不同的方法,放弃领导者的概念,并允许任何副本直接接受来自客户端的写入。一些最早的复制数据系统是无领导的[1,44],但是在关系数据库主导时代,这个想法大多被遗忘。在亚马逊将其用于其内部的Dynamo系统之后,它再一次成为数据库的一种时尚架构[37] .(Dynamo不适用于Amazon以外的用户。 令人困惑的是,AWS提供了一个名为DynamoDB的托管数据库产品,它使用了完全不同的体系结构:它基于单引导程序复制。) Riak,Cassandra和Voldemort是由Dynamo启发的无领导复制模型的开源数据存储,所以这类数据库也被称为*Dynamo风格*。 + +在一些无领导者的实现中,客户端直接将其写入到几个副本中,而在另一些情况下,协调器节点代表客户端进行写入。但是,与领导者数据库不同,协调员不执行特定的写入顺序。我们将会看到,这种设计上的差异对数据库的使用方式有着深远的影响。 + +### 当节点故障时写入数据库 + +假设你有一个带有三个副本的数据库,而其中一个副本目前不可用,或许正在重新启动以安装系统更新。在基于主机的配置中,如果要继续处理写入,则可能需要执行故障切换(请参阅第133页的「处理节点中断」)。 + +另一方面,在无领导配置中,故障切换不存在。图5-10显示了发生了什么事情:客户端(用户1234)并行发送写入到所有三个副本,并且两个可用副本接受写入,但是不可用副本错过了它。假设三个副本中的两个承认写入是足够的:在用户1234已经收到两个确定的响应之后,我们认为写入成功。客户简单地忽略了其中一个副本错过了写入的事实。 + +![](img/fig5-10.png) + +**图5-10 仲裁写入,法定读取,并在节点中断后读取修复。** + +现在想象一下,不可用的节点重新联机,客户端开始读取它。节点关闭时发生的任何写入都从该节点丢失。因此,如果您从该节点读取数据,则可能会将陈旧(过时)值视为响应。 + +为了解决这个问题,当一个客户端从数据库中读取数据时,它不仅仅发送它的请求到一个副本:读请求也被并行地发送到多个节点。客户可能会从不同的节点获得不同的响应。即来自一个节点的最新值和来自另一个节点的陈旧值。版本号用于确定哪个值更新(请参阅第174页的“检测并发写入”)。 + +#### 阅读修复和反熵 + +复制方案应确保最终将所有数据复制到每个副本。在一个不可用的节点重新联机之后,它如何赶上它错过的写入? + +在Dynamo风格的数据存储中经常使用两种机制: + +* *修复读* + +当客户端并行读取多个节点时,它可以检测到任何陈旧的响应。例如,在图5-10中,用户2345获得了来自Replica 3的版本6值和来自副本1和2的版本7值。客户端发现副本3具有陈旧值,并将新值写回复制品。这种方法适用于频繁阅读的值。 + +* *反熵过程* + +此外,一些数据存储具有后台进程,该进程不断查找副本之间的数据差异,并将任何缺少的数据从一个副本复制到另一个副本。与基于领导者的复制中的复制日志不同,此反熵过程不会以任何特定的顺序复制写入,并且在复制数据之前可能会有显着的延迟。 + +并不是所有的系统都实现了这两个;例如,Voldemort目前没有反熵过程。请注意,如果没有反熵过程,某些副本中很少读取的值可能会丢失,从而降低了持久性,因为只有在应用程序读取值时才执行读取修复。 + +#### 法定人数的读写 + +在图5-10的示例中,我们认为即使仅在三个副本中的两个上进行处理,写入仍然是成功的。如果三个副本中只有一个接受了写入,会怎样?我们能推多远呢? + +如果我们知道,每个成功的写操作意味着在三个副本中至少有两个出现,这意味着至多有一个副本可能是陈旧的。因此,如果我们从至少两个副本读取,我们可以确定至少有一个是最新的。如果第三个副本停机或响应速度缓慢,则读取仍可以继续返回最新值。 + +更一般地说,如果有n个副本,每个写入必须由w节点确认才能被认为是成功的,并且我们必须至少为每个读取查询r个节点。 (在我们的例子中,$n = 3,w = 2,r = 2$)。只要$w + r> n$,我们期望在读取时获得最新的值,因为至少有一个r节点从阅读必须是最新的。读取和写入服从这些r和w值被称为法定读取和写入。[44] (有时候这种法定人数被称为严格的法定人数,与马虎法定人数形成对比(见第183页“马虎法定人数和暗示交接法”)你可以认为r和w作为读或写所需的最低票数是有效的。 + +在Dynamo风格的数据库中,参数n,w和r通常是可配置的。一个常见的选择是使n为奇数(通常为3或5)并设置 $w = r =(n + 1)/ 2$(向上舍入)。但是,您可以根据需要更改数字。例如,设置$w = n$和$r = 1$的写入很少且读取次数较多的工作负载可能会受益。这使得读取速度更快,但具有只有一个失败节点导致所有数据库写入失败的缺点。 + +*可能有多于n个节点的集群,但是任何给定的值只能存储在n个节点上。 这允许对数据集进行分区,从而支持比您可以放在一个节点上的数据集更大的数据集。 我们将在第6章回到分区。* + +仲裁条件$w + r> n$允许系统容忍不可用的节点,如下所示: + +* 如果$w n,读取r个副本,至少有一个r副本必然包含了最近的成功写入** + +如果少于所需的w或r节点可用,则写入或读取将返回错误。 由于许多原因,节点可能不可用:因为由于执行操作的错误(由于磁盘已满而无法写入)导致节点关闭(崩溃,关闭电源),由于客户端和服务器之间的网络中断 节点,或任何其他原因。 我们只关心节点是否返回了成功的响应,而不需要区分不同类型的错误。 + + + +### 仲裁一致性的局限性 + +如果你有n个副本,并且你选择w和r,使得$w + r> n$,你通常可以期望每个读取返回为一个键写的最近的值。情况就是这样,因为你写的节点集合和你读过的节点集合必须重叠。也就是说,您读取的节点中必须至少有一个具有最新值的节点(如图5-11所示)。 + +通常,r和w被选为多数(超过 n/2 )节点,因为这确保了$w + r> n$,同时仍然容忍多达n / 2个节点故障。但是,法定人数不一定必须是大多数,只是读和写操作使用的节点集合至少需要在一个节点上重叠。其他法定人数的分配是可能的,这在分布式算法的设计中有一定的灵活性[45]。 + +您也可以将w和r设置为较小的数字,以使$w + r≤n$(即法定条件不满足)。在这种情况下,读取和写入操作仍将被发送到n个节点,但操作成功需要少量的成功响应。 + +对于较小的w和r,您更可能读取陈旧的值,因为您的读取更有可能不包含具有最新值的节点。另一方面,这种配置允许更低的延迟和更高的可用性:如果存在网络中断,并且许多副本变得无法访问,则可以继续处理读取和写入的机会更大。只有当可达副本的数量低于w或r时,数据库才分别变得不可用于写入或读取。 + +但是,即使在$w + r> n$的情况下,也可能存在返回陈旧值的边缘情况。这取决于实施,但可能的情况包括: + +* 如果使用松散的法定人数(请参阅第181页上的“马虎的仲裁与暗示交接”),写入可能会以不同于r读取的节点结束,因此r节点和w之间不再有保证重叠节点[46]。 +* 如果两个写入同时发生,不清楚哪一个先发生。在这种情况下,唯一安全的解决方案是合并并发写入(请参阅第171页的“处理写入冲突”)。如果根据时间戳(最后写入成功)挑选出胜者,则由于时钟偏差[35],写入可能会丢失。我们将返回第184页上的“检测并发写入”中的此主题。 +* 如果写操作与读操作同时发生,写操作可能仅反映在某些副本上。在这种情况下,不确定读取是返回旧值还是新值。 +* 如果在某些副本上写入成功,而在其他节点上写入失败(例如,因为某些节点上的磁盘已满),并且总体z上成功的次数少于w个副本,不会在成功的副本上回滚。这意味着如果一个写入报告失败了,后续的读取可能会或可能不会返回写入的值[47]。 +* 如果携带新值的节点失败,并且其数据从带有旧值的副本中恢复,则存储新值的副本数可能会低于w,从而打破法定条件。 +* 即使一切工作正常,也会出现边缘情况,在这种情况下,您可能会感到不安,因为我们将在第334页上的“线性化和法定人数”中看到。 + +因此,尽管法定人数似乎保证读取返回最新的写入值,但在实践中并不那么简单。 Dynamo风格的数据库通常针对可以容忍最终一致性的用例进行优化。参数w和r允许您调整陈旧值读取的概率,但不要把它们作为绝对保证。 + +尤其是,您通常没有得到第161页上的“与延迟有关的问题”(读取您的写入,单调读取或一致的前缀读取)中讨论的保证,因此前面提到的异常可能会发生在应用程序中。更强有力的担保通常需要交易或共识。我们将在第七章和第九章回到这些话题。 + +#### 监控陈旧度 + +从操作的角度来看,监视你的数据库是否返回最新的结果是很重要的。即使您的应用程序可以容忍陈旧的读取,您也需要了解复制的健康状况。如果显着落后,应该提醒您,以便您可以调查原因(例如,网络中的问题或超载节点)。 + +对于基于领导者的复制,数据库通常会公开复制滞后的度量标准,您可以将其提供给监视系统。这是可能的,因为写入按照相同的顺序应用于领导者和追随者,并且每个节点在复制日志中具有一个位置(在本地应用的写入次数)。通过从领导者的当前位置中减去随从者的当前位置,您可以测量复制滞后量。 + +然而,在无领导者复制的系统中,没有固定的写入顺序,这使得监控变得更加困难。而且,如果数据库只使用读取修复(没有反熵),那么对于一个值可能会有多大的限制是没有限制的 - 如果一个值很少被读取,那么由一个陈旧副本返回的值可能是古老的。 + +已经有一些关于衡量无主复制数据库中的复制陈旧度的研究,并根据参数n,w和r来预测陈旧读取的预期百分比[48]。不幸的是,这还不是很常见的做法,但是将过时测量值包含在数据库的标准度量标准中是一件好事。最终的一致性是故意模糊的保证,但是对于可操作性来说,能够量化“最终”是很重要的。 + +### 松散仲裁与意见交换 + +具有适当配置的仲裁的数据库可以容忍个别节点的故障,而不需要故障切换。他们也可以容忍个别节点变慢,因为请求不必等待所有n个节点响应 - 当w或r节点响应时它们可以返回。这些特性使得数据库具有无需复制的吸引力,适用于需要高可用性和低延迟的用例,并且可以容忍偶尔的陈旧读取。 + +然而,法定人数(如迄今为止所描述的)并不像它们可能的那样具有容错性。网络中断可以很容易地将客户端从大量的数据库节点上切断。虽然这些节点是活着的,而其他客户端可能能够连接到它们,但是从数据库节点切断的客户端,它们也可能已经死亡。在这种情况下,剩余的可用节点可能会少于可用节点,因此客户端可能无法达到法定人数。 + +在一个大型的群集中(节点数量明显多于n个),在网络中断期间,客户端可能连接到某些数据库节点,而不是为了为特定值组装法定数量的节点。在这种情况下,数据库设计人员需要权衡一下: + +* 将错误返回给我们无法达到w或r节点的法定数量的所有请求是否更好? +* 或者我们是否应该接受写入,然后将它们写入一些可达的节点,但不在n值通常存在的n个节点之间? + +后者被认为是一个马虎的法定人数[37]:写和读仍然需要w和r成功的响应,但是那些可能包括不在指定的n个“主”节点中的值。比方说,如果你把自己锁在房子外面,你可能会敲开邻居的门,问你是否可以暂时停留在沙发上。 + +一旦网络中断得到解决,代表另一个节点临时接受的一个节点的任何写入都被发送到适当的“本地”节点。这就是所谓的提示。 (一旦你再次找到你的房子的钥匙,你的邻居礼貌地要求你离开沙发回家。) + +松散仲裁提高写入可用性特别有用:只要有任何w节点可用,数据库就可以接受写入。然而,这意味着即使当$w + r> n$时,也不能确定读取某个键的最新值,因为最新的值可能已经临时写入了n之外的某些节点[47]。 + +因此,在传统意义上,一个马虎的法定人数实际上不是一个法定人数。这只是一个保证,即数据存储在w节点的地方。不能保证r节点的读取直到提示已经完成。 + +在所有常见的Dynamo实现中,松散的仲裁是可选的。在Riak中,它们默认是启用的,而在Cassandra和Voldemort中它们默认是禁用的[46,49,50]。 + +#### 多数据中心操作 + +我们先前讨论了跨数据中心复制作为多主复制的用例(请参阅第162页的“多重复制复制”)。无主复制还适用于多数据中心操作,因为它旨在容忍冲突的并发写入,网络中断和延迟尖峰。 + +Cassandra和Voldemort在正常的无主模型中实现了他们的多数据中心支持:副本的数量n包括所有数据中心的节点,在配置中,您可以指定每个数据中心中您想拥有的副本的数量。无论数据中心如何,每个来自客户端的写入都会发送到所有副本,但客户端通常只等待来自其本地数据中心内的法定节点的确认,从而不会受到跨数据中心链路延迟和中断的影响。对其他数据中心的高延迟写入通常被配置为异步发生,尽管配置有一定的灵活性[50,51]。 +Riak将客户端和数据库节点之间的所有通信保持在一个数据中心本地,因此n描述了一个数据中心内的副本数量。数据库集群之间的跨数据中心复制在后台异步发生,其风格类似于多领导者复制[52]。 + +### 侦测并发写入 + +Dynamo风格的数据库允许多个客户端同时写入相同的Key,这意味着即使使用严格的法定人数也会发生冲突。这种情况与多领导者复制相似(请参阅第171页的“处理写冲突”),但在Dynamo样式的数据库中,在读取修复或提示性切换期间也可能会产生冲突。 + +问题在于,由于可变的网络延迟和部分故障,事件可能在不同的节点以不同的顺序到达。例如,图5-12显示了两个客户机A和B同时写入三节点数据存储区中的键X: + +* 节点1接收来自A的写入,但由于暂时中断,从不接收来自B的写入。 +* 节点2首先接收来自A的写入,然后接收来自B的写入。 +* 节点3首先接收来自B的写入,然后从A写入。 + +![](img/fig5-12.png) + +**图5-12 并发写入Dynamo风格的数据存储:没有明确定义的顺序。** + +如果每个节点只要接收到来自客户端的写入请求就简单地覆盖了某个键的值,那么节点就会永久地不一致,如图5-12中的最终获取请求所示:节点2认为X的最终值是B,而其他节点认为值是A. + +为了最终达成一致,副本应该趋于相同的价值。他们如何做到这一点?有人可能希望复制的数据库能够自动处理,但不幸的是,大多数的实现都很糟糕:如果你想避免丢失数据,你(应用程序开发人员)需要知道很多有关数据库冲突处理的内部信息。 + +我们在第171页的“处理写冲突”中简要介绍了一些解决冲突的技术。在总结本章之前,让我们来更详细地探讨这个问题。 + +#### 最后写入胜(丢弃并发写入) + +实现最终融合的一种方法是声明每个副本只需要存储最“最近”的值,并允许“更旧”的值被覆盖和抛弃。然后,只要我们有一种明确的方式来确定哪个写是“最近的”,并且每个写入最终都被复制到每个副本,那么复制最终会收敛到相同的值。 + +正如“最近”的引用所表明的,这个想法其实颇具误导性。在图5-12的例子中,当客户端向数据库节点发送写入请求时,客户端都不知道另一个客户端,因此不清楚哪一个先发生了。事实上,说“发生”是没有意义的:我们说写入是并发的,所以它们的顺序是不确定的。 + +即使写入没有自然排序,我们也可以强制任意排序。例如,我们可以为每个写入附加一个时间戳,挑选最“最近”的最大时间戳,并丢弃具有较早时间戳的任何写入。这种冲突解决算法被称为最后写入胜利(LWW),是Cassandra [53]唯一支持的冲突解决方法,也是Riak [35]中的一个可选特征。 + +LWW实现了最终收敛的目标,但是以持久性为代价:如果同一个Key有多个并发写入,即使它们都被报告为客户端成功(因为它们被写入w个副本),其中一个写道会生存下来,其他的将被无声丢弃。此外,LWW甚至可能会删除不是并发的写入,我们将在第291页的“有序事件的时间戳”中讨论。 + +有一些情况,如缓存,其中丢失的写入可能是可以接受的。如果丢失数据是不可接受的,LWW是解决冲突的一个很烂的选择。 + +与LWW一起使用数据库的唯一安全方法是确保一个Key只写入一次,然后视为不可变,从而避免对同一个密钥进行并发更新。例如,推荐使用Cassandra的方法是使用UUID作为键,从而为每个写操作提供一个唯一的键[53]。 + +#### “此前发生”的关系和并发 + +我们如何判断两个操作是否是并发的?发展一个直觉,让我们看看一些例子: + +* 在图5-9中,两个写入不是并发的:A的插入发生在B的增量之前,因为B递增的值是A插入的值。换句话说,B的操作建立在A的操作上,所以B的操作必须有后来发生。我们也可以说B是因果依赖于A +* 另一方面,图5-12中的两个写入是并发的:当每个客户端启动操作时,它不知道另一个客户端也正在执行操作同样的Key。因此,操作之间不存在因果关系。 + +如果操作B了解操作A,或者依赖于A,或者以某种方式构建于操作A之上,则操作A在另一个操作B之前发生。在另一个操作之前是否发生一个操作是定义什么并发的关键。事实上,我们可以简单地说,如果两个操作都不在另一个之前发生,那么两个操作是并发的(即,两个操作都不知道另一个)[54]。 + +因此,只要有两个操作A和B,就有三种可能性:A在B之前发生,或者B在A之前发生,或者A和B并发。我们需要的是一个算法来告诉我们两个操作是否是并发的。如果一个操作发生在另一个操作之前,则后面的操作应该覆盖较早的操作,但是如果这些操作是并发的,则存在需要解决的冲突。 + + + +#### *并发性,时间和相对性* + +如果两个操作“同时”发生,似乎应该称为并发 - 但事实上,它们是否在时间上重叠并不重要。由于分布式系统中的时钟问题,实际上很难判断两个事件是否同时发生,这个问题我们将在第8章中详细讨论。 + +为了定义并发性,确切的时间并不重要:如果两个操作都不知道对方,我们只需调用两个并发操作,而不管它们发生的物理时间。人们有时把这个原理和狭义相对论的物理学联系起来[54],它引入了信息不能比光速更快的思想。因此,如果事件之间的时间短于光通过它们之间的距离,那么发生一定距离的两个事件不可能相互影响。 + +在计算机系统中,即使光速原则上允许一个操作影响另一个操作,但两个操作可能是并行的。例如,如果网络缓慢或中断,两个操作可能会发生一段时间,并且仍然是并发的,因为网络问题阻止一个操作能够知道另一个操作。 + +#### 捕捉此前发生关系 + +让我们来看一个算法,它确定两个操作是并发的,还是一个在另一个之前。为了简单起见,我们从一个只有一个副本的数据库开始。一旦我们已经制定了如何在单个副本上完成这项工作,我们可以将该方法概括为具有多个副本的无领导者数据库。 + +图5-13显示了两个客户端同时向同一购物车添加项目。 (如果这样的例子让你觉得太麻烦了,那么可以想象,两个空中交通管制员同时把飞机添加到他们正在跟踪的部门。)最初,购物车是空的。在它们之间,客户端向数据库发出五次写入: + +1. 客户1将牛奶加入购物车。这是第一次写入该Key,所以服务器成功存储并为其分配版本1.服务器还将值与版本号一起回送给客户端。 +2. 客户2将鸡蛋加入购物车,不知道客户1同时添加了牛奶(客户2认为其蛋是购物车中的唯一物品)。服务器为此写入分配版本2,并将蛋和牛奶存储为两个单独的值。然后它将这两个值和版本号2一起返回给客户端。 +3. 客户1不知道客户2的写入,想要将面粉加入购物车,因此认为当前的购物车内容应该是[牛奶,面粉]。它将此值与服务器先前向客户端1提供的版本号1一起发送到服务器。服务器可以从版本号中知道[牛奶,面粉]的写入取代了[牛奶]的先前值,但与[鸡蛋]同时出现。因此,服务器将版本3分配给[牛奶,面粉],覆盖版本1值[牛奶],但保留版本2值[蛋]并将剩余的值返回给客户端。 +4. 同时,客户2想要加入火腿,不知道客户1刚刚加了面粉。客户端2在最后一个响应中从服务器收到了两个值[牛奶]和[蛋],所以客户现在合并这些值,并添加火腿形成一个新的值,[鸡蛋,牛奶,火腿]。它将这个值发送到服务器以及以前的版本2.服务器检测到版本2会覆盖[eggs],但与[milk,flour]同时发生,所以剩下的两个值是[milk,flour]版本3,和[鸡蛋,牛奶,火腿]与版本4。 +5. 最后,客户1想要加培根。它以前在版本3中从服务器接收[牛奶,面粉]和[鸡蛋],所以它合并这些,添加培根,并将最终值[牛奶,面粉,鸡蛋,培根]连同版本号3.这会覆盖[牛奶,面粉](请注意[鸡蛋]已经在最后一步被覆盖),但与[鸡蛋,牛奶,火腿]并发,所以服务器保留这两个并发值。 + +![](img/fig5-13.png) + +**图5-13 捕获两个客户端之间的因果关系,同时编辑购物车。** + +图5-13中的操作之间的数据流如图5-14所示。 箭头表示哪个操作发生在其他操作之前,意味着后面的操作知道或依赖于较早的操作。 在这个例子中,客户端永远不会完全掌握服务器上的数据,因为总是有另一个操作同时进行。 但是,旧版本的值最终会被覆盖,并且不会丢失任何写入。 + + + +![](img/fig5-14.png) + +**图5-14 图5-13中的因果依赖关系图。** + +请注意,服务器可以通过查看版本号来确定两个操作是否是并发的 - 它不需要解释该值本身(因此该值可以是任何数据结构)。该算法的工作原理如下: + +* 服务器为每个密钥保留一个版本号,每次写入密钥时都增加版本号,并将新版本号与写入的值一起存储。 +* 当客户端读取密钥时,服务器将返回所有未覆盖的值以及最新的版本号。在写之前,客户必须先读钥匙。 +* 客户端写入密钥时,必须包含之前读取的版本号,并且必须将之前读取的所有值合并在一起。 (来自写入请求的响应可以像读取一样,返回所有当前值,这使得我们可以像购物车示例那样链接多个写入。) +* 当服务器接收到具有特定版本号的写入时,它可以覆盖该版本号或更低版本的所有值(因为它知道它们已经被合并到新的值中),但是它必须保持所有值更高版本号(因为这些值与传入的写入同时发生)。 + +当一个写入包含前一次读取的版本号时,它会告诉我们写入的是哪一种状态。如果在不包含版本号的情况下进行写操作,则与所有其他写操作并发,因此它不会覆盖任何内容 - 只会在随后的读取中作为其中一个值返回。 + +#### 合并同时写入的值 + +这种算法可以确保没有数据被无声地丢弃,但不幸的是,客户端需要做一些额外的工作:如果多个操作并发发生,则客户端必须通过合并并行写入的值来进行清理。 Riak称这些并发值兄弟姐妹。 + +合并兄弟值本质上是与多领导者复制中的冲突解决相同的问题,我们先前讨论过(请参阅第171页的“处理写冲突”)。一个简单的方法是根据版本号或时间戳(最后写入胜利)选择一个值,但这意味着丢失数据。所以,你可能需要在应用程序代码中做更聪明的事情。 + +以购物车为例,合并兄弟姐妹的一种合理方法就是参加工会。在图5-14中,最后的两个兄弟姐妹是[牛奶,面粉,鸡蛋,熏肉]和[鸡蛋,牛奶,火腿]。注意牛奶和鸡蛋出现在两个,即使他们每个只写一次。合并的价值可能是像[牛奶,面粉,鸡蛋,培根,火腿],没有重复。 + +然而,如果你想让人们也可以从他们的手推车中删除东西,而不是仅仅添加东西,那么把兄弟姐妹联合起来可能不会产生正确的结果:如果你合并了两个兄弟手推车,并且只有一个那么被删除的项目会重新出现在兄弟姐妹的联合中[37]。为了防止这个问题,一个项目在删除时不能简单地从数据库中删除;相反,系统必须留下一个具有合适版本号的标记,以指示合并兄弟时该项目已被删除。这种删除标记被称为墓碑。 (我们之前在第72页的“哈希索引”中的日志压缩环境中看到了墓碑。) + +因为在应用程序代码中合并兄弟是复杂且容易出错的,所以设计数据结构可以自动执行这种合并,如“自动冲突解决”(第174页)中讨论的。例如,Riak的数据类型支持使用称为CRDT的数据结构家族[38,39,55]可以以合理的方式自动合并兄弟,包括保留删除。 + +#### 版本向量 + +图5-13中的示例只使用一个副本。如果有多个副本,但没有领导者,算法如何改变? + +图5-13使用单个版本号来捕获操作之间的依赖关系,但是当多个副本接受写入连接时,这是不够的。相反,我们需要使用每个副本的版本号以及每个密钥。每个副本在处理写入时增加自己的版本号,并且跟踪从其他副本中看到的版本号。这个信息指出了要覆盖哪些值以及保留哪些值作为兄弟。 + +所有副本的版本号集合称为版本向量[56]。这个想法的一些变体正在使用,但最有趣的可能是在Riak 2.0 [58,59]中使用的虚线版本矢量[57]。我们不会深入细节,但是它的工作方式与我们在购物车示例中看到的非常相似。 + +与图5-13中的版本号一样,当读取值时,版本向量会从数据库副本发送到客户端,并且随后写入值时需要将其发送回数据库。 (Riak将版本向量编码为一个字符串,它称为因果上下文)。版本向量允许数据库区分重写和并发写入。 + +另外,就像在单个副本的例子中,应用程序可能需要合并兄弟。版本向量结构确保从一个副本读取并随后写回到另一个副本是安全的。这样做可能会导致兄弟姐妹被创建,但只要兄弟姐妹合并正确,就不会丢失数据。 + +> #### 版本矢量和矢量时钟 +> +> 版本矢量有时也被称为矢量时钟,即使它们不完全相同。 细微差别 - 请参阅参考资料的细节[57,60,61]。 简而言之,在比较副本的状态时,版本向量是正确的数据结构。 +> + + + +## 本章小结 + +在本章中,我们考察了复制的问题。复制可以用于几个目的: + +***高可用性*** + +即使在一台机器(或多台机器,或整个数据中心)停机的情况下也能保持系统正常运行 + +***断开连接的操作*** + +允许应用程序在网络中断时继续工作 + +***延迟*** + +将数据放置在距离用户较近的地方,以便用户能够更快地与其交互 + +***可扩展性*** + +能够处理比单个机器更高的读取量可以通过对副本进行读取来处理 + +尽管是一个简单的目标 - 在几台机器上保留相同数据的副本,但复制却是一个非常棘手的问题。它需要仔细考虑并发和所有可能出错的事情,并处理这些故障的后果。至少,我们需要处理不可用的节点和网络中断(甚至不考虑更隐蔽的故障,例如由于软件错误导致的无提示数据损坏)。 + +我们讨论了复制的三种主要方法: + +***单主复制*** + +客户端将所有写入操作发送到单个节点(领导者),该节点将数据更改事件流发送到其他副本(追随者)。读取可以在任何副本上执行,但从追随者读取可能是陈旧的。 + +***多主复制*** + +客户端发送每个写入到几个领导节点之一,其中任何一个都可以接受写入。领导者将数据更改事件流发送给彼此以及任何跟随者节点。 + +***无主复制*** + +客户端发送每个写入到几个节点,并从多个节点并行读取,以检测和纠正具有陈旧数据的节点。 +每种方法都有优点和缺点。单引导复制是非常流行的,因为它很容易理解,不需要担心冲突解决。在出现故障节点,网络中断和延迟峰值的情况下,多领导者和无领导者复制可以更加稳健,但代价很难推理,只能提供非常弱的一致性保证。 + +复制可以是同步的,也可以是异步的,在发生故障时对系统行为有深远的影响。尽管在系统运行平稳时异步复制速度很快,但是在复制滞后增加和服务器故障时要弄清楚会发生什么,这一点很重要。如果一个领导者失败了,并且你推动一个异步更新的追随者成为新的领导者,那么最近承诺的数据可能会丢失。 + +我们研究了一些可能由复制滞后引起的奇怪效应,我们讨论了一些有助于决定应用程序在复制滞后时的行为的一致性模型: + +***读写后一致性*** + +用户应该总是看到自己提交的数据。 + +***单调读取*** + +当用户在某个时间点看到数据后,他们不应该在较早的时间点看到数据。 + +***一致的前缀读取*** + +用户应该将数据视为具有因果意义的状态:例如,按照正确的顺序查看问题及其答复。 + +最后,我们讨论了多领导者和无领导者复制方法所固有的并发问题:因为他们允许多个写入并发发生冲突。我们研究了一个数据库可能使用的算法来确定一个操作是否发生在另一个操作之前,或者它们是否同时发生。我们还谈到了通过合并并发更新来解决冲突的方法。 + +在下一章中,我们将继续研究分布在多个机器上的数据,通过复制的对应方式:将大数据集分割成分区。 + + + + + +## 参考文献 + +1. Bruce G. Lindsay, Patricia Griffiths Selinger, C. Galtieri, et al.: + “[Notes on Distributed Databases](http://domino.research.ibm.com/library/cyberdig.nsf/papers/A776EC17FC2FCE73852579F100578964/$File/RJ2571.pdf),” IBM Research, Research Report RJ2571(33471), July 1979. + +1. “[Oracle Active Data Guard Real-Time Data Protection and Availability](http://www.oracle.com/technetwork/database/availability/active-data-guard-wp-12c-1896127.pdf),” Oracle White Paper, June 2013. + +1. “[AlwaysOn Availability Groups](http://msdn.microsoft.com/en-us/library/hh510230.aspx),” in *SQL Server Books Online*, Microsoft, 2012. + +1. Lin Qiao, Kapil Surlaker, Shirshanka Das, et al.: + “[On Brewing Fresh Espresso: LinkedIn’s Distributed Data Serving Platform](http://www.slideshare.net/amywtang/espresso-20952131),” at *ACM International Conference on + Management of Data* (SIGMOD), June 2013. + +1. Jun Rao: + “[Intra-Cluster Replication for Apache Kafka](http://www.slideshare.net/junrao/kafka-replication-apachecon2013),” at *ApacheCon North America*, February 2013. + +1. “[Highly Available Queues](https://www.rabbitmq.com/ha.html),” in *RabbitMQ Server Documentation*, Pivotal Software, Inc., 2014. + +1. Yoshinori Matsunobu: + “[Semi-Synchronous Replication at Facebook](http://yoshinorimatsunobu.blogspot.co.uk/2014/04/semi-synchronous-replication-at-facebook.html),” *yoshinorimatsunobu.blogspot.co.uk*, April 1, 2014. + +1. Robbert van Renesse and Fred B. Schneider: + “[Chain Replication for Supporting High Throughput and Availability](http://static.usenix.org/legacy/events/osdi04/tech/full_papers/renesse/renesse.pdf),” at *6th USENIX Symposium on + Operating System Design and Implementation* (OSDI), December 2004. + +1. Jeff Terrace and Michael J. Freedman: + “[Object Storage on CRAQ: High-Throughput Chain Replication for Read-Mostly Workloads](https://www.usenix.org/legacy/event/usenix09/tech/full_papers/terrace/terrace.pdf),” at *USENIX + Annual Technical Conference* (ATC), June 2009. + +1. Brad Calder, Ju Wang, Aaron Ogus, et al.: + “[Windows Azure Storage: A Highly Available Cloud Storage Service with Strong Consistency](http://sigops.org/sosp/sosp11/current/2011-Cascais/printable/11-calder.pdf),” at *23rd ACM + Symposium on Operating Systems Principles* (SOSP), October 2011. + +1. Andrew Wang: + “[Windows Azure Storage](http://umbrant.com/blog/2016/windows_azure_storage.html),” + *umbrant.com*, February 4, 2016. + +1. “[Percona Xtrabackup - Documentation](https://www.percona.com/doc/percona-xtrabackup/2.1/index.html),” Percona LLC, 2014. + +1. Jesse Newland: + “[GitHub Availability This Week](https://github.com/blog/1261-github-availability-this-week),” *github.com*, September 14, 2012. + +1. Mark Imbriaco: + “[Downtime Last Saturday](https://github.com/blog/1364-downtime-last-saturday),” + *github.com*, December 26, 2012. + +1. John Hugg: + “[‘All in’ with Determinism for Performance and Testing in Distributed Systems](https://www.youtube.com/watch?v=gJRj3vJL4wE),” at *Strange Loop*, September 2015. + +1. Amit Kapila: + “[WAL Internals of PostgreSQL](http://www.pgcon.org/2012/schedule/attachments/258_212_Internals%20Of%20PostgreSQL%20Wal.pdf),” at *PostgreSQL Conference* (PGCon), May 2012. + +1. *MySQL + Internals Manual*. Oracle, 2014. + +1. Yogeshwer Sharma, Philippe Ajoux, Petchean Ang, et al.: + “[Wormhole: Reliable Pub-Sub to Support Geo-Replicated Internet Services](https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-sharma.pdf),” at *12th USENIX + Symposium on Networked Systems Design and Implementation* (NSDI), May 2015. + +1. “[Oracle GoldenGate 12c: Real-Time Access to Real-Time Information](http://www.oracle.com/us/products/middleware/data-integration/oracle-goldengate-realtime-access-2031152.pdf),” Oracle White Paper, October 2013. + +1. Shirshanka Das, Chavdar Botev, Kapil Surlaker, et al.: + “[All Aboard the Databus!](http://www.socc2012.org/s18-das.pdf),” at + *ACM Symposium on Cloud Computing* (SoCC), October 2012. + +1. Greg Sabino Mullane: + “[Version 5 of Bucardo Database Replication System](http://blog.endpoint.com/2014/06/bucardo-5-multimaster-postgres-released.html),” *blog.endpoint.com*, June 23, 2014. + +1. Werner Vogels: + “[Eventually Consistent](http://queue.acm.org/detail.cfm?id=1466448),” + *ACM Queue*, volume 6, number 6, pages 14–19, October 2008. + [doi:10.1145/1466443.1466448](http://dx.doi.org/10.1145/1466443.1466448) + +1. Douglas B. Terry: + “[Replicated Data Consistency Explained Through Baseball](http://research.microsoft.com/pubs/157411/ConsistencyAndBaseballReport.pdf),” Microsoft Research, Technical Report + MSR-TR-2011-137, October 2011. + +1. Douglas B. Terry, Alan J. Demers, Karin Petersen, et al.: + “[Session Guarantees for Weakly Consistent Replicated Data](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.71.2269&rep=rep1&type=pdf),” at *3rd International Conference + on Parallel and Distributed Information Systems* (PDIS), September 1994. + [doi:10.1109/PDIS.1994.331722](http://dx.doi.org/10.1109/PDIS.1994.331722) + +1. Terry Pratchett: *Reaper Man: A Discworld + Novel*. Victor Gollancz, 1991. ISBN: 978-0-575-04979-6 + +1. “[Tungsten Replicator](http://tungsten-replicator.org/),” Continuent, Inc., 2014. + +1. “[BDR 0.10.0 Documentation](http://bdr-project.org/docs/next/index.html),” The PostgreSQL Global Development Group, *bdr-project.org*, 2015. + +1. Robert Hodges: + “[If You *Must* Deploy Multi-Master Replication, Read This First](http://scale-out-blog.blogspot.co.uk/2012/04/if-you-must-deploy-multi-master.html),” *scale-out-blog.blogspot.co.uk*, + March 30, 2012. + +1. J. Chris Anderson, Jan Lehnardt, and Noah + Slater: *CouchDB: The Definitive Guide*. O'Reilly Media, 2010. + ISBN: 978-0-596-15589-6 + +1. AppJet, Inc.: + “[Etherpad and EasySync Technical Manual](https://github.com/ether/etherpad-lite/blob/e2ce9dc/doc/easysync/easysync-full-description.pdf),” *github.com*, March 26, 2011. + +1. John Day-Richter: + “[What’s Different About the New Google Docs: Making Collaboration Fast](http://googledrive.blogspot.com/2010/09/whats-different-about-new-google-docs.html),” *googledrive.blogspot.com*, + 23 September 2010. + +1. Martin Kleppmann and Alastair R. Beresford: + “[A Conflict-Free Replicated JSON Datatype](http://arxiv.org/abs/1608.03960),” + arXiv:1608.03960, August 13, 2016. + +1. Frazer Clement: + “[Eventual Consistency – Detecting Conflicts](http://messagepassing.blogspot.co.uk/2011/10/eventual-consistency-detecting.html),” *messagepassing.blogspot.co.uk*, October 20, 2011. + +1. Robert Hodges: + “[State of the Art for MySQL Multi-Master Replication](https://www.percona.com/live/mysql-conference-2013/sessions/state-art-mysql-multi-master-replication),” at *Percona Live: MySQL Conference & + Expo*, April 2013. + +1. John Daily: + “[Clocks Are Bad, or, Welcome to the Wonderful World of Distributed Systems](http://basho.com/clocks-are-bad-or-welcome-to-distributed-systems/),” *basho.com*, November 12, 2013. + +1. Riley Berton: + “[Is Bi-Directional Replication (BDR) in Postgres Transactional?](http://sdf.org/~riley/blog/2016/01/04/is-bi-directional-replication-bdr-in-postgres-transactional/),” *sdf.org*, January 4, 2016. + +1. Giuseppe DeCandia, Deniz Hastorun, Madan Jampani, et al.: + “[Dynamo: Amazon's Highly Available Key-Value Store](http://www.allthingsdistributed.com/files/amazon-dynamo-sosp2007.pdf),” at *21st ACM Symposium on Operating + Systems Principles* (SOSP), October 2007. + +1. Marc Shapiro, Nuno Preguiça, Carlos Baquero, + and Marek Zawirski: “[A Comprehensive Study of Convergent and Commutative Replicated Data Types](http://hal.inria.fr/inria-00555588/),” INRIA Research Report no. 7506, + January 2011. + +1. Sam Elliott: + “[CRDTs: An UPDATE (or Maybe Just a PUT)](https://speakerdeck.com/lenary/crdts-an-update-or-just-a-put),” at *RICON West*, October 2013. + +1. Russell Brown: + “[A Bluffers Guide to CRDTs in Riak](https://gist.github.com/russelldb/f92f44bdfb619e089a4d),” *gist.github.com*, October 28, 2013. + +1. Benjamin Farinier, Thomas Gazagnaire, and + Anil Madhavapeddy: “[Mergeable Persistent Data Structures](http://gazagnaire.org/pub/FGM15.pdf),” at *26es Journées Francophones des Langages Applicatifs* (JFLA), + January 2015. + +1. Chengzheng Sun and Clarence Ellis: + “[Operational Transformation in Real-Time Group Editors: Issues, Algorithms, and Achievements](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.53.933&rep=rep1&type=pdf),” at + *ACM Conference on Computer Supported Cooperative Work* (CSCW), November 1998. + +1. Lars Hofhansl: + “[HBASE-7709: Infinite Loop Possible in Master/Master Replication](https://issues.apache.org/jira/browse/HBASE-7709),” *issues.apache.org*, January 29, 2013. + +1. David K. Gifford: + “[Weighted Voting for Replicated Data](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.84.7698),” + at *7th ACM Symposium on Operating Systems Principles* (SOSP), December 1979. + [doi:10.1145/800215.806583](http://dx.doi.org/10.1145/800215.806583) + +1. Heidi Howard, Dahlia Malkhi, and Alexander Spiegelman: + “[Flexible Paxos: Quorum Intersection Revisited](https://arxiv.org/abs/1608.06696),” + *arXiv:1608.06696*, August 24, 2016. + +1. Joseph Blomstedt: + “[Re: Absolute Consistency](http://lists.basho.com/pipermail/riak-users_lists.basho.com/2012-January/007157.html),” email to *riak-users* mailing list, *lists.basho.com*, + January 11, 2012. + +1. Joseph Blomstedt: + “[Bringing Consistency to Riak](https://vimeo.com/51973001),” at *RICON West*, + October 2012. + +1. Peter Bailis, Shivaram Venkataraman, + Michael J. Franklin, et al.: + “[Quantifying Eventual Consistency with PBS](http://www.bailis.org/papers/pbs-cacm2014.pdf),” + *Communications of the ACM*, volume 57, number 8, pages 93–102, August 2014. + [doi:10.1145/2632792](http://dx.doi.org/10.1145/2632792) + +1. Jonathan Ellis: + “[Modern Hinted Handoff](http://www.datastax.com/dev/blog/modern-hinted-handoff),” + *datastax.com*, December 11, 2012. + +1. “[Project Voldemort Wiki](https://github.com/voldemort/voldemort/wiki),” *github.com*, 2013. + +1. “[Apache Cassandra 2.0 Documentation](http://www.datastax.com/documentation/cassandra/2.0/index.html),” DataStax, Inc., 2014. + +1. “[Riak Enterprise: Multi-Datacenter Replication](http://basho.com/assets/MultiDatacenter_Replication.pdf).” Technical whitepaper, Basho Technologies, Inc., + September 2014. + +1. Jonathan Ellis: + “[Why Cassandra Doesn't Need Vector Clocks](http://www.datastax.com/dev/blog/why-cassandra-doesnt-need-vector-clocks),” *datastax.com*, September 2, 2013. + +1. Leslie Lamport: + “[Time, Clocks, and the Ordering of Events in a Distributed System](http://research.microsoft.com/en-US/um/people/Lamport/pubs/time-clocks.pdf),” *Communications of the ACM*, + volume 21, number 7, pages 558–565, July 1978. + [doi:10.1145/359545.359563](http://dx.doi.org/10.1145/359545.359563) + +1. Joel Jacobson: + “[Riak 2.0: Data Types](http://blog.joeljacobson.com/riak-2-0-data-types/),” + *blog.joeljacobson.com*, March 23, 2014. + +1. D. Stott Parker Jr., Gerald J. Popek, Gerard Rudisin, et al.: + “[Detection of Mutual Inconsistency in Distributed Systems](http://zoo.cs.yale.edu/classes/cs426/2013/bib/parker83detection.pdf),” *IEEE Transactions on Software Engineering*, + volume 9, number 3, pages 240–247, May 1983. + [doi:10.1109/TSE.1983.236733](http://dx.doi.org/10.1109/TSE.1983.236733) + +1. Nuno Preguiça, Carlos Baquero, Paulo Sérgio + Almeida, et al.: “[Dotted Version Vectors: Logical Clocks for Optimistic Replication](http://arxiv.org/pdf/1011.5808v1.pdf),” arXiv:1011.5808, November 26, + 2010. + +1. Sean Cribbs: + “[A Brief History of Time in Riak](https://www.youtube.com/watch?v=HHkKPdOi-ZU),” + at *RICON*, October 2014. + +1. Russell Brown: + “[Vector Clocks Revisited Part 2: Dotted Version Vectors](http://basho.com/posts/technical/vector-clocks-revisited-part-2-dotted-version-vectors/),” *basho.com*, November 10, 2015. + +1. Carlos Baquero: + “[Version Vectors Are Not Vector Clocks](https://haslab.wordpress.com/2011/07/08/version-vectors-are-not-vector-clocks/),” *haslab.wordpress.com*, July 8, 2011. + +1. Reinhard Schwarz and Friedemann Mattern: + “[Detecting Causal Relationships in Distributed Computations: In Search of the Holy Grail](http://dcg.ethz.ch/lectures/hs08/seminar/papers/mattern4.pdf),” *Distributed + Computing*, volume 7, number 3, pages 149–174, March 1994. + [doi:10.1007/BF02277859](http://dx.doi.org/10.1007/BF02277859) + + + + diff --git a/ddia/ch6.md b/ddia/ch6.md new file mode 100644 index 0000000..32ada5e --- /dev/null +++ b/ddia/ch6.md @@ -0,0 +1,401 @@ +# 6. 分片 + +![](img/ch6.png) + +> 显而易见,我们必须从电脑指令序列与限制约束中跳出来。 我们应当叙述定义、元数据、关系,而不是过程。 +> +> —— Grace Murray Hopper,未来的计算机及其管理(1962) +> + +------------- + +[TOC] + +在第5章中,我们讨论了复制 - 即在不同节点上有相同数据的多个副本。对于非常大的数据集或非常高的查询吞吐量,这是不够的:我们需要将数据拆分成分区,也称为sharding[^i] + +[^i]: 正如本章所讨论的,分区是一种有意将大型数据库分解成小型数据库的方式。它与网络分区(net splits)无关,这是节点之间网络中的一种故障类型。我们将在第8章讨论这些错误。 + + + +> ##### 术语澄清 +> +> 我们在这里称之为分区`partition`的东西,在MongoDB,Elasticsearch和Solr Cloud中被称为**分片(shard)**;在HBase中称之为**区域(Region)**,Bigtable中的 `tablet`,Cassandra和Riak中的`vnode`以及Couchbase中的`vBucket`。但是,分区(partition)是最为重要的术语,所以我们坚持使用它。 +> + + + +通常情况下,分区是这样定义的,即每条数据(每条记录,每行或每个文档)只属于一个分区。有很多方法可以实现这一点,本章将深入讨论。实际上,每个分区都是自己的小型数据库,尽管数据库可能支持同时触及多个分区的操作。 + +要分区数据的主要原因是可扩展性。不同的分区可以放在不共享的集群中的不同节点上(请参阅[第二部分](part-ii.md)关于无共享架构的定义)。因此,大数据集可以分布在多个磁盘上,并且查询负载可以分布在多个处理器上。 + +对于在单个分区上运行的查询,每个节点可以独立执行对其自己的分区的查询,因此可以通过添加更多的节点来缩放查询吞吐量。大型,复杂的查询可能会跨越多个节点进行并行处理,尽管这会变得非常困难。 + +分区数据库在20世纪80年代由Teradata和NonStop SQL[【1】][1]等产品率先推出,最近又被NoSQL数据库和基于Hadoop的数据仓库重新发明。有些系统是为事务性工作负载设计的,其他系统则用于分析(请参阅第90页上的“事务处理或分析?”):这种差异会影响系统的调整方式,但是分区的基本原理适用于这两种工作负载。 + +在本章中,我们将首先介绍分割大型数据集的不同方法,并观察数据索引如何与分区交互。然后,我们将讨论重新平衡,如果您想要添加或删除群集中的节点,则必须进行重新平衡。最后,我们将概述数据库如何将请求路由到正确的分区并执行查询。 + + + +## 分片与复制 + +![](img/fig6-1.png) + +**图6-1 组合使用复制和分区:每个节点充当某些分区的领导者,其他分区充当追随者。** + + + +## 键值数据的分片 + +假设你有大量的数据,你想分割它。你如何决定在哪些节点上存储哪些记录? + +我们的分区目标是将数据和查询负载均匀分布在各个节点上。如果每个节点都公平分享,那么理论上10个节点应该能够处理10倍的数据量和10倍的单个节点的读写吞吐量(目前忽略复制)。 + +如果分区是不公平的,那么一些分区比其他分区有更多的数据或查询,我们称之为偏斜。歪斜的存在使分区效率下降得多。在极端的情况下,所有的负载都可能在一个分区上,所以10个节点中有9个是空闲的,你的瓶颈就是单个的繁忙节点。一个负载不均衡的分区被称为热点。 + +避免热点的最简单方法是将记录随机分配给节点。这将在整个节点上平均分配数据,但是它有一个很大的缺点:当你试图读取一个特定的项目时,你无法知道它在哪个节点上,所以你必须并行地查询所有的节点。 + +我们可以做得更好。现在让我们假设您有一个简单的键值数据模型,其中您总是通过其主键访问记录。例如,在一篇老式的纸质百科全书中,你可以通过标题来查找一个条目;由于所有条目按字母顺序排序,因此您可以快速找到您要查找的条目。 + + +### 根据键的范围分片 + +分区的一种方法是为每个分区分配一个连续的键范围(从最小值到最大值),如纸百科全书的卷(图6-2)。如果知道范围之间的界限,则可以轻松确定哪个分区包含给定的键。如果您还知道哪个分区分配给哪个节点,那么您可以直接向相应的节点发出请求(或者在百科全书的情况下,从书架上选取正确的书籍)。 + +![](img/fig6-2.png) + +**图6-2 印刷版百科全书按照关键字范围进行分区** + +键的范围不一定均匀分布,因为您的数据可能不均匀分布。例如,在图6-2中,第1卷包含以A和B开头的单词,但第12卷则包含以T,U,V,X,Y和Z开头的单词。每个字母的两个字母只有一个音量导致一些卷比其他卷更大。为了均匀分配数据,分区边界需要适应数据。 + +分区边界可以由管理员手动选择,也可以由数据库自动选择(我们将在第209页的“重新平衡分区”中更详细地讨论分区边界的选择)。 Bigtable使用了这种分区策略,其开源的HBase [2,3],RethinkDB和2.4版本之前的MongoDB [4][4]。 + +在每个分区中,我们可以按照排序的顺序保存键(请参见第70页上的“SSTables和LSM-树”)。这具有范围扫描非常简单的优点,您可以将键作为连接索引来处理,以便在一个查询中获取多个相关记录(请参阅第79页的“多列索引”)。例如,考虑存储来自传感器网络的数据的应用程序,其中关键是测量的时间戳(年 - 月 - 日 - 时 - 分 - 秒)。范围扫描在这种情况下非常有用,因为它们让您轻松获取某个月份的所有读数。 + +然而,关键范围分区的缺点是某些访问模式会导致热点。 如果密钥是时间戳,则分区对应于时间范围,例如,每天一个分区。 不幸的是,由于我们在测量发生时将数据从传感器写入数据库,因此所有写入操作都会转到同一个分区(即今天的分区),这样分区可能会因写入而过载,而其他分区则处于空闲状态[5]。 + +为了避免传感器数据库中的这个问题,您需要使用除时间戳以外的其他内容作为密钥的第一个元素。 例如,您可以在每个时间戳前添加传感器名称,以便分区首先按传感器名称,然后按时间。 假设同时有许多传感器处于活动状态,则写入负载将最终均匀分布在分区上。 现在,当您想要在一个时间范围内获取多个传感器的值时,您需要为每个传感器名称执行一个单独的范围查询。 + +### 根据键的哈希分片 + +由于这种倾斜和热点的风险,许多分布式数据存储使用散列函数来确定给定密钥的分区。 + +一个好的散列函数需要偏斜的数据并使其均匀分布。假设你有一个带有字符串的32位散列函数。无论何时给它一个新的字符串,它将返回一个0到232-1之间的表面上的随机数。即使输入的字符串非常相似,它们的散列也会均匀分布在这个数字范围内。 + +对于分区目的来说,散列函数不需要密码强壮:例如,Cassandra和MongoDB使用MD5,Voldemort使用Fowler-Noll-Vo函数。许多编程语言都有内置的简单哈希函数(因为它们用于哈希表),但是它们可能不适合分区:例如,在Java的Object.hashCode()和Ruby的Object#哈希中,同一个键可能有不同的过程中不同的哈希值[6]。 + +一旦你有一个合适的密钥散列函数,你可以为每个分区分配一个散列范围(而不是一系列的密钥),每个散列落在分区范围内的密钥将被存储在该分区中。如图6-3所示。 + +![](img/fig6-3.png) + +**图6-3 按哈希键分区** + +这种技术擅长在分区之间分配密钥。分区边界可以是均匀间隔的,也可以是伪随机选择的(在这种情况下,该技术有时被称为一致性散列)。 + +> 一致性哈希 +> 一致性哈希由Karger等人定义。[7] 是一种平均分配负载的方法,通过内容分发网络(CDN)等互联网系统的缓存。 它使用随机选择的分区边界来避免中央控制或分布式共识的需要。 请注意,这里的一致性与复制一致性(请参阅第5章)或ACID一致性(请参阅第7章)无关,而是描述了重新平衡的特定方法。 +> +> 正如我们将在第209页的“重新平衡分区”中所看到的,这种特殊的方法对于数据库实际上并不是很好,所以在实际中很少使用(某些数据库的文档仍然指的是一致性哈希,但是它 往往是不准确的)。 因为这太混乱了,所以最好避免使用一致性哈希这个术语,而只是把它称为散列分区(hash partitioning)。 + +不幸的是,通过使用Key散列进行分区,我们失去了键范围分区的一个很好的属性:执行高效范围查询的能力。曾经相邻的密钥现在分散在所有分区中,所以它们的排序顺序就会丢失。在MongoDB中,如果您启用了基于散列的分片模式,则任何范围查询都必须发送到所有分区[4]。主键上的范围查询不受Riak [9],Couchbase [10]或Voldemort的支持。 + +Cassandra在两个分区策略之间达成了一个折衷[11,12,13]。 Cassandra中的表可以使用由多个列组成的复合主键来声明。只有该密钥的第一部分被散列来确定分区,而其他列则被用作Cas- sandra的SSTables中排序数据的连接索引。因此,查询无法在组合键的第一列中搜索一系列值,但如果为第一列指定了固定值,则可以对该键的其他列执行有效的范围扫描。 + +串联索引方法为一对多关系提供了一个优雅的数据模型。例如,在社交媒体网站上,一个用户可能会发布很多更新。如果更新的主键被选择为`(user_id,update_timestamp)`,那么您可以有效地检索特定用户在某个时间间隔内按时间戳排序的所有更新。不同的用户可以存储在不同的分区上,但是在每个用户中,更新按时间戳顺序存储在单个分区上。 + +### 负载倾斜与消除热点 + +如前所述,哈希键确定其分区可以帮助减少热点。但是,它不能完全避免它们:在极端情况下,所有的读写操作都是针对同一个密钥的,所有的请求都会被路由到同一个分区。 + +这种工作量也许并不常见,但并非闻所未闻:例如,在社交媒体网站上,一个拥有数百万追随者的名人用户在做某事时可能会引发一场风暴[14]。这个事件可能导致大量写入同一个密钥(密钥可能是名人的用户ID,或者人们正在评论的动作的ID)。哈希键不起作用,因为两个相同ID的哈希值仍然是相同的。 + +如今,大多数数据系统无法自动补偿这种高度偏斜的工作负载,因此应用程序有责任减少偏斜。例如,如果一个密钥被认为是非常热的,一个简单的方法是在密钥的开始或结尾添加一个随机数。只要一个两位数的十进制随机数就可以将写入密钥分散到100个不同的密钥中,从而允许这些密钥分配到不同的分区。 + +然而,在不同的密钥之间进行分割,任何读取都必须要做额外的工作,因为他们必须从所有100个密钥中读取数据并将其合并。此技术还需要额外的簿记:只为少量热键附加随机数是有意义的;对于写入吞吐量低的绝大多数密钥,这将是不必要的开销。因此,您还需要一些方法来跟踪哪些键被分割。 + +也许在将来,数据系统将能够自动检测和补偿偏斜的工作负载;但现在,您需要考虑自己的应用程序的权衡。 + + +## 分片与次级索引 + +到目前为止,我们讨论的分区方案依赖于键值数据模型。如果只通过主键访问记录,我们可以从该键确定分区,并使用它来将读写请求路由到负责该键的分区。 + +如果涉及二级索引,情况会变得更加复杂(另见“其他索引结构”在第85页)。辅助索引通常不能唯一地标识记录,而是搜索特定值的发生的方式:查找用户123的所有操作,查找包含词语hogwash的所有文章,查找所有颜色为红色的车辆等等上。 + +二级索引是关系数据库的吃饭家伙,在文档数据库中也是通用的。许多键值存储(如HBase和Volde-mort)由于增加了实现的复杂性而避免了二级索引,但是一些(如Riak)已经开始添加它们,因为它们对于数据建模非常有用。最后,二级索引是Solr和Elasticsearch等搜索服务器的存在理由。 + +二级索引的问题是它们不能整齐地映射到分区。有两种主要的方法可以用二级索引分区数据库:基于文档的分区和基于词的分区。 + +### 按文档的二级索引 + +例如,假设您正在经营一个销售二手车的网站(如图6-4所示)。 每个列表都有一个唯一的ID--称之为文档ID--并且用文档ID对数据库进行分区(例如,分区0中的ID 0到499,分区1中的ID 500到999等)。 + +你想让用户搜索汽车,允许他们通过颜色和通过过滤,所以你需要一个二级索引的颜色和(在文档数据库中这些将是字段(field),在关系数据库中,他们将是列(column) )。 如果您声明了索引,则数据库可以自动执行索引。例如,无论何时将红色汽车添加到数据库,数据库分区都会自动将其添加到索引条目`color:red`的文档ID列表中。 + +[^ii]: 如果您的数据库仅支持键值模型,则可能会尝试通过在应用程序代码中创建从值到文档ID的映射来实现辅助索引。 如果您沿着这条路线走下去,您需要非常小心,以确保您的索引与基础数据保持一致。 竞争条件和间歇性写入失败(其中一些更改已保存,但其他更改未保存)很容易导致数据不同步 - 请参见第231页上的“多对象事务的需要”。。 + +![](img/fig6-4.png) + +**图6-4 按文档分区二级索引** + +在这种索引方法中,每个分区是完全独立的:每个分区维护自己的二级索引,仅覆盖该分区中的文档。它不关心哪些数据存储在其他分区中。无论何时您需要写入数据库(添加,删除或更新文档),只需处理包含您正在编写的文档ID的分区即可。出于这个原因,文档分区索引也被称为本地索引(而不是全局索引,在下一节中描述)。 + +但是,从文档分区索引中读取需要注意:除非您对文档ID做了特别的处理,否则没有理由将所有具有特定颜色或特定品牌的汽车放在同一个分区中。在图6-4中,红色汽车出现在分区0和分区1中。因此,如果要搜索红色汽车,则需要将查询发送到所有分区,并合并所有返回的结果。 + +这种查询分区数据库的方法有时被称为分散/聚集,并且可能会使二级索引的读取查询相当昂贵。即使您并行查询分区,分散/聚集也容易导致尾部延迟放大(请参阅第16页的“实践中的百分比”)。然而,它被广泛使用:MonDBDB,Riak [15],Cassandra [16],Elasticsearch [17],SolrCloud [18]和VoltDB [19]都使用文档分区二级索引。大多数数据库供应商建议您构建分区方案,以便可以从单个分区提供二级索引查询,但这并不总是可行,尤其是当您在单个查询中使用多个二级索引时(例如按颜色并通过在同一时间)。 + + + +### 根据Term的二级索引 + +我们可以构建覆盖所有分区数据的全局索引,而不是每个分区都有自己的二级索引(本地索引)。但是,我们不能只把这个索引存储在一个节点上,因为它可能会成为一个瓶颈,打破了分区的目的。全局索引也必须进行分区,但可以与主键索引进行不同的分区。 + +![](img/fig6-5.png) + +**图6-5 按术语对二级索引进行分区** + +图6-5说明了这可能是什么情况:来自所有分区的红色汽车在索引中显示为红色:索引中的红色,但索引是分区的,以便从字母a到r开始的颜色出现在分区0中,颜色以s开始z出现在第1部分。汽车制造商的指数也是相似的(分区边界在f和h之间)。 + +我们将这种索引术语称为分割术语,因为我们期待的术语决定了索引的分割。在这里,例如,一个术语将是颜色:红色。名称术语来自全文索引(一种特定的二级索引),其中术语是文档中出现的所有单词。 + +和以前一样,我们可以通过术语本身来划分索引,或者使用术语的散列。通过术语本身进行划分对于范围扫描是有用的(例如,数字特性,例如汽车的要价),而对术语的哈希进行划分给出了负载的更均匀的分布。 + +全局(术语分区)索引优于文档分区索引的优点是它可以使读取更有效率:而不是分散/收集所有分区,客户端只需要向包含术语的分区发出请求它想要的。但是,全局索引的缺点在于写入速度较慢且较为复杂,因为写入单个文档现在可能会影响索引的多个分区(文档中的每个术语可能位于不同的分区上,位于不同的节点上) 。 + +在理想的世界里,索引总是最新的,写入数据库的每个文档都会立即反映在索引中。但是,在分区索引中,这将需要跨所有受写入影响的分区进行分布式事务,这在所有数据库中都不受支持(请参阅第7章和第9章)。 + +在实践中,对全局二级索引的更新通常是异步的(也就是说,如果在写入之后不久读取索引,刚才所做的更改可能尚未反映在索引中)。例如,Amazon DynamoDB指出,在正常情况下,其全局次级索引会在不到一秒的时间内更新,但在基础架构出现故障的情况下可能会经历更长的传播延迟[20]。 + +全局术语分区索引的其他用途包括Riak的搜索功能[21]和Oracle数据仓库,它允许您在本地索引和全局索引之间进行选择[22]。我们将回到第12章中实施分词二级索引的主题。 + + + +## 平衡分区 + +在数据库中,随着时间的推移,事情也在起变化。 + +* 查询吞吐量增加,所以您想要添加更多的CPU来处理负载。 +* 数据集大小增加,所以您想添加更多的磁盘和RAM来存储它。 +* 机器出现故障,其他机器需要接管故障机器的责任。 + +所有这些更改都要求数据和请求从一个节点移动到另一个节点。 从集群中的一个节点向另一个节点移动负载的过程称为重置。 +无论使用哪种分区方案,重新平衡通常都会满足一些最低要求: + +* 重新平衡之后,负载(数据存储,读取和写入请求)应该在集群中的节点之间公平地共享。 +* 重新平衡正在发生时,数据库应该继续接受读取和写入。 +* 节点之间不应移动超过所需的数据,以便快速重新配置,并尽量减少网络和磁盘I / O负载。 + +### 平衡策略 + +有几种不同的分区分配方式[23]。让我们依次简要讨论一下。 + +#### 别这样做:hash mod N + +我们在前面说过(图6-3),最好将可能的散列分成不同的范围,并将每个范围分配给一个分区(例如,如果0≤散列(键),则将键分配给分区0)如果b0≤散列(关键字) 一些作者声称,由于其带来的性能或可用性问题,一般的两阶段提交支持太昂贵了。 我们认为应用程序员应该处理由于过度使用而导致的性能问题,而不是在缺乏事务的情况下编写代码。 +> +> ​ ——James Corbett等人,Spanner:Google的全球分布式数据库(2012) + +-------- + +[TOC] + + + +在数据系统的严酷现实中,很多事情可能会出错: + +* 数据库软件或硬件可能随时发生故障(包括写操作过程中)。 +* 应用程序可能随时崩溃(包括一系列操作的中途)。 +* 网络中断可能会意外地切断来自数据库的应用程序,或从另一个数据库节点切断应用程序。 +* 多个客户端可能会同时写入数据库,覆盖彼此的更改。 +* 客户可能读取的数据无意义,因为它只是部分更新。 +* 客户之间的竞争条件可能导致令人惊讶的错误。 + +为了可靠,系统必须处理这些故障并确保它们不会导致整个系统的灾难性故障。但是,实现容错机制是很多工作。它需要仔细考虑所有可能出错的事情,并进行大量的测试以确保解决方案真正起作用。 + +数十年来,交易一直是简化这些问题的首选机制。事务是应用程序将多个读取和写入组合成逻辑单元的一种方式。从概念上讲,事务中的所有读写都是作为一个操作执行的:整个事务成功(提交)或失败(中止,回滚)。如果失败,应用程序可以安全地重试。对于事务来说,应用程序的错误处理变得简单多了,因为它不需要担心部分失败,即某些操作成功,有些失败(无论出于何种原因)的情况。 +如果你花了数年时间处理交易,看起来很明显,但我们不应该把它们视为理所当然。交易不是一种自然规律;它们是为了简化访问数据库的应用程序的编程模型而创建的。通过使用事务,应用程序可以自由地忽略某些潜在的错误情况和并发问题,因为数据库会替代它们(我们称之为安全保证)。 +并不是所有的应用程序都需要事务处理,有时候有利于削弱事务保证或完全放弃它们(例如,为了获得更高的性能或更高的可用性)。一些安全属性可以在没有交易的情况 +你怎么知道你是否需要交易?为了回答这个问题,我们首先需要确切地理解交易可以提供的安全保障,以及与这些交易相关的成本。尽管乍一看交易似乎很简单,但实际上有许多微妙而重要的细节正在发挥作用。 +在本章中,我们将研究许多可能出错的事例,并探讨数据库用于防范这些问题的算法。我们将在并发控制领域特别深入地讨论可能发生的各种竞争条件以及数据库如何实现读取提交,快照隔离和可串行化等隔离级别。 +本章适用于单节点和分布式数据库;在第8章中,我们将重点讨论仅在分布式系统中出现的特殊挑战。 + + + +## 事务的棘手概念 + +现在几乎所有的关系数据库和一些非关系数据库都支持事务处理。他们中的大多数遵循IBM系统R(第一个SQL数据库)在1975年引入的风格[1,2,3]。尽管一些实现细节已经改变,但总体思路在40年中几乎保持不变:MySQL,PostgreSQL,Oracle,SQL Server等中的事务支持与系统R的支持非常相似。 +在二十一世纪末期,非关系(NoSQL)数据库开始普及。他们的目标是通过提供新的数据模型选择(参见第2章),并通过默认包括复制(第5章)和分区(第6章)来改善关系现状。交易是这种运动的主要原因:这些新一代数据库中的许多数据库完全放弃了交易,或者重新定义了这个词来描述比以前更为理解的更弱的一套保证[4]。 +随着这种新型分布式数据库的炒作,人们普遍认为交易是可扩展性的对立面,任何大型系统都必须放弃交易以保持良好的性能和高可用性[5, 6]。另一方面,数据库供应商有时将交易保证作为“重要应用”和“有价值数据”的基本要求。这两种观点都是纯粹的夸张。 +事实并非如此简单:与其他技术设计选择一样,交易具有优势和局限性。为了理解这些权衡,让我们进入交易可以提供的保证的细节 - 无论是在正常运行中还是在各种极端(但是现实的)情况下。 + +### ACID的含义 + +交易所提供的安全保证通常由众所周知的首字母缩略词ACID来描述,ACID代表原子性,一致性,隔离性和耐久性。它由TheoHärder和Andreas Reuter于1983年创建,旨在为数据库中的容错机制建立精确的术语。 +但实际上,一个数据库的ACID实现并不等于另一个实现。例如,我们将会看到,围绕着隔离的含义有许多含糊不清[8]。高层的想法是健全的,但恶魔是在细节。今天,当一个系统声称是“符合ACID”的时候,目前还不清楚你可以期待什么保证。不幸的是,ACID主要是一个营销术语。 +(不符合ACID标准的系统有时被称为BASE,它代表基本可用性,软状态和最终一致性[9],这比ACID的定义更加模糊,似乎BASE的唯一合理的定义是“不是ACID”,即它几乎可以代表任何你想要的东西。) +让我们深入了解原子性,一致性,隔离性和持久性的定义,因为这可以让我们改进我们的交易思想。 + +#### Atomicity: 原子性 + +一般来说,原子是指不能分解成小部分的东西。这个词在计算的不同分支中意味着相似但又微妙不同的东西。例如,在多线程编程中,如果一个线程执行一个原子操作,这意味着另一个线程无法看到该操作的一半结果。系统只能处于操作之前或操作之后的状态,而不是介于两者之间的状态。 +相比之下,在ACID的情况下,原子性不是关于并发性。它没有描述如果几个进程试图同时访问相同的数据会发生什么情况,因为它包含在字母I下,用于隔离(请参见“隔离”(第195页))。 +而是,ACID原子性描述了如果客户想要进行多次写入会发生什么情况,但是在处理了一些写入之后发生故障,例如进程崩溃,网络连接中断,磁盘变满或者某种完整性约束被违反。如果这些写入被分组到一个原子事务中,并且该事务由于错误而不能完成(提交),则该事务将被中止,并且数据库必须丢弃或撤消该事务中迄今为止所做的任何写入。 +没有原子性,如果通过多次更改发生错误,很难知道哪些更改已经生效,哪些没有生效。该应用程序可以再试一次,但冒着两次相同的风险,导致重复或不正确的数据。原子性简化了这个问题:如果事务被中止,应用程序可以确定它没有改变任何东西,所以它可以安全地重试。 +ACID原子性的定义特征是能够在错误中止事务并且丢弃来自该事务的所有写入的能力。或许堕胎将是一个比原子性更好的术语,但是我们将坚持原子性,因为这是通常的词。 + +#### Consistency:一致性 + +一致性这个词非常重要: +•在第5章中,我们讨论了副本一致性以及在异步复制系统中出现的最终一致性问题(请参阅第161页上的“复制滞后问题”)。 +•一致性散列是某些系统用于重新分区的一种分区方法(请参阅“一致性散列”第191页)。 +•在CAP定理(参见第9章)中,一致性一词用于表示可线性化(请参见“线性化”(第295页))。 +•在ACID的情况下,一致性是指数据库的应用程序特定概念处于“良好状态”。 +不幸的是,同一个词至少有四种不同的含义。 +ACID一致性的概念是,您对数据(不变量)有一定的陈述,这些陈述必须始终是真实的,例如,在会计系统中,所有账户的信用和借记必须始终保持平衡。如果一个事务以一个根据这些不变量有效的数据库开始,并且在事务处理期间的任何写入保持有效性,那么你可以确定不变量总是被满足。 +但是,这种一致性的思想取决于应用程序的不变量的概念,应用程序有责任正确定义它的事务,以保持一致性。这不是数据库可以保证的事情:如果您编写的数据违反了您的不变量,数据库无法阻止您。 (一些特定类型的不变量可以由数据库检查,例如使用外键约束或唯一性约束,但是一般来说,应用程序定义什么数据是有效的或者无效的 - 数据库只存储它。 +原子性,隔离性和持久性是数据库的属性,而一致性(在ACID意义上)是应用程序的属性。应用程序可能依赖于数据库的原子性和隔离属性来实现一致性,但这并不取决于数据库本身。因此,字母C不属于ACID.i + +#### Isolation:隔离 + +大多数数据库同时被多个客户端访问。如果他们读取和写入数据库的不同部分,这是没有问题的,但是如果他们正在访问相同的数据库记录,则可能会遇到并发问题(竞争条件)。 +图7-1是这类问题的一个简单例子。假设你有两个客户同时增加一个存储在数据库中的计数器。每个客户端需要读取当前值,加1,并写回新值(假设数据库中没有增加操作)。在图7-1中,柜台应该从42增加到44,因为两个增量发生了,但实际上由于竞态条件只能到43。 +ACID意义上的隔离意味着同时执行的事务是相互隔离的:它们不能彼此的脚趾。传统的数据库教科书将隔离形式化为可序列化,这意味着每个事务可以假装它是在整个数据库上运行的唯一事务。数据库确保当事务已经提交时,结果与它们连续运行(一个接一个)是一样的,尽管实际上它们可能已经运行了[10]。 + +![](img/fig7-1.png) + +**图7-1 两个客户之间的竞争状态同时递增计数器。** + +然而,在实践中,很少使用可序列化隔离,因为它带来了性能损失。一些流行的数据库,如Oracle 11g,甚至没有实现它。在Oracle中有一个名为“serializable”的隔离级别,但实际上它实现了一种叫做快照隔离的功能,这是一种比serializability更弱的保证[8,11]。我们将在第233页的“弱等级”中探索快照隔离和其他形式的隔离。 + +#### Durability 持久性 + +数据库系统的目的是提供一个安全的地方,可以存储数据而不用担心丢失数据。耐久性是一个承诺,即一旦交易成功完成,即使存在硬件故障或数据库崩溃,所写的任何数据也不会被遗忘。 + +在单节点数据库中,耐久性通常意味着数据已被写入非易失性存储设备,如硬盘驱动器或SSD。它通常还包括预写日志或类似的文件(请参阅第77页的“使B树可靠”),以便在磁盘上的数据结构损坏时进行恢复。在复制的数据库中,可用性可能意味着数据已成功复制到某些节点。为了提供持久性保证,数据库必须等到这些写入或复制完成后才能报告事务成功提交。 + +如第6页的“可靠性”中所述,完美的持久性不存在:如果所有硬盘和所有备份同时被销毁,那么显然没有任何数据库能够为您节省。 + + + +> #### 复制和持久性 +> +> 从历史上看,耐用性意味着写入存档磁带。然后它被理解为写入磁盘或SSD。最近,它已被改编为意味着复制。哪个实施更好? +> 事实是,没有什么是完美的: +> •如果您写入磁盘并且机器死机,即使您的数据没有丢失,在您修复机器或将磁盘传输到另一台机器之前,也无法访问。复制的系统可以保持可用。 +> •一个相关的故障(停电或一个可能导致特定输入的每个节点崩溃的错误)可能会一次性删除所有副本(请参阅第6页的「可靠性」),丢失任何仅在内存中的数据。因此写入磁盘仍然与内存数据库相关。 +> •在异步复制系统中,当引导器变得不可用时,最近的写入操作可能会丢失(请参阅第156页的「处理节点中断」)。 +> •当电源突然断电时,特别是固态硬盘被证明有时违反了应有的保证:甚至fsync也不能保证正常工作[12]。磁盘固件可能有错误,就像任何其他类型的软件一样[13,14]。 +> 存储引擎和文件系统之间的微妙交互可能会导致难以追踪的错误,并可能导致磁盘上的文件在崩溃后被损坏[15,16]。 +> •磁盘上的数据可能会逐渐被破坏而不会被检测到[17]。如果数据已损坏一段时间,副本和最近的备份也可能损坏。在这种情况下,您将需要尝试从历史备份中恢复数据。 +> •一项关于固态硬盘的研究发现,在运行的前四年,30%到80%的硬盘至少发生一个坏块[18]。磁性硬盘驱动器的坏道率较低,但比SSD更高的完全故障率。 +> •如果SSD断电,可能会在几周内开始丢失数据,具体取决于温度[19]。 +> 在实践中,没有一种技术可以提供绝对的保证。只有各种降低风险的技术,包括写入磁盘,复制到远程机器和备份 - 它们可以并且应该一起使用。与往常一样,采取任何理论上的“保证”,用一粒健康的盐都是明智的做法。 + +### 单对象和多对象操作 + +回顾一下,在ACID中,原子性和隔离性描述了如果客户在同一事务中进行多次写入时数据库应该做的事情: + +* 原子性 + +如果在一系列写操作的中途发生错误,则应中止事务处理,并废除写入该处的写操作。换句话说,数据库不必担心部分失败,通过提供全或无的保证。 + +* 隔离 + +同时运行的交易不应该互相干扰。例如,如果一个事务进行多次写入,则另一个事务应该看到全部或者全部写入,而不是一些子集。 + +这些定义假定您想一次修改几个对象(行,文档,记录)。如果需要保持多个数据同步,则通常需要这种多对象事务。图7-2显示了一个来自电子邮件应用程序的例子。要显示用户的未读邮件数量,可以查询如下所示的内容: + +```sql +SELECT COUNT(*)FROM emails WHERE recipient_id = 2 AND unread_flag = true +``` + +但是,如果电子邮件太多,您可能会发现这个查询太慢,并决定将未读邮件的数量存储在一个单独的字段(一种规范化)中。现在,每当一个新的消息进来,你也必须增加未读的计数器,每当一个消息被标记为已读,你也必须减少未读的计数器。 + +在图7-2中,用户2遇到异常情况:邮箱列表显示未读消息,但计数器显示零未读消息,因为计数器增量还没有发生.ii隔离将通过确保用户2看到插入的电子邮件和更新的计数器,或者都不是,但不是一个不一致的中间点。 + +[^ii]: 可以说,电子邮件应用程序中的错误计数器并不是特别重要的问题。 或者,考虑一个客户账户余额,而不是一个未读的柜台,而不是一个电子邮件的支付交易。 + +![](img/fig7-2.png) + +**图7-2 违反隔离:一个事务读取另一个事务的未被执行的写入(“脏读”)。** + +图7-3说明了对原子性的需求:如果在事务过程中发生错误,邮箱和未读计数器的内容可能会失去同步。在原子事务中,如果对计数器的更新失败,事务将被中止,并且插入的电子邮件将被回滚。 + +![](img/fig7-3.png) + +**图7-3。原子性可以确保,如果发生错误,则该事务的任何先前写入都会被撤消,以避免不一致的状态。** + +多对象事务需要某种方式来确定哪些读写操作属于同一个事务。在关系数据库中,通常基于客户端到数据库服务器的TCP连接来完成:在任何特定连接上,`BEGIN TRANSACTION`和`COMMIT`语句之间的所有内容都被认为是同一事务的一部分.[^iii] + +[^iii]: 这并不理想。如果TCP连接中断,则事务必须中止。如果中断发生在客户端请求提交之后,但在服务器确认提交发生之前,客户端不知道交易是否已提交。为了解决这个问题,事务管理器可以通过一个唯一的事务标识符对操作进行分组,这个标识符没有绑定到特定的TCP连接。我们将回到第516页的“数据库的端到端的争论”中的这个主题。 + +另一方面,许多非关系数据库并没有将这些操作组合在一起的方法。即使存在多对象API(例如,键值存储可能具有在一个操作中更新几个键的多重放置操作),但这并不一定意味着它具有事务语义:该命令可能成功一些键和其他的失败,使数据库处于部分更新的状态。 + +#### 单对象写入 + +当一个对象被改变时,原子性和隔离也是适用的。例如,假设您正在向数据库写入一个20 KB的JSON文档: + +* 如果在发送第一个10 KB之后网络连接中断,数据库是否存储了不可解析的10 KB JSON片段? +* 如果在数据库正在覆盖磁盘上的前一个值的过程中电源发生故障,是否最终将新旧值拼接在一起? +* 如果另一个客户端在写入过程中读取该文档,是否会看到部分更新的值? + +这些问题会令人难以置信的混淆,因此存储引擎几乎普遍的目的是在一个节点上的单个对象(例如键值对)上提供原子性和隔离性。原子性可以通过使用日志来实现崩溃恢复(请参阅第82页的“使B树可靠”),并且可以使用每个对象的锁来实现隔离(每次只允许一个线程访问对象) )。 + +一些数据库也提供更复杂的原子操作,例如增量操作,这样就不需要像图7-1那样的读 - 修改 - 写循环。同样流行的是比较和设置操作,只有当其他人没有同时更改该值时才允许进行写操作(请参见“比较和设置”一节第245页)。 + +这些单对象操作很有用,因为它们可以防止在多个客户端尝试同时写入同一个对象时丢失更新(请参阅“防止丢失的更新”第221页)。但是,它们不是通常意义上的交易。比较和设置以及其他单一对象操作被称为“轻量级事务”,甚至被称为“ACID”[20,21,22],但是这个术语是误导性的。事务通常被理解为将多个对象上的多个操作分组为一个执行单元的机制。[^iv] + +[^iv]: 严格地说,原子增量这个术语在多线程编程的意义上使用了原子这个词。 在ACID的情况下,它实际上应该被称为孤立的或可序列化的增量。 但是,这是越来越nitpicky。 + +#### 需要多对象事务 + +许多分布式数据存储已经放弃了多对象事务,因为它们很难跨分区实现,而且在需要高可用性或高性能的情况下,它们可能会遇到阻碍。但是,没有什么能从根本上防止分布式数据库中的事务,我们将在第9章讨论分布式事务的实现。 +但是我们是否需要多对象交易?是否有可能只用键值数据模型和单对象操作来实现任何应用程序? +有一些使用情况下,单对象插入,更新和删除是足够的。但是,在许多其他情况下,需要协调写入几个不同的对象: +•在关系数据模型中,一个表中的行通常具有对另一个表中的行的外键引用。 (同样,在一个类似图形的数据模型中,一个顶点与其他顶点有边)。多对象事务允许你确保这些引用保持有效:当插入几个相互引用的记录时,外键有是正确的和最新的,或者数据变得荒谬。 +•在文档数据模型中,需要一起更新的字段通常在同一个文档中,这被视为单个对象 - 更新单个文档时不需要多对象事务。但是,缺乏连接功能的文档数据库也会鼓励非规范化(请参阅第38页上的“与文档数据库相关的对比”)。当需要更新非规范化的信息时,如图7-2所示,您需要一次更新多个文档。事务在这种情况下非常有用,可以防止非规范化的数据不同步。 +•在具有二级索引的数据库中(几乎除了纯粹的键值存储以外的所有内容),每次更改值时都需要更新索引。从事务角度来看,这些索引是不同的数据库对象:例如,如果没有事务隔离,记录可能出现在一个索引中,而不是另一个索引中,因为第二个索引的更新还没有发生。 +这些应用程序仍然可以在没有交易的情况然而,没有原子性,错误处理就变得复杂得多,缺乏隔离性会导致并发问题。我们将在第233页的“弱隔离级别”中讨论这些问题,并在第12章中探讨其他方法。 + +#### 处理错误和中止 + +事务的一个关键特性是,如果发生错误,它可以被中止并安全地重试。 ACID数据库是基于这样一种理念:如果数据库有违反其原子性,隔离性或持久性的危险,则完全放弃交易,而不是完全放弃交易。 + +不是所有的系统都遵循这个理念。具体而言,具有无引导复制的数据存储(请参见第167页的“无引导复制”)在“尽力而为”的基础上工作得更多,可以概括为“数据库将尽其所能,并且运行到一个错误,它不会撤消它已经完成的事情“ - 所以这是应用程序的责任,从错误中恢复。 + +错误将不可避免地发生,但许多软件开发人员更喜欢只考虑快乐的道路,而不是错误处理的复杂性。例如,像Rails的ActiveRecord和Django这样的弹性对象关系映射(ORM)框架不会重试异常事务 - 这个错误通常会导致一个异常,导致堆栈出现异常,所以任何用户输入都会被丢弃,一个错误信息。这是一个耻辱,因为整个中止的重点是安全的重试。 + +虽然重试一个中止的事务是一个简单而有效的错误处理机制,但它并不完美: + +* 如果事务实际上成功了,但是在服务器试图确认成功提交给客户端(所以客户端认为失败)时网络发生故障,那么重试事务会导致它被执行两次,除非你有一个额外的应用程序,级别的重复数据删除机制已到位。 +* 如果错误是由于过载造成的,则重试交易将使问题变得更糟,而不是更好。为了避免这种反馈周期,您可以限制重试次数,使用指数回退,并处理与过载相关的错误(与可能的情况不同)。 +* 仅在暂时性错误(例如,由于死锁,异常情况,临时性网络中断和故障转移)之后才值得重试。在发生永久性错误(例如,违反约束)之后,重试将毫无意义。 +* 如果事务在数据库之外也有副作用,即使事务被中止,也可能发生这些副作用。例如,如果您正在发送电子邮件,则每次重试交易时都不会再发送电子邮件。如果您想确保几个不同的系统提交或放弃在一起,两阶段提交可以提供帮助(我们将在第354页的“原子提交和两阶段提交(2PC)”中讨论这个问题)。 +* 如果客户端进程在重试时失败,则任何试图写入数据库的数据都将丢失。 + + + +## 弱隔离级别 + +如果两个事务不触及相同的数据,它们可以安全地并行运行,因为两者都不依赖于另一个。当一个事务读取由另一个事务同时修改的数据时,或者当两个事务试图同时修改相同的数据时,并发问题(竞争条件)才起作用。 + +并发性错误很难通过测试找到,因为这样的错误只有在运行不正常时才会触发。这样的时间问题可能很少发生,通常很难重现。并发性也很难推理,特别是在大型应用程序中,您不一定知道哪些其他代码正在访问数据库。如果您一次只有一个用户,应用程序开发就非常困难;有许多并发用户使得它更加困难,因为任何一个数据块都可能随时改变。 + +出于这个原因,数据库一直试图通过提供事务隔离来隐藏应用程序开发者的并发问题。从理论上讲,隔离应该让你假装没有并发发生,让你的生活更轻松:可序列化的隔离意味着数据库保证事务的效果与连续运行(即一次一个没有任何并发)是一样的。 + +实际上,隔离不幸并不那么简单。可序列化的隔离性能很高,许多数据库不愿意支付这个价格[8]。因此,系统通常使用较弱的隔离级别来防止一些并发问题,但不是全部。这些孤立的程度难以理解,并且会导致微妙的错误,但是它们仍然在实践中被使用[23]。 + +并发性错误导致的并发性错误不仅仅是一个理论问题。他们造成了大量的资金损失[24,25],导致了财务审计人员的调查[26],并导致客户数据被破坏[27]。关于这类问题的一个流行的评论是“如果你正在处理财务数据,请使用一个ACID数据库!” - 但是这一点没有提到。即使是很多流行的关系型数据库系统(通常被认为是“ACID”)也使用弱隔离,所以它们不一定能防止这些错误的发生。 + +我们不必盲目依赖工具,而应该对存在的并发问题的种类以及如何防止这些问题有一个很好的理解。然后,我们可以使用我们所掌握的工具来构建可靠和正确的应用程序。 + +在本节中,我们将看几个在实践中使用的弱(不可串行化)隔离级别,并详细讨论哪种竞争条件可以发生也可能不发生,以便您可以决定什么级别适合您的应用程序。一旦我们完成了这个工作,我们将详细讨论可串行性(请参阅“可序列化”第259页)。我们讨论的隔离级别将是非正式的,使用示例。如果你需要严格的定义和分析它们的属性,你可以在学术文献中找到它们[28,29,30]。 + +### 读已提交 + +最基本的事务隔离级别是read committed.v它提供了两个保证: + +1. 从数据库读取时,只能看到已提交的数据(没有脏读)。 +2. 写入数据库时,只会覆盖已经写入的数据(没有脏写入)。 + +我们来更详细地讨论这两个保证。 + +#### 没有脏读 + +设想一个事务已经将一些数据写入数据库,但事务还没有提交或中止。另一个事务可以看到未提交的数据吗?如果是的话,那就叫做脏读[2][2]。 + +在读提交隔离级别运行的事务必须防止脏读。这意味着事务的任何写入操作只有在该事务提交时才能被其他人看到(然后所有的写入操作都会立即可见)。如图7-4所示,用户1设置了`x = 3`,但用户2的`get x`仍旧返回旧值2,而用户1尚未提交。 + +![](img/fig7-4.png) + +**图7-4 没有脏读:用户2只有在用户1的事务已经提交后才能看到x的新值。** + +[^v]: 某些数据库支持甚至更弱的隔离级别,称为读取未提交。它可以防止脏写入,但不防止脏读。 + +有几个原因为什么需要防止脏读: + +- 如果事务需要更新多个对象,脏读取意味着另一个事务可能会看到一些更新,而不是其他更新。例如,在图7-2中,用户看到新的未读电子邮件,但看不到更新的计数器。这是电子邮件的一个肮脏的阅读。看到处于部分更新状态的数据库会对用户造成混淆,并可能导致其他事务做出错误的决定。 +- 如果事务中止,则所有写入操作都需要回滚(如图7-3所示)。如果数据库允许脏读,那就意味着一个事务可能会看到稍后回滚的数据,即从未实际提交给数据库的数据。有关后果的推理很快变成了令人费解的想法。 + +#### 没有脏写 + +如果两个事务同时尝试更新数据库中的相同对象,会发生什么情况?我们不知道写入的顺序是怎样的,但是我们通常认为后面的写入会覆盖前面的写入。 + +但是,如果先前的写入是尚未提交的事务的一部分,那么会发生什么情况,因此后面的写入会覆盖未提交的值?这被称为肮脏的写作[28]。在读取提交的隔离级别上运行的事务必须防止脏写入,通常是延迟第二次写入,直到第一次写入事务提交或中止为止。 + +通过防止脏写,这个隔离级别避免了一些并发问题: + +* 如果事务更新多个对象,脏写入会导致不好的结果。例如,考虑图7-5,图7-5说明了一个二手车销售网站,Alice和Bob两个人同时试图购买同一辆车。购买汽车需要两次数据库写入:网站上的商品需要更新以反映买家,销售发票需要发送给买家。在图7-5的情况下,销售授予鲍勃(因为他执行获奖更新列表表),但发票发送给爱丽丝(因为她执行获奖更新发票表)。阅读承诺这样的事故。 +* 但是,提交读取并不能防止图7-1中两个计数器增量之间的竞争状态。在这种情况下,第二次写入发生在第一个事务提交后,所以它不是一个肮脏的写。这仍然是不正确的,但是出于不同的原因,在第242页的“防止丢失的更新”中,我们将讨论如何使这种计数器增量安全。 + +![](img/fig7-5.png) + +**图7-5 使用脏写,来自不同事务的冲突写入可能会混淆在一起** + +#### 实现读已提交 + +读提交是一个非常流行的隔离级别。这是Oracle 11g,PostgreSQL,SQL Server 2012,MemSQL和其他许多数据库的默认设置[8]。 + +最常见的情况是,数据库通过使用行级锁来防止脏写入:当事务想要修改特定对象(行或文档)时,它必须首先获得该对象的锁。然后必须保持该锁,直到交易被提交或中止。只有一个事务可以保存任何给定对象的锁;如果另一个事务要写入同一个对象,则必须等到第一个事务被提交或中止后才能获取该锁并继续。这种锁定是通过读取提交模式(或更强的隔离级别)中的数据库自动完成的。 + +我们如何防止脏读?一种选择是使用相同的锁,并要求任何想要读取对象的事务来简单地获取该锁,然后在读取之后立即再次释放该锁。这将确保读取不会发生,而对象有一个肮脏的,未提交的值(因为在那段时间锁将由举行了写的事务)。 + +但是,要求读取锁定的方法在实际中并不奏效,因为长时间运行的写入事务会强制许多只读事务等待长时间运行的事务完成。这会损害只读事务的响应时间,并且不利于可操作性:由于等待锁定,应用程序的某个部分的减速可能会在完全不同的应用程序中产生连锁效应。 + +出于这个原因,大多数数据库[^vi]使用上述方法防止脏读。如图7-4:对于写入的每个对象,数据库都会记住旧的提交值和由当前持有写入锁的事务设置的新值。 当交易正在进行时,任何其他读取对象的交易都被赋予旧的价值。 只有当提交新值时,交易才会切换到读取新值。 + +### 快照隔离和可重复读取 + +如果你从表面上看读取承诺的隔离,你可以原谅它认为事务需要做的一切事情:它允许中止(原子性要求),它防止读取不完整的事务结果,并排写入的并发写入。事实上,这些是非常有用的功能,而且比没有交易的系统可以得到更多的保证。 + +但是,在使用此隔离级别时,仍然有很多方法可能会导致并发错误。例如,图7-6说明了提交读取时可能发生的问题。 + +![](img/fig7-6.png) + +**图7-6 读取偏斜:Alice观察数据库处于不一致的状态** + +说爱丽丝在银行有1000美元的储蓄,分两个账户,每个500美元。现在一笔交易从她的账户中转移了100美元到另一笔账户。如果她不幸在交易正在处理的同一时间查看其账户余额列表,则可以在收到付款之前的一段时间看到一个账户余额(余额为500美元),另一个外汇转账完成后的账户(新余额为400美元)。对于爱丽丝来说,现在她的账户似乎只有900美元 - 看起来100美元已经消失了。 + +[^vi]: 在撰写本文时,唯一使用读取提交隔离锁定的主流数据库是`read_committed_snapshot = off`配置中的IBM DB2和Microsoft SQL Server [23,36]。 + +这种异常被称为不可重复读取或读取歪斜:如果Alice在交易结束时再次读取账户1的余额,她将看到与她之前的查询中看到的不同的值(600美元)。在阅读承诺的隔离条件下,阅读偏差被认为是可接受的:Alice看到的帐户余额确实是在阅读时确定的。 + +> 不幸的是,skew 这个词倾斜是超负荷的:我们以前使用它是因为热点的不平衡工作量(请参阅第205页上的“偏斜的工作负荷和减轻热点”),而这意味着计时异常。 + + + +在Alice的情况下,这不是一个长期的问题,因为如果她几秒钟后重新加载在线银行网站,她很可能会看到一致的帐户余额。但是,有些情况不能容忍这种暂时的不一致: + +* *备份* + + 进行备份需要复制整个数据库,这可能需要花费数小时才能完成。在备份过程正在运行的过程中,将继续写入数据库。因此,您可能会得到包含较旧版本数据的备份部分以及包含较新版本的其他部分。如果您需要从这样的备份中恢复,那么不一致(如消失的钱)就会变成永久的。 + +* *分析查询和完整性检查* + + 有时,您可能需要运行一个查询,扫描大部分的数据库。这样的查询在分析中很常见(请参阅第90页的“事务处理或分析?”),也可能是定期完整性检查(即监视数据损坏)的一部分。如果这些查询在不同时间点观察数据库的某些部分,则这些查询可能会返回无意义的结果。 + +快照隔离[28]是这个问题最常见的解决方案。这个想法是,每个事务都从数据库的一致快照中读取 - 也就是说,事务处理可以看到事务开始时在数据库中提交的所有数据。即使数据随后被另一个事务更改,每个事务也只能看到该特定时间点的旧数据。 + +快照隔离对长时间运行的只读查询(如备份和分析)非常有用。如果查询的运行数据在查询执行的同时发生变化,则很难推理查询的含义。当一个事务可以看到数据库的一致快照,并在某个特定的时间点被冻结时,它会更容易理解。 + +快照隔离是一个流行的功能:PostgreSQL支持,MySQL与InnoDB存储引擎,Oracle,SQL Server等[23,31,32]。 + +#### 实现快照隔离 + +与读取提交的隔离类似,快照隔离的实现通常使用写入锁来防止脏写入(请参阅“实施读取已提交”(第217页)),这意味着进行写入的事务可以阻止另一个写入同一事务的进程目的。但是,读取不需要任何锁定。从性能的角度来看,快照隔离的一个关键原则是读者不会阻止作者,作者也不会阻止读者。这允许数据库在处理正常写入的同时处理一致快照上的长时间运行的读取查询,而两者之间没有任何锁定争用。 + +为了实现快照隔离,数据库使用了我们看到的用于防止图7-4中的脏读的机制的一般化。数据库必须可能保留一个对象的几个不同的提交版本,因为各种正在进行的交易可能需要在不同的时间点看到数据库的状态。因为它并排维护着多个版本的对象,所以这种技术被称为多版本并发控制(MVCC)。 + +如果一个数据库只需要提供读提交的隔离,而不提供快照的隔离,那么保留一个对象的两个版本就足够了:提交的版本和被覆盖但尚未提交的版本。但是,支持快照隔离的存储引擎通常也使用MVCC作为读提交隔离级别。一种典型的方法是提交读取为每个查询使用单独的快照,而快照隔离对整个事务使用相同的快照。 + +图7-7说明了如何在PostgreSQL中实现基于MVCC的快照隔离[31](其他实现类似)。当一个事务开始时,它被赋予一个唯一的,永远增长的事务ID(txid)。每当事务向数据库写入任何内容时,它所写入的数据都会被标记为写入者的事务ID。 + +[^vii]: 事实上,事务ID是32位整数,所以它们在大约40亿次事务之后溢出。 PostgreSQL的真空过程执行清理,确保溢出不会影响数据。 + +![](img/fig7-7.png) + +**图7-7 使用多版本对象实现快照隔离** + +表中的每一行都有一个created_by字段,其中包含将该行插入到表中的事务的ID。此外,每行都有一个deleted_by字段,最初是空的。如果某个事务删除了一行,那么该行实际上并未从数据库中删除,而是通过将deleted_by字段设置为请求删除的事务的ID来标记为删除。在稍后的时间,当确定没有事务可以再访问已删除的数据时,数据库中的垃圾收集过程将删除标记为删除的行并释放它们的空间。 + +更新内部翻译成删除和创建。例如,在图7-7中,交易13从账户2中扣除100美元,将余额从500美元改为400美元。账户表现在实际上包含账户2的两行:一笔余额为\$500的行被标记为被事务13删除,一行的余额为\$400,由事务13创建。 + +#### 可见性规则用于观察一致的快照 + +当一个事务从数据库中读取时,事务ID被用来决定它可以看到哪些对象,哪些是不可见的。通过仔细定义可见性规则, + +数据库可以向应用程序呈现一致的数据库快照。这工作如下: + +1. 在每次交易开始时,数据库列出当时所有其他交易(尚未提交或中止)的交易清单。即使交易随后提交,任何写入的交易都会被忽略。 +2. 被中止的事务所做的任何写入都被忽略。 +3. 由具有较晚的事务ID(即,在当前事务开始之后开始的)的事务所做的任何写入都被忽略,而不管这些事务是否已经提交。 +4. 所有其他写入对应用程序的查询都是可见的。 + +这些规则适用于创建和删除对象。在图7-7中,当交易12从账户2读取时,它看到$ 500的余额,因为$ 500余额的删除是由交易13完成的(根据规则3,交易12看不到交易13所做的删除),并且400美元的余额的创建还不可见(按照相同的规则)。 + +换句话说,如果以下两个条件都成立,则可见一个对象: + +* 读者事务开始时,创建该对象的事务已经提交。 +* 对象未被标记为删除,或者如果是,请求删除的事务在读者事务开始时尚未提交。 + +长时间运行的事务可能会长时间继续使用快照,并继续读取(从其他事务的角度来看)早已被覆盖或删除的值。由于从不更新值,而是每次更改值时创建一个新版本,数据库可以提供一致的快照,同时只产生一个小的开销。 + +#### 索引和快照隔离 + +索引如何在多版本数据库中工作?一种选择是使索引简单地指向对象的所有版本并且需要索引查询来过滤掉当前事务不可见的任何对象版本。当垃圾收集删除任何事务不再可见的旧对象版本时,相应的索引条目也可以被删除。 +在实践中,许多实现细节决定了多版本并发控制的性能。例如,如果同一对象的不同版本可以放在同一个页面上,PostgreSQL就可以避免索引更新的优化[31]。 + +在CouchDB,Datomic和LMDB中使用另一种方法。虽然它们也使用B树(请参阅第79页上的“B树”),但它们使用仅附加/正在写入的变体,它们在更新时不覆盖树的页面,而是创建一个新的每个修改页面的副本。父页面,直到树的根,被复制和更新,以指向他们的子页面的新版本。任何不受写入影响的页面都不需要被复制,并且保持不变[33,34,35]。 + +使用仅附加B树,每个写入事务(或一批事务)都会创建一个新的B树根,而且特定的根在数据库创建时是一致的快照。没有必要根据事务ID过滤掉对象,因为后续写入不能修改现有的B-树;他们只能创建新的树根。但是,这种方法也需要一个压缩和垃圾收集的后台进程。 + +#### 可重复的读取和命名混淆 + +快照隔离是一个有用的隔离级别,特别是对于只读事务。但是,许多实现它的数据库都是通过不同的名称来调用它。在Oracle中称为可序列化的,在PostgreSQL和MySQL中称为可重复读取[23]。 + +这种命名混淆的原因是SQL标准没有快照隔离的概念,因为标准是基于System R 1975年定义的隔离级别[2],并且快照隔离尚未发明然后。相反,它定义了可重复读取,这看起来表面上与快照隔离很相似。 PostgreSQL和MySQL调用其快照隔离级别可重复读取,因为它符合标准的要求,所以他们可以要求遵守标准。 + +不幸的是,SQL标准对隔离级别的定义是有缺陷的 - 它是模糊的,不精确的,而不是像标准那样独立于实现[28]。尽管有几个数据库实现了可重复读取,但它们实际提供的保证存在很大的差异,尽管表面上是标准化的[23]。在研究文献[29,30]中已经有了可重复阅读的正式定义,但是大多数的实现并不能满足这个正式的定义。最后,IBM DB2使用“可重复读”来引用可串行化[8]。 + +结果,没有人真正知道可重复的读取手段。 + +### 防止丢失的更新 + +到目前为止,我们讨论的读取提交和快照隔离级别主要是保证只读事务在并发写入时可以看到什么。我们主要忽略了同时写入两个事务的问题 - 我们只讨论了脏写入(请参阅第235页的“无肮脏的写入”),可能会发生一种特定类型的写入 - 写入冲突。 + +并发写作交易之间还有其他几种有趣的冲突。其中最着名的是丢失更新问题,如图7-1所示,以两个并发计数器增量为例。 + +如果应用程序从数据库中读取一些值,修改它并写回修改的值(读取 - 修改 - 写入周期),则可能会发生丢失的更新问题。如果两个事务同时执行,则其中一个修改可能会丢失,因为第二个写入不包括第一个修改。 (我们有时会说后面写的是先前写的。)这种模式发生在各种不同的情况下: + +* 增加计数器或更新账户余额(需要读取当前值,计算新值并写回更新后的值) +* 将本地更改设置为复杂的值,例如,将元素添加到JSON文档中的列表(需要解析文档,进行更改并写回修改的文档) +* 两个用户同时编辑wiki页面,每个用户通过将整个页面内容发送到服务器来保存其更改,覆盖数据库中当前的任何内容 + +由于这是一个普遍的问题,所以已经开发了各种解决方案。 + +#### 原子写 + +许多数据库提供了原子更新操作,从而消除了在应用程序代码中执行读取 - 修改 - 写入循环的需要。如果您的代码可以用这些操作来表示,那么它们通常是最好的解决方案。例如,下面的指令在大多数关系数据库中是并发安全的: + +```sql +UPDATE counters SET value = value + 1 WHERE key = 'foo'; +``` + +类似地,像MongoDB这样的文档数据库提供了对JSON文档的一部分进行本地修改的原子操作,Redis提供了修改数据结构(如优先级队列)的原子操作。并不是所有的写操作都可以用原子操作的方式来表达,例如维基页面的更新涉及到任意文本编辑[^viii],但是在可以使用原子操作的情况下,它们通常是最好的选择。 + +原子操作通常通过在读取对象时对其进行独占锁定来实现,以便在更新完成之前没有其他事务可以读取它 + +[^viii]: 将文本文档的编辑表示为原子突变流是可能的,尽管相当复杂。有关指针的信息,请参阅第174页上的“自动冲突解决”。 + +应用。这种技术有时被称为游标稳定性[36,37]。另一个选择是仅仅强制所有的原子操作在一个线程上执行。 + +不幸的是,对象关系映射框架可以很容易地意外编写执行不安全读取 - 修改 - 写入循环的代码,而不是使用数据库提供的原子操作[38]。如果你知道自己在做什么,这不是一个问题,但它可能是测试难以发现的微妙错误的来源。 + +#### 显式锁定 + +如果数据库的内置原子操作没有提供必要的功能,防止丢失更新的另一个选择是让应用程序显式地锁定将要更新的对象。然后应用程序可以执行读 - 修改 - 写周期,如果任何其他事务尝试同时读取同一个对象,则强制等待,直到第一个读 - 修改 - 写周期完成。 + +例如,考虑一个多人游戏,其中几个玩家可以同时移动相同的数字。在这种情况下,一个原子操作可能是不够的,因为应用程序还需要确保一个玩家的移动遵守游戏规则,这涉及到一些你不能合理地实现的作为数据库查询的逻辑。相反,您可以使用锁来防止两名玩家同时移动相同的棋子,如例7-1所示。 + +##### 例7-1 显式锁定行以防止丢失更新 + +```plsql +BEGIN TRANSACTION; +SELECT * +FROM figures +WHERE name = 'robot' AND game_id = 222 +FOR UPDATE; +-- Check whether move is valid, then update the position -- of the piece that was returned by the previous SELECT. UPDATE figures SET position = 'c4' WHERE id = 1234; +COMMIT; +``` + +* FOR UPDATE子句指示数据库应该对所有行进行锁定由此查询返回。 + + + +这是有效的,但要做到这一点,你需要仔细考虑你的应用逻辑。忘记在代码的某个地方添加一个必要的锁,很容易引入竞争条件。 + +#### 自动检测丢失的更新 + +原子操作和锁定是通过强制读取 - 修改 - 写入循环顺序发生来防止丢失更新的方法。另一种方法是允许它们并行执行,如果事务管理器检测到丢失的更新,则中止事务并强制它重试其读 - 修改 - 写周期。 + +这种方法的一个优点是数据库可以结合快照隔离高效地执行此检查。事实上,PostgreSQL的可重复读取,Oracle的可串行化和SQL Server的快照隔离级别会自动检测丢失更新何时发生,并中止违规事务。但是,MySQL / InnoDB的可重复读取没有检测到丢失的更新[23]。一些作者[28,30]认为,数据库必须防止丢失的更新,以便有资格提供快照隔离,所以MySQL在这个定义下不提供快照隔离。 + +丢失更新检测是一个很好的功能,因为它不需要应用程序代码来使用任何特殊的数据库功能,您可能会忘记使用锁定或原子操作,从而引入一个错误,但丢失的更新检测自动发生,因此不太容易出错。 + +#### 比较和设置 + +在不提供事务的数据库中,您有时会发现原子比较和设置操作(先前在第230页上的“单对象写入”中提到)。此操作的目的是为了避免丢失的更新,只有在上次读取该值时才更新。如果当前值与先前读取的值不匹配,则更新不起作用,并且必须重试读取 - 修改 - 写入周期。 +例如,为了防止两个用户同时更新同一个wiki页面,可以尝试类似这样的方式,只有当用户开始编辑页面内容时,才会发生更新: + +根据数据库的实现情况,这可能也可能不安全 + +```sql +-- This may or may not be safe, depending on the database implementation +UPDATE wiki_pages +SET content = 'new content' +WHERE id = 1234 AND content = 'old content'; +``` + +如果内容已经更改并且不再与“旧内容”相匹配,则此更新将不起作用,因此您需要检查更新是否生效,并在必要时重试。但是,如果数据库允许从旧快照中读取WHERE子句,则此语句可能无法防止丢失的更新,因为即使发生了另一个并发写入,条件也可能为真。在依赖数据库之前,检查数据库的比较和设置操作是否安全。 + +#### 冲突解决和复制 + +在复制的数据库中(参见第5章),防止丢失的更新需要另一个维度:由于它们具有多个节点上的数据副本,并且可能会在不同节点上同时修改数据,因此需要采取一些额外的步骤来防止丢失更新。 + +锁和比较和设置操作假定有一个最新的数据副本。但是,具有多领导者或无领导者复制的数据库通常允许同时发生多个写入并异步复制,因此他们无法保证数据的最新副本。因此,基于锁或比较和设置的技术不适用于这种情况。 (我们将在第324页的“Linearizability”中更详细地讨论这个问题。) + +相反,如“检测并发写入”一节第184页中所述,这种复制数据库中的一种常见方法是允许并发写入创建多个冲突版本的值(也称为同级),并使用应用程序代码或特殊数据结构在事实之后解决和合并这些版本。 + +原子操作可以在复制的上下文中很好地工作,特别是如果它们是兼容的(即,可以在不同的副本上以不同的顺序应用它们,并且仍然可以得到相同的结果)。例如,递增计数器或向元素添加元素是可交换的操作。这是Riak 2.0数据类型背后的思想,它可以防止复制副本丢失更新。当不同的客户端同时更新一个值时,Riak自动将更新合并在一起,以免丢失更新[39]。 + +另一方面,最后一次写入胜利(LWW)冲突解决方法很容易丢失更新,如第188页的“上次写入胜出(放弃并发写入)”中所述。不幸的是,LWW是许多复制数据库中的默认值。 + +#### 写倾斜和幻读 + +前面的章节中,我们看到了肮脏的写入和丢失的更新,当不同的事务并发地尝试写入相同的对象时,会出现两种竞争条件。为了避免数据损坏,这些竞争条件需要被阻止 - 既可以由数据库自动执行,也可以通过手动安全措施(如使用锁定或原子写入操作)来防止。 + +但是,这不是并发写入之间可能发生的竞争条件列表的末尾。在本节中,我们将看到一些更微妙的冲突的例子。 +首先,想象一下这个例子:你正在写医生的一个应用程序来管理他们在医院的轮班。医院通常会同时要求几位医生随叫随到,但必须至少有一位医生随时待命。医生 + +可以放弃他们的班次(例如,如果他们自己生病了),只要至少有一个同事在这个班次中继续工作[40,41]。 +现在想象一下,爱丽丝和鲍勃是两位值班医生。两人都感到不适,所以他们都决定请假。不幸的是,他们恰好在同一时间点击按钮关闭电话。图7-8说明了接下来的事情。 + +![](img/fig7-8.png) + +**图7-8 写入歪斜导致应用程序错误的示例** + +在每次交易中,您的申请首先检查两个或两个以上的医生是否正在通话;如果是的话,它假定一名医生可以安全地接通电话。由于数据库使用快照隔离,两个检查都返回2,所以两个事务都进入下一个阶段。爱丽丝更新自己的记录,让自己脱离呼叫,而鲍勃也更新自己的记录。这两个交易承诺,现在没有医生在接电话。您的电话至少有一名医生的要求已被违反。 + +#### 表征写入歪斜 + +这种异常称为写歪斜[28]。它既不是一个肮脏的写作,也不是一个丢失的更新,因为这两个事务正在更新两个不同的对象(分别是Alice和Bob的待命记录)。在这里发生冲突并不那么明显,但是这显然是一个竞争条件:如果两个交易一个接一个地运行,那么第二个 + +医生会被阻止接听电话。异常行为只有在交易同时进行时才有可能。 +您可以将写入歪斜视为丢失更新问题的一般化。如果两个事务读取相同的对象,然后更新其中一些对象(不同的事务可能更新不同的对象),则可能发生写入歪斜。在不同的事务更新相同的对象的特殊情况下,你会得到一个脏写或丢失更新异常(取决于时间)。 +我们看到,有各种不同的方法来防止丢失的更新。随着写歪斜,我们的选择更受限制: +•由于涉及多个对象,原子单对象操作不起作用。 +在一些快照隔离的实现中,你发现丢失的更新的自动检测不幸并没有帮助:在PostgreSQL的可重复读取,MySQL / InnoDB的可重复读取,Oracle可序列化或SQL Server的快照隔离中,不会自动检测到写入歪斜级别[23]。自动防止写入歪斜需要真正的可序列化隔离(请参见“可串行化”(第217页))。 +•某些数据库允许您配置约束,然后由数据库强制执行(例如,唯一性,外键约束或特定值限制)。但是,为了指定至少有一名医生必须在线,您需要一个涉及多个对象的约束。大多数数据库没有内置的对这种约束的支持,但是你可以使用触发器或者实体化视图来实现它们,这取决于数据库[42]。 +•如果无法使用可序列化的隔离级别,则此情况下的次优选项可能是显式锁定事务所依赖的行。在例子中,你可以写下如下的代码: + +```sql +BEGIN TRANSACTION; +SELECT * +FROM doctors +WHERE on_call = TRUE + AND shift_id = 1234 +FOR UPDATE; +UPDATE doctors +SET on_call = FALSE +WHERE name = 'Alice' AND shift_id = 1234; +COMMIT; +``` + +和以前一样,FOR UPDATE告诉数据库锁定这个返回的所有行用于查询。 + +#### 更多的写歪斜的例子 + +写起来歪斜起初可能看起来像是一个深奥的问题,但是一旦你意识到这一点,你可能会注意到更多的情况发生。以下是一些例子: + +***会议室预订系统*** + +假设你想强制执行,同一时间不能同时在两个会议室预订[43]。当有人想要预订时,首先检查是否存在相互冲突的预订(即预订时间范围重叠的同一房间),如果没有找到,则创建会议(请参见示例7-2)[^ix]。 + +**例7-2 会议室预订系统试图避免重复预订(在快照隔离下不安全)** + +```sql +BEGIN TRANSACTION; +-- Check for any existing bookings that overlap with the period of noon-1pm +SELECT COUNT(*) +FROM bookings +WHERE room_id = 123 AND + end_time > '2015-01-01 12:00' AND start_time < '2015-01-01 13:00'; +-- If the previous query returned zero: +INSERT INTO bookings +(room_id, start_time, end_time, user_id) +VALUES (123, '2015-01-01 12:00', '2015-01-01 13:00', 666); +COMMIT; +``` + +不幸的是,快照隔离并不能防止另一个用户同时插入冲突的会议。为了保证你不会调度冲突,你再一次需要可序列化的隔离。 + +***多人游戏*** + +在例7-1中,我们使用一个锁来防止丢失的更新(也就是确保两个玩家不能同时移动同一个数字)。但是,锁定并不妨碍玩家将两个不同的数字移动到棋盘上的相同位置,或者可能采取其他违反游戏规则的行为。根据您正在执行的规则类型,您可能可以使用唯一的约束,但否则您很容易发生写入歪斜。 + +***声称一个用户名*** + +在每个用户拥有唯一用户名的网站上,两个用户可能会尝试同时创建具有相同用户名的帐户。您可以使用交易来检查名称是否被采用,如果没有,则使用该名称创建账户。但是,像在前面的例子中那样,在快照隔离下是不安全的。幸运的是,一个唯一的约束是一个简单的解决方案(第二个事务,试图注册用户名将被中止,由于违反约束)。 + +***防止双重开支*** + +允许用户花钱或积分的服务需要检查用户花费的不多。你可以通过在用户的帐户中插入一个消费项目来实现这一点,列出帐户中的所有项目,并检查总和是否为正值[44]。有了写歪斜,可能会发生两个支出项目同时插入,一起导致余额变为负值,但这两个交易都不会注意到另一个。 + +[^ix]: 在PostgreSQL中,您可以使用范围类型更优雅地执行此操作,但在其他数据库中并未得到广泛支持。 + +#### 导致写入歪斜的幻影 + +所有这些例子都遵循类似的模式: + +1. SELECT查询通过搜索与某些搜索条件相匹配的行来检查是否满足某些要求(至少有两名医生正在通话,当时没有该房间的现有预订,该板上的位置不已经有了另外一个数字,用户名还没有被占用,账户里还有钱)。 +2. 根据第一个查询的结果,应用程序代码决定如何继续(可能继续操作,或者向用户报告错误并中止)。 +3. 如果应用程序决定继续执行,它将向数据库写入(INSERT,UPDATE或DELETE)并提交事务。 + +这个写的效果改变了步骤2的决定的前提条件。换句话说,如果在提交写入后重复从步骤1开始的SELECT查询,将得到不同的结果,因为写改变了一组行符合搜索条件(现在有一个电话的医生减少了,那时候会议室现在已经预订了,现在这个位置已经被移动了,现在用户名已经被占用,现在有了更少的钱在帐户中)。 + +这些步骤可能以不同的顺序发生。例如,您可以首先进行写入,然后进行SELECT查询,最后根据查询结果决定是放弃还是提交。在医生调用示例的情况下,在步骤3中修改的行是在步骤1中返回的行之一,所以我们可以通过锁定步骤1中的行(SELECT FOR UPDATE)来使事务安全并避免写入歪斜。但是,其他四个例子是不同的:它们检查是否存在与某些搜索条件相匹配的行,写入会添加一个匹配相同条件的行。如果步骤1中的查询没有返回任何行,则SELECT FOR UPDATE不能将锁附加到任何内容。 + +这种效应,在一个事务中的写入改变另一个事务中的搜索查询的结果,被称为幻影[3]。快照隔离避免了只读查询中的幻影,但是在像我们讨论的例子那样的读写事务中,幻影会导致特别棘手的写歪斜情况。 + +#### 实现冲突 + +如果虚幻的问题是没有对象可以附加锁,也许我们可以人为地在数据库中引入一个锁对象? + +例如,在会议室预订案例中,您可以想象创建一个时间表和房间表。此表中的每一行对应于特定时间段(例如15分钟)的特定房间。您可以提前创建所有可能的房间和时间组合的行,例如在接下来的六个月。 + +现在,要创建预订的事务可以锁定(SELECT FOR UPDATE)表中与所需房间和时间段对应的行。在获得锁定之后,它可以检查重叠的预订并像以前一样插入新的预订。请注意,附加表格不用于存储有关预订的信息 - 它完全是一组锁,用于防止同时修改同一房间和时间范围内的预订。 + +这种方法被称为物化冲突,因为它需要一个幻像,并将其变成数据库中存在的一组具体行的锁定冲突[11]。不幸的是,弄清楚如何实现冲突可能很难,也很容易出错,而让并发控制机制泄漏到应用程序数据模型是很难的。出于这些原因,如果没有其他办法可以实现,冲突的实现应被视为最后的手段。在大多数情况下,可序列化的隔离级别是更可取的。 + + + +## 串行执行 + +在本章中,我们已经看到了几个易于出现竞争条件的交易的例子。读取提交和快照隔离级别会阻止某些竞争条件,但其他竞争条件则不会。我们遇到了一些特别棘手的例子,写有歪斜和幻影。这是一个可悲的情况: + +* 隔离级别难以理解,并且在不同的数据库中不一致地实现(例如,“可重复读取”的含义差别很大)。 +* 如果您查看应用程序代码,很难判断在特定的隔离级别运行是否安全 - 特别是在大型应用程序中,您可能并不知道可能同时发生的所有事情。 + +没有好的工具来帮助我们检测竞争状况。原则上,静态分析可能有助于[26],但研究技术还没有找到实际应用的方法。并发问题的测试是很难的,因为它们通常是非确定性的 - 只有在不及时的情况下才会出现问题。 + +这不是一个新问题,自20世纪70年代以来,这种情况一直是这样,当时首先引入了较弱的隔离级别[2]。一直以来,研究人员的答案都很简单:使用可序列化的隔离! + +可串行隔离通常被认为是最强的隔离级别。它保证即使事务可以并行执行,最终的结果也是一样的,就好像它们连续执行一个一样,没有任何并发性。因此,数据库保证,如果事务在单独运行时正常运行,则它们在并发运行时继续保持正确 - 换句话说,数据库可以防止所有可能的竞争条件。 + +但是,如果可序列化隔离比弱隔离级别的混乱好得多,那么为什么不是每个人都使用它?为了回答这个问题,我们需要看看实现可串行化的选项,以及它们如何执行。目前大多数提供可串行化的数据库都使用了三种技术之一,本章后面将会介绍这些技术。 + +* 以串行顺序从字面上执行事务(请参见第252页的“实际的串行执行”) +* 两相锁定(参见第257页上的“两相锁定(2PL)”),几十年来唯一可行的选择 +* 开放式并发控制技术,例如可序列化的快照隔离(请参阅“可序列化的快照隔离(SSI)” + +目前,我们将主要在单节点数据库的背景下讨论这些技术;在第9章中,我们将研究如何将它们推广到涉及分布式系统中多个节点的事务。 + + + +#### 现实世界中的串行执行 + +避免并发问题的最简单方法是完全去除并发:在一个线程上按顺序一次只执行一个事务。通过这样做,我们完全回 + +避了检测和防止事务间冲突的问题:由此产生的隔离按定义是可序列化的。 + +尽管这似乎是一个明显的想法,但数据库设计人员只是在2007年左右才决定,执行事务的单线程循环是可行的[45]。如果多线程并发在过去的30年中被认为是获得良好性能的关键所在,那么为了使单线程执行成为可能,改变了什么呢? + +两个事态发展引起了这个反思: + +* RAM变得足够便宜,现在许多用例可以将整个活动数据集保存在内存中(请参阅第88页的“将所有内容保留在内存中”)。当事务需要访问的所有数据都在内存中时,事务处理的执行速度要比等待数据从磁盘加载时快得多。 +* 数据库设计人员意识到OLTP事务通常很短,只能进行少量的读写操作(请参阅“事务处理或分析?”)。相比之下,长时间运行的分析查询通常是只读的,因此它们可以在串行执行循环之外的一致快照(使用快照隔离)上运行。 + +串行执行事务的方法在VoltDB / H-Store,Redis和Datomic中实现[46,47,48]。设计用于单线程执行的系统有时可以比支持并发的系统更好,因为它可以避免锁定的协调开销。但是,其吞吐量仅限于单个CPU内核的吞吐量。为了充分利用单一线索,交易需要与传统形式不同的结构。 + +#### 在存储过程中封装事务 + +在数据库的早期阶段,意图是数据库事务可以包含整个用户活动流程。例如,预订机票是一个多阶段的过程(搜索路线,票价和可用座位,决定行程,在行程的每个航班上预订座位,输入旅客详细信息,付款)。数据库设计者认为,如果整个过程是一个事务,那么它就可以被原子化地执行。 + +不幸的是,人类做出决定和回应的速度非常缓慢。如果数据库事务需要等待来自用户的输入,则数据库需要支持潜在的大量并发事务,其中大部分是空闲的。大多数数据库不能高效地完成这项工作,因此几乎所有的OLTP应用程序都通过避免交互式地等待交易中的用户来保持交易的简短。在Web上,这意味着事务在同一个HTTP请求中被提交 - 一个事务不会跨越多个请求。一个新的HTTP请求开始一个新的事务。 + +即使人类已经找出了关键路径,事务仍然以交互式的客户端/服务器风格执行,一次一个语句。应用程序进行查询,读取结果,可能根据第一个查询的结果进行另一个查询,依此类推。查询和结果在应用程序代码(在一台机器上运行)和数据库服务器(在另一台机器上)之间来回发送。 + +在这种交互式的事务方式中,应用程序和数据库之间的网络通信耗费了大量的时间。如果您不允许在数据库中进行并发处理,并且一次只处理一个事务,则吞吐量将会非常糟糕,因为数据库将花费大部分时间等待应用程序发出当前事务的下一个查询。在这种数据库中,为了获得合理的性能,需要同时处理多个事务。 + +出于这个原因,具有单线程串行事务处理的系统不允许交互式多语句事务。相反,应用程序必须提前将整个事务代码作为存储过程提交给数据库。这些方法之间的差异如图7-9所示。如果事务所需的所有数据都在内存中,则存储过程可以非常快地执行,而不用等待任何网络或磁盘I/O。 + +![](img/fig7-9.png) + +**图7-9 交互式事务和存储过程之间的区别(使用图7-8的示例事务)** + +#### 存储过程的优点和缺点 + +存储过程在关系型数据库中已经存在了一段时间了,自1999年以来它们一直是SQL标准(SQL/PSM)的一部分。由于各种原因,它们的名声不太好: + +* 每个数据库供应商都有自己的存储过程语言(Oracle有PL/SQL,SQL Server有T-SQL,PostgreSQL有PL/pgSQL等)。这些语言并没有跟上通用编程语言的发展,所以从今天的角度来看它们看起来相当丑陋和陈旧,而且它们缺乏大多数编程语言中能找到的库的生态系统。 +* 与应用程序服务器相,比在数据库中运行的代码难以管理,调试更为困难,版本控制和部署起来也比较尴尬,更难测试,难与监控系统集成。 +* 数据库通常比应用程序服务器对性能敏感的多,因为单个数据库实例通常由许多应用程序服务器共享。数据库中一个写得不好的存储过程(例如,使用大量内存或CPU时间)会比在应用程序服务器中写入相同的代码造成更多的麻烦。 + +但是这些问题都是可以克服的。现代的存储过程实现放弃了PL/SQL,而是使用现有的通用编程语言:VoltDB使用Java或Groovy,Datomic使用Java或Clojure,而Redis使用Lua。 + +使用存储过程和内存数据,在单个线程上执行所有事务变得可行。由于它们不需要等待I / O,并且避免了其他并发控制机制的开销,它们可以在单个线程上实现相当好的吞吐量。 + +VoltDB还使用存储过程进行复制:不是将事务的写入从一个节点复制到另一个节点,而是在每个节点上执行相同的存储过程。因此,VoltDB要求存储过程是确定性的(当在不同的节点上运行时,它们必须产生相同的结果)。如果事务需要使用当前的日期和时间,则必须通过特定的确定性API来实现。 + +#### 分区 + +并发执行所有事务使得并发控制更为简单,但是将数据库的事务吞吐量限制在单个机器上单个CPU内核的速度。只读事务可以使用快照隔离在其他地方执行,但对于写入吞吐量较高的应用程序,单线程事务处理器可能成为一个严重的瓶颈。 + +为了扩展到多个CPU核心和多个节点,您可以对您的数据进行分区(参见第6章),这在VoltDB中是受支持的。如果您可以找到一种对数据集进行分区的方法,以便每个事务只需要在单个分区中读写数据,那么每个分区就可以拥有自己独立运行的事务处理线程。在这种情况下,您可以为每个CPU内核分配一个自己的分区,这样您的事务吞吐量就可以与CPU内核数量成线性比例关系[47]。 + +但是,对于需要访问多个分区的任何事务,数据库必须在触及的所有分区之间协调事务。存储过程需要跨越所有分区锁定执行,以确保整个系统的可串行性。 + +由于跨分区事务具有额外的协调开销,所以它们比单分区事务慢得多。 VoltDB报告的吞吐量大约是每秒1000个跨分区写入,比单分区吞吐量低几个数量级,并且不能通过增加更多的机器来增加[49]。 + +事务是否可以是单分区很大程度上取决于应用程序使用的数据的结构。简单的键值数据通常可以非常容易地进行分区,但是具有多个二级索引的数据可能需要大量的跨分区协调(请参阅第206页的“分区和二级索引”)。 + +#### 串行执行摘要 + +事务的串行执行已成为在一定的约束条件下实现可序列化的隔离的一种可行方法: + +* 每笔交易都必须小而快,因为只需一个缓慢的交易即可拖延所有交易处理。 +* 仅限于使用活动数据集可以放入内存的情况。很少访问的数据可能会被移动到磁盘,但是如果需要在单线程事中访问,系统会变得非常慢[^x]。 +* 写入吞吐量必须足够低才能在单个CPU内核上处理,否则事务需要进行分区而不需要跨分区协调。 +* 交叉分区交易是可能的,但是它们的使用程度有很大的限制。 + +[^x]: 如果事务需要访问不在内存中的数据,最好的解决方案可能是放弃事务,异步地将数据提取到内存中,同时继续处理其他事务,然后在数据加载时重新启动事务。这种方法被称为反高速缓存,正如前面在第88页“将所有内容保存在内存”中所述。 + +### 两相锁定(2PL) + +大约30年来,在数据库中只有一种广泛使用的可串行化算法:双相锁定(2PL) + +> #### 2PL不是2PC +> +> 请注意,虽然两阶段锁定(2PL)听起来非常类似于两阶段提交(2PC),但它们是完全不同的东西。我们将在第9章讨论2PC。 + +之前我们看到锁通常用于防止脏写入(请参阅“无脏写”一节第217页):如果两个事务同时尝试写入同一个对象,则锁可确保第二个写入器必须等到第一个写入器完成交易(中止或承诺),然后才能继续。 + +两相锁定类似,但使锁定要求更强。只要没有人写信,就允许多个事务同时读取同一个对象。但只要有人想写(修改或删除)对象,就需要独占访问权限: + +* 如果事务A读取了一个对象,并且事务B想要写入该对象,那么B必须等到A提交或中止才能继续。 (这确保B不能在A后面意外地改变对象。) +* 如果事务A写入了一个对象,并且事务B想要读取该对象,则B必须等到A提交或中止才能继续。 (如图7-1所示,读取旧版本的对象在2PL下是不可接受的。) + +在第二方物流中,作家不只是阻碍其他作家,他们也阻挡读者,反之亦然。快照隔离使得读者永远不会阻止写入者,编写者也不会阻止读取者(请参阅“实施快照隔离”在本页221),该功能捕获快照隔离和两阶段锁定之间的这一关键区别。另一方面,因为2PL提供了可串行性,它可以防止早先讨论的所有竞争条件,包括丢失更新和写入歪斜。 + +#### 执行两阶段锁定 + +2PL用于MySQL(InnoDB)和SQL Server中的可序列化隔离级别,以及DB2中的可重复读取隔离级别[23,36]。 + +[^xi]: 有时被称为强有力的严格的两阶段锁定(SS2PL),以区别于2PL的其他变种。 + +读写器的阻塞是通过锁定数据库中的每个对象来实现的。锁可以处于共享模式或独占模式。锁使用如下: + +* 如果事务要读取对象,则必须先以共享模式获取锁。允许多个事务同时保持共享模式下的锁定,但是如果另一个事务已经在对象上拥有独占锁定,则这些事务必须等待。 +* 如果一个事务要写入一个对象,它必须首先以独占模式获取该锁。没有其他事务可以同时持有锁(共享或独占模式),所以如果对象上存在任何锁,事务必须等待。 +* 如果事务首先读取并写入对象,则可能会将其共享锁升级为独占锁。升级工作与直接获得排他锁相同。 +* 事务获得锁之后,必须继续保持锁直到事务结束(提交或中止)。这就是“两阶段”这个名字的来源:第一阶段(当事务正在执行时)是获取锁的时间,第二阶段(在事务结束时)是所有的锁被释放。 + +由于使用了这么多的锁,因此事务A可能很容易发生,等待事务B释放它的锁,反之亦然。这种情况叫做死锁。数据库自动检测事务之间的死锁,并中止其中的一个,以便其他人可以取得进展。被中止的事务需要被应用程序重试。 + +#### 二阶段锁定的性能 + +两阶段锁定的大缺点以及自1970年代以来没有被大家使用的原因是性能:两阶段锁定下的查询的事务吞吐量和响应时间要比弱隔离下的要严重得多。 + +这部分是由于获取和释放所有这些锁的开销,但更重要的是由于并发性的降低。按照设计,如果两个并发事务试图做任何可能导致竞争条件的事情,那么必须等待另一个完成。 + +传统的关系数据库不限制事务的持续时间,因为它们是为等待人类输入的交互式应用而设计的。因此,当一个交易需要等待另一个交易时,可以等待多长时间没有限制。即使你保证你所有的交易都是短暂的,如果有多个交易想要访问同一个对象,那么可能会形成一个队列,所以交易可能需要等待几个其他交易才能完成。 + +因此,运行2PL的数据库可能具有相当不稳定的等待时间,如果在工作负载中存在争用,那么在高百分比情况下它们可能非常慢(请参阅第13页的“描述性能”)。它可能只需要一个缓慢的事务,或者一个访问大量数据并获取许多锁的事务,导致系统的其他部分停下来。当需要稳健的操作时,这种不稳定性是有问题的。 + +虽然基于锁定的读取提交隔离级别可能发生死锁,但在2PL可序列化隔离(取决于事务的访问模式)下,它们发生得更为频繁。这可能是一个额外的性能问题:当事务由于死锁而被中止并被重试时,它需要重新做它的工作。如果僵局频繁,这可能意味着重大的浪费。 + +#### 预测锁 + +在前面关于锁的描述中,我们掩盖了一个微妙而重要的细节。在第250页的“导致写入歪斜的幻像”中,我们讨论了一些性能问题,即一个事务改变另一个事务的搜索查询的结果。具有可序列化隔离的数据库必须防止幻像。 + +在会议室预订的例子中,这意味着如果一个交易在某个时间窗口内搜索了一个房间的现有预订(见例7-2),则另一个交易不能同时插入或更新同一房间的另一个预订,时间范围。 (可以同时插入其他房间的预订,或在不影响预定预订的不同时间的同一房间)。 + +我们如何实现这一点?从概念上讲,我们需要一个谓词锁定[3]。它类似于前面描述的共享/排它锁,但不属于特定的对象(例如,表中的一行),它属于所有符合某些搜索条件的对象,如: + +```sql +SELECT * +FROM bookings +WHERE room_id = 123 AND + end_time > '2018-01-01 12:00' AND start_time < '2018-01-01 13:00'; +``` + +谓词锁限制访问,如下所示: + +* 如果事务A想要读取匹配某些条件的对象,就像在该SELECT查询中那样,它必须获取对查询条件的共享模式谓词锁定。如果另一个事务B当前对与这些条件匹配的任何对象具有排他锁,那么A必须等到B释放它的锁之后才允许它进行查询。 +* 如果事务A想要插入,更新或删除任何对象,则必须首先检查旧值或新值是否与任何现有的谓词锁匹配。如果事务B持有匹配的谓词锁,那么A必须等到B已经提交或中止后才能继续。 + +这里的关键思想是,谓词锁定甚至适用于数据库中尚不存在的对象,但可能在将来添加(幻像)对象。如果两阶段锁定包含谓词锁定,则数据库将阻止所有形式的写入歪斜和其他竞争条件,因此其隔离可以串行化。 + +#### 索引范围的锁 + +不幸的是,谓词锁定性能不佳:如果活动事务中存在许多锁定,则检查匹配的锁定会变得非常耗时。因此,大多数使用2PL的数据库实际上实现了索引范围锁定(也称为下一个锁定),这是一个简化的谓词锁定近似[41,50]。 + +通过使谓词与更多的对象相匹配来简化谓词是安全的。例如,如果在中午和下午1点之间预订123号房间的谓词锁,您可以随时锁定123号房间的预订,或者通过锁定所有房间(不只是123号房间)中午至下午1点这是安全的,因为任何与原始谓词相匹配的书写都肯定会匹配近似值。 + +在房间预订数据库中,您可能会在room_id列上有一个索引,并且/或者在start_time和end_time上有索引(否则前面的查询在大型数据库上的速度会非常慢): + +* 假设您的索引位于room_id上,并且数据库使用此索引查找123号房间的现有预订。现在数据库可以简单地将共享锁附加到此索引条目,指示交易已搜索123号房间的预订。 +* 或者,如果数据库使用基于时间的索引来查找现有预订,那么它可以将共享锁附加到该索引中的一系列值,指示交易已搜索与中午的时间段重叠的预订到下午1点2018年1月1日 + +无论哪种方式,搜索条件的近似值都附加到其中一个索引上。现在,如果另一个交易想要插入,更新或删除同一个房间和/或重叠时间段的预订,则它将不得不更新索引的相同部分。在这样做的过程中,它会遇到共享锁,它将被迫等到锁被释放。 + +这提供了有效的防止幻影和写歪斜。索引范围锁并不像谓词锁那样精确(它们可能会锁定更大范围的对象,而不是维持可串行化所必需的范围),但是由于它们的开销较低,所以它们是一个很好的折衷。 + +如果在可以连接范围锁的地方没有合适的索引,则数据库可以回退到整个表上的共享锁。这对性能不利,因为它会阻止所有其他交易写入表格,但这是一个安全的回退位置。 + + + +### 可串行快照隔离(SSI) + +本章描绘了数据库中并发控制的黯淡画面。一方面,我们实现了不能很好执行的串行化(两阶段锁定)或不能很好地扩展(串行执行)。另一方面,我们有较弱的隔离级别,性能良好,但容易出现各种竞争条件(丢失更新,写歪斜,幻影等)。序列化的隔离和良好的性能从根本上相互矛盾? + +也许不是:一个称为可序列化快照隔离(SSI)的算法是非常有前途的。它提供了完全的可串行性,但与快照隔离相比,性能损失只有很小的一部分。 SSI是相当新的:它在2008年首次被描述[40],并且是Michael Cahill的博士论文[51]的主题。 + +今天,SSI既用于单节点数据库(自9.1版以来PostgreSQL中的可序列化隔离级别)和分布式数据库(FoundationDB使用类似的算法)。由于SSI与其他并发控制机制相比还很年轻,它在实践中仍然可以证明其性能,但它有可能成为未来的新的违约。 + +#### 悲观与乐观的并发控制 + +两阶段锁定是一种所谓的悲观并发控制机制:它是基于如果有什么可能出错(如另一个事务所持有的锁所表示的)的原则,最好等到情况再次安全做任何事情。这就像互斥,用于保护多线程编程中的数据结构。 + +从某种意义上说,串行执行是极端悲观的:在事务持续期间,每个事务对整个数据库(或数据库的一个分区)具有独占锁定,这基本相当。我们通过使每笔交易执行得非常快来弥补悲观情绪,所以只需要短时间保持“锁定”。 + +相比之下,可串行化的快照隔离是一种乐观的并发控制技术。在这种情况下乐观意味着,如果发生某种可能的危险,不要阻止交易,反而继续交易,希望一切都会好起来。当一个事务想要提交时,数据库检查是否有什么不好的事情发生(即隔离是否被违反);如果是的话,交易将被中止,并且必须重试。只有可序列化的事务才被允许提交。 + +乐观并发控制是一个古老的想法[52],其优点和缺点已经争论了很长时间[53]。如果存在较高的意图(很多事务试图访问相同的对象),则表现不佳,因为这会导致很大一部分事务需要中止。如果系统已经接近最大吞吐量,来自重试交易的额外交易负载可能会使性能变差。 + +但是,如果有足够的备用容量,并且交易之间的争用不是太高,乐观的并发控制技术往往比悲观的要好。可交换的原子操作可以减少争用:例如,如果多个事务同时要增加一个计数器,那么应用增量的顺序(只要计数器不在同一个事务中读取)就无关紧要了,所以并发增量可以全部应用而不冲突。 + +顾名思义,SSI基于快照隔离 - 也就是说,事务中的所有读取操作都是通过数据库的一致快照创建的(请参见第237页的“快照隔离和可重复读取”)。与早期的乐观并发控制技术相比,这是主要的区别。在快照隔离的基础上,SSI添加了一种算法来检测写入之间的序列化冲突,并确定要中止哪些事务。 + +#### 基于过时前提的决定 + +当我们先前讨论了快照隔离中的写入歪斜(请参阅“写入歪斜和幻像”第221页)时,我们观察到一个循环模式:事务从数据库读取一些数据,检查查询的结果,并决定采取一些操作(写入数据库)根据它看到的结果。但是,在快照隔离的情况下,原始查询的结果在事务提交时可能不再是最新的,因为数据可能在同一时间被修改。 +换句话说,交易正在基于一个前提采取行动(交易一开始就是事实,例如“目前有两名医生正在通话”)。之后,当交易要提交时,原始数据可能已经改变 - 前提可能不再成立。 + +当应用程序进行查询时(例如,“当前有多少医生正在调用?”),数据库不知道应用程序逻辑如何使用该查询的结果。为了安全,数据在这种情况下,交易可能在一个过时的前提下采取了行动并放弃交易。 + +数据库如何知道查询结果是否可能已经改变?有两种情况需要考虑: + +* 检测陈旧的MVCC对象版本的读取(在读取之前发生未提交的写入) +* 检测影响先前读取的写入(写入发生在读取之后) + + + +#### 检测陈旧的MVCC读取 + +回想一下,快照隔离通常是通过多版本并发控制(MVCC;见图7-10)来实现的。当一个事务从MVCC数据库中一致的快照中读取时,它将忽略在拍摄快照时尚未提交的任何其他事务所做的写入。在图7-10中,事务43将Alice看作具有on_call = true,因为事务42(修改Alice的待命状态)未被提交。然而,在事务43想要提交时,事务42已经提交。这意味着从一致性快照读取时被忽略的写入已经生效,并且事务43的前提不再是真实的。 + +![](img/fig7-10.png) + +**图7-10 检测事务何时从MVCC快照读取过时的值** + +为了防止这种异常,数据库需要跟踪一个事务由于MVCC可见性规则而忽略另一个事务的写入。当事务想要提交时,数据库检查是否有任何被忽略的写入现在已经被提交。如果是这样,交易必须中止。 + +为什么要等到提交?当检测到陈旧的读取时,为什么不立即中止事务43?那么,如果事务43是只读事务,则不需要中止,因为没有写歪斜的风险。当事务43进行读取时,数据库还不知道事务是否要稍后执行写操作。此外,交易42可能在交易43被提交的时候中止或者可能仍然未被提交,因此读取可能终究不是陈旧的。通过避免不必要的中止,SSI保留快照隔离对从一致快照中长时间运行的读取的支持。 + +#### 检测影响之前读取的写入 + +第二种情况要考虑的是另一个事务在读取数据之后修改数据。这种情况如图7-11所示。 + +![](img/fig7-11.png) + +**图7-11 在可序列化快照隔离中,检测一个事务何时修改另一个事务的读取。** + +在两阶段锁定的上下文中,我们讨论了索引范围锁(请参阅“索引范围锁”,第259页),它允许数据库锁定与某个搜索查询匹配的所有行的访问权,例如WHERE shift_id = 1234。可以在这里使用类似的技术,除了SSI锁不会阻塞其他事务。 + +在图7-11中,交易42和43都在轮班1234期间寻找在线医生。如果在shift_id上有索引,则数据库可以使用索引条目1234来记录事务42和43读取这个数据的事实。 (如果没有索引,这个信息可以在表级进行跟踪)。这个信息只需要保留一段时间:在一个事务完成(提交或中止)之后,所有的并发事务完成之后,数据库可能会忘记它读取的数据。 + +当事务写入数据库时,它必须在索引中查找最近读取受影响数据的其他事务。这个过程类似于在受影响的密钥范围上获取写入锁定,而不是在读取器提交之前阻塞,锁定作为tripwire:它只是通知事务他们读取的数据可能不再是最新的。 + +在图7-11中,交易43通知交易42其先前的阅读已过时,反之亦然。事务42首先提交,并且成功:尽管事务43的写入受到影响42,43尚未提交,所以写入尚未生效。然而,当事务43想要提交时,来自42的冲突写入已经被提交,所以43必须中止。 + +#### 可序列化的快照隔离的性能 + +与往常一样,许多工程细节会影响算法在实践中的效果。例如,一个权衡是跟踪事务的读取和写入的粒度。如果数据库非常详细地跟踪每个事务的活动,那么可以准确地确定哪些事务需要中止,但是记帐开销可能变得很重要。不太详细的跟踪速度更快,但可能会导致更多的交易被中止。 + +在某些情况下,事务可以读取被另一个事务覆盖的信息:取决于发生了什么,有时可以证明执行的结果是可序列化的。 PostgreSQL使用这个理论来减少不必要的中止次数[11,41]。 + +与两阶段锁定相比,可序列化快照隔离的最大优点是一个事务不需要阻塞等待另一个事务所持有的锁。就像在快照隔离下一样,编写者不会阻止读者,反之亦然。这种设计原则使得查询延迟更可预测,变量更少。特别是,只读查询可以运行在一致的快照上,而不需要任何锁定,这对于读取繁重的工作负载非常有吸引力。 + +与串行执行相比,可串行化的快照隔离并不局限于单个CPU内核的吞吐量:FoundationDB将检测到的串行冲突分布在多台机器上,允许扩展到很高的吞吐量。即使数据可能跨多台机器进行分区,事务也可以在保证可串行化隔离的同时读写多个分区中的数据[54]。 + +中止率显着影响SSI的整体表现。例如,长时间读取和写入数据的事务很可能会发生冲突并中止,因此SSI要求读写事务相当短(长时间运行的只读事务可能没有问题)。但是,SSI可能比两阶段锁定或串行执行更不敏感。 + + + +## 本章小结 + +事务是一个抽象层,允许应用程序假装某些并发问题和某些类型的硬件和软件故障不存在。大量的错误被简化为简单的事务中止,而应用程序只需要再次尝试。 + +在本章中,我们看到许多交易有助于防止的问题。并非所有的应用程序都容易出现所有这些问题:具有非常简单的访问模式的应用程序(例如只读和写一条记录)可能无需事务管理。但是,对于更复杂的访问模式,事务可以大大减少您需要考虑的潜在错误情况的数量。 + +没有事务处理,各种错误情况(进程崩溃,网络中断,停电,磁盘已满,意外并发等)意味着数据可能以各种方式变得不一致。例如,非规格化的数据可能很容易与源数据不同步。如果没有事务处理,就很难推断复杂的交互访问可能对数据库造成的影响。 + +在本章中,我们深入到并发控制的主题。我们讨论了几个广泛使用的隔离级别,特别是读取提交,快照隔离(有时称为可重复读取)和可序列化。我们通过讨论种族条件的各种示例来描述这些竟态条件: + +***脏读*** + +​ 一个客户端在提交之前读取另一个客户端的写入。读提交的隔离级别和更强的级别防止脏读。 + +***脏写*** + +​ 一个客户端会覆盖另一个客户端已经写入但尚未提交的数据。几乎所有的事务实现都可以防止脏写入。 + +***读取偏斜(不可重复读)*** +​ 客户在不同的时间点看到数据库的不同部分。快照隔离最常遇到这个问题,它允许事务在一个时间点从一致的快照中读取数据。它通常使用多版本并发控制(MVCC)来实现。 + +***丢失的更新*** + +​ 两个客户端同时执行读 - 修改 - 写周期。一个覆盖另一个的写入而不包含其变化,所以数据丢失。快照隔离的一些实现可以自动防止这种异常,而另一些实现则需要手动锁定(SELECT FOR UPDATE)。 + +***写歪斜*** +​ 一个事务读取一些东西,根据它所看到的值作出决定,并将决定写入数据库。但是,写作的时候,决定的前提不再是真实的。只有可序列化的隔离才能防止这种异常。 + +***幻读*** +​ 事务读取符合某些搜索条件的对象。另一个客户端进行写入,影响搜索结果。快照隔离可以防止直接的幻像读取,但是写入歪斜环境中的幻影需要特殊处理,例如索引范围锁定。 + +弱隔离级别可以防止这些异常情况,但是让应用程序开发人员手动处理其他应用程序(例如,使用显式锁定)。只有可序列化的隔离才能防范所有这些问题。我们讨论了实现可序列化事务的三种不同方法: + +***按照连续顺序从字面上执行交易*** +​ 如果您可以使每个事务的执行速度非常快,并且事务吞吐量足够低以在单个CPU内核上处理,这是一个简单而有效的选择。 + +***两相锁定*** +​ 数十年来,这一直是实现可串行化的标准方式,但是许多应用程序由于其性能特征而避免使用它。 +***可串行化快照隔离(SSI)*** + +​ 一个相当新的算法,避免了以前方法的大部分缺点。它使用乐观的方法,允许事务进行而不会阻塞。当一个事务想要提交时,它会被检查,如果执行不可序列化,它将被中止。 + +本章中的示例使用关系数据模型。但是,正如在讨论中 + +无论使用哪种数据模型,第231页上的“多对象事务的需要”事务都是有价值的数据库功能。 + +在本章中,我们主要探讨了在一台机器上运行数据库的情况下的想法和算法。分布式数据库中的事务开启了一系列新的困难挑战,我们将在接下来的两章中讨论。 + + + +## 参考文献 + + +1. Donald D. Chamberlin, Morton M. Astrahan, Michael W. Blasgen, et al.: + “[A History and Evaluation of System R](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.84.348&rep=rep1&type=pdf),” *Communications of the ACM*, + volume 24, number 10, pages 632–646, October 1981. + [doi:10.1145/358769.358784](http://dx.doi.org/10.1145/358769.358784) + +1. Jim N. Gray, Raymond A. Lorie, Gianfranco R. Putzolu, and Irving L. Traiger: + “[Granularity of Locks and Degrees of Consistency in a Shared Data Base](http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.92.8248&rep=rep1&type=pdf),” in *Modelling in Data + Base Management Systems: Proceedings of the IFIP Working Conference on Modelling in Data Base + Management Systems*, edited by G. M. Nijssen, pages + 364–394, Elsevier/North Holland Publishing, 1976. Also in *Readings in Database Systems*, 4th edition, edited by Joseph M. + Hellerstein and Michael Stonebraker, MIT Press, 2005. ISBN: 978-0-262-69314-1 + +1. Kapali P. Eswaran, Jim N. Gray, Raymond A. Lorie, and Irving L. Traiger: + “[The Notions of Consistency and Predicate Locks in a Database System](http://research.microsoft.com/en-us/um/people/gray/papers/On%20the%20Notions%20of%20Consistency%20and%20Predicate%20Locks%20in%20a%20Database%20System%20CACM.pdf),” *Communications of the + ACM*, volume 19, number 11, pages 624–633, November 1976. + +1. “[ACID Transactions Are Incredibly Helpful](http://web.archive.org/web/20150320053809/https://foundationdb.com/acid-claims),” FoundationDB, LLC, 2013. + +1. John D. Cook: + “[ACID Versus BASE for Database Transactions](http://www.johndcook.com/blog/2009/07/06/brewer-cap-theorem-base/),” *johndcook.com*, July 6, 2009. + +1. Gavin Clarke: + “[NoSQL's CAP Theorem Busters: We Don't Drop ACID](http://www.theregister.co.uk/2012/11/22/foundationdb_fear_of_cap_theorem/),” *theregister.co.uk*, November 22, 2012. + +1. Theo Härder and Andreas Reuter: + “[Principles of Transaction-Oriented Database Recovery](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.87.2812&rep=rep1&type=pdf),” *ACM Computing Surveys*, + volume 15, number 4, pages 287–317, December 1983. + [doi:10.1145/289.291](http://dx.doi.org/10.1145/289.291) + +1. Peter Bailis, Alan Fekete, Ali Ghodsi, et al.: + “[HAT, not CAP: Towards Highly Available Transactions](http://www.bailis.org/papers/hat-hotos2013.pdf),” + at *14th USENIX Workshop on Hot Topics in Operating Systems* (HotOS), May 2013. + +1. Armando Fox, Steven D. Gribble, Yatin Chawathe, et al.: + “[Cluster-Based Scalable Network Services](http://www.cs.berkeley.edu/~brewer/cs262b/TACC.pdf),” at + *16th ACM Symposium on Operating Systems Principles* (SOSP), October 1997. + +1. Philip A. Bernstein, Vassos Hadzilacos, and Nathan Goodman: + *Concurrency + Control and Recovery in Database Systems*. Addison-Wesley, 1987. ISBN: 978-0-201-10715-9, + available online at *research.microsoft.com*. + +1. Alan Fekete, Dimitrios Liarokapis, Elizabeth O'Neil, et al.: + “[Making Snapshot Isolation Serializable](https://www.cse.iitb.ac.in/infolab/Data/Courses/CS632/2009/Papers/p492-fekete.pdf),” *ACM Transactions on Database Systems*, + volume 30, number 2, pages 492–528, June 2005. + [doi:10.1145/1071610.1071615](http://dx.doi.org/10.1145/1071610.1071615) + +1. Mai Zheng, Joseph Tucek, Feng Qin, and Mark Lillibridge: + “[Understanding the Robustness of SSDs Under Power Fault](https://www.usenix.org/system/files/conference/fast13/fast13-final80.pdf),” at *11th USENIX Conference on File and + Storage Technologies* (FAST), February 2013. + +1. Laurie Denness: + “[SSDs: A Gift and a Curse](https://laur.ie/blog/2015/06/ssds-a-gift-and-a-curse/),” + *laur.ie*, June 2, 2015. + +1. Adam Surak: + “[When Solid State Drives Are Not That Solid](https://blog.algolia.com/when-solid-state-drives-are-not-that-solid/),” *blog.algolia.com*, June 15, 2015. + +1. Thanumalayan Sankaranarayana Pillai, Vijay Chidambaram, + Ramnatthan Alagappan, et al.: “[All File Systems Are Not Created Equal: On the Complexity of Crafting Crash-Consistent Applications](http://research.cs.wisc.edu/wind/Publications/alice-osdi14.pdf),” + at *11th USENIX Symposium on Operating Systems Design and Implementation* (OSDI), + October 2014. + +1. Chris Siebenmann: + “[Unix's File Durability Problem](https://utcc.utoronto.ca/~cks/space/blog/unix/FileSyncProblem),” *utcc.utoronto.ca*, April 14, 2016. + +1. Lakshmi N. Bairavasundaram, Garth R. + Goodson, Bianca Schroeder, et al.: + “[An Analysis of Data Corruption in the Storage Stack](http://research.cs.wisc.edu/adsl/Publications/corruption-fast08.pdf),” at *6th USENIX Conference on File and Storage + Technologies* (FAST), February 2008. + +1. Bianca Schroeder, Raghav Lagisetty, and Arif Merchant: + “[Flash Reliability in Production: The Expected and the Unexpected](https://www.usenix.org/conference/fast16/technical-sessions/presentation/schroeder),” at *14th USENIX Conference on + File and Storage Technologies* (FAST), February 2016. + +1. Don Allison: + “[SSD Storage – Ignorance of Technology Is No Excuse](https://blog.korelogic.com/blog/2015/03/24),” *blog.korelogic.com*, March 24, 2015. + +1. Dave Scherer: + “[Those Are Not Transactions (Cassandra 2.0)](http://web.archive.org/web/20150526065247/http://blog.foundationdb.com/those-are-not-transactions-cassandra-2-0),” *blog.foundationdb.com*, September 6, 2013. + +1. Kyle Kingsbury: + “[Call Me Maybe: Cassandra](http://aphyr.com/posts/294-call-me-maybe-cassandra/),” + *aphyr.com*, September 24, 2013. + +1. “[ACID Support in Aerospike](http://www.aerospike.com/docs/architecture/assets/AerospikeACIDSupport.pdf),” Aerospike, Inc., June 2014. + +1. Martin Kleppmann: + “[Hermitage: Testing the 'I' in ACID](http://martin.kleppmann.com/2014/11/25/hermitage-testing-the-i-in-acid.html),” *martin.kleppmann.com*, November 25, 2014. + +1. Tristan D'Agosta: + “[BTC Stolen from Poloniex](https://bitcointalk.org/index.php?topic=499580),” + *bitcointalk.org*, March 4, 2014. + +1. bitcointhief2: + “[How I Stole Roughly 100 BTC from an Exchange and How I Could Have Stolen More!](http://www.reddit.com/r/Bitcoin/comments/1wtbiu/how_i_stole_roughly_100_btc_from_an_exchange_and/),” *reddit.com*, + February 2, 2014. + +1. Sudhir Jorwekar, Alan Fekete, Krithi Ramamritham, and S. Sudarshan: + “[Automating the Detection of Snapshot Isolation Anomalies](http://www.vldb.org/conf/2007/papers/industrial/p1263-jorwekar.pdf),” at *33rd International Conference on + Very Large Data Bases* (VLDB), September 2007. + +1. Michael Melanson: + “[Transactions: The Limits of Isolation](http://www.michaelmelanson.net/2014/03/20/transactions/),” *michaelmelanson.net*, March 20, 2014. + +1. Hal Berenson, Philip A. Bernstein, Jim N. Gray, et al.: + “[A Critique of ANSI SQL Isolation Levels](http://research.microsoft.com/pubs/69541/tr-95-51.pdf),” + at *ACM International Conference on Management of Data* (SIGMOD), May 1995. + +1. Atul Adya: “[Weak Consistency: A Generalized Theory and Optimistic Implementations for Distributed Transactions](http://pmg.csail.mit.edu/papers/adya-phd.pdf),” + PhD Thesis, Massachusetts Institute of Technology, March 1999. + +1. Peter Bailis, Aaron Davidson, Alan Fekete, et al.: + “[Highly Available Transactions: Virtues and Limitations (Extended Version)](http://arxiv.org/pdf/1302.0309.pdf),” at *40th International Conference on Very Large Data Bases* + (VLDB), September 2014. + +1. Bruce Momjian: + “[MVCC Unmasked](http://momjian.us/main/presentations/internals.html#mvcc),” *momjian.us*, + July 2014. + +1. Annamalai Gurusami: + “[Repeatable Read Isolation Level in InnoDB – How Consistent Read View Works](https://blogs.oracle.com/mysqlinnodb/entry/repeatable_read_isolation_level_in),” *blogs.oracle.com*, + January 15, 2013. + +1. Nikita Prokopov: + “[Unofficial Guide to Datomic Internals](http://tonsky.me/blog/unofficial-guide-to-datomic-internals/),” *tonsky.me*, May 6, 2014. + +1. Baron Schwartz: + “[Immutability, MVCC, and Garbage Collection](http://www.xaprb.com/blog/2013/12/28/immutability-mvcc-and-garbage-collection/),” *xaprb.com*, December 28, 2013. + +1. J. Chris Anderson, Jan Lehnardt, and Noah Slater: + *CouchDB: The Definitive Guide*. O'Reilly Media, 2010. + ISBN: 978-0-596-15589-6 + +1. Rikdeb Mukherjee: + “[Isolation in DB2 (Repeatable Read, Read Stability, Cursor Stability, Uncommitted Read) with Examples](http://mframes.blogspot.co.uk/2013/07/isolation-in-cursor.html),” + *mframes.blogspot.co.uk*, July 4, 2013. + +1. Steve Hilker: + “[Cursor Stability (CS) – IBM DB2 Community](http://www.toadworld.com/platforms/ibmdb2/w/wiki/6661.cursor-stability-cs.aspx),” *toadworld.com*, March 14, 2013. + +1. Nate Wiger: + “[An Atomic Rant](http://www.nateware.com/an-atomic-rant.html),” *nateware.com*, + February 18, 2010. + +1. Joel Jacobson: + “[Riak 2.0: Data Types](http://blog.joeljacobson.com/riak-2-0-data-types/),” + *blog.joeljacobson.com*, March 23, 2014. + +1. Michael J. Cahill, Uwe Röhm, and Alan Fekete: + “[Serializable Isolation for Snapshot Databases](http://www.cs.nyu.edu/courses/fall12/CSCI-GA.2434-001/p729-cahill.pdf),” at *ACM International Conference on + Management of Data* (SIGMOD), June 2008. + [doi:10.1145/1376616.1376690](http://dx.doi.org/10.1145/1376616.1376690) + +1. Dan R. K. Ports and Kevin Grittner: + “[Serializable Snapshot Isolation in PostgreSQL](http://drkp.net/papers/ssi-vldb12.pdf),” + at *38th International Conference on Very Large Databases* (VLDB), August 2012. + +1. Tony Andrews: + “[Enforcing Complex Constraints in Oracle](http://tonyandrews.blogspot.co.uk/2004/10/enforcing-complex-constraints-in.html),” *tonyandrews.blogspot.co.uk*, October 15, 2004. + +1. Douglas B. Terry, Marvin M. Theimer, Karin Petersen, et al.: + “[Managing Update Conflicts in Bayou, a Weakly Connected Replicated Storage System](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.141.7889&rep=rep1&type=pdf),” at + *15th ACM Symposium on Operating Systems Principles* (SOSP), December 1995. + [doi:10.1145/224056.224070](http://dx.doi.org/10.1145/224056.224070) + +1. Gary Fredericks: + “[Postgres Serializability Bug](https://github.com/gfredericks/pg-serializability-bug),” *github.com*, September 2015. + +1. Michael Stonebraker, Samuel Madden, Daniel J. Abadi, et al.: + “[The End of an Architectural Era (It’s Time for a Complete Rewrite)](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.137.3697&rep=rep1&type=pdf),” at *33rd International + Conference on Very Large Data Bases* (VLDB), September 2007. + +1. John Hugg: + “[H-Store/VoltDB Architecture vs. CEP Systems and Newer Streaming Architectures](https://www.youtube.com/watch?v=hD5M4a1UVz8),” at *Data @Scale Boston*, + November 2014. + +1. Robert Kallman, Hideaki Kimura, Jonathan Natkins, et al.: + “[H-Store: A High-Performance, Distributed Main Memory Transaction Processing System](http://www.vldb.org/pvldb/1/1454211.pdf),” *Proceedings of the VLDB + Endowment*, volume 1, number 2, pages 1496–1499, August 2008. + +1. Rich Hickey: + “[The Architecture of Datomic](http://www.infoq.com/articles/Architecture-Datomic),” *infoq.com*, November 2, 2012. + +1. John Hugg: + “[Debunking Myths About the VoltDB In-Memory Database](http://voltdb.com/blog/debunking-myths-about-voltdb-memory-database),” *voltdb.com*, May 12, 2014. + +1. Joseph M. Hellerstein, Michael Stonebraker, and James Hamilton: + “[Architecture of a Database System](http://db.cs.berkeley.edu/papers/fntdb07-architecture.pdf),” + *Foundations and Trends in Databases*, volume 1, number 2, pages 141–259, November 2007. + [doi:10.1561/1900000002](http://dx.doi.org/10.1561/1900000002) + +1. Michael J. Cahill: + “[Serializable Isolation for Snapshot Databases](http://cahill.net.au/wp-content/uploads/2010/02/cahill-thesis.pdf),” PhD Thesis, University of Sydney, July 2009. + +1. D. Z. Badal: + “[Correctness of Concurrency Control and Implications in Distributed Databases](http://ieeexplore.ieee.org/abstract/document/762563/),” at *3rd International IEEE Computer Software and + Applications Conference* (COMPSAC), November 1979. + +1. Rakesh Agrawal, Michael J. Carey, and Miron Livny: + “[Concurrency Control Performance Modeling: Alternatives and Implications](http://www.eecs.berkeley.edu/~brewer/cs262/ConcControl.pdf),” *ACM Transactions on Database + Systems* (TODS), volume 12, number 4, pages 609–654, December 1987. + [doi:10.1145/32204.32220](http://dx.doi.org/10.1145/32204.32220) + +1. Dave Rosenthal: + “[Databases at 14.4MHz](http://web.archive.org/web/20150427041746/http://blog.foundationdb.com/databases-at-14.4mhz),” + *blog.foundationdb.com*, December 10, 2014. + diff --git a/ddia/ch8.md b/ddia/ch8.md new file mode 100644 index 0000000..b88d16e --- /dev/null +++ b/ddia/ch8.md @@ -0,0 +1,360 @@ +# 8. 分布式系统的麻烦 + +![](img/ch8.png) + +> 邂逅相遇 +> +> 网络延迟 +> +> 存之为吾 +> +> 无食我数 +> +> ​ ——凯尔金斯伯里,卡莉赖杰普森,网络分区的危险(2013年) + +--------- + +[TOC] + + + +## 故障与部分失效 + +### 云计算与超级计算机 + + + +## 不可靠的网络 + +### 真实世界的网络故障 + +### 检测故障 + +### 超时与无穷的延迟 + +### 同步网络 vs 异步网络 + + + +## 不可靠的时钟 + +### 单调时钟与Time-Of-Day时钟 + +### 时钟同步与准确性 + +### 依赖同步时钟 + +### 暂停进程 + + + +## 知识、真相与谎言 + +### 真理掌握在大多数人手中 + +### 拜占庭故障 + +### 系统模型与现实 + + + +## 本章小结 + + + +## 参考文献 + + +1. Mark Cavage: + “[There’s Just No Getting Around It: You’re Building a Distributed System](http://queue.acm.org/detail.cfm?id=2482856),” *ACM Queue*, volume 11, number 4, pages 80-89, April 2013. + [doi:10.1145/2466486.2482856](http://dx.doi.org/10.1145/2466486.2482856) +1. Jay Kreps: + “[Getting Real About Distributed System Reliability](http://blog.empathybox.com/post/19574936361/getting-real-about-distributed-system-reliability),” *blog.empathybox.com*, March 19, 2012. +1. Sydney Padua: *The Thrilling Adventures of + Lovelace and Babbage: The (Mostly) True Story of the First Computer*. Particular Books, April + 2015. ISBN: 978-0-141-98151-2 +1. Coda Hale: + “[You Can’t Sacrifice Partition Tolerance](http://codahale.com/you-cant-sacrifice-partition-tolerance/),” *codahale.com*, October 7, 2010. +1. Jeff Hodges: + “[Notes on Distributed Systems for Young Bloods](http://www.somethingsimilar.com/2013/01/14/notes-on-distributed-systems-for-young-bloods/),” *somethingsimilar.com*, January 14, 2013. +1. Antonio Regalado: + “[Who Coined 'Cloud Computing’?](http://www.technologyreview.com/news/425970/who-coined-cloud-computing/),” *technologyreview.com*, October 31, 2011. +1. Luiz André Barroso, Jimmy Clidaras, and Urs Hölzle: + “[The Datacenter as a Computer: An Introduction to the Design of Warehouse-Scale Machines, Second Edition](http://www.morganclaypool.com/doi/abs/10.2200/S00516ED2V01Y201306CAC024),” + *Synthesis Lectures on Computer Architecture*, volume 8, number 3, + Morgan & Claypool Publishers, July 2013. + [doi:10.2200/S00516ED2V01Y201306CAC024](http://dx.doi.org/10.2200/S00516ED2V01Y201306CAC024), + ISBN: 978-1-627-05010-4 +1. David Fiala, Frank Mueller, Christian Engelmann, et al.: + “[Detection and Correction of Silent Data Corruption for Large-Scale High-Performance Computing](http://moss.csc.ncsu.edu/~mueller/ftp/pub/mueller/papers/sc12.pdf),” at + *International Conference for High Performance Computing, Networking, Storage and + Analysis* (SC12), November 2012. +1. Arjun Singh, Joon Ong, Amit Agarwal, et al.: + “[Jupiter Rising: A Decade of Clos Topologies and Centralized Control in Google’s Datacenter Network](http://conferences.sigcomm.org/sigcomm/2015/pdf/papers/p183.pdf),” at + *Annual Conference of the ACM Special Interest Group on Data Communication* (SIGCOMM), August 2015. + [doi:10.1145/2785956.2787508](http://dx.doi.org/10.1145/2785956.2787508) +1. Glenn K. Lockwood: + “[Hadoop's Uncomfortable Fit in HPC](http://glennklockwood.blogspot.co.uk/2014/05/hadoops-uncomfortable-fit-in-hpc.html),” *glennklockwood.blogspot.co.uk*, May 16, 2014. +1. John von Neumann: + “[Probabilistic Logics and the Synthesis of Reliable Organisms from Unreliable Components](https://ece.uwaterloo.ca/~ssundara/courses/prob_logics.pdf),” in *Automata Studies (AM-34)*, + edited by Claude E. Shannon and John McCarthy, Princeton University Press, 1956. + ISBN: 978-0-691-07916-5 +1. Richard W. Hamming: + *The Art of Doing Science and Engineering*. Taylor & Francis, 1997. + ISBN: 978-9-056-99500-3 +1. Claude E. Shannon: + “[A Mathematical Theory of Communication](http://cs.brynmawr.edu/Courses/cs380/fall2012/shannon1948.pdf),” *The Bell System Technical Journal*, volume 27, number 3, + pages 379–423 and 623–656, July 1948. +1. Peter Bailis and Kyle Kingsbury: + “[The Network Is Reliable](https://queue.acm.org/detail.cfm?id=2655736),” + *ACM Queue*, volume 12, number 7, pages 48-55, July 2014. + [doi:10.1145/2639988.2639988](http://dx.doi.org/10.1145/2639988.2639988) +1. Joshua B. Leners, Trinabh Gupta, Marcos K. Aguilera, and Michael Walfish: + “[Taming Uncertainty in Distributed Systems with Help from the Network](http://www.cs.nyu.edu/~mwalfish/papers/albatross-eurosys15.pdf),” at *10th European Conference on + Computer Systems* (EuroSys), April 2015. + [doi:10.1145/2741948.2741976](http://dx.doi.org/10.1145/2741948.2741976) +1. Phillipa Gill, Navendu Jain, and Nachiappan Nagappan: + “[Understanding Network Failures in Data Centers: Measurement, Analysis, and Implications](http://conferences.sigcomm.org/sigcomm/2011/papers/sigcomm/p350.pdf),” at + *ACM SIGCOMM Conference*, August 2011. + [doi:10.1145/2018436.2018477](http://dx.doi.org/10.1145/2018436.2018477) +1. Mark Imbriaco: + “[Downtime Last Saturday](https://github.com/blog/1364-downtime-last-saturday),” + *github.com*, December 26, 2012. +1. Will Oremus: + “[The Global Internet Is Being Attacked by Sharks, Google Confirms](http://www.slate.com/blogs/future_tense/2014/08/15/shark_attacks_threaten_google_s_undersea_internet_cables_video.html),” *slate.com*, August 15, + 2014. +1. Marc A. Donges: + “[Re: bnx2 cards Intermittantly Going Offline](http://www.spinics.net/lists/netdev/msg210485.html),” Message to Linux *netdev* mailing list, *spinics.net*, September 13, 2012. +1. Kyle Kingsbury: + “[Call Me Maybe: Elasticsearch](https://aphyr.com/posts/317-call-me-maybe-elasticsearch),” *aphyr.com*, June 15, 2014. +1. Salvatore Sanfilippo: + “[A Few Arguments About Redis Sentinel Properties and Fail Scenarios](http://antirez.com/news/80),” *antirez.com*, October 21, 2014. +1. Bert Hubert: + “[The Ultimate SO_LINGER Page, or: Why Is My TCP Not Reliable](http://blog.netherlabs.nl/articles/2009/01/18/the-ultimate-so_linger-page-or-why-is-my-tcp-not-reliable),” *blog.netherlabs.nl*, January 18, 2009. +1. Nicolas Liochon: + “[CAP: If All You Have Is a Timeout, Everything Looks Like a Partition](http://blog.thislongrun.com/2015/05/CAP-theorem-partition-timeout-zookeeper.html),” *blog.thislongrun.com*, + May 25, 2015. +1. Jerome H. Saltzer, David P. Reed, and + David D. Clark: “[End-To-End Arguments in System Design](http://www.ece.drexel.edu/courses/ECE-C631-501/SalRee1984.pdf),” *ACM Transactions on Computer Systems*, volume 2, number 4, + pages 277–288, November 1984. + [doi:10.1145/357401.357402](http://dx.doi.org/10.1145/357401.357402) +1. Matthew P. Grosvenor, Malte Schwarzkopf, Ionel Gog, et al.: + “[Queues Don’t Matter When You Can JUMP Them!](https://www.usenix.org/system/files/conference/nsdi15/nsdi15-paper-grosvenor_update.pdf),” at *12th USENIX Symposium on Networked + Systems Design and Implementation* (NSDI), May 2015. +1. Guohui Wang and T. S. Eugene Ng: + “[The Impact of Virtualization on Network Performance of Amazon EC2 Data Center](http://www.cs.rice.edu/~eugeneng/papers/INFOCOM10-ec2.pdf),” at *29th IEEE + International Conference on Computer Communications* (INFOCOM), March 2010. + [doi:10.1109/INFCOM.2010.5461931](http://dx.doi.org/10.1109/INFCOM.2010.5461931) +1. Van Jacobson: + “[Congestion Avoidance and Control](http://www.cs.usask.ca/ftp/pub/discus/seminars2002-2003/p314-jacobson.pdf),” at *ACM Symposium on Communications Architectures and + Protocols* (SIGCOMM), August 1988. + [doi:10.1145/52324.52356](http://dx.doi.org/10.1145/52324.52356) +1. Brandon Philips: + “[etcd: Distributed Locking and Service Discovery](https://www.youtube.com/watch?v=HJIjTTHWYnE),” at *Strange Loop*, September 2014. +1. Steve Newman: + “[A Systematic Look at EC2 I/O](http://blog.scalyr.com/2012/10/a-systematic-look-at-ec2-io/),” *blog.scalyr.com*, October 16, 2012. +1. Naohiro Hayashibara, Xavier Défago, Rami Yared, and + Takuya Katayama: “[The ϕ Accrual Failure Detector](http://hdl.handle.net/10119/4784),” Japan Advanced Institute of Science and Technology, School of Information + Science, Technical Report IS-RR-2004-010, May 2004. +1. Jeffrey Wang: + “[Phi Accrual Failure Detector](http://ternarysearch.blogspot.co.uk/2013/08/phi-accrual-failure-detector.html),” *ternarysearch.blogspot.co.uk*, August 11, 2013. +1. Srinivasan Keshav: *An Engineering Approach + to Computer Networking: ATM Networks, the Internet, and the Telephone Network*. + Addison-Wesley Professional, May 1997. ISBN: 978-0-201-63442-6 +1. Cisco, “[Integrated Services Digital Network](http://docwiki.cisco.com/wiki/Integrated_Services_Digital_Network),” *docwiki.cisco.com*. +1. Othmar Kyas: *ATM Networks*. + International Thomson Publishing, 1995. ISBN: 978-1-850-32128-6 +1. “[InfiniBand FAQ](http://www.mellanox.com/related-docs/whitepapers/InfiniBandFAQ_FQ_100.pdf),” Mellanox Technologies, December 22, 2014. +1. Jose Renato Santos, Yoshio Turner, and G. (John) Janakiraman: + “[End-to-End Congestion Control for InfiniBand](http://www.hpl.hp.com/techreports/2002/HPL-2002-359.pdf),” at *22nd Annual Joint Conference of the IEEE Computer and + Communications Societies* (INFOCOM), April 2003. Also published by HP Laboratories Palo + Alto, Tech Report HPL-2002-359. + [doi:10.1109/INFCOM.2003.1208949](http://dx.doi.org/10.1109/INFCOM.2003.1208949) +1. Ulrich Windl, David Dalton, Marc Martinec, and Dale R. Worley: + “[The NTP FAQ and HOWTO](http://www.ntp.org/ntpfaq/NTP-a-faq.htm),” *ntp.org*, + November 2006. +1. John Graham-Cumming: + “[How and why the leap second affected Cloudflare DNS](https://blog.cloudflare.com/how-and-why-the-leap-second-affected-cloudflare-dns/),” *blog.cloudflare.com*, January 1, 2017. +1. David Holmes: + “[Inside the Hotspot VM: Clocks, Timers and Scheduling Events – Part I – Windows](https://blogs.oracle.com/dholmes/entry/inside_the_hotspot_vm_clocks),” *blogs.oracle.com*, + October 2, 2006. +1. Steve Loughran: + “[Time on Multi-Core, Multi-Socket Servers](http://steveloughran.blogspot.co.uk/2015/09/time-on-multi-core-multi-socket-servers.html),” *steveloughran.blogspot.co.uk*, September 17, 2015. +1. James C. Corbett, Jeffrey Dean, Michael Epstein, et al.: + “[Spanner: Google’s Globally-Distributed Database](http://research.google.com/archive/spanner.html),” at *10th USENIX Symposium on Operating System Design and + Implementation* (OSDI), October 2012. +1. M. Caporaloni and R. Ambrosini: + “[How Closely Can a Personal Computer Clock Track the UTC Timescale Via the Internet?](https://iopscience.iop.org/0143-0807/23/4/103/),” *European Journal of + Physics*, volume 23, number 4, pages L17–L21, June 2012. + [doi:10.1088/0143-0807/23/4/103](http://dx.doi.org/10.1088/0143-0807/23/4/103) +1. Nelson Minar: + “[A Survey of the NTP Network](http://alumni.media.mit.edu/~nelson/research/ntp-survey99/),” + *alumni.media.mit.edu*, December 1999. +1. Viliam Holub: + “[Synchronizing Clocks in a Cassandra Cluster Pt. 1 – The Problem](https://blog.logentries.com/2014/03/synchronizing-clocks-in-a-cassandra-cluster-pt-1-the-problem/),” *blog.logentries.com*, March 14, 2014. +1. Poul-Henning Kamp: + “[The One-Second War (What Time Will You Die?)](http://queue.acm.org/detail.cfm?id=1967009),” *ACM Queue*, volume 9, number 4, pages 44–48, April 2011. + [doi:10.1145/1966989.1967009](http://dx.doi.org/10.1145/1966989.1967009) +1. Nelson Minar: + “[Leap Second Crashes Half the Internet](http://www.somebits.com/weblog/tech/bad/leap-second-2012.html),” *somebits.com*, July 3, 2012. +1. Christopher Pascoe: + “[Time, Technology and Leaping Seconds](http://googleblog.blogspot.co.uk/2011/09/time-technology-and-leaping-seconds.html),” *googleblog.blogspot.co.uk*, September 15, 2011. +1. Mingxue Zhao and Jeff Barr: + “[Look Before You Leap – The Coming Leap Second and AWS](https://aws.amazon.com/blogs/aws/look-before-you-leap-the-coming-leap-second-and-aws/),” *aws.amazon.com*, May 18, 2015. +1. Darryl Veitch and Kanthaiah Vijayalayan: + “[Network Timing and the 2015 Leap Second](http://crin.eng.uts.edu.au/~darryl/Publications/LeapSecond_camera.pdf),” at *17th International Conference on Passive and Active + Measurement* (PAM), April 2016. + [doi:10.1007/978-3-319-30505-9_29](http://dx.doi.org/10.1007/978-3-319-30505-9_29) +1. “[Timekeeping in VMware Virtual Machines](http://www.vmware.com/resources/techresources/238),” Information Guide, VMware, Inc., December 2011. +1. “[MiFID II / MiFIR: Regulatory Technical and Implementing Standards – Annex I (Draft)](https://www.esma.europa.eu/sites/default/files/library/2015/11/2015-esma-1464_annex_i_-_draft_rts_and_its_on_mifid_ii_and_mifir.pdf),” + European Securities and Markets Authority, Report ESMA/2015/1464, September 2015. +1. Luke Bigum: + “[Solving MiFID II Clock Synchronisation With Minimum Spend (Part 1)](https://www.lmax.com/blog/staff-blogs/2015/11/27/solving-mifid-ii-clock-synchronisation-minimum-spend-part-1/),” *lmax.com*, November 27, 2015. +1. Kyle Kingsbury: + “[Call Me Maybe: Cassandra](https://aphyr.com/posts/294-call-me-maybe-cassandra/),” *aphyr.com*, September 24, 2013. +1. John Daily: + “[Clocks Are Bad, or, Welcome to the Wonderful World of Distributed Systems](http://basho.com/clocks-are-bad-or-welcome-to-distributed-systems/),” *basho.com*, + November 12, 2013. +1. Kyle Kingsbury: + “[The Trouble with Timestamps](https://aphyr.com/posts/299-the-trouble-with-timestamps),” *aphyr.com*, October 12, 2013. +1. Leslie Lamport: + “[Time, Clocks, and the Ordering of Events in a Distributed System](http://research.microsoft.com/en-US/um/people/Lamport/pubs/time-clocks.pdf),” *Communications of the ACM*, volume + 21, number 7, pages 558–565, July 1978. + [doi:10.1145/359545.359563](http://dx.doi.org/10.1145/359545.359563) +1. Sandeep Kulkarni, Murat Demirbas, Deepak Madeppa, et al.: + “[Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases](http://www.cse.buffalo.edu/tech-reports/2014-04.pdf),” State University of New York at + Buffalo, Computer Science and Engineering Technical Report 2014-04, May 2014. +1. Justin Sheehy: + “[There Is No Now: Problems With Simultaneity in Distributed Systems](https://queue.acm.org/detail.cfm?id=2745385),” *ACM Queue*, volume 13, number 3, pages 36–41, March 2015. + [doi:10.1145/2733108](http://dx.doi.org/10.1145/2733108) +1. Murat Demirbas: + “[Spanner: Google's Globally-Distributed Database](http://muratbuffalo.blogspot.co.uk/2013/07/spanner-googles-globally-distributed_4.html),” *muratbuffalo.blogspot.co.uk*, July 4, 2013. +1. Dahlia Malkhi and Jean-Philippe Martin: + “[Spanner's Concurrency Control](http://www.cs.cornell.edu/~ie53/publications/DC-col51-Sep13.pdf),” *ACM SIGACT News*, volume 44, number 3, pages 73–77, September 2013. + [doi:10.1145/2527748.2527767](http://dx.doi.org/10.1145/2527748.2527767) +1. Manuel Bravo, Nuno Diegues, Jingna Zeng, et al.: + “[On the Use of Clocks to Enforce Consistency in the Cloud](http://sites.computer.org/debull/A15mar/p18.pdf),” *IEEE Data Engineering Bulletin*, + volume 38, number 1, pages 18–31, March 2015. +1. Spencer Kimball: + “[Living Without Atomic Clocks](http://www.cockroachlabs.com/blog/living-without-atomic-clocks/),” *cockroachlabs.com*, February 17, 2016. +1. Cary G. Gray and David R. Cheriton: + “[Leases: An Efficient Fault-Tolerant Mechanism for Distributed File Cache Consistency](http://web.stanford.edu/class/cs240/readings/89-leases.pdf),” at + *12th ACM Symposium on Operating Systems Principles* (SOSP), December 1989. + [doi:10.1145/74850.74870](http://dx.doi.org/10.1145/74850.74870) +1. Todd Lipcon: + “[Avoiding Full GCs in Apache HBase with MemStore-Local Allocation Buffers: Part 1](http://blog.cloudera.com/blog/2011/02/avoiding-full-gcs-in-hbase-with-memstore-local-allocation-buffers-part-1/),” + *blog.cloudera.com*, February 24, 2011. +1. Martin Thompson: + “[Java Garbage Collection Distilled](http://mechanical-sympathy.blogspot.co.uk/2013/07/java-garbage-collection-distilled.html),” *mechanical-sympathy.blogspot.co.uk*, July 16, 2013. +1. Alexey Ragozin: + “[How to Tame Java GC Pauses? Surviving 16GiB Heap and Greater](http://java.dzone.com/articles/how-tame-java-gc-pauses),” *java.dzone.com*, June 28, 2011. +1. Christopher Clark, Keir Fraser, Steven Hand, et al.: + “[Live Migration of Virtual Machines](http://www.cl.cam.ac.uk/research/srg/netos/papers/2005-nsdi-migration.pdf),” at *2nd USENIX Symposium on Symposium on + Networked Systems Design & Implementation* (NSDI), May 2005. +1. Mike Shaver: + “[fsyncers and Curveballs](http://shaver.off.net/diary/2008/05/25/fsyncers-and-curveballs/),” *shaver.off.net*, May 25, 2008. +1. Zhenyun Zhuang and Cuong Tran: + “[Eliminating Large JVM GC Pauses Caused by Background IO Traffic](https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic),” *engineering.linkedin.com*, February 10, + 2016. +1. David Terei and Amit Levy: + “[Blade: A Data Center Garbage Collector](http://arxiv.org/pdf/1504.02578.pdf),” + arXiv:1504.02578, April 13, 2015. +1. Martin Maas, Tim Harris, Krste Asanović, and John Kubiatowicz: + “[Trash Day: Coordinating Garbage Collection in Distributed Systems](https://timharris.uk/papers/2015-hotos.pdf),” at *15th USENIX Workshop on Hot Topics in Operating + Systems* (HotOS), May 2015. +1. “[Predictable Low Latency](http://cdn2.hubspot.net/hubfs/1624455/Website_2016/content/White%20papers/Cinnober%20on%20GC%20pause%20free%20Java%20applications.pdf),” Cinnober Financial Technology AB, *cinnober.com*, November 24, 2013. +1. Martin Fowler: + “[The LMAX Architecture](http://martinfowler.com/articles/lmax.html),” + *martinfowler.com*, July 12, 2011. +1. Flavio P. Junqueira and Benjamin Reed: + *ZooKeeper: Distributed Process Coordination*. O'Reilly Media, 2013. + ISBN: 978-1-449-36130-3 +1. Enis Söztutar: + “[HBase and HDFS: Understanding Filesystem Usage in HBase](http://www.slideshare.net/enissoz/hbase-and-hdfs-understanding-filesystem-usage),” at *HBaseCon*, + June 2013. +1. Caitie McCaffrey: + “[Clients Are Jerks: AKA How Halo 4 DoSed the Services at Launch & How We Survived](http://caitiem.com/2015/06/23/clients-are-jerks-aka-how-halo-4-dosed-the-services-at-launch-how-we-survived/),” *caitiem.com*, + June 23, 2015. +1. Leslie Lamport, Robert Shostak, and Marshall Pease: + “[The Byzantine Generals Problem](http://research.microsoft.com/en-us/um/people/lamport/pubs/byz.pdf),” *ACM Transactions on Programming Languages and + Systems* (TOPLAS), volume 4, number 3, pages 382–401, July 1982. + [doi:10.1145/357172.357176](http://dx.doi.org/10.1145/357172.357176) +1. Jim N. Gray: + “[Notes on Data Base Operating Systems](http://research.microsoft.com/en-us/um/people/gray/papers/DBOS.pdf),” in *Operating Systems: An Advanced Course*, Lecture + Notes in Computer Science, volume 60, edited by R. Bayer, R. M. Graham, and G. Seegmüller, + pages 393–481, Springer-Verlag, 1978. ISBN: 978-3-540-08755-7 +1. Brian Palmer: + “[How Complicated Was the Byzantine Empire?](http://www.slate.com/articles/news_and_politics/explainer/2011/10/the_byzantine_tax_code_how_complicated_was_byzantium_anyway_.html),” *slate.com*, October 20, 2011. +1. Leslie Lamport: + “[My Writings](http://research.microsoft.com/en-us/um/people/lamport/pubs/pubs.html),” *research.microsoft.com*, December 16, 2014. This page can be found by searching the + web for the 23-character string obtained by removing the hyphens from the string + allla-mport-spubso-ntheweb. +1. John Rushby: + “[Bus Architectures for Safety-Critical Embedded Systems](http://www.csl.sri.com/papers/emsoft01/emsoft01.pdf),” at *1st International Workshop on Embedded Software* + (EMSOFT), October 2001. +1. Jake Edge: + “[ELC: SpaceX Lessons Learned](http://lwn.net/Articles/540368/),” *lwn.net*, + March 6, 2013. +1. Andrew Miller and Joseph J. LaViola, Jr.: + “[Anonymous Byzantine Consensus from Moderately-Hard Puzzles: A Model for Bitcoin](http://nakamotoinstitute.org/static/docs/anonymous-byzantine-consensus.pdf),” University of Central + Florida, Technical Report CS-TR-14-01, April 2014. +1. James Mickens: + “[The Saddest Moment](https://www.usenix.org/system/files/login-logout_1305_mickens.pdf),” *USENIX ;login: logout*, May 2013. +1. Evan Gilman: + “[The Discovery of Apache ZooKeeper’s Poison Packet](http://www.pagerduty.com/blog/the-discovery-of-apache-zookeepers-poison-packet/),” *pagerduty.com*, May 7, 2015. +1. Jonathan Stone and Craig Partridge: + “[When the CRC and TCP Checksum Disagree](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.27.7611&rep=rep1&type=pdf),” at *ACM Conference on Applications, + Technologies, Architectures, and Protocols for Computer Communication* (SIGCOMM), August 2000. + [doi:10.1145/347059.347561](http://dx.doi.org/10.1145/347059.347561) +1. Evan Jones: + “[How Both TCP and Ethernet Checksums Fail](http://www.evanjones.ca/tcp-and-ethernet-checksums-fail.html),” *evanjones.ca*, October 5, 2015. +1. Cynthia Dwork, Nancy Lynch, and Larry Stockmeyer: + “[Consensus in the Presence of Partial Synchrony](http://www.net.t-labs.tu-berlin.de/~petr/ADC-07/papers/DLS88.pdf),” *Journal of the ACM*, volume 35, number 2, pages 288–323, + April 1988. [doi:10.1145/42282.42283](http://dx.doi.org/10.1145/42282.42283) +1. Peter Bailis and Ali Ghodsi: + “[Eventual Consistency Today: Limitations, Extensions, and Beyond](http://queue.acm.org/detail.cfm?id=2462076),” *ACM Queue*, volume 11, number 3, pages 55-63, March 2013. + [doi:10.1145/2460276.2462076](http://dx.doi.org/10.1145/2460276.2462076) +1. Bowen Alpern and Fred B. Schneider: + “[Defining Liveness](https://www.cs.cornell.edu/fbs/publications/DefLiveness.pdf),” + *Information Processing Letters*, volume 21, number 4, pages 181–185, October 1985. + [doi:10.1016/0020-0190(85)90056-0](http://dx.doi.org/10.1016/0020-0190(85)90056-0) +1. Flavio P. Junqueira: + “[Dude, Where’s My Metadata?](http://fpj.me/2015/05/28/dude-wheres-my-metadata/),” + *fpj.me*, May 28, 2015. +1. Scott Sanders: + “[January 28th Incident Report](https://github.com/blog/2106-january-28th-incident-report),” *github.com*, February 3, 2016. +1. Jay Kreps: + “[A Few Notes on Kafka and Jepsen](http://blog.empathybox.com/post/62279088548/a-few-notes-on-kafka-and-jepsen),” *blog.empathybox.com*, September 25, 2013. +1. Thanh Do, Mingzhe Hao, Tanakorn + Leesatapornwongsa, et al.: + “[Limplock: Understanding the Impact of Limpware on Scale-out Cloud Systems](http://ucare.cs.uchicago.edu/pdf/socc13-limplock.pdf),” at *4th ACM Symposium on Cloud Computing* + (SoCC), October 2013. + [doi:10.1145/2523616.2523627](http://dx.doi.org/10.1145/2523616.2523627) +1. Frank McSherry, Michael Isard, and Derek G. Murray: + “[Scalability! But at What COST?](http://www.frankmcsherry.org/assets/COST.pdf),” + at *15th USENIX Workshop on Hot Topics in Operating Systems* (HotOS), + May 2015. + + + + + +![](img/fig8-1.png) + +图8-1 + +![](img/fig8-2.png) + +图8-2 + +![](img/fig8-3.png) + +图8-3 + +![](img/fig8-4.png) + +图8-4 + +![](img/fig8-5.png) + +图8-5 \ No newline at end of file diff --git a/ddia/ch9.md b/ddia/ch9.md new file mode 100644 index 0000000..f7e02a6 --- /dev/null +++ b/ddia/ch9.md @@ -0,0 +1,506 @@ +# 9. 一致性与共识 + +![](img/ch9.png) + +> 好死不如赖活着 +> —— Jay Kreps, 关于Kafka与 Jepsen的若干笔记 (2013) + +--------------- + + + +## 一致性保证 + +## 线性一致性 + +## 顺序保证 + +## 分布式事务与共识 + +## 本章小结 + + + +## 参考文献 + + +1. Peter Bailis and Ali Ghodsi: + “[Eventual Consistency Today: Limitations, Extensions, and Beyond](http://queue.acm.org/detail.cfm?id=2462076),” *ACM Queue*, volume 11, number 3, pages 55-63, March 2013. + [doi:10.1145/2460276.2462076](http://dx.doi.org/10.1145/2460276.2462076) + +1. Prince Mahajan, Lorenzo Alvisi, and Mike Dahlin: + “[Consistency, Availability, and Convergence](http://apps.cs.utexas.edu/tech_reports/reports/tr/TR-2036.pdf),” University of Texas at Austin, Department of Computer + Science, Tech Report UTCS TR-11-22, May 2011. + +1. Alex Scotti: + “[Adventures in Building Your Own Database](http://www.slideshare.net/AlexScotti1/allyourbase-55212398),” at *All Your Base*, November 2015. + +1. Peter Bailis, Aaron Davidson, Alan Fekete, et al.: + “[Highly Available Transactions: Virtues and Limitations](http://arxiv.org/pdf/1302.0309.pdf),” at *40th International Conference on Very Large Data Bases* (VLDB), + September 2014. Extended version published as pre-print arXiv:1302.0309 [cs.DB]. + +1. Paolo Viotti and Marko Vukolić: + “[Consistency in Non-Transactional Distributed Storage Systems](http://arxiv.org/abs/1512.00168),” arXiv:1512.00168, 12 April 2016. + +1. Maurice P. Herlihy and Jeannette M. Wing: + “[Linearizability: A Correctness Condition for Concurrent Objects](http://cs.brown.edu/~mph/HerlihyW90/p463-herlihy.pdf),” *ACM Transactions on Programming + Languages and Systems* (TOPLAS), volume 12, number 3, pages 463–492, July 1990. + [doi:10.1145/78969.78972](http://dx.doi.org/10.1145/78969.78972) + +1. Leslie Lamport: + “[On interprocess communication](http://research.microsoft.com/en-us/um/people/lamport/pubs/interprocess.pdf),” *Distributed Computing*, volume 1, number 2, pages 77–101, + June 1986. [doi:10.1007/BF01786228](http://dx.doi.org/10.1007/BF01786228) + +1. David K. Gifford: + “[Information Storage in a Decentralized Computer System](http://www.mirrorservice.org/sites/www.bitsavers.org/pdf/xerox/parc/techReports/CSL-81-8_Information_Storage_in_a_Decentralized_Computer_System.pdf),” Xerox Palo Alto Research Centers, CSL-81-8, June 1981. + +1. Martin Kleppmann: + “[Please Stop Calling Databases CP or AP](http://martin.kleppmann.com/2015/05/11/please-stop-calling-databases-cp-or-ap.html),” *martin.kleppmann.com*, May 11, 2015. + +1. Kyle Kingsbury: + “[Call Me Maybe: MongoDB Stale Reads](https://aphyr.com/posts/322-call-me-maybe-mongodb-stale-reads),” *aphyr.com*, April 20, 2015. + +1. Kyle Kingsbury: + “[Computational Techniques in Knossos](https://aphyr.com/posts/314-computational-techniques-in-knossos),” *aphyr.com*, May 17, 2014. + +1. Peter Bailis: + “[Linearizability Versus Serializability](http://www.bailis.org/blog/linearizability-versus-serializability/),” *bailis.org*, September 24, 2014. + +1. Philip A. Bernstein, Vassos Hadzilacos, and Nathan Goodman: + *Concurrency + Control and Recovery in Database Systems*. Addison-Wesley, 1987. ISBN: 978-0-201-10715-9, + available online at *research.microsoft.com*. + +1. Mike Burrows: + “[The Chubby Lock Service for Loosely-Coupled Distributed Systems](http://research.google.com/archive/chubby.html),” at *7th USENIX Symposium on Operating System + Design and Implementation* (OSDI), November 2006. + +1. Flavio P. Junqueira and Benjamin Reed: + *ZooKeeper: Distributed Process Coordination*. O'Reilly Media, 2013. + ISBN: 978-1-449-36130-3 + +1. “[etcd 2.0.12 Documentation](https://coreos.com/etcd/docs/2.0.12/),” CoreOS, Inc., 2015. + +1. “[Apache Curator](http://curator.apache.org/),” Apache Software Foundation, *curator.apache.org*, 2015. + +1. Morali Vallath: + *Oracle 10g RAC Grid, Services & Clustering*. Elsevier Digital Press, 2006. + ISBN: 978-1-555-58321-7 + +1. Peter Bailis, Alan Fekete, Michael J Franklin, et al.: + “[Coordination-Avoiding Database Systems](http://arxiv.org/pdf/1402.2237.pdf),” + *Proceedings of the VLDB Endowment*, volume 8, number 3, pages 185–196, November 2014. + +1. Kyle Kingsbury: + “[Call Me Maybe: etcd and Consul](https://aphyr.com/posts/316-call-me-maybe-etcd-and-consul),” *aphyr.com*, June 9, 2014. + +1. Flavio P. Junqueira, Benjamin C. Reed, and Marco Serafini: + “[Zab: High-Performance Broadcast for Primary-Backup Systems](https://pdfs.semanticscholar.org/b02c/6b00bd5dbdbd951fddb00b906c82fa80f0b3.pdf),” at *41st IEEE International Conference on Dependable + Systems and Networks* (DSN), June 2011. + [doi:10.1109/DSN.2011.5958223](http://dx.doi.org/10.1109/DSN.2011.5958223) + +1. Diego Ongaro and John K. Ousterhout: + “[In Search of an Understandable Consensus Algorithm (Extended Version)](http://ramcloud.stanford.edu/raft.pdf),” at *USENIX Annual Technical Conference* + (ATC), June 2014. + +1. Hagit Attiya, Amotz Bar-Noy, and Danny Dolev: + “[Sharing Memory Robustly in Message-Passing Systems](http://www.cse.huji.ac.il/course/2004/dist/p124-attiya.pdf),” *Journal of the ACM*, volume 42, number 1, pages 124–142, January 1995. + [doi:10.1145/200836.200869](http://dx.doi.org/10.1145/200836.200869) + +1. Nancy Lynch and Alex Shvartsman: + “[Robust Emulation of Shared Memory Using Dynamic Quorum-Acknowledged Broadcasts](http://groups.csail.mit.edu/tds/papers/Lynch/FTCS97.pdf),” at *27th Annual International Symposium on + Fault-Tolerant Computing* (FTCS), June 1997. + [doi:10.1109/FTCS.1997.614100](http://dx.doi.org/10.1109/FTCS.1997.614100) + +1. Christian Cachin, Rachid Guerraoui, and Luís Rodrigues: + *Introduction to Reliable and Secure Distributed + Programming*, 2nd edition. Springer, 2011. ISBN: 978-3-642-15259-7, + [doi:10.1007/978-3-642-15260-3](http://dx.doi.org/10.1007/978-3-642-15260-3) + +1. Sam Elliott, Mark Allen, and Martin Kleppmann: + [personal communication](https://twitter.com/lenary/status/654761711933648896), + thread on *twitter.com*, October 15, 2015. + +1. Niklas Ekström, Mikhail Panchenko, and Jonathan Ellis: + “[Possible Issue with Read Repair?](http://mail-archives.apache.org/mod_mbox/cassandra-dev/201210.mbox/%3CFA480D1DC3964E2C8B0A14E0880094C9%40Robotech%3E),” email thread on *cassandra-dev* mailing list, October 2012. + +1. Maurice P. Herlihy: + “[Wait-Free Synchronization](https://cs.brown.edu/~mph/Herlihy91/p124-herlihy.pdf),” + *ACM Transactions on Programming Languages and Systems* (TOPLAS), volume 13, number 1, + pages 124–149, January 1991. + [doi:10.1145/114005.102808](http://dx.doi.org/10.1145/114005.102808) + +1. Armando Fox and Eric A. Brewer: + “[Harvest, Yield, and Scalable Tolerant Systems](http://radlab.cs.berkeley.edu/people/fox/static/pubs/pdf/c18.pdf),” at *7th Workshop on Hot Topics in Operating + Systems* (HotOS), March 1999. + [doi:10.1109/HOTOS.1999.798396](http://dx.doi.org/10.1109/HOTOS.1999.798396) + +1. Seth Gilbert and Nancy Lynch: + “[Brewer’s Conjecture and the Feasibility of Consistent, Available, Partition-Tolerant Web Services](http://www.comp.nus.edu.sg/~gilbert/pubs/BrewersConjecture-SigAct.pdf),” + *ACM SIGACT News*, volume 33, number 2, pages 51–59, June 2002. + [doi:10.1145/564585.564601](http://dx.doi.org/10.1145/564585.564601) + +1. Seth Gilbert and Nancy Lynch: + “[Perspectives on the CAP Theorem](http://groups.csail.mit.edu/tds/papers/Gilbert/Brewer2.pdf),” *IEEE Computer Magazine*, volume 45, number 2, pages 30–36, February 2012. + [doi:10.1109/MC.2011.389](http://dx.doi.org/10.1109/MC.2011.389) + +1. Eric A. Brewer: + “[CAP Twelve Years Later: How the 'Rules' Have Changed](http://cs609.cs.ua.edu/CAP12.pdf),” *IEEE Computer Magazine*, volume 45, number 2, pages 23–29, February 2012. + [doi:10.1109/MC.2012.37](http://dx.doi.org/10.1109/MC.2012.37) + +1. Susan B. Davidson, Hector Garcia-Molina, and Dale Skeen: + “[Consistency in Partitioned Networks](http://delab.csd.auth.gr/~dimitris/courses/mpc_fall05/papers/invalidation/acm_csur85_partitioned_network_consistency.pdf),” *ACM Computing Surveys*, volume 17, number 3, pages 341–370, September 1985. + [doi:10.1145/5505.5508](http://dx.doi.org/10.1145/5505.5508) + +1. Paul R. Johnson and Robert H. Thomas: + “[RFC 677: The Maintenance of Duplicate Databases](https://tools.ietf.org/html/rfc677),” Network Working Group, January 27, 1975. + +1. Bruce G. Lindsay, Patricia Griffiths Selinger, C. Galtieri, et al.: + “[Notes on Distributed Databases](http://domino.research.ibm.com/library/cyberdig.nsf/papers/A776EC17FC2FCE73852579F100578964/$File/RJ2571.pdf),” IBM Research, Research Report RJ2571(33471), July 1979. + +1. Michael J. Fischer and Alan Michael: + “[Sacrificing Serializability to Attain High Availability of Data in an Unreliable Network](http://www.cs.ucsb.edu/~agrawal/spring2011/ugrad/p70-fischer.pdf),” at + *1st ACM Symposium on Principles of Database Systems* (PODS), March 1982. + [doi:10.1145/588111.588124](http://dx.doi.org/10.1145/588111.588124) + +1. Eric A. Brewer: + “[NoSQL: Past, Present, Future](http://www.infoq.com/presentations/NoSQL-History),” + at *QCon San Francisco*, November 2012. + +1. Henry Robinson: + “[CAP Confusion: Problems with 'Partition Tolerance,'](http://blog.cloudera.com/blog/2010/04/cap-confusion-problems-with-partition-tolerance/)” *blog.cloudera.com*, April 26, 2010. + +1. Adrian Cockcroft: + “[Migrating to Microservices](http://www.infoq.com/presentations/migration-cloud-native),” at *QCon London*, March 2014. + +1. Martin Kleppmann: + “[A Critique of the CAP Theorem](http://arxiv.org/abs/1509.05393),” arXiv:1509.05393, + September 17, 2015. + +1. Nancy A. Lynch: + “[A Hundred Impossibility Proofs for Distributed Computing](http://groups.csail.mit.edu/tds/papers/Lynch/podc89.pdf),” at *8th ACM Symposium on Principles of Distributed + Computing* (PODC), August 1989. + [doi:10.1145/72981.72982](http://dx.doi.org/10.1145/72981.72982) + +1. Hagit Attiya, Faith Ellen, and Adam Morrison: + “[Limitations of Highly-Available Eventually-Consistent Data Stores](http://www.cs.technion.ac.il/people/mad/online-publications/podc2015-replds.pdf),” at *ACM Symposium on Principles of + Distributed Computing* (PODC), July 2015. + [doi:10.1145/2767386.2767419](http://dx.doi.org/10.1145/2767386.2767419) + +1. Peter Sewell, Susmit Sarkar, + Scott Owens, et al.: + “[x86-TSO: A Rigorous and Usable Programmer's Model for x86 Multiprocessors](http://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf),” *Communications of the ACM*, + volume 53, number 7, pages 89–97, July 2010. + [doi:10.1145/1785414.1785443](http://dx.doi.org/10.1145/1785414.1785443) + +1. Martin Thompson: + “[Memory Barriers/Fences](http://mechanical-sympathy.blogspot.co.uk/2011/07/memory-barriersfences.html),” *mechanical-sympathy.blogspot.co.uk*, July 24, 2011. + +1. Ulrich Drepper: + “[What Every Programmer Should Know About Memory](http://www.akkadia.org/drepper/cpumemory.pdf),” *akkadia.org*, November 21, 2007. + +1. Daniel J. Abadi: + “[Consistency Tradeoffs in Modern Distributed Database System Design](http://cs-www.cs.yale.edu/homes/dna/papers/abadi-pacelc.pdf),” *IEEE Computer Magazine*, + volume 45, number 2, pages 37–42, February 2012. + [doi:10.1109/MC.2012.33](http://dx.doi.org/10.1109/MC.2012.33) + +1. Hagit Attiya and Jennifer L. Welch: + “[Sequential Consistency Versus Linearizability](http://courses.csail.mit.edu/6.852/01/papers/p91-attiya.pdf),” *ACM Transactions on Computer Systems* (TOCS), + volume 12, number 2, pages 91–122, May 1994. + [doi:10.1145/176575.176576](http://dx.doi.org/10.1145/176575.176576) + +1. Mustaque Ahamad, Gil Neiger, James E. Burns, et al.: + “[Causal Memory: Definitions, Implementation, and Programming](http://www-i2.informatik.rwth-aachen.de/i2/fileadmin/user_upload/documents/Seminar_MCMM11/Causal_memory_1996.pdf),” *Distributed + Computing*, volume 9, number 1, pages 37–49, March 1995. + [doi:10.1007/BF01784241](http://dx.doi.org/10.1007/BF01784241) + +1. Wyatt Lloyd, Michael J. Freedman, + Michael Kaminsky, and David G. Andersen: + “[Stronger Semantics for Low-Latency Geo-Replicated Storage](https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final149.pdf),” at *10th USENIX Symposium on Networked + Systems Design and Implementation* (NSDI), April 2013. + +1. Marek Zawirski, Annette Bieniusa, Valter Balegas, et al.: + “[SwiftCloud: Fault-Tolerant Geo-Replication Integrated All the Way to the Client Machine](http://arxiv.org/abs/1310.3107),” INRIA Research Report 8347, August 2013. + +1. Peter Bailis, Ali Ghodsi, Joseph M Hellerstein, and Ion Stoica: + “[Bolt-on Causal Consistency](http://db.cs.berkeley.edu/papers/sigmod13-bolton.pdf),” at + *ACM International Conference on Management of Data* (SIGMOD), June 2013. + +1. Philippe Ajoux, Nathan Bronson, Sanjeev + Kumar, et al.: + “[Challenges to Adopting Stronger Consistency at Scale](https://www.usenix.org/system/files/conference/hotos15/hotos15-paper-ajoux.pdf),” at *15th USENIX Workshop on Hot Topics in + Operating Systems* (HotOS), May 2015. + +1. Peter Bailis: + “[Causality Is Expensive (and What to Do About It)](http://www.bailis.org/blog/causality-is-expensive-and-what-to-do-about-it/),” *bailis.org*, February 5, 2014. + +1. Ricardo Gonçalves, Paulo Sérgio Almeida, + Carlos Baquero, and Victor Fonte: + “[Concise Server-Wide Causality Management for Eventually Consistent Data Stores](http://haslab.uminho.pt/tome/files/global_logical_clocks.pdf),” at *15th IFIP International + Conference on Distributed Applications and Interoperable Systems* (DAIS), June 2015. + [doi:10.1007/978-3-319-19129-4_6](http://dx.doi.org/10.1007/978-3-319-19129-4_6) + +1. Rob Conery: + “[A Better ID Generator for PostgreSQL](http://rob.conery.io/2014/05/29/a-better-id-generator-for-postgresql/),” *rob.conery.io*, May 29, 2014. + +1. Leslie Lamport: + “[Time, Clocks, and the Ordering of Events in a Distributed System](http://research.microsoft.com/en-US/um/people/Lamport/pubs/time-clocks.pdf),” *Communications of the ACM*, + volume 21, number 7, pages 558–565, July 1978. + [doi:10.1145/359545.359563](http://dx.doi.org/10.1145/359545.359563) + +1. Xavier Défago, André Schiper, and Péter Urbán: + “[Total Order Broadcast and Multicast Algorithms: Taxonomy and Survey](https://dspace.jaist.ac.jp/dspace/bitstream/10119/4883/1/defago_et_al.pdf),” *ACM Computing + Surveys*, volume 36, number 4, pages 372–421, December 2004. + [doi:10.1145/1041680.1041682](http://dx.doi.org/10.1145/1041680.1041682) + +1. Hagit Attiya and Jennifer Welch: *Distributed + Computing: Fundamentals, Simulations and Advanced Topics*, 2nd edition. + John Wiley & Sons, 2004. ISBN: 978-0-471-45324-6, + [doi:10.1002/0471478210](http://dx.doi.org/10.1002/0471478210) + +1. Mahesh + Balakrishnan, Dahlia Malkhi, Vijayan Prabhakaran, et al.: + “[CORFU: A Shared Log Design for Flash Clusters](https://www.usenix.org/system/files/conference/nsdi12/nsdi12-final30.pdf),” at *9th USENIX Symposium on Networked + Systems Design and Implementation* (NSDI), April 2012. + +1. Fred B. Schneider: + “[Implementing Fault-Tolerant Services Using the State Machine Approach: A Tutorial](http://www.cs.cornell.edu/fbs/publications/smsurvey.pdf),” *ACM Computing Surveys*, volume + 22, number 4, pages 299–319, December 1990. + +1. Alexander Thomson, Thaddeus Diamond, Shu-Chun Weng, et al.: + “[Calvin: Fast Distributed Transactions for Partitioned Database Systems](http://cs.yale.edu/homes/thomson/publications/calvin-sigmod12.pdf),” at *ACM International Conference + on Management of Data* (SIGMOD), May 2012. + +1. Mahesh Balakrishnan, Dahlia Malkhi, Ted Wobber, et al.: + “[Tango: Distributed Data Structures over a Shared Log](http://research.microsoft.com/pubs/199947/Tango.pdf),” at *24th ACM Symposium on Operating Systems + Principles* (SOSP), November 2013. + [doi:10.1145/2517349.2522732](http://dx.doi.org/10.1145/2517349.2522732) + +1. Robbert van Renesse and Fred B. Schneider: + “[Chain Replication for Supporting High Throughput and Availability](http://static.usenix.org/legacy/events/osdi04/tech/full_papers/renesse/renesse.pdf),” at *6th USENIX + Symposium on Operating System Design and Implementation* (OSDI), December 2004. + +1. Leslie Lamport: + “[How to Make a Multiprocessor Computer That Correctly Executes Multiprocess Programs](http://research-srv.microsoft.com/en-us/um/people/lamport/pubs/multi.pdf),” *IEEE + Transactions on Computers*, volume 28, number 9, pages 690–691, September 1979. + [doi:10.1109/TC.1979.1675439](http://dx.doi.org/10.1109/TC.1979.1675439) + +1. Enis Söztutar, Devaraj Das, and Carter Shanklin: + “[Apache HBase High Availability at the Next Level](http://hortonworks.com/blog/apache-hbase-high-availability-next-level/),” *hortonworks.com*, January 22, 2015. + +1. Brian F Cooper, Raghu Ramakrishnan, Utkarsh Srivastava, et al.: + “[PNUTS: Yahoo!’s Hosted Data Serving Platform](http://www.mpi-sws.org/~druschel/courses/ds/papers/cooper-pnuts.pdf),” at *34th International Conference on Very Large Data + Bases* (VLDB), August 2008. + [doi:10.14778/1454159.1454167](http://dx.doi.org/10.14778/1454159.1454167) + +1. Tushar Deepak Chandra and Sam Toueg: + “[Unreliable Failure Detectors for Reliable Distributed Systems](http://courses.csail.mit.edu/6.852/08/papers/CT96-JACM.pdf),” *Journal of the ACM*, + volume 43, number 2, pages 225–267, March 1996. + [doi:10.1145/226643.226647](http://dx.doi.org/10.1145/226643.226647) + +1. Michael J. Fischer, Nancy Lynch, and Michael S. Paterson: + “[Impossibility of Distributed Consensus with One Faulty Process](https://groups.csail.mit.edu/tds/papers/Lynch/jacm85.pdf),” *Journal of the ACM*, volume 32, number 2, pages 374–382, April 1985. + [doi:10.1145/3149.214121](http://dx.doi.org/10.1145/3149.214121) + +1. Michael Ben-Or: “Another Advantage of Free + Choice: Completely Asynchronous Agreement Protocols,” at *2nd ACM Symposium on Principles of + Distributed Computing* (PODC), August 1983. + [doi:10.1145/800221.806707](http://dl.acm.org/citation.cfm?id=806707) + +1. Jim N. Gray and Leslie Lamport: + “[Consensus on Transaction Commit](http://db.cs.berkeley.edu/cs286/papers/paxoscommit-tods2006.pdf),” *ACM Transactions on Database Systems* (TODS), volume 31, + number 1, pages 133–160, March 2006. + [doi:10.1145/1132863.1132867](http://dx.doi.org/10.1145/1132863.1132867) + +1. Rachid Guerraoui: + “[Revisiting the Relationship Between Non-Blocking Atomic Commitment and Consensus](https://pdfs.semanticscholar.org/5d06/489503b6f791aa56d2d7942359c2592e44b0.pdf),” at *9th International + Workshop on Distributed Algorithms* (WDAG), September 1995. + [doi:10.1007/BFb0022140](http://dx.doi.org/10.1007/BFb0022140) + +1. Thanumalayan Sankaranarayana Pillai, Vijay Chidambaram, + Ramnatthan Alagappan, et al.: “[All File Systems Are Not Created Equal: On the Complexity of Crafting Crash-Consistent Applications](http://research.cs.wisc.edu/wind/Publications/alice-osdi14.pdf),” + at *11th USENIX Symposium on Operating Systems Design and Implementation* (OSDI), + October 2014. + +1. Jim Gray: + “[The Transaction Concept: Virtues and Limitations](http://research.microsoft.com/en-us/um/people/gray/papers/theTransactionConcept.pdf),” at *7th International Conference on + Very Large Data Bases* (VLDB), September 1981. + +1. Hector Garcia-Molina and Kenneth Salem: + “[Sagas](http://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf),” at + *ACM International Conference on Management of Data* (SIGMOD), May 1987. + [doi:10.1145/38713.38742](http://dx.doi.org/10.1145/38713.38742) + +1. C. Mohan, Bruce G. Lindsay, and Ron Obermarck: + “[Transaction Management in the R* Distributed Database Management System](https://cs.brown.edu/courses/csci2270/archives/2012/papers/dtxn/p378-mohan.pdf),” + *ACM Transactions on Database Systems*, volume 11, number 4, pages 378–396, December 1986. + [doi:10.1145/7239.7266](http://dx.doi.org/10.1145/7239.7266) + +1. “[Distributed Transaction Processing: The XA Specification](http://pubs.opengroup.org/onlinepubs/009680699/toc.pdf),” X/Open Company Ltd., Technical Standard + XO/CAE/91/300, December 1991. ISBN: 978-1-872-63024-3 + +1. Mike Spille: + “[XA Exposed, Part II](http://www.jroller.com/pyrasun/entry/xa_exposed_part_ii_schwartz),” + *jroller.com*, April 3, 2004. + +1. Ivan Silva Neto and Francisco Reverbel: + “[Lessons Learned from Implementing WS-Coordination and WS-AtomicTransaction](http://www.ime.usp.br/~reverbel/papers/icis2008.pdf),” at *7th IEEE/ACIS International Conference on + Computer and Information Science* (ICIS), May 2008. + [doi:10.1109/ICIS.2008.75](http://dx.doi.org/10.1109/ICIS.2008.75) + +1. James E. Johnson, David E. Langworthy, Leslie Lamport, + and Friedrich H. Vogt: + “[Formal Specification of a Web Services Protocol](http://research.microsoft.com/en-us/um/people/lamport/pubs/wsfm-web.pdf),” at *1st International Workshop on Web Services and + Formal Methods* (WS-FM), February 2004. + [doi:10.1016/j.entcs.2004.02.022](http://dx.doi.org/10.1016/j.entcs.2004.02.022) + +1. Dale Skeen: + “[Nonblocking Commit Protocols](http://www.cs.utexas.edu/~lorenzo/corsi/cs380d/papers/Ske81.pdf),” at *ACM International Conference on Management of Data* (SIGMOD), April 1981. + [doi:10.1145/582318.582339](http://dx.doi.org/10.1145/582318.582339) + +1. Gregor Hohpe: + “[Your Coffee Shop Doesn’t Use Two-Phase Commit](http://www.martinfowler.com/ieeeSoftware/coffeeShop.pdf),” *IEEE Software*, volume 22, number 2, pages 64–66, March 2005. + [doi:10.1109/MS.2005.52](http://dx.doi.org/10.1109/MS.2005.52) + +1. Pat Helland: + “[Life Beyond Distributed Transactions: An Apostate’s Opinion](http://www-db.cs.wisc.edu/cidr/cidr2007/papers/cidr07p15.pdf),” at *3rd Biennial Conference on Innovative Data Systems + Research* (CIDR), January 2007. + +1. Jonathan Oliver: + “[My Beef with MSDTC and Two-Phase Commits](http://blog.jonathanoliver.com/my-beef-with-msdtc-and-two-phase-commits/),” *blog.jonathanoliver.com*, April 4, 2011. + +1. Oren Eini (Ahende Rahien): + “[The Fallacy of Distributed Transactions](http://ayende.com/blog/167362/the-fallacy-of-distributed-transactions),” *ayende.com*, July 17, 2014. + +1. Clemens Vasters: + “[Transactions in Windows Azure (with Service Bus) – An Email Discussion](https://blogs.msdn.microsoft.com/clemensv/2012/07/30/transactions-in-windows-azure-with-service-bus-an-email-discussion/),” *vasters.com*, July 30, 2012. + +1. “[Understanding Transactionality in Azure](https://docs.particular.net/nservicebus/azure/understanding-transactionality-in-azure),” NServiceBus Documentation, Particular Software, 2015. + +1. Randy Wigginton, Ryan Lowe, + Marcos Albe, and Fernando Ipar: + “[Distributed Transactions in MySQL](https://www.percona.com/live/mysql-conference-2013/sites/default/files/slides/XA_final.pdf),” at *MySQL Conference and Expo*, April 2013. + +1. Mike Spille: + “[XA Exposed, Part I](http://www.jroller.com/pyrasun/entry/xa_exposed),” + *jroller.com*, April 3, 2004. + +1. Ajmer Dhariwal: + “[Orphaned MSDTC Transactions (-2 spids)](http://www.eraofdata.com/orphaned-msdtc-transactions-2-spids/),” *eraofdata.com*, December 12, 2008. + +1. Paul Randal: + “[Real World Story of DBCC PAGE Saving the Day](http://www.sqlskills.com/blogs/paul/real-world-story-of-dbcc-page-saving-the-day/),” *sqlskills.com*, June 19, 2013. + +1. “[in-doubt xact resolution Server Configuration Option](https://msdn.microsoft.com/en-us/library/ms179586.aspx),” SQL Server 2016 documentation, Microsoft, Inc., + 2016. + +1. Cynthia Dwork, Nancy Lynch, and Larry Stockmeyer: + “[Consensus in the Presence of Partial Synchrony](http://www.net.t-labs.tu-berlin.de/~petr/ADC-07/papers/DLS88.pdf),” *Journal of the ACM*, volume 35, number 2, pages 288–323, + April 1988. [doi:10.1145/42282.42283](http://dx.doi.org/10.1145/42282.42283) + +1. Miguel Castro and Barbara H. Liskov: + “[Practical Byzantine Fault Tolerance and Proactive Recovery](http://zoo.cs.yale.edu/classes/cs426/2012/bib/castro02practical.pdf),” *ACM Transactions on Computer Systems*, + volume 20, number 4, pages 396–461, November 2002. + [doi:10.1145/571637.571640](http://dx.doi.org/10.1145/571637.571640) + +1. Brian M. Oki and Barbara H. Liskov: + “[Viewstamped Replication: A New Primary Copy Method to Support Highly-Available Distributed Systems](http://www.cs.princeton.edu/courses/archive/fall11/cos518/papers/viewstamped.pdf),” at + *7th ACM Symposium on Principles of Distributed Computing* (PODC), August 1988. + [doi:10.1145/62546.62549](http://dx.doi.org/10.1145/62546.62549) + +1. Barbara H. Liskov and James Cowling: + “[Viewstamped Replication Revisited](http://pmg.csail.mit.edu/papers/vr-revisited.pdf),” + Massachusetts Institute of Technology, Tech Report MIT-CSAIL-TR-2012-021, July 2012. + +1. Leslie Lamport: + “[The Part-Time Parliament](http://research.microsoft.com/en-us/um/people/lamport/pubs/lamport-paxos.pdf),” *ACM Transactions on Computer Systems*, volume 16, number 2, + pages 133–169, May 1998. + [doi:10.1145/279227.279229](http://dx.doi.org/10.1145/279227.279229) + +1. Leslie Lamport: + “[Paxos Made Simple](http://research.microsoft.com/en-us/um/people/lamport/pubs/paxos-simple.pdf),” *ACM SIGACT News*, volume 32, number 4, pages 51–58, December 2001. + +1. Tushar Deepak Chandra, Robert Griesemer, and Joshua + Redstone: “[Paxos Made Live – An Engineering Perspective](http://www.read.seas.harvard.edu/~kohler/class/08w-dsi/chandra07paxos.pdf),” at *26th ACM Symposium on Principles of Distributed + Computing* (PODC), June 2007. + +1. Robbert + van Renesse: “[Paxos Made Moderately Complex](http://www.cs.cornell.edu/home/rvr/Paxos/paxos.pdf),” *cs.cornell.edu*, March 2011. + +1. Diego Ongaro: + “[Consensus: Bridging Theory and Practice](https://github.com/ongardie/dissertation),” + PhD Thesis, Stanford University, August 2014. + +1. Heidi Howard, Malte Schwarzkopf, Anil Madhavapeddy, + and Jon Crowcroft: “[Raft Refloated: Do We Have Consensus?](http://www.cl.cam.ac.uk/~ms705/pub/papers/2015-osr-raft.pdf),” *ACM SIGOPS Operating Systems Review*, volume 49, + number 1, pages 12–21, January 2015. + [doi:10.1145/2723872.2723876](http://dx.doi.org/10.1145/2723872.2723876) + +1. André Medeiros: + “[ZooKeeper’s Atomic Broadcast Protocol: Theory and Practice](http://www.tcs.hut.fi/Studies/T-79.5001/reports/2012-deSouzaMedeiros.pdf),” Aalto University School of Science, March 20, 2012. + +1. Robbert van Renesse, Nicolas Schiper, and + Fred B. Schneider: “[Vive La Différence: Paxos vs. Viewstamped Replication vs. Zab](http://arxiv.org/abs/1309.5671),” *IEEE Transactions on Dependable and Secure Computing*, + volume 12, number 4, pages 472–484, September 2014. + [doi:10.1109/TDSC.2014.2355848](http://dx.doi.org/10.1109/TDSC.2014.2355848) + +1. Will + Portnoy: “[Lessons Learned from Implementing Paxos](http://blog.willportnoy.com/2012/06/lessons-learned-from-paxos.html),” *blog.willportnoy.com*, June 14, 2012. + +1. Heidi Howard, Dahlia Malkhi, and Alexander Spiegelman: + “[Flexible Paxos: Quorum Intersection Revisited](https://arxiv.org/abs/1608.06696),” + *arXiv:1608.06696*, August 24, 2016. + +1. Heidi Howard and Jon Crowcroft: + “[Coracle: Evaluating Consensus at the Internet Edge](http://www.sigcomm.org/sites/default/files/ccr/papers/2015/August/2829988-2790010.pdf),” at *Annual Conference of the ACM Special Interest + Group on Data Communication* (SIGCOMM), August 2015. + [doi:10.1145/2829988.2790010](http://dx.doi.org/10.1145/2829988.2790010) + +1. Kyle Kingsbury: + “[Call Me Maybe: Elasticsearch 1.5.0](https://aphyr.com/posts/323-call-me-maybe-elasticsearch-1-5-0),” *aphyr.com*, April 27, 2015. + +1. Ivan Kelly: + “[BookKeeper Tutorial](https://github.com/ivankelly/bookkeeper-tutorial),” + *github.com*, October 2014. + +1. Camille Fournier: + “[Consensus Systems for the Skeptical Architect](http://www.ustream.tv/recorded/61483409),” at *Craft Conference*, Budapest, Hungary, April 2015. + +1. Kenneth P. Birman: + “[A History of the Virtual Synchrony Replication Model](https://www.truststc.org/pubs/713.html),” in *Replication: Theory and Practice*, Springer LNCS volume 5959, chapter 6, + pages 91–120, 2010. ISBN: 978-3-642-11293-5, + [doi:10.1007/978-3-642-11294-2_6](http://dx.doi.org/10.1007/978-3-642-11294-2_6) + + + + +![](img/fig9-1.png) + +##### 图9-1 + +![](img/fig9-2.png) +##### 图9-2 + +![](img/fig9-3.png) +##### 图9-3 + +![](img/fig9-4.png) +##### 图9-4 + +![](img/fig9-5.png) +##### 图9-5 + +![](img/fig9-6.png) +##### 图9-6 + +![](img/fig9-7.png) +##### 图9-7 + +![](img/fig9-8.png) +##### 图9-8 + +![](img/fig9-9.png) +##### 图9-9 + +![](img/fig9-10.png) +##### 图9-10 diff --git a/ddia/img/ch1.png b/ddia/img/ch1.png new file mode 100644 index 0000000..be2d8b8 Binary files /dev/null and b/ddia/img/ch1.png differ diff --git a/ddia/img/ch10.png b/ddia/img/ch10.png new file mode 100644 index 0000000..a9750f1 Binary files /dev/null and b/ddia/img/ch10.png differ diff --git a/ddia/img/ch11.png b/ddia/img/ch11.png new file mode 100644 index 0000000..4549abd Binary files /dev/null and b/ddia/img/ch11.png differ diff --git a/ddia/img/ch12.png b/ddia/img/ch12.png new file mode 100644 index 0000000..906911a Binary files /dev/null and b/ddia/img/ch12.png differ diff --git a/ddia/img/ch2.png b/ddia/img/ch2.png new file mode 100644 index 0000000..33ff058 Binary files /dev/null and b/ddia/img/ch2.png differ diff --git a/ddia/img/ch3.png b/ddia/img/ch3.png new file mode 100644 index 0000000..23bb471 Binary files /dev/null and b/ddia/img/ch3.png differ diff --git a/ddia/img/ch4.png b/ddia/img/ch4.png new file mode 100644 index 0000000..9ad3571 Binary files /dev/null and b/ddia/img/ch4.png differ diff --git a/ddia/img/ch5.png b/ddia/img/ch5.png new file mode 100644 index 0000000..82a5035 Binary files /dev/null and b/ddia/img/ch5.png differ diff --git a/ddia/img/ch6.png b/ddia/img/ch6.png new file mode 100644 index 0000000..edea693 Binary files /dev/null and b/ddia/img/ch6.png differ diff --git a/ddia/img/ch7.png b/ddia/img/ch7.png new file mode 100644 index 0000000..d1ef150 Binary files /dev/null and b/ddia/img/ch7.png differ diff --git a/ddia/img/ch8.png b/ddia/img/ch8.png new file mode 100644 index 0000000..b770a4e Binary files /dev/null and b/ddia/img/ch8.png differ diff --git a/ddia/img/ch9.png b/ddia/img/ch9.png new file mode 100644 index 0000000..857a07e Binary files /dev/null and b/ddia/img/ch9.png differ diff --git a/ddia/img/fig1-1.png b/ddia/img/fig1-1.png new file mode 100644 index 0000000..83c151d Binary files /dev/null and b/ddia/img/fig1-1.png differ diff --git a/ddia/img/fig1-2.png b/ddia/img/fig1-2.png new file mode 100644 index 0000000..54d9374 Binary files /dev/null and b/ddia/img/fig1-2.png differ diff --git a/ddia/img/fig1-3.png b/ddia/img/fig1-3.png new file mode 100644 index 0000000..31a6740 Binary files /dev/null and b/ddia/img/fig1-3.png differ diff --git a/ddia/img/fig1-4.png b/ddia/img/fig1-4.png new file mode 100644 index 0000000..14ae4b4 Binary files /dev/null and b/ddia/img/fig1-4.png differ diff --git a/ddia/img/fig1-5.png b/ddia/img/fig1-5.png new file mode 100644 index 0000000..21b29b7 Binary files /dev/null and b/ddia/img/fig1-5.png differ diff --git a/ddia/img/fig10-1.png b/ddia/img/fig10-1.png new file mode 100644 index 0000000..0dcff52 Binary files /dev/null and b/ddia/img/fig10-1.png differ diff --git a/ddia/img/fig10-2.png b/ddia/img/fig10-2.png new file mode 100644 index 0000000..1e8554a Binary files /dev/null and b/ddia/img/fig10-2.png differ diff --git a/ddia/img/fig10-3.png b/ddia/img/fig10-3.png new file mode 100644 index 0000000..e6092c2 Binary files /dev/null and b/ddia/img/fig10-3.png differ diff --git a/ddia/img/fig11-1.png b/ddia/img/fig11-1.png new file mode 100644 index 0000000..488963c Binary files /dev/null and b/ddia/img/fig11-1.png differ diff --git a/ddia/img/fig11-2.png b/ddia/img/fig11-2.png new file mode 100644 index 0000000..4845c34 Binary files /dev/null and b/ddia/img/fig11-2.png differ diff --git a/ddia/img/fig11-3.png b/ddia/img/fig11-3.png new file mode 100644 index 0000000..e940d5a Binary files /dev/null and b/ddia/img/fig11-3.png differ diff --git a/ddia/img/fig11-4.png b/ddia/img/fig11-4.png new file mode 100644 index 0000000..32c2f4b Binary files /dev/null and b/ddia/img/fig11-4.png differ diff --git a/ddia/img/fig11-5.png b/ddia/img/fig11-5.png new file mode 100644 index 0000000..982afde Binary files /dev/null and b/ddia/img/fig11-5.png differ diff --git a/ddia/img/fig11-6.png b/ddia/img/fig11-6.png new file mode 100644 index 0000000..3c20283 Binary files /dev/null and b/ddia/img/fig11-6.png differ diff --git a/ddia/img/fig11-7.png b/ddia/img/fig11-7.png new file mode 100644 index 0000000..876c3cf Binary files /dev/null and b/ddia/img/fig11-7.png differ diff --git a/ddia/img/fig12-1.png b/ddia/img/fig12-1.png new file mode 100644 index 0000000..dd4473c Binary files /dev/null and b/ddia/img/fig12-1.png differ diff --git a/ddia/img/fig2-1.png b/ddia/img/fig2-1.png new file mode 100644 index 0000000..6a1a9b9 Binary files /dev/null and b/ddia/img/fig2-1.png differ diff --git a/ddia/img/fig2-2.png b/ddia/img/fig2-2.png new file mode 100644 index 0000000..2f1801d Binary files /dev/null and b/ddia/img/fig2-2.png differ diff --git a/ddia/img/fig2-3.png b/ddia/img/fig2-3.png new file mode 100644 index 0000000..228188a Binary files /dev/null and b/ddia/img/fig2-3.png differ diff --git a/ddia/img/fig2-4.png b/ddia/img/fig2-4.png new file mode 100644 index 0000000..93bec6b Binary files /dev/null and b/ddia/img/fig2-4.png differ diff --git a/ddia/img/fig2-5.png b/ddia/img/fig2-5.png new file mode 100644 index 0000000..f28e7e5 Binary files /dev/null and b/ddia/img/fig2-5.png differ diff --git a/ddia/img/fig2-6.png b/ddia/img/fig2-6.png new file mode 100644 index 0000000..77c9671 Binary files /dev/null and b/ddia/img/fig2-6.png differ diff --git a/ddia/img/fig3-1.png b/ddia/img/fig3-1.png new file mode 100644 index 0000000..8434588 Binary files /dev/null and b/ddia/img/fig3-1.png differ diff --git a/ddia/img/fig3-10.png b/ddia/img/fig3-10.png new file mode 100644 index 0000000..fdfe4d5 Binary files /dev/null and b/ddia/img/fig3-10.png differ diff --git a/ddia/img/fig3-11.png b/ddia/img/fig3-11.png new file mode 100644 index 0000000..6eb144f Binary files /dev/null and b/ddia/img/fig3-11.png differ diff --git a/ddia/img/fig3-12.png b/ddia/img/fig3-12.png new file mode 100644 index 0000000..d0178d8 Binary files /dev/null and b/ddia/img/fig3-12.png differ diff --git a/ddia/img/fig3-2.png b/ddia/img/fig3-2.png new file mode 100644 index 0000000..c3a6ae6 Binary files /dev/null and b/ddia/img/fig3-2.png differ diff --git a/ddia/img/fig3-3.png b/ddia/img/fig3-3.png new file mode 100644 index 0000000..3d2f3c5 Binary files /dev/null and b/ddia/img/fig3-3.png differ diff --git a/ddia/img/fig3-4.png b/ddia/img/fig3-4.png new file mode 100644 index 0000000..bda5d25 Binary files /dev/null and b/ddia/img/fig3-4.png differ diff --git a/ddia/img/fig3-5.png b/ddia/img/fig3-5.png new file mode 100644 index 0000000..7956675 Binary files /dev/null and b/ddia/img/fig3-5.png differ diff --git a/ddia/img/fig3-6.png b/ddia/img/fig3-6.png new file mode 100644 index 0000000..99ab40d Binary files /dev/null and b/ddia/img/fig3-6.png differ diff --git a/ddia/img/fig3-7.png b/ddia/img/fig3-7.png new file mode 100644 index 0000000..48f1d22 Binary files /dev/null and b/ddia/img/fig3-7.png differ diff --git a/ddia/img/fig3-8.png b/ddia/img/fig3-8.png new file mode 100644 index 0000000..acecd9e Binary files /dev/null and b/ddia/img/fig3-8.png differ diff --git a/ddia/img/fig3-9.png b/ddia/img/fig3-9.png new file mode 100644 index 0000000..9d219fa Binary files /dev/null and b/ddia/img/fig3-9.png differ diff --git a/ddia/img/fig4-1.png b/ddia/img/fig4-1.png new file mode 100644 index 0000000..6e25249 Binary files /dev/null and b/ddia/img/fig4-1.png differ diff --git a/ddia/img/fig4-2.png b/ddia/img/fig4-2.png new file mode 100644 index 0000000..8c0430a Binary files /dev/null and b/ddia/img/fig4-2.png differ diff --git a/ddia/img/fig4-3.png b/ddia/img/fig4-3.png new file mode 100644 index 0000000..77ed56b Binary files /dev/null and b/ddia/img/fig4-3.png differ diff --git a/ddia/img/fig4-4.png b/ddia/img/fig4-4.png new file mode 100644 index 0000000..6ba750e Binary files /dev/null and b/ddia/img/fig4-4.png differ diff --git a/ddia/img/fig4-5.png b/ddia/img/fig4-5.png new file mode 100644 index 0000000..cb5c820 Binary files /dev/null and b/ddia/img/fig4-5.png differ diff --git a/ddia/img/fig4-6.png b/ddia/img/fig4-6.png new file mode 100644 index 0000000..ebb0eaf Binary files /dev/null and b/ddia/img/fig4-6.png differ diff --git a/ddia/img/fig4-7.png b/ddia/img/fig4-7.png new file mode 100644 index 0000000..8c0b8fe Binary files /dev/null and b/ddia/img/fig4-7.png differ diff --git a/ddia/img/fig5-1.png b/ddia/img/fig5-1.png new file mode 100644 index 0000000..72266ff Binary files /dev/null and b/ddia/img/fig5-1.png differ diff --git a/ddia/img/fig5-10.png b/ddia/img/fig5-10.png new file mode 100644 index 0000000..e806a54 Binary files /dev/null and b/ddia/img/fig5-10.png differ diff --git a/ddia/img/fig5-11.png b/ddia/img/fig5-11.png new file mode 100644 index 0000000..37ec2f1 Binary files /dev/null and b/ddia/img/fig5-11.png differ diff --git a/ddia/img/fig5-12.png b/ddia/img/fig5-12.png new file mode 100644 index 0000000..1ba1b64 Binary files /dev/null and b/ddia/img/fig5-12.png differ diff --git a/ddia/img/fig5-13.png b/ddia/img/fig5-13.png new file mode 100644 index 0000000..5a40776 Binary files /dev/null and b/ddia/img/fig5-13.png differ diff --git a/ddia/img/fig5-14.png b/ddia/img/fig5-14.png new file mode 100644 index 0000000..809ebfc Binary files /dev/null and b/ddia/img/fig5-14.png differ diff --git a/ddia/img/fig5-2.png b/ddia/img/fig5-2.png new file mode 100644 index 0000000..667cd65 Binary files /dev/null and b/ddia/img/fig5-2.png differ diff --git a/ddia/img/fig5-3.png b/ddia/img/fig5-3.png new file mode 100644 index 0000000..59f629b Binary files /dev/null and b/ddia/img/fig5-3.png differ diff --git a/ddia/img/fig5-4.png b/ddia/img/fig5-4.png new file mode 100644 index 0000000..0c8ad77 Binary files /dev/null and b/ddia/img/fig5-4.png differ diff --git a/ddia/img/fig5-5.png b/ddia/img/fig5-5.png new file mode 100644 index 0000000..58d0e41 Binary files /dev/null and b/ddia/img/fig5-5.png differ diff --git a/ddia/img/fig5-6.png b/ddia/img/fig5-6.png new file mode 100644 index 0000000..f9cd033 Binary files /dev/null and b/ddia/img/fig5-6.png differ diff --git a/ddia/img/fig5-7.png b/ddia/img/fig5-7.png new file mode 100644 index 0000000..7a69883 Binary files /dev/null and b/ddia/img/fig5-7.png differ diff --git a/ddia/img/fig5-8.png b/ddia/img/fig5-8.png new file mode 100644 index 0000000..33fd416 Binary files /dev/null and b/ddia/img/fig5-8.png differ diff --git a/ddia/img/fig5-9.png b/ddia/img/fig5-9.png new file mode 100644 index 0000000..c9eb213 Binary files /dev/null and b/ddia/img/fig5-9.png differ diff --git a/ddia/img/fig6-1.png b/ddia/img/fig6-1.png new file mode 100644 index 0000000..a1a3e3f Binary files /dev/null and b/ddia/img/fig6-1.png differ diff --git a/ddia/img/fig6-2.png b/ddia/img/fig6-2.png new file mode 100644 index 0000000..1aab7c5 Binary files /dev/null and b/ddia/img/fig6-2.png differ diff --git a/ddia/img/fig6-3.png b/ddia/img/fig6-3.png new file mode 100644 index 0000000..7b0fc58 Binary files /dev/null and b/ddia/img/fig6-3.png differ diff --git a/ddia/img/fig6-4.png b/ddia/img/fig6-4.png new file mode 100644 index 0000000..7ef15fc Binary files /dev/null and b/ddia/img/fig6-4.png differ diff --git a/ddia/img/fig6-5.png b/ddia/img/fig6-5.png new file mode 100644 index 0000000..ac5fbf4 Binary files /dev/null and b/ddia/img/fig6-5.png differ diff --git a/ddia/img/fig6-6.png b/ddia/img/fig6-6.png new file mode 100644 index 0000000..071d30a Binary files /dev/null and b/ddia/img/fig6-6.png differ diff --git a/ddia/img/fig6-7.png b/ddia/img/fig6-7.png new file mode 100644 index 0000000..0280a38 Binary files /dev/null and b/ddia/img/fig6-7.png differ diff --git a/ddia/img/fig6-8.png b/ddia/img/fig6-8.png new file mode 100644 index 0000000..7f05977 Binary files /dev/null and b/ddia/img/fig6-8.png differ diff --git a/ddia/img/fig7-1.png b/ddia/img/fig7-1.png new file mode 100644 index 0000000..134ea3a Binary files /dev/null and b/ddia/img/fig7-1.png differ diff --git a/ddia/img/fig7-10.png b/ddia/img/fig7-10.png new file mode 100644 index 0000000..7d8f0ab Binary files /dev/null and b/ddia/img/fig7-10.png differ diff --git a/ddia/img/fig7-11.png b/ddia/img/fig7-11.png new file mode 100644 index 0000000..5249292 Binary files /dev/null and b/ddia/img/fig7-11.png differ diff --git a/ddia/img/fig7-2.png b/ddia/img/fig7-2.png new file mode 100644 index 0000000..d6c48ea Binary files /dev/null and b/ddia/img/fig7-2.png differ diff --git a/ddia/img/fig7-3.png b/ddia/img/fig7-3.png new file mode 100644 index 0000000..cfc7a73 Binary files /dev/null and b/ddia/img/fig7-3.png differ diff --git a/ddia/img/fig7-4.png b/ddia/img/fig7-4.png new file mode 100644 index 0000000..75ab266 Binary files /dev/null and b/ddia/img/fig7-4.png differ diff --git a/ddia/img/fig7-5.png b/ddia/img/fig7-5.png new file mode 100644 index 0000000..c88fa54 Binary files /dev/null and b/ddia/img/fig7-5.png differ diff --git a/ddia/img/fig7-6.png b/ddia/img/fig7-6.png new file mode 100644 index 0000000..c07b7d5 Binary files /dev/null and b/ddia/img/fig7-6.png differ diff --git a/ddia/img/fig7-7.png b/ddia/img/fig7-7.png new file mode 100644 index 0000000..87a60d1 Binary files /dev/null and b/ddia/img/fig7-7.png differ diff --git a/ddia/img/fig7-8.png b/ddia/img/fig7-8.png new file mode 100644 index 0000000..dc4cf2e Binary files /dev/null and b/ddia/img/fig7-8.png differ diff --git a/ddia/img/fig7-9.png b/ddia/img/fig7-9.png new file mode 100644 index 0000000..8fd7744 Binary files /dev/null and b/ddia/img/fig7-9.png differ diff --git a/ddia/img/fig8-1.png b/ddia/img/fig8-1.png new file mode 100644 index 0000000..b73be21 Binary files /dev/null and b/ddia/img/fig8-1.png differ diff --git a/ddia/img/fig8-2.png b/ddia/img/fig8-2.png new file mode 100644 index 0000000..7fa099f Binary files /dev/null and b/ddia/img/fig8-2.png differ diff --git a/ddia/img/fig8-3.png b/ddia/img/fig8-3.png new file mode 100644 index 0000000..fc6a20d Binary files /dev/null and b/ddia/img/fig8-3.png differ diff --git a/ddia/img/fig8-4.png b/ddia/img/fig8-4.png new file mode 100644 index 0000000..330f0a8 Binary files /dev/null and b/ddia/img/fig8-4.png differ diff --git a/ddia/img/fig8-5.png b/ddia/img/fig8-5.png new file mode 100644 index 0000000..35f983e Binary files /dev/null and b/ddia/img/fig8-5.png differ diff --git a/ddia/img/fig9-1.png b/ddia/img/fig9-1.png new file mode 100644 index 0000000..f862310 Binary files /dev/null and b/ddia/img/fig9-1.png differ diff --git a/ddia/img/fig9-10.png b/ddia/img/fig9-10.png new file mode 100644 index 0000000..b25b1e5 Binary files /dev/null and b/ddia/img/fig9-10.png differ diff --git a/ddia/img/fig9-2.png b/ddia/img/fig9-2.png new file mode 100644 index 0000000..3e82685 Binary files /dev/null and b/ddia/img/fig9-2.png differ diff --git a/ddia/img/fig9-3.png b/ddia/img/fig9-3.png new file mode 100644 index 0000000..3ef538e Binary files /dev/null and b/ddia/img/fig9-3.png differ diff --git a/ddia/img/fig9-4.png b/ddia/img/fig9-4.png new file mode 100644 index 0000000..2d9ba52 Binary files /dev/null and b/ddia/img/fig9-4.png differ diff --git a/ddia/img/fig9-5.png b/ddia/img/fig9-5.png new file mode 100644 index 0000000..b3b2292 Binary files /dev/null and b/ddia/img/fig9-5.png differ diff --git a/ddia/img/fig9-6.png b/ddia/img/fig9-6.png new file mode 100644 index 0000000..1abc411 Binary files /dev/null and b/ddia/img/fig9-6.png differ diff --git a/ddia/img/fig9-7.png b/ddia/img/fig9-7.png new file mode 100644 index 0000000..0733c5d Binary files /dev/null and b/ddia/img/fig9-7.png differ diff --git a/ddia/img/fig9-8.png b/ddia/img/fig9-8.png new file mode 100644 index 0000000..39e97c8 Binary files /dev/null and b/ddia/img/fig9-8.png differ diff --git a/ddia/img/fig9-9.png b/ddia/img/fig9-9.png new file mode 100644 index 0000000..824db11 Binary files /dev/null and b/ddia/img/fig9-9.png differ diff --git a/ddia/img/figii-1.png b/ddia/img/figii-1.png new file mode 100644 index 0000000..301e4b8 Binary files /dev/null and b/ddia/img/figii-1.png differ diff --git a/ddia/img/title.png b/ddia/img/title.png new file mode 100644 index 0000000..99f996b Binary files /dev/null and b/ddia/img/title.png differ diff --git a/ddia/part-i.md b/ddia/part-i.md new file mode 100644 index 0000000..dc19128 --- /dev/null +++ b/ddia/part-i.md @@ -0,0 +1,22 @@ +# 第一部分 数据系统基础 + +本书前四章介绍了数据系统底层的基础概念,无论是在单台机器上运行的单点数据系统,还是分布在多台机器上的分布式数据系统都适用。 + +1. [第一章](ch1.md)将介绍本书使用的术语和方法。**可靠性,可扩展性和可维护性** ,这些词汇到底意味着什么?以及如何实现这些目标。 +2. [第二章](ch2.md)将对几种不同的**数据模型和查询语言**进行比较。从程序员的角度看,这是数据库之间最明显的区别。不同的数据模型适用于不同的应用场景。 +3. [第三章](ch3.md)将深**存储引擎**内部,并研究数据库如何在磁盘上摆放数据。不同的存储引擎针对不同的负载进行优化,选择合适的存储引擎对系统性能有巨大影响。 +4. [第四章](ch4)将对几种不同的**数据编码(序列化)**进行比较。特别讨论了在应用需求经常变化、模式需要随时间调整的环境中这些格式的适用情况。 + +第二部分将专门讨论在**分布式数据系统**中才有的特殊问题。 + + + +## 目录 + + +1. [可靠性、可扩展性、可维护性](ch1.md) +2. [数据模型与查询语言](ch2.md) +3. [存储与检索](ch3.md) +4. [编码与演化](ch4.md) + + diff --git a/ddia/part-ii.md b/ddia/part-ii.md new file mode 100644 index 0000000..18969e2 --- /dev/null +++ b/ddia/part-ii.md @@ -0,0 +1,87 @@ +# 第二部分: 分布式数据 + +> 一个成功的技术,可行性的优先级必须高于PR,你可以糊弄别人,但糊弄不了自然规律。 +> +> ——罗杰斯委员会报告(1986) +> + +------- + +在本书的第一部分中,我们讨论了数据存储在一台机器上数据系统的方方面面。现在到了第二部分中,我们提一个更高级的问题:如果**多台机器**参与数据的存储和检索会发生什么? +将数据库分布到多台机器上可能会有很多原因: + +***可扩展性*** + +如果您的数据量读取负载/写入负载比单台计算机可以处理的还要大,则可以将负载分散到多台计算机上。 + +***容错/高可用性*** + +如果即使一台机器(或多台机器,网络或整个数据中心)出现故障,您的应用程序仍然需要继续工作,您可以使用多台机器来提供冗余。当一台故障时,另一台可以接管。 + +***延迟*** + +如果在世界各地都有用户,也许你会考虑在全球多处部署服务器,既而每个用户从地理上相近的数据中心获取服务,以免用户需要等待网络数据包穿越半个世界。 + +### 扩展到更高的负载 + +如果只是需要扩展到支持更高的负载,最简单的方法就是购买更强大的机器(有时称为垂直扩展 vertical scaling或scaling up)。许多CPU、RAM芯片和磁盘可以在一个操作系统内相互连接,快速互连允许任何CPU访问存储器或磁盘的任何部分。在这种共享内存架构中,所有的组件都可以看作是一台单独的机器(在大型机中,尽管任何CPU都可以访问内存的任何部分,但是总有一些内存区域与一些CPU更接近[^i]。 为了有效地利用这种架构特性,需要对处理进行细分,以便每个CPU主要访问临近的内存,这意味着底层仍然进行了分区,即使表面上看起来只有一台机器在运行) + +[^i]: 称为非均匀内存访问 nonuniform memory access,或者NUMA + +共享内存方法的问题在于,开销的增长速度快于线性增长:一台机器的CPU数量翻倍,内存大小也翻倍,磁盘大小翻倍,但成本通常多的可不止一倍。而且由于瓶颈存在,一台双倍规格机器不一定能处理双倍的负载。 + +共享内存体系结构可以提供有限的容错能力,相比之下,使用高端机器虽然具有热插拔组件(可以不关机更换磁盘,内存模块,甚至CPU),但是它必然局限于单个地理位置。 + +另一种方法是**共享磁盘架构**,它使用多台具有独立CPU和RAM的机器,但将数据存储在机器之间共享的磁盘阵列上,这些磁盘通过快速网络连接(Network Attached Storage, Storage Area Network)。此架构用于某些数据仓储,但竞争和锁定的开销限制了共享磁盘方法的可扩展性[2]。 + +#### 无共享架构 + +相比之下,**无共享架构**(shared-nothing 有时称为水平扩展 horizontal scaling 或 scaling out)已经相当普及。在这种架构中,运行数据库软件的每台机器/虚拟机都称为节点(node)。每个节点只使用各自的CPU,内存和磁盘。节点之间的任何协调都是在软件层面使用传统网络实现的。 + +无共享系统不需要特殊的硬件,所以你可以用任何性价比最好的机器。也许可以跨多个地理区域分发数据从而减少用户延迟,也许可以在整个数据中心丢失的情况下幸免于难。随着云端虚拟机部署的出现,即使是小公司,现在无需Google级别的运维,也可以实现多地分布式架构。 + +在这一部分里,我们将重点放在无共享架构上,并不是因为它们一定是每个用例的最佳选择,而是因为它们要求应用程序开发人员最为谨慎。如果你的数据分布在多个节点上,你需要意识到这样一个分布式系统中约束和权衡 ——数据库并不能神奇地把这些东西藏起来。 + +虽然分布式无共享架构具有许多优点,但它通常也会给应用程序带来额外的复杂性,有时也会限制您可以使用的数据模型表现力。在某些情况下,一个简单的单线程程序可以比一个拥有100多个CPU核心的集群表现得更好[4]。另一方面,无共享系统可以非常强大。接下来的几章将详细讨论分布式数据的问题。 + +### 复制 vs 分区 + +数据分布在多个节点上有两种常见的方式: + +- 复制(Replication) + + 在几个不同的节点上保存相同数据的副本,可能位于不同的位置。 复制提供了冗余:如果某些节点不可用,则仍然可以从其余节点提供数据。 复制也可以帮助提高性能。 + + [第五章](ch5.md)将讨论复制。 + +- 分区 (Partitioning) + + 将大型数据库拆分成称为分区的较小子集,以便不同的分区可以指派:给不同的节点(也称为分片 sharding)。 + + [第六章](ch6.md)将讨论分区。 + +复制和分区是不同的机制,但它们经常同时使用。如图II-1所示。 + +![](img/figii-1.png) + +**图II-1 复制与分区** + + + +理解了这些概念,就可以开始讨论在分布式系统中需要做出的困难抉择。 + +[第七章](ch7.md)将讨论**事务(Transaction)**,这对了解数据系统中可能出现的所有问题以及可以做些什么很有帮助。 + +[第八章](ch8.md)和[第九章](ch9.md)将讨论分布式系统的基本局限性 + +在本书的第三部分中,将讨论如何将多个(可能分布的)数据存储集成到一个更大的系统中,以满足复杂应用程序的需求。 但首先我们来谈谈分布式的数据。 + + + +## 索引 + +5. [复制](ch5.md) [机翻] +6. [分片](ch6.md) [未翻] +7. [事务](ch7.md) [未翻] +8. [分布式系统的麻烦](ch8.md) [未翻] +9. [一致性与共识](ch9.md) [未翻] \ No newline at end of file diff --git a/ddia/part-iii.md b/ddia/part-iii.md new file mode 100644 index 0000000..a68adfe --- /dev/null +++ b/ddia/part-iii.md @@ -0,0 +1,46 @@ +# 第三部分:衍生数据 + +本书的第一部分和第二部分,自底向上把所有关于分布式数据库的主要考量都讲了一遍。从数据在磁盘上的布局,一直到故障时关于分布式系统一致性的局限性。但所有的讨论都假定了应用程序中只用了一种数据库。 + +现实世界中的数据系统往往更为复杂。大型应用程序经常需要以多种方式访问和处理数据,没有一个数据库可以同时满足所有这些不同的需求。因此应用程序通常组合使用多种组件:数据存储,索引,缓存,分析系统,等等,并实现在这些组件中移动数据的机制。 + +本书的最后一部分,会研究将多个不同数据系统(可能有着不同数据模型,并针对不同的访问模式进行优化)协调一致地集成入应用架构时会遇到的问题。软件供应商经常会忽略这一方面的系统建设,并声称他们的产品能够满足您的所有需求。在现实世界中,集成不同的系统是实际应用中最重要的事情之一。 + +## 记录和派生数据系统 + +从高层次上看,存储和处理数据的系统可以分为两大类: + +#### 记录系统(System of record) + +一个关于记录的系统,也被称为*真相源*(source of truth),保留了权威版本的数据。当新的数据进入时(例如,用户输入)首先会在这里记录。每个事实恰好只表示一次(表示通常是标准化的 normalized)。如果另一个系统和记录系统之间有任何差异,那么记录系统中的值是(根据定义)是正确的。 + +#### 衍生数据系统(Derived data systems) + +衍生系统中的数据,通常来自另一个系统的现有数据,以某种方式进行转换或处理的结果。如果丢失衍生的数据,您可以从原始来源重新创建它。典型的例子是*缓存*:如果存在,数据可以从缓存中提供;如果缓存不包含所需数据,总是可以降级由底层数据库提供。非规范化的值,索引和物化视图也属于这个类别。在推荐系统中,预测汇总数据通常派生自来用户日志。 + +从技术上讲,派生数据是**冗余的(redundant)**,因为它重复了已有的信息。但是衍生数据对于获得良好的读性能通常是至关重要的。它通常是非规范化的。可以从同一个来源派生出多个不同的数据集,使得用户可以从不同的“视角”去洞察数据。 + +并不是所有的系统都在其架构中明确区分记录系统和派生系统,但是这是一种有用的区分方式,因为它明确了系统中的数据流:系统的哪一部分具有哪些输入和哪些输出,以及它们如何相互依赖。 + +大多数数据库,存储引擎和查询语言,本质上既不是记录系统也不是派生系统。数据库只是一个工具:如何使用它取决于你。记录系统和衍生数据系统之间的区别不在于工具,而在于应用程序中的使用方式。 + +通过梳理数据的派生关系,可以清楚地理解一个令人困惑的系统架构。这将贯穿本书的这一部分。 + +## 章节概述 + +[第十章](ch10.md)通过研究面向批处理的数据流系统,例如MapReduce,看看它们是如何给我们提供构建大规模数据系统的优秀工具和原理的。 + +[第十一章](ch11.md)将把这些概念应用到*流式数据*(data streams)中,使同样的任务能以更低的延迟完成。 + +[第十二章](ch12.md)将对本书进行总结,探讨使用这些工具来构建可靠,可扩展和可维护的应用程序的思路。 + + + + + +## 索引 + +10. [批处理](ch10.md) [未翻] +11. [流处理](ch11.md) [未翻] +12. [数据系统的未来](ch12.md) [未翻] + diff --git a/ddia/preface.md b/ddia/preface.md new file mode 100644 index 0000000..cb1af90 --- /dev/null +++ b/ddia/preface.md @@ -0,0 +1,101 @@ +# 序 + +如果您这几年从事软件工程领域,特别是服务器端和后端开发,那么你很可能已经被大量关于数据存储和处理的时髦词汇轰炸过了: NoSQL!大数据!Web-Scale!分片!最终一致性!ACID! CAP定理!云服务!MapReduce!实时! + +在过去的十年中,我们看到了数据库,分布式系统以及在其上构建应用程序的许多有趣的发展。这些发展有不同的推动力量: + +* 谷歌,雅虎,亚马逊,Facebook,LinkedIn,微软和Twitter等互联网公司正在处理大量的数据和流量,迫使他们创造新的工具,使他们能够有效地处理这种规模。 +* 企业需要敏捷,便宜地测试假设,通过缩短开发周期和数据模型的灵活性,快速响应新的市场洞察。 +* 免费和开放源代码软件已经非常成功,现在在许多环境中更倾向于商业或定制的内部软件。 +* CPU主频几乎没有增长,但是多核处理器已经成为标配,网络变得更快。这意味着并行性只会增加。 +* 即使您在一个小团队中工作,现在也可以构建分布在多台计算机甚至多个地理区域的系统,这要归功于亚马逊网络服务等基础架构即服务(IaaS)。 +* 现在,许多服务都要求高可用,因为停电、维修导致的服务不可用变得越来越不可接受。 + +数据密集型应用正在通过利用这些技术发展推动可能的边界。如果**数据是其主要挑战**(数据量,数据复杂度或更改速度),我们称这类应用为**数据密集型**,与之相对的是**计算密集型**,即CPU速度是瓶颈。 + +帮助数据密集型应用程序存储和处理数据的工具和技术已经迅速适应这些变化。新型数据库系统(“NoSQL”)已经引起了人们的关注,但消息队列,缓存,搜索索引,批处理和流处理框架以及相关技术也非常重要。许多应用程序使用这些技术的组合。 + +充满这种空间的流行语是对新的可能性的热情的表现,这是一件好事。但是,作为软件工程师和架构师,如果我们想要建立良好的应用程序,我们还需要对技术上的精确和准确的理解,以及各种技术和权衡。为了这个理解,我们必须深入挖掘流行语。 + +幸运的是,在技术快速变化的背后,无论您使用的是什么版本的特定工具,都有持久的原则。如果你了解这些原则,你就可以看到每个工具的适用位置,如何充分利用它,以及如何避免陷阱。这是本书的出处。 + +本书的目标是帮助您浏览处理和存储数据的各种技术快速变化的领域。这本书不是一个特定工具的教程,也不是一本充满干枯理论的教科书。相反,我们将看看成功的数据系统示例:构成许多流行应用程序基础的技术,必须满足每天生产中的可伸缩性,性能和可靠性要求。 + +我们将深入这些系统的内部,梳理他们的关键算法,讨论他们的原则和他们必须做出的权衡。在这个过程中,我们将尝试寻找有用的数据系统思考方式 - 不仅仅是它们是如何工作的,还有为什么它们以这种方式工作,以及我们需要问什么问题。 + +阅读本书后,您将能够很好地决定哪种技术适合哪种用途,并了解如何将工具组合起来,形成良好应用架构的基础。您不会准备从头开始构建自己的数据库存储引擎,但幸运的是,这是很少需要的。然而,你将会对你的系统在底层做什么有一个很好的直觉,这样你就可以推断他们的行为,做出好的设计决定,并追踪可能出现的问题。 + + + +## 本书的目标读者 + +如果您开发的应用程序具有用于存储或处理数据的某种服务器/后端,并且您的应用程序使用互联网(例如,Web应用程序,移动应用程序或连接到互联网的传感器),那么本书就是为您准备的。 + +本书适用于喜欢编写代码的软件工程师,软件架构师和技术经理。如果您需要就您所从事的系统架构做出决定,例如您需要选择解决某个特定问题的工具,并找出如何最好地应用这些问题,那么这一点尤其重要。但即使你没有选择你的工具,这本书将帮助你更好地了解自己的长处和短处。 + +您应该具有构建基于Web的应用程序或网络服务的一些经验,并且您应该熟悉关系数据库和SQL。任何非关系型数据库和其他与数据相关的工具都是红利,但不是必需的。常见的网络协议如TCP和HTTP的一般理解是有帮助的。编程语言或框架的选择对本书没有任何不同之处。 + +如果以下任何一条对你是真的,你会发现这本书很有价值: + +* 您想了解如何使数据系统可扩展,例如,为数百万用户支持Web或移动应用程序。 +* 您需要提高应用程序的可用性(最大限度地减少停机时间)和运行稳定。 +* 您正在寻找使系统长期易于维护的方法,即使随着需求和技术的变化而变化。 +* 您对事物的运作方式有着天然的好奇心,并且希望知道主要网站和在线服务的内容。这本书打破了各种数据库和数据处理系统的内幕,探索他们设计的光明思维是非常有趣的。 + +有时在讨论可扩展的数据系统时,人们会评论:“你又不在谷歌或亚马逊,别操心可扩展性了,直接上关系数据库。“,这个陈述有一定的道理:但为了实际用不到的可扩展性扩展系统,不仅浪费不必要的精力,并且可能会把你锁死在一个不灵活的设计中。实际上这是“过早优化”的一种形式。不过,选择合适的工具确实很重要,不同的技术各有优缺点。我们将会看到,关系数据库虽然很重要,但绝不是数据处理的终章。 + + + +## 本书涉及的领域 + +本书不会试图给出详细的指导,说明如何安装或使用特定的软件包或API,因为已经有大量的文档。相反,我们讨论了基本的数据系统的各种原则和权衡,并探讨了不同产品所做出的不同设计决策。 + +在电子书版本中,我们包含了在线资源全文的链接。所有链接都在发布时进行了验证,但不幸的是,由于网络的性质,链接往往频繁地中断。如果您遇到链接断开的情况,或者您正在阅读本书的打印副本,则可以使用搜索引擎查找引用。对于学术论文,您可以在Google学术搜索中搜索标题以查找开放获取的PDF文件。或者,您可以在https://github.com/ept/ddia-references上找到所有的参考资料,我们在那里维护最新的链接。 + +我们主要关注数据系统的体系结构以及它们被集成到数据密集型应用程序中的方式。本书没有涉及部署,操作,安全,管理等领域的空间 - 这些都是复杂而重要的话题,本书的表面方面笔记我们不会公平。他们配得上一本独自的书。 + +本书中描述的许多技术在**大数据**这个时髦词的范畴中。然而“大数据”这个术语被滥用,缺乏明确定义,以至于在严肃的工程讨论中没有用处。这本书使用较少含糊的术语,如单点系统 vs 分布式系统,或 在线/交互式 与 离线/批处理系统。 + +本书对自由和开放源码软件(FOSS)有偏好,因为阅读,修改和执行源代码是了解详细工作原理的好方法。开放平台还可以降低供应商锁定的风险。然而,在适当的情况下,我们也讨论专有软件(封闭源码软件,软件即服务,或公司内部软件,仅在文献中描述但未公开发布)。 + + + +## 本书纲要 + +本书分为三部分: + +1. 在第一部分中,我们讨论支持数据密集型应用程序设计的基本思想。我们从第1章开始,讨论我们实际要实现的功能:可靠性,可伸缩性和可维护性;我们该如何考虑他们;以及我们如何实现它们。在第2章中,我们比较了几种不同的数据模型和查询语言,看看它们是如何适用于不同的情况的。在第3章中,我们讨论存储引擎:数据库如何在磁盘上安排数据,以便我们可以再次有效地找到它。第四章转向数据编码(序列化)和架构演变的格式。 + +2. 在第二部分中,我们从存储在一台机器上的数据转移到分布在多台机器上的数据。这对于可伸缩性通常是必需的,但带来了各种独特的挑战。我们首先讨论复制(第5章),分区/分片(第6章)和事务(第7章)。我们然后进去 + + 关于分布式系统问题的更多细节(第8章),以及在分布式系统中实现一致性和一致性意味着什么(第9章)。 + +3. 在第三部分中,我们讨论从其他数据集派生一些数据集的系统。派生数据经常发生在异构系统中:当没有一个数据库可以做得很好时,应用程序需要集成几个不同的数据库,缓存,索引等等。在第10章中,我们将从批处理方法开始介绍派生数据,我们将在第11章中以流处理为基础进行构建。最后,在第12章中,我们将所有内容放在一起,并讨论如何构建可靠,可伸缩和可维护的应用程序未来。 + + + +## 参考和进一步阅读 + +我们在本书中讨论的大部分内容已经在其他地方以某种形式出现了 - 在会议演示文稿,研究论文,博客文章,代码,错误跟踪器,邮件列表和工程民俗中。 这本书总结了来自许多不同来源的最重要的想法,其中包括指向原始文献的指针。 如果您想更深入地探索一个地区,那么每章末尾的参考资料都是很好的资源,其中大部分可以在线免费获取。 + + + +## O‘Reilly广告 + +blah blah 不想翻 + + + +## 致谢 + +本书融合了学术研究和工业实践的经验,是大量其他人的思想和知识的融合与系统化。在计算中,我们往往会被新的东西所吸引,但是我认为我们有很多东西可以从以前的东西中学习。这本书有超过800篇文章,博客文章,讲座,文档等参考资料,对我来说这是一个宝贵的学习资源。我非常感谢这份材料的作者分享他们的知识。 + +我也从个人对话中学到了很多东西,这要感谢大量的人花时间讨论想法,耐心地向我解释。特别感谢Joe Adler, Ross Anderson, Peter Bailis, Márton Balassi, Alastair Beresford, Mark Callaghan, Mat Clayton, Patrick Collison, Sean Cribbs, Shirshanka Das, Niklas Ekström, Stephan Ewen, Alan Fekete, Gyula Fóra, Camille Fournier, Andres Freund, John Garbutt, Seth Gilbert, Tom Haggett, Pat Hel‐ land, Joe Hellerstein, Jakob Homan, Heidi Howard, John Hugg, Julian Hyde, Conrad Irwin, Evan Jones, Flavio Junqueira, Jessica Kerr, Kyle Kingsbury, Jay Kreps, Carl Lerche, Nicolas Liochon, Steve Loughran, Lee Mallabone, Nathan Marz, Caitie McCaffrey, Josie McLellan, Christopher Meiklejohn, Ian Meyers, Neha Narkhede, Neha Narula, Cathy O’Neil, Onora O’Neill, Ludovic Orban, Zoran Perkov, Julia Powles, Chris Riccomini, Henry Robinson, David Rosenthal, Jennifer Rullmann, Matthew Sackman, Martin Scholl, Amit Sela, Gwen Shapira, Greg Spurrier, Sam Stokes, Ben Stopford, Tom Stuart, Diana Vasile, Rahul Vohra, Pete Warden, 以及 Brett Wooldridge. + +通过审阅草案并提供反馈意见,更多的人对本书的撰写非常有价值。对于这些贡献,我特别感谢Raul Agepati,Tyler Akidau,Mattias Andersson,Sasha Baranov,Veena Basavaraj,David Beyer,Jim Brikman,Paul Carey,Raul Castro Fernandez,Joseph Chow,Derek Elkins,Sam Elliott,Alexander Gallego,Mark Grover ,斯图尔·万洛威(Stu Hallow Halloway),海蒂·霍华德(Heidi Howard),尼科拉·克莱普曼(Nicola Kleppmann),斯特凡·克鲁帕(Stefan Kruppa),比约恩·马德森(Bjorn Madsen),麦克尔·桑德(Sandder Mak),斯特凡·波德科维斯基(Stefan Podkowinski),菲尔·波特(Phil Potter)当然,对于本书中的任何遗留错误或不愉快的意见,我承担全部责任。 + +为了帮助这本书变得真实,并且耐心地处理我缓慢的写作和不寻常的要求,我感谢编辑Marie Beaugureau,Mike Loukides,Ann Spencer和O'Reilly的所有团队。为了帮助找到合适的单词,我要感谢Rachel Head。尽管有其他的工作承诺给了我写作的时间和自由,但我要感谢Alastair Beresford,Susan Goodhue,Neha Narkhede和Kevin Scott。 + +非常特别的感谢是Shabbir Diwan和Edie Freedman,他们非常小心的说明了各章的地图。他们以非常规的创作地图的想法,使他们如此美丽和令人兴奋,真是太棒了。 + +最后,我的爱情传到了我的家人和朋友身上,没有他,我将无法完成这个花了近四年时间的写作过程。你是最好的。 \ No newline at end of file diff --git a/pg/README.md b/pg/README.md new file mode 100644 index 0000000..8c5ddd8 --- /dev/null +++ b/pg/README.md @@ -0,0 +1,52 @@ +# PostgreSQL Notes + +## SQL Language + +* PostgreSQL字面值 + +## Adminstration + +* [安装/install](pg-admin-install.md) +* [设置/setup](pg-admin-setup.md) +* [配置/config](pg-admin-config.md) +* [认证/auth](pg-admin-auth.md) +* 角色/role +* 数据库管理/database +* 本地化 +* 日常工作 +* 备份 +* 高可用、负载均衡、副本 +* 恢复 +* [监控](pg-admin-monitor.md) +* 磁盘使用 +* WAL与可靠性 +* 逻辑复制 +* 回归测试 + + + + +## Client Interface + +* Python: psycopg2 +* Go: lib/pq, go-pg, jackc/pgx + +## Server Programming + +## PostgreSQL Internal + +## Foreign Data Wrapper +* [Using FileFDW fetching sysinfo](fdw-file_fdw-intro.md) +* [MongoFDW install](fdw-mongo_fdw-install.md) +* [RedisFDW install](fdw-redis_fdw-installmd) +* [PgAdmin install](gui-pgadmin-install.md) + + + + +## PostGIS + +* [PostGIS install](postgis-install.md) + + +## Tricks diff --git a/pg/ext-file_fdw-intro.md b/pg/ext-file_fdw-intro.md new file mode 100644 index 0000000..dad62e7 --- /dev/null +++ b/pg/ext-file_fdw-intro.md @@ -0,0 +1,134 @@ +# file_fdw妙用无穷——从数据库读取系统信息 + +PostgreSQL是最先进的开源数据库,其中一个非常给力的特性就是FDW:外部数据包装器(Foreign Data Wrapper)。通过FDW,用户可以用统一的方式从Pg中访问各类外部数据源。`file_fdw`就是其中随数据库附赠的两个fdw之一。随着pg10的更新,`file_fdw`也添加了一颗赛艇的功能:从程序输出读取。 + +小霸王妙用无穷,我们能通过`file_fdw`,轻松查看操作系统信息,拉取网络数据,把各种各样的数据源轻松喂进数据库里统一查看管理。 + + + +## 安装与配置 + +`file_fdw`是Pg自带的组件,不需要奇怪的配置,在数据库中执行以下命令即可启用`file_fdw`: + +```plsql +CREATE EXTENSION file_fdw; +``` + +启用FDW插件之后,需要创建一个实例,也是一行SQL搞定,创建一个名为`fs`的FDW Server实例。 + +```plsql +CREATE SERVER fs FOREIGN DATA WRAPPER file_fdw; +``` + + + +## 创建外部表 + +举个栗子,如果我想从数据库中读取操作系统中正在运行的进程信息,该怎么做呢? + +最典型,也是最常用的外部数据格式就是CSV啦。不过系统命令输出的结果并不是很规整: + +```bash +>>> ps ux +USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND +vonng 2658 0.0 0.2 148428 2620 ? S 11:51 0:00 sshd: vonng@pts/0,pts/2 +vonng 2659 0.0 0.2 115648 2312 pts/0 Ss+ 11:51 0:00 -bash +vonng 4854 0.0 0.2 115648 2272 pts/2 Ss 15:46 0:00 -bash +vonng 5176 0.0 0.1 150940 1828 pts/2 R+ 16:06 0:00 ps -ux +vonng 26460 0.0 1.2 271808 13060 ? S 10月26 0:22 /usr/local/pgsql/bin/postgres +vonng 26462 0.0 0.2 271960 2640 ? Ss 10月26 0:00 postgres: checkpointer process +vonng 26463 0.0 0.2 271808 2148 ? Ss 10月26 0:25 postgres: writer process +vonng 26464 0.0 0.5 271808 5300 ? Ss 10月26 0:27 postgres: wal writer process +vonng 26465 0.0 0.2 272216 2096 ? Ss 10月26 0:31 postgres: autovacuum launcher process +vonng 26466 0.0 0.1 126896 1104 ? Ss 10月26 0:54 postgres: stats collector process +vonng 26467 0.0 0.1 272100 1588 ? Ss 10月26 0:01 postgres: bgworker: logical replication launcher + +``` + +可以通过`awk`,将`ps`的命令输出规整为分隔符为`\x1F`的csv格式。 + +``` +ps aux | awk '{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,substr($0,index($0,$11))}' OFS='\037' +``` + +正戏来啦!通过以下DDL创建一张外表定义 + +```plsql +CREATE FOREIGN TABLE process_status ( + username TEXT, + pid INTEGER, + cpu NUMERIC, + mem NUMERIC, + vsz BIGINT, + rss BIGINT, + tty TEXT, + stat TEXT, + start TEXT, + time TEXT, + command TEXT +) SERVER fs OPTIONS ( +PROGRAM $$ps aux | awk '{print $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,substr($0,index($0,$11))}' OFS='\037'$$, +FORMAT 'csv', DELIMITER E'\037', HEADER 'TRUE'); +``` + +这里,关键是通过`CREATE FOREIGN TABLE OPTIONS (xxxx)`中的`OPTIONS`提供相应的参数,在`PROGRAM`参数中填入上面的命令,pg就会在查询这张表的时候自动执行此命令,并读取其输出。`FORMAT`参数可以指定为`CSV`,`DELIMITER`参数指定为之前使用的`\x1F`,并通过`HEADER 'TRUE'`忽略CSV的第一行 + + + +那么结果如何呢? + +![](img/file_fdw.png) + + + +## 有什么用 + +最简单的场景,原本系统指标监控需要编写各种监测脚本,部署在奇奇怪怪的地方。然后定期执行拉取metric,再存进数据库。现在通过file_fdw的方式,可以将感兴趣的指标直接录入数据库表,一步到位,而且维护方便,部署简单,更加可靠。在外表上加上视图,定期拉取聚合,将原本一个监控系统完成的事情,在数据库中一条龙解决了。 + +因为可以从程序输出读取结果,因此file_fdw可以与linux生态里各类强大的命令行工具配合使用,发挥出强大的威力。 + + + +## 其他栗子 + +诸如此类,实际上后来我发现Facebook貌似有一个类似的产品,叫OSQuery,也是干了差不多的事。通过SQL查询操作系统的指标。但明显PostgreSQL这种方法最简单粗暴高效啦,只要定义表结构,和命令数据源就能轻松对接指标数据,用不了一天就能做出一个功能差不多的东西来。 + +用于读取系统用户列表的DDL: + +```plsql +CREATE FOREIGN TABLE etc_password ( + username TEXT, + password TEXT, + user_id INTEGER, + group_id INTEGER, + user_info TEXT, + home_dir TEXT, + shell TEXT +) SERVER fs OPTIONS ( + PROGRAM $$awk -F: 'NF && !/^[:space:]*#/ {print $1,$2,$3,$4,$5,$6,$7}' OFS='\037' /etc/passwd$$, + FORMAT 'csv', DELIMITER E'\037' +); +``` + +用于读取磁盘用量的DDL: + +```plsql +CREATE FOREIGN TABLE disk_free ( + file_system TEXT, + blocks_1m BIGINT, + used_1m BIGINT, + avail_1m BIGINT, + capacity TEXT, + iused BIGINT, + ifree BIGINT, + iused_pct TEXT, + mounted_on TEXT +) SERVER fs OPTIONS (PROGRAM $$df -ml| awk '{print $1,$2,$3,$4,$5,$6,$7,$8,$9}' OFS='\037'$$, FORMAT 'csv', HEADER 'TRUE', DELIMITER E'\037' +); +``` + +当然,用file_fdw只是一个很Naive的FDW,譬如这里就只能读,不能改。 + +自己编写FDW实现增删改查逻辑也非常简单,例如Multicorn就是使用Python编写FDW的项目。 + +SQL over everything,让世界变的更简单~ \ No newline at end of file diff --git a/pg/ext-mongo_fdw-install.md b/pg/ext-mongo_fdw-install.md new file mode 100755 index 0000000..b7d53cf --- /dev/null +++ b/pg/ext-mongo_fdw-install.md @@ -0,0 +1,102 @@ +# PostgreSQL MongoDB FDW安装部署 + +最近有业务要求通过PostgreSQL FDW去访问MongoDB。开始我觉得这是个很轻松的任务。但接下来的事真是让人恶心的吐了。MongoDB FDW编译起来真是要人命:混乱的依赖,临时下载和Hotpatch,错误的编译参数,以及最过分的是错误的文档。总算,我在生产环境(Linux RHEL7u2)和开发环境(Mac OS X 10.11.5)都编译成功了。赶紧记录下来,省的下次蛋疼。 + +## 环境概述 +理论上编译这套东西,GCC版本至少为4.1。 +生产环境 (RHEL7.2 + PostgreSQL9.5.3 + GCC 4.8.5) +本地环境 (Mac OS X 10.11.5 + PostgreSQL9.5.3 + clang-703.0.31) + +## mongo_fdw的依赖 +总的来说,能用包管理解决的问题,尽量用包管理解决。 +[mongo_fdw](https://github.com/EnterpriseDB/mongo_fdw "mongo_fdw")是我们最终要安装的包 +它的直接依赖有三个 +* [json-c 0.12](https://github.com/json-c/json-c/tree/json-c-0.12 "json-c 0.12") +* [libmongoc-1.3.1](https://github.com/mongodb/mongo-c-driver/tree/r1.3 "libmongoc-1.3.1") +* [libbson-1.3.1](https://github.com/mongodb/libbson/tree/r1.3 "libbson-1.3.1") + +总的来说,mongo_fdw是使用mongo提供的C驱动程序完成功能的。所以我们需要安装libbson与libmongoc。其中libmongoc就是MongoDB的C语言驱动库,它依赖于libbson。 +所以最后的安装顺序是: +`libbson` → `libmongoc` → `json-c`→ `mongo_fdw` + +#### 间接依赖 +默认依赖的GNU Build全家桶,文档是不会告诉你的。 +下面列出一些比较简单的,可以通过包管理解决的依赖。 +请一定按照以下顺序安装`GNU Autotools` + +`m4-1.4.17` → `autoconf-2.69` → `automake-1.15` → `libtool-2.4.6` → `pkg-config-0.29.1`。 +总之,用yum也好,apt也好,homebrew也好,都是一行命令能搞定的事。 +还有一个依赖是libmongoc的依赖:`openssl-devel`,不要忘记装。 + + +### 安装 `libbson-1.3.1` +```bash +git clone -b r1.3 https://github.com/mongodb/libbson; +cd libbson; +git checkout 1.3.1; +./autogen.sh; +make && sudo make install; +make test; +``` + +### 安装 `libmongoc-1.3.1` +```bash +git clone -b r1.3 https://github.com/mongodb/mongo-c-driver +cd mongo-c-driver; +git checkout 1.3.1; +./autogen.sh; +# 下一步很重要,一定要使用刚才安装好的系统中的libbson。 +./configure --with-libbson=system; +make && sudo make install; +``` + +这里为什么要使用1.3.1的版本?这也是有讲究的。因为mongo_fdw中默认使用的是1.3.1的mongo-c-driver。但是它在文档里说只要1.0.0+就可以,其实是在放狗屁。mongo-c-driver与libbson版本是一一对应的。1.0.0版本的libbson脑子被驴踢了,使用了超出C99的特性,比如复数类型。要是用了默认版本就傻逼了。 + +#### 安装`json-c` +首先,我们来解决json-c的问题 +``` +git clone https://github.com/json-c/json-c; +cd json-c +git checkout json-c-0.12 +``` +`./configure`完了可不要急着Make,这个版本的json-c编译参数有问题。 +** 打开Makefile,找到`CFLAGS`,在编译参数后面添加`-fPIC` ** +这样GCC会生成位置无关代码,不这样做的话mongo_fdw链接会报错。 + + +### 安装 `Mongo FDW` +真正恶心的地方来咯。 +```bash +git clone https://github.com/EnterpriseDB/mongo_fdw; +``` +好了,如果这时候想当然的运行`./autogen.sh --with-master`,它就会去重新下一遍上面几个包了……,而且都是从墙外亚马逊的云主机去下。靠谱的方法就是手动一条条的执行autogen里面的命令。 + +首先把上面的json-c目录复制到mongo_fdw的根目录内。 +然后添加libbson和libmongoc的include路径。 +``` +export C_INCLUDE_PATH="/usr/local/include/libbson-1.0/:/usr/local/include/libmongoc-1.0:$C_INCLUDE_PATH" +``` +查看`autogen.sh`,发现里面根据`--with-legacy`和`--with-master`的不同选项,会有不同的操作。具体来说,当指定`--with-master`选项时,它会创建一个config.h,里面定义了一个META_DRIVER的宏变量。当有这个宏变量时,mongo_fdw会使用mongoc.h头文件,也就是所谓的“master”,新版的mongo驱动。当没有时,则会使用"mongo.h"头文件,也就是老版的mongo驱动。这里,我们直接`vi config.h`,添加一行 +``` +#define META_DRIVER +``` +这时候,基本上才能算万事大吉。 +在最终build之前,别忘了执行:`ldconfig` +``` +[sudo] ldconfig +``` +回到mongo_fdw根目录`make`,不出意外,这个`mongo_fdw.so`就出来了。 + + +### 试一试吧? +``` +sudo make install; +psql +admin=# CREATE EXTENSION mongo_fdw; +``` + +如果提示找不到libmongoc.so和libbson.so,直接把它们丢进pgsql的lib目录即可。 +``` +sudo cp /usr/local/lib/libbson* /usr/local/pgsql/lib/ +sudo cp /usr/local/lib/libmongoc* /usr/local/pgsql/lib/ +``` diff --git a/pg/ext-pgadmin-install.md b/pg/ext-pgadmin-install.md new file mode 100755 index 0000000..52055b2 --- /dev/null +++ b/pg/ext-pgadmin-install.md @@ -0,0 +1,68 @@ +# PostgreSQL安装手册 + + + +## PgAdmin4的安装与配置 + +PgAdmin是一个为PostgreSQL定制设计的GUI。用起来很不错。可以以本地GUI程序或者Web服务的方式运行。因为Retina屏幕下面PgAdmin依赖的GUI组件显示效果有点问题,这里主要介绍如何以Web服务方式(Python Flask)配置运行PgAdmin4。 + +### 下载 + +PgAdmin可以从官方FTP下载。 + +[postgresql网站FTP目录地址](https://ftp.postgresql.org/pub/pgadmin3/pgadmin4) + +```bash +wget https://ftp.postgresql.org/pub/pgadmin3/pgadmin4/v1.1/source/pgadmin4-1.1.tar.gz +tar -xf pgadmin4-1.1.tar.gz && cd pgadmin4-1.1/ +``` + +也可以从官方[Git Repo](git://git.postgresql.org/git/pgadmin4.git)下载: + +```bash +git clone git://git.postgresql.org/git/pgadmin4.git +cd pgadmin4 +``` + + + +### 安装依赖 + +首先,需要安装Python,2或者3都可以。这里使用管理员权限安装Anaconda3发行版作为示例。 + +首先创建一个虚拟环境,当然直接上物理环境也是可以的…… + +```bash +conda create -n pgadmin python=3 anaconda +``` + +根据对应的Python版本,按照对应的依赖文件安装依赖。 + +```bash +sudo pip install -r requirements_py3.txt +``` + + + +### 配置选项 + +首先执行初始化脚本,创立PgAdmin的管理员用户。 +```bash +python web/setup.py +``` +按照提示输入Email和密码即可。 + + +编辑`web/config.py`,修改默认配置,主要是改监听地址和端口。 + +```python +DEFAULT_SERVER = 'localhost' +DEFAULT_SERVER_PORT = 5050 +``` +修改监听地址为`0.0.0.0`以便从任意IP访问。 +按需修改端口。 + + + + + diff --git a/pg/ext-postgis-install.md b/pg/ext-postgis-install.md new file mode 100644 index 0000000..efc4a2f --- /dev/null +++ b/pg/ext-postgis-install.md @@ -0,0 +1,227 @@ +# PostGIS installation with yum + +> 参考 + +### 1. 安装环境 + +- CentOS 7 +- PostgreSQL10 +- PostGIS2.4 +- PGROUTING2.5.2 + + + +### 2. PostgreSQL10安装 + +##### 2.1 确定系统环境 + +``` +uname -a + +Linux localhost.localdomain 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux +``` + +##### 2.2 安装正确的rpm包 + +``` + rpm -ivh https://download.postgresql.org/pub/repos/yum/10/redhat/rhel-7-x86_64/pgdg-centos10-10-2.noarch.rpm +``` + +不同的系统使用不同的rpm源,你可以从 获取相应的平台链接。 + +##### 2.3 查看rpm包是否正确安装 + +``` +yum list | grep pgdg + +pgdg-centos10.noarch 10-2 installed +CGAL.x86_64 4.7-1.rhel7 pgdg10 +CGAL-debuginfo.x86_64 4.7-1.rhel7 pgdg10 +CGAL-demos-source.x86_64 4.7-1.rhel7 pgdg10 +CGAL-devel.x86_64 4.7-1.rhel7 pgdg10 +MigrationWizard.noarch 1.1-3.rhel7 pgdg10 +... +``` + +##### 2.4 安装PG + +``` +yum install -y postgresql10 postgresql10-server postgresql10-libs postgresql10-contrib postgresql10-devel +``` + +你可以根据需要选择安装相应的rpm包。 + +##### 2.5 启动服务 + +默认情况下,PG安装目录为`/usr/pgsql-10/`,data目录为`/var/lib/pgsql/`,系统默认创建用户`postgres` + +``` +passwd postgres # 为系统postgres设置密码 +su - postgres # 切换到用户postgres +/usr/pgsql-10/bin/initdb -D /var/lib/pgsql/10/data/ # 初始化数据库 +/usr/pgsql-10/bin/pg_ctl -D /var/lib/pgsql/10/data/ -l logfile start # 启动数据库 +/usr/pgsql-10/bin/psql postgres postgres # 登录 +``` + +### 3. PostGIS安装 + +``` +yum install postgis24_10-client postgis24_10 +``` + +> 如果遇到错误如下: +> +> ``` +> --> 解决依赖关系完成 +> 错误:软件包:postgis24_10-client-2.4.2-1.rhel7.x86_64 (pgdg10) +> 需要:libproj.so.0()(64bit) +> 错误:软件包:postgis24_10-2.4.2-1.rhel7.x86_64 (pgdg10) +> 需要:gdal-libs >= 1.9.0 +> ``` +> 你可以尝试通过以下命令解决:```yum -y install epel-release``` + +### 4. fdw安装 + +``` +yum install ogr_fdw10 +``` + +### 5. pgrouting安装 + +``` +yum install pgrouting_10 +``` + +### 6. 验证测试 + +``` +# 登录pg后执行以下命令,无报错则证明成功 +CREATE EXTENSION postgis; +CREATE EXTENSION postgis_topology; +CREATE EXTENSION ogr_fdw; + +SELECT postgis_full_version(); +``` + + + +## 一些依赖组件的编译方式 + + + +## 编译工具 + +此类工具一般系统都自带。 + +* GCC与G++,版本至少为`4.x`。 +* GNU Make,CMake, Autotools +* Git + +CentOS下直接通过`sudo yum install gcc gcc-c++ git autoconf automake libtool m4 `安装。 + + + +## 必选依赖 + +### PostgreSQL + +PostgreSQL是PostGIS的宿主平台。这里以10.1为例。 + + + +### GEOS + +GEOS是Geometry Engine, Open Source的缩写,是一个C++版本的几何库。是PostGIS的核心依赖。 + +PostGIS 2.4用到了GEOS 3.7的一些新特性。不过截止到现在,GEOS官方发布的最新版本是3.6.2,3.7版本的GEOS可以通过[Nightly snapshot](http://geos.osgeo.org/snapshots/)获取。所以目前如果希望用到所有新特性,需要从源码编译安装GEOS 3.7。 + +```bash +# 滚动的每日更新,此URL有可能过期,检查这里http://geos.osgeo.org/snapshots/ +wget -P ./ http://geos.osgeo.org/snapshots/geos-20171211.tar.bz2 +tar -jxf geos-20171211.tar.bz2 +cd geos-20171211 +./configure +make +sudo make install +cd .. +``` + +### Proj + +为PostGIS提供坐标投影支持,目前最新版本为4.9.3 :[下载](http://proj4.org/download.html) + +```bash +# 此URL有可能过期,检查这里http://proj4.org/download.html +wget -P . http://download.osgeo.org/proj/proj-4.9.3.tar.gz +tar -zxf proj-4.9.3.tar.gz +cd proj-4.9.3 +make +sudo make install +``` + +### JSON-C + +目前用于导入GeoJSON格式的数据,函数`ST_GeomFromGeoJson`用到了这个库。 + +编译`json-c`需要用到`autoconf, automake, libtool`。 + +```bash +git clone https://github.com/json-c/json-c +cd json-c +sh autogen.sh + +./configure # --enable-threading +make +make install +``` + +### LibXML2 + +目前用于导入GML与KML格式的数据,函数`ST_GeomFromGML`和`ST_GeomFromKML`依赖这个库。 + +目前可以在这个[FTP](ftp://xmlsoft.org/libxml2/)服务器上搞到,目前使用的版本是`2.9.7` + +```bash +tar -zxf libxml2-sources-2.9.7.tar.gz +cd libxml2-sources-2.9.7 +./configure +make +sudo make install +``` + + + +### GADL + +```bash +wget -P . http://download.osgeo.org/gdal/2.2.3/gdal-2.2.3.tar.gz +``` + + + +### SFCGAL + +SFCGAL是CGAL的扩展包装,虽说是可选项,但是很多函数都会经常用到,因此这里也需要安装。[下载页面](http://oslandia.github.io/SFCGAL/installation.html) + +SFCGAL依赖的东西比较多。包括`CMake, CGAL, Boost, MPFR, GMP`等,其中,`CGAL`在上面手动安装过了。这里还需要手动安装BOOST + +```bash +wget -P . https://github.com/Oslandia/SFCGAL/archive/v1.3.0.tar.gz + +``` + + + +### Boost + +Boost是C++的常用库,SFCGAL依赖BOOST,[下载页面](http://www.boost.org) + +```bash +wget -P . https://dl.bintray.com/boostorg/release/1.65.1/source/boost_1_65_1.tar.gz +tar -zxf boost_1_65_1.tar.gz +cd boost_1_65_1 +./bootstrap.sh +./b2 +``` + + diff --git a/pg/ext-redis_fdw-install.md b/pg/ext-redis_fdw-install.md new file mode 100644 index 0000000..91e3b35 --- /dev/null +++ b/pg/ext-redis_fdw-install.md @@ -0,0 +1,18 @@ +# Redis FDW 安装 + +``` +# 先安装hiredis +git clone https://github.com/redis/hiredis +cd hiredis +make -j8 +sudo make install + +# 再安装redis_fdw +git clone https://github.com/pg-redis-fdw/redis_fdw +cd redis_fdw +PGSQL_BIN="/usr/local/pgsql/bin/" +git checkout REL9_5_STABLE +PATH="$PGSQL_BIN:$PATH" make USE_PGXS=1 +sudo PATH="$PGSQL_BIN:$PATH" make USE_PGXS=1 install +``` + diff --git a/pg/img/file_fdw.png b/pg/img/file_fdw.png new file mode 100644 index 0000000..4d0d827 Binary files /dev/null and b/pg/img/file_fdw.png differ diff --git a/pg/pg-admin-auth.md b/pg/pg-admin-auth.md new file mode 100644 index 0000000..68bba7a --- /dev/null +++ b/pg/pg-admin-auth.md @@ -0,0 +1,172 @@ +# Client Authentication + +连接数据库出现出现奇怪的权限问题时,首先检查`postgresql.conf` + +配置`listen_addresses`为合适的值(例如`*`)以允许外部连接。默认情况下`listen_addresses=localhost`,只允许本地TCP连接。 + +相关视图:`pg_hba_file_rules` + +## 认证配置 + +* 认证通过配置文件`pg_hba.conf`控制。hba是`host based authentication`的缩写。 +* `pg_hba.conf`默认在数据目录的顶层,但可以通过`postgresql.conf`中`hba_file`来指明其他位置。 +* `pg_hba`的配置项为文本文件,每行一条记录 +* 每条记录包括``五个字段。 +* 字段间使用空白字符分隔,引号扩起的值内部可以包含空白。 +* 每条记录的格式为 + * 什么样的连接方式:`local, host, hostssl, hostnossl` + * 什么样的数据库:`all, sameuser, samerole, replication, ` + * 什么样的用户:`all, , +` + * 什么样的地址:IP地址,CIDR地址,`0.0.0.0/0`表示所有机器。 + * 什么样的行为:`trust, reject, md5, password, ident, peer...` + + +下面是这五个字段的可选值 + +### 连接方式 + +- `local`:使用 Unix 域套接字的连接。如果没有这种类型的记录,就不允许 Unix 域套接字连接。 + +- `host`:使用 TCP/IP 建立的连接,可以使用SSL也可以不使用。 + +- `hostssl`:使用TCP/IP + SSL连接 + +- `hostnossl`:与`hostssl`相反:使用不启用SSL的TCP/IP连接。 + + +### 数据库 + +- ``:指定记录所匹配的数据库名称。 + +- `all`:指定该记录匹配所有数据库。 +- `sameuser`:指定如果被请求的数据库和请求的用户同名,则匹配。 + +### 用户 + +指定这条记录匹配哪些数据库用户名。值`all`指定它匹配所有用户。否则,它要么是一个特定数据库用户的名字或者是一个有前导`+`的组名称(回想一下,在PostgreSQL里,用户和组没有真正的区别,`+`实际表示"匹配这个角色的任何直接或间接成员角色",而没有`+`记号的名字只匹配指定的角色)。出于这个目的,如果超级用户显式的是一个角色的成员(直接或间接),那么超级用户将只被认为是该角色的一个成员而不是作为一个超级用户。多个用户名可以通过用逗号分隔的方法提供。一个包含用户名的文件可以通过在文件名前面加上`@`来指定。 + +- `*address*` + + 指定这个记录匹配的客户端机器地址。这个域可以包含一个主机名、一个 IP 地址范围或下文提到的特殊关键字之一。一个 IP 地址范围以该范围的开始地址的标准数字记号指定,然后是一个斜线(`/`) 和一个CIDR掩码长度。掩码长度表示客户端 IP 地址必须匹配的高序二进制位位数。在给出的 IP 地址中,这个长度的右边的二进制位必须为零。 在 IP 地址、`/`和 CIDR 掩码长度之间不能有空白。这种方法指定一个 IPv4 地址范围的典型例子是: `172.20.143.89/32`用于一个主机, `172.20.143.0/24`用于一个小型网络, `10.6.0.0/16`用于一个大型网络。 一个单主机的 IPv6 地址范围看起来像这样:`::1/128`(IPv6 回环地址), 一个小型网络的 IPv6 地址范围则类似于:`fe80::7a31:c1ff:0000:0000/96`。 `0.0.0.0/0`表示所有 IPv4 地址,并且`::0/0`表示所有 IPv6 地址。要指定一个单一主机,IPv4 用一个长度为 32 的 CIDR 掩码或者 IPv6 用 长度为 128 的 CIDR 掩码。在一个网络地址中,不要省略结尾的零。一个以 IPv4 格式给出的项将只匹配 IPv4 连接并且一个以 IPv6 格式给出的项将只匹配 IPv6 连接,即使对应的地址在 IPv4-in-IPv6 范围内。请注意如果系统的 C 库不支持 IPv6 地址,那么 IPv6 格式中的项将被拒绝。你也可以写`all`来匹配任何 IP 地址、写`samehost`来匹配任何本服务器自身的 IP 地址或者写`samenet`来匹配本服务器直接连接到的任意子网的任意地址。若果指定了一个主机名(任何除 IP 地址单位或特殊关键字之外的都被作为主机名处理), 该名称会与客户端的 IP 地址的反向名字解析(例如使用 DNS 时的反向 DNS 查找)结果进行比较。主机名比较是大小写敏感的。如果匹配上,那么将在主机名上执行一次正向名字解析(例如正向 DNS 查找)来检查它解析到的任何地址是否等于客户端的 IP 地址。如果两个方向都匹配,则该项被认为匹配(`pg_hba.conf`中使用的主机名应该是客户端 IP 地址的地址到名字解析返回的结果,否则该行将不会匹配。某些主机名数据库允许将一个 IP 地址关联多个主机名,但是当被要求解析一个 IP 地址时,操作系统将只返回一个主机名)。一个以点号(`.`)开始的主机名声明匹配实际主机名的后缀。因此`.example.com`将匹配`foo.example.com`(但不匹配`example.com`)。当主机名在`pg_hba.conf`中被指定时,你应该保证名字解析很快。建立一个类似`nscd`的本地名字解析缓存是一种不错的选择。另外,你可能希望启用配置参数`log_hostname`来在日志中查看客户端的主机名而不是 IP 地址。这个域只适用于`host`、 `hostssl`和`hostnossl`记录。用户有时候会疑惑为什么这样处理的主机名看起来很复杂,因为需要两次名字解析(包括一次 客户端 IP 地址的反向查找)。在客户端的反向 DNS 项没有建立或者得到某些意料之外的主机 名的情况下,这种方式会让该特性的使用变得复杂。这样做主要是为了效率:通过这种方式,一次 连接尝试要求最多两次解析器查找,一次逆向以及一次正向。如果有一个解析器对于该地址有问 题,这仅仅是客户端的问题。一种假想的替代实现是只做前向查找,这种方法不得不在每一次连接 尝试期间解析`pg_hba.conf`中提到的每一个主机名。如果列出了很多 名称,这就会很慢。并且如果主机名之一有解析器问题,它会变成所有人的问题。另外,一次反向查找也是实现后缀匹配特性所需的,因为需要知道实际的客户端主机名来与模式进行匹配。注意这种行为与其他流行的基于主机名的访问控制实现相一致,例如 Apache HTTP Server 和 TCP Wrappers。 + +- `*IP-address*``*IP-mask*` + + 这两个域可以被用作`*IP-address*``/` `*mask-length*`记号法的替代方案。和指定掩码长度不同,实际的掩码被指 定在一个单独的列中。例如,`255.0.0.0`表示 IPv4 CIDR 掩码长度 8,而`255.255.255.255`表示 CIDR 掩码长度 32。这些域只适用于`host`、`hostssl`和`hostnossl`记录。 + +`*auth-method*` + +指定当一个连接匹配这个记录时,要使用的认证方法。下面对可能的选择做了概述,详见[第 20.3 节](http://www.postgres.cn/docs/9.6/auth-methods.html)。`trust`无条件地允许连接。这种方法允许任何可以与PostgreSQL数据库服务器连接的用户以他们期望的任意PostgreSQL数据库用户身份登入,而不需要口令或者其他任何认证。详见[第 20.3.1 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-TRUST)。`reject`无条件地拒绝连接。这有助于从一个组中"过滤出"特定主机,例如一个`reject`行可以阻塞一个特定的主机连接,而后面一行允许一个特定网络中的其余主机进行连接。`md5`要求客户端提供一个双重 MD5 加密的口令进行认证。详见[第 20.3.2 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-PASSWORD)。`password`要求客户端提供一个未加密的口令进行认证。因为口令是以明文形式在网络上发送的,所以我们不应该在不可信的网络上使用这种方式。详见[第 20.3.2 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-PASSWORD)。`gss`用 GSSAPI 认证用户。只对 TCP/IP 连接可用。详见[第 20.3.3 节](http://www.postgres.cn/docs/9.6/auth-methods.html#GSSAPI-AUTH)。`sspi`用 SSPI 来认证用户。只在 Windows 上可用。详见[第 20.3.4 节](http://www.postgres.cn/docs/9.6/auth-methods.html#SSPI-AUTH)。`ident`通过联系客户端的 ident 服务器获取客户端的操作系统名,并且检查它是否匹配被请求的数据库用户名。Ident 认证只能在 TCIP/IP 连接上使用。当为本地连接指定这种认证方式时,将用 peer 认证来替代。详见[第 20.3.5 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-IDENT)。`peer`从操作系统获得客户端的操作系统用户,并且检查它是否匹配被请求的数据库用户名。这只对本地连接可用。详见[第 20.3.6 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-PEER)。`ldap`使用LDAP服务器认证。详见[第 20.3.7 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-LDAP)。`radius`用 RADIUS 服务器认证。详见[第 20.3.8 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-RADIUS)。`cert`使用 SSL 客户端证书认证。详见[第 20.3.9 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-CERT)。`pam`使用操作系统提供的可插入认证模块服务(PAM)认证。详见[第 20.3.10 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-PAM)。`bsd`使用由操作系统提供的 BSD 认证服务进行认证。详见[第 20.3.11 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-BSD)。 + +- `*auth-options*` + + 在`*auth-method*`域的后面,可以是形如`*name*``=``*value*`的域,它们指定认证方法的选项。关于哪些认证方法可以用哪些选项的细节请见下文。除了下文列出的与方法相关的选项之外,还有一个与方法无关的认证选项`clientcert`,它可以在任何`hostssl`记录中指定。当被设置为`1`时,这个选项要求客户端在认证方法的其他要求之外出示一个有效的(可信的)SSL 证书。 + +用`@`结构包括的文件被读作一个名字列表,它们可以用空白或者逗号分隔。注释用`#`引入,就像在`pg_hba.conf`中那样,并且允许嵌套`@`结构。除非跟在`@`后面的文件名是一个绝对路径, 文件名都被认为是相对于包含引用文件的目录。 + +因为每一次连接尝试都会顺序地检查`pg_hba.conf`记录,所以这些记录的顺序是非常关键的。通常,靠前的记录有比较严的连接匹配参数和比较弱的认证方法,而靠后的记录有比较松的匹配参数和比较强的认证方法。 例如,我们希望对本地 TCP/IP 连接使用`trust`认证,而对远程 TCP/IP 连接要求口令。在这种情况下为来自于 127.0.0.1 的连接指定`trust`认证的记录将出现在为一个更宽范围的客户端 IP 地址指定口令认证的记录前面。 + +在启动以及主服务器进程收到SIGHUP信号时,`pg_hba.conf`文件会被读取。 如果你在活动的系统上编辑了该文件,你将需要通知 postmaster(使用`pg_ctl reload`或`kill -HUP`)重新读取该文件。 + +> **提示: **要连接到一个特定数据库,一个用户必须不仅要通过`pg_hba.conf`检查,还必须要有该数据库上的`CONNECT`权限。如果你希望限制哪些用户能够连接到哪些数据库,授予/撤销`CONNECT`权限通常比在`pg_hba.conf`项中设置规则简单。 + +[例 20-1](http://www.postgres.cn/docs/9.6/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF)中展示了`pg_hba.conf`项的一些例子。不同认证方法的详情请见下一节。 + +**例 20-1. 示例 pg_hba.conf 项** + +``` +# 允许本地系统上的任何用户 +# 通过 Unix 域套接字以任意 +# 数据库用户名连接到任意数据库 +#(本地连接的默认值)。 +# +# TYPE DATABASE USER ADDRESS METHOD +local all all trust + +# 相同的规则,但是使用本地环回 TCP/IP 连接。 +# +# TYPE DATABASE USER ADDRESS METHOD +host all all 127.0.0.1/32 trust + +# 和前一行相同,但是使用了一个独立的掩码列 +# +# TYPE DATABASE USER IP-ADDRESS IP-MASK METHOD +host all all 127.0.0.1 255.255.255.255 trust + +# IPv6 上相同的规则 +# +# TYPE DATABASE USER ADDRESS METHOD +host all all ::1/128 trust + +# 使用主机名的相同规则(通常同时覆盖 IPv4 和 IPv6)。 +# +# TYPE DATABASE USER ADDRESS METHOD +host all all localhost trust + +# 允许来自任意具有 IP 地址 +# 192.168.93.x 的主机上任意 +# 用户以 ident 为该连接所 +# 报告的相同用户名连接到 +# 数据库 "postgres" +#(通常是操作系统用户名)。 +# +# TYPE DATABASE USER ADDRESS METHOD +host postgres all 192.168.93.0/24 ident + +# 如果用户的口令被正确提供, +# 允许来自主机 192.168.12.10 +# 的任意用户连接到数据库 "postgres"。 +# +# TYPE DATABASE USER ADDRESS METHOD +host postgres all 192.168.12.10/32 md5 + +# 如果用户的口令被正确提供, +# 允许 example.com 中主机上 +# 的任意用户连接到任意数据库。 +# +# TYPE DATABASE USER ADDRESS METHOD +host all all .example.com md5 + +# 如果没有前面的 "host" 行,这两 +# 行将拒绝所有来自 192.168.54.1 +# 的连接(因为那些项将首先被匹配), +# 但是允许来自互联网其他任何地方的 +# GSSAPI 连接。零掩码导致主机 +# IP 地址中的所有位都不会被考虑, +# 因此它匹配任意主机。 +# +# TYPE DATABASE USER ADDRESS METHOD +host all all 192.168.54.1/32 reject +host all all 0.0.0.0/0 gss + +# 允许来自 192.168.x.x 主机的用户 +# 连接到任意数据库,如果它们能够 +# 通过 ident 检查。例如,假设 ident +# 说用户是 "bryanh" 并且他要求以 +# PostgreSQL 用户 "guest1" 连接, +# 如果在 pg_ident.conf 有一个映射 +# "omicron" 的选项说 "bryanh" 被 +# 允许以 "guest1" 连接,则该连接将被允许。 +# +# TYPE DATABASE USER ADDRESS METHOD +host all all 192.168.0.0/16 ident map=omicron + +# 如果这些是本地连接的唯一三行, +# 它们将允许本地用户只连接到它们 +# 自己的数据库(与其数据库用户名 +# 同名的数据库),不过管理员和角 +# 色 "support" 的成员除外(它们可 +# 以连接到所有数据库)。文件 +# $PGDATA/admins 包含一个管理员 +# 名字的列表。在所有情况下都要求口令。 +# +# TYPE DATABASE USER ADDRESS METHOD +local sameuser all md5 +local all @admins md5 +local all +support md5 + +# 上面的最后两行可以被整合为一行: +local all @admins,+support md5 + +# 数据库列也可以用列表和文件名: +local db1,db2,@demodbs all md5 +``` diff --git a/pg/pg-admin-backup.md b/pg/pg-admin-backup.md new file mode 100644 index 0000000..0669d29 --- /dev/null +++ b/pg/pg-admin-backup.md @@ -0,0 +1,701 @@ +--- +author: "Vonng" +description: "PostgreSQL备份与恢复" +categories: ["DBA"] +tags: ["PostgreSQL","Admin"] +type: "post" +--- + + + +# PostgreSQL备份与恢复 + +备份是DBA的安身立命之本,有备份,就不用慌。 + +备份有三种形式:SQL转储,文件系统备份,连续归档 + + + +## 1. SQL转储 + +SQL 转储方法的思想是: + +创建一个由SQL命令组成的文件,服务器能利用其中的SQL命令重建与转储时状态一样的数据库。 + +### 1.1 转储 + +工具`pg_dump`、`pg_dumpall`用于进行SQL转储。结果输出到stdout。 + +```bash +pg_dump dbname > filename +pg_dump dbname -f filename +``` + +* `pg_dump`是一个普通的PostgreSQL客户端应用。可以在任何可以访问该数据库的远端主机上进行备份工作。 +* `pg_dump`不会以任何特殊权限运行,必须要有你想备份的表的读权限,同时它也遵循同样的HBA机制。 +* 要备份整个数据库,几乎总是需要一个数据库超级用户。 +* 该备份方式的重要优势是,它是跨版本、跨机器架构的备份方式。(最远回溯至7.0) +* `pg_dump`的备份是内部一致的,是转储开始时刻的数据库快照,转储期间的更新不被包括在内。 +* `pg_dump`不会阻塞其他数据库操作,但需要排它锁的命令除外(例如大多数 ALTER TABLE) + +### 1.2 恢复 + +文本转储文件可由psql读取,从转储中恢复的常用命令是: + +```bash +psql dbname < infile +``` + +* 这条命令不会创建数据库`dbname`,必须在执行psql前自己从`template0`创建。例如,用命令`createdb -T template0 dbname`。默认`template1`和`template0`是一样的,新创建的数据库默认以`template1`为模板。 + + `CREATE DATABASE dbname TEMPLATE template0;` + +* 非文本文件转储可以使用[pg_restore](http://www.postgres.cn/docs/9.6/app-pgrestore.html)工具来恢复。 + +* 在开始恢复之前,转储库中对象的拥有者以及在其上被授予了权限的用户必须已经存在。如果它们不存在,那么恢复过程将无法将对象创建成具有原来的所属关系以及权限(有时候这就是你所需要的,但通常不是)。 + +* 恢复时遇到错误自动终止,则可以设置`ON_ERROR_STOP`变量来运行psql,遇到SQL错误后退出并返回状态3: + +```bash +psql --set ON_ERROR_STOP=on dbname < infile +``` + +* 恢复时可以使用单个事务来保证要么完全正确恢复,要么完全回滚。使用`-1`或`--single-transaction` +* pg_dump和psql可以通过管道on-the-fly做转储与恢复 + +``` +pg_dump -h host1 dbname | psql -h host2 dbname +``` + +### 1.3 全局转储 + +一些信息属于数据库集簇,而不是单个数据库的,例如角色、表空间。如果希望转储这些,可使用`pg_dumpall` + +``` +pg_dumpall > outfile +``` + +如果只想要全局的数据(角色与表空间),则可以使用`-g, --globals-only`参数。 + +转储的结果可以使用psql恢复,通常将转储载入到一个空集簇中可以用`postgres`作为数据库名 + +``` +psql -f infile postgres +``` + +* 在恢复一个pg_dumpall转储时常常需要具有数据库超级用户访问权限,因为它需要恢复角色和表空间信息。 +* 如果使用了表空间,请确保转储中的表空间路径适合于新的安装。 +* pg_dumpall工作步骤是,先创建角色、表空间转储,再为每一个数据库做pg_dump。这意味着每个数据库自身是一致的,但是不同数据库的快照并不同步。 + +### 1.4 命令实践 + +准备环境,创建测试数据库 + +```bash +psql postgres -c "CREATE DATABASE testdb;" +psql postgres -c "CREATE ROLE test_user LOGIN;" +psql testdb -c "CREATE TABLE test_table(i INTEGER);" +psql testdb -c "INSERT INTO test_table SELECT generate_series(1,16);" +``` + +```bash +# dump到本地文件 +pg_dump testdb -f testdb.sql + +# dump并用xz压缩,-c指定从stdio接受,-d指定解压模式 +pg_dump testdb | xz -cd > testdb.sql.xz + +# dump,压缩,分割为1m的小块 +pg_dump testdb | xz | split -b 1m - testdb.sql.xz +cat testdb.sql.xz* | xz -cd | psql # 恢复 + +# pg_dump 常用参数参考 +-s --schema-only +-a --data-only +-t --table +-n --schema +-c --clean +-f --file + +--inserts +--if-exists +-N --exclude-schema +-T --exclude-table +``` + + + + + +## 2. 文件系统转储 + +SQL 转储方法的思想是:拷贝数据目录的所有文件。为了得到一个可用的备份,所有备份文件都应当保持一致。 + +所以通常比而且为了得到一个可用的备份,所有备份文件都应当保持一致。 + +* 文件系统拷贝不做逻辑解析,只是简单拷贝文件。好处是执行快,省掉了逻辑解析和重建索引的时间,坏处是占用空间更大,而且只能用于整个数据库集簇的备份 + +- 最简单的方式:停机,直接拷贝数据目录的所有文件。 + + +- 有办法通过文件系统(例如xfs)获得一致的冻结快照也可以不停机,但wal和数据目录必须是一致的。 +- 可以通过制作pg_basebackup进行远程归档备份,可以不停机。 + + +- 可以通过停机执行rsync的方式向远端增量同步数据变更。 + + + + + + +## 3. PITR 连续归档与时间点恢复 + +Pg在运行中会不断产生WAL,WAL记录了操作日志,从某一个基础的全量备份开始回放后续的WAL,就可以恢复数据库到任意的时刻的状态。为了实现这样的功能,就需要配置WAL归档,将数据库生成的WAL不断保存起来。 + +WAL在逻辑上是一段无限的字节流。`pg_lsn`类型(bigint)可以标记WAL中的位置,`pg_lsn`代表一个WAL中的字节位置偏移量。但实践中WAL不是连续的一个文件,而被分割为每16MB一段。 + +WAL文件名是有规律的,而且归档时不允许更改。通常为24位十六进制数字,`000000010000000000000003`,其中前面8位十六进制数字表示时间线,后面的16位表示16MB块的序号。即`lsn >> 24`的值。 + +查看`pg_lsn`时,例如`0/84A8300`,只要去掉最后六位hex,就可以得到WAL文件序号的后面部分,这里,也就是`8`,如果使用的是默认时间线1,那么对应的WAL文件就是`000000010000000000000008`。 + +### 3.1 准备环境 + +```bash +# 目录: +# 使用/var/lib/pgsql/data 作为主库目录,使用/var/lib/pgsql/wal作为日志归档目录 +# sudo mkdir /var/lib/pgsql && sudo chown postgres:postgres /var/lib/pgsql/ +pg_ctl stop -D /var/lib/pgsql/data +rm -rf /var/lib/pgsql/{data,wal} && mkdir -p /var/lib/pgsql/{data,wal} + +# 初始化: +# 初始化主库并修改配置文件 +pg_ctl -D /var/lib/pgsql/data init + +# 配置文件 +# 创建默认额外配置文件夹,并在postgresql.conf中配置include_dir +mkdir -p /var/lib/pgsql/data/conf.d +cat >> /var/lib/pgsql/data/postgresql.conf <<- 'EOF' +include_dir = 'conf.d' +EOF +``` + +### 3.2 配置自动归档命令 + +```bash +# 归档配置 +# %p 代表 src wal path, %f 代表 filename +cat > /var/lib/pgsql/data/conf.d/archive.conf <<- 'EOF' +archive_mode = on +archive_command = 'conf.d/archive.sh %p %f' +EOF + +# 归档脚本 +cat > /var/lib/pgsql/data/conf.d/archive.sh <<- 'EOF' +test ! -f /var/lib/pgsql/wal/${2} && cp ${1} /var/lib/pgsql/wal/${2} +EOF +chmod a+x /var/lib/pgsql/data/conf.d/archive.sh +``` + +归档脚本可以简单到只是一个`cp`,也可以非常复杂。但需要注意以下事项: + +- 归档命令使用数据库用户`postgres`执行,最好放在0700的目录下面。 +- 归档命令应当拒绝覆盖现有文件,出现覆盖时,返回一个错误代码。 +- 归档命令可以通过reload配置更新。 + + +- 处理归档失败时的情形 + +- 归档文件应当保留原有文件名。 + +- WAL不会记录对配置文件的变更。 + +- 归档命令中:`%p` 会替换为生成待归档WAL的路径,而`%f`会替换为待归档WAL的文件名 + +- 归档脚本可以使用更复杂的逻辑,例如下面的归档命令,在归档目录中每天创建一个以日期YYYYMMDD命名的文件夹,在每天12点移除前一天的归档日志。每天的归档日志使用xz压缩存储。 + + ```bash + wal_dir=/var/lib/pgsql/wal; + [[ $(date +%H%M) == 1200 ]] && rm -rf ${wal_dir}/$(date -d"yesterday" +%Y%m%d); /bin/mkdir -p ${wal_dir}/$(date +%Y%m%d) && \ + test ! -f ${wal_dir}/ && \ + xz -c %p > ${wal_dir}/$(date +%Y%m%d)/%f.xz + ``` + +- 归档也可以使用外部专用备份工具进行。例如`pgbackrest`与`barman`等。 + + +### 3.3 测试归档 + +```bash +# 启动数据库 +pg_ctl -D /var/lib/pgsql/data start + +# 确认配置 +psql postgres -c "SELECT name,setting FROM pg_settings where name like '%archive%';" +``` + +在当前shell开启监视循环,不断查询WAL的位置,以及归档目录和`pg_wal`中的文件变化 + +```bash +for((i=0;i<100;i++)) do + sleep 1 && \ + ls /var/lib/pgsql/data/pg_wal && ls /var/lib/pgsql/data/pg_wal/archive_status/ + psql postgres -c 'SELECT pg_current_wal_lsn() as current, pg_current_wal_insert_lsn() as insert, pg_current_wal_flush_lsn() as flush;' +done +``` + +在另一个Shell中创建一张测试表`foobar`,包含单一的时间戳列,并引入负载,每秒写入一万条记录: + +```bash +psql postgres -c 'CREATE TABLE foobar(ts TIMESTAMP);' +for((i=0;i<1000;i++)) do + sleep 1 && \ + psql postgres -c 'INSERT INTO foobar SELECT now() FROM generate_series(1,10000)' && \ + psql postgres -c 'SELECT pg_current_wal_lsn() as current, pg_current_wal_insert_lsn() as insert, pg_current_wal_flush_lsn() as flush;' +done +``` + +#### 自然切换WAL + +可以看到,当WAL LSN的位置超过16M(可以由后6个hex表示)之后,就会rotate到一个新的WAL文件,归档命令会将写完的WAL归档。 + +```bash +000000010000000000000001 archive_status + current | insert | flush +-----------+-----------+----------- + 0/1FC2630 | 0/1FC2630 | 0/1FC2630 +(1 row) + +# rotate here + +000000010000000000000001 000000010000000000000002 archive_status +000000010000000000000001.done + current | insert | flush +-----------+-----------+----------- + 0/205F1B8 | 0/205F1B8 | 0/205F1B8 +``` + +#### 手工切换WAL + +再开启一个Shell,执行`pg_switch_wal`,强制写入一个新的WAL文件 + +```bash +psql postgres -c 'SELECT pg_switch_wal();' +``` + +可以看到,虽然位置才到`32C1D68`,但立即就跳到了下一个16MB的边界点。 + +```bash +000000010000000000000001 000000010000000000000002 000000010000000000000003 archive_status +000000010000000000000001.done 000000010000000000000002.done + current | insert | flush +-----------+-----------+----------- + 0/32C1D68 | 0/32C1D68 | 0/32C1D68 +(1 row) + +# switch here + +000000010000000000000001 000000010000000000000002 000000010000000000000003 archive_status +000000010000000000000001.done 000000010000000000000002.done 000000010000000000000003.done + current | insert | flush +-----------+-----------+----------- + 0/4000000 | 0/4000028 | 0/4000000 +(1 row) + +000000010000000000000001 000000010000000000000002 000000010000000000000003 000000010000000000000004 archive_status +000000010000000000000001.done 000000010000000000000002.done 000000010000000000000003.done + current | insert | flush +-----------+-----------+----------- + 0/409CBA0 | 0/409CBA0 | 0/409CBA0 +(1 row) +``` + +#### 强制kill数据库 + +数据库因为故障异常关闭,重启之后,会从最近的检查点,也就是`0/2FB0160`开始重放WAL。 + +```bash +[17:03:37] vonng@vonng-mac /var/lib/pgsql +$ ps axu | grep postgres | grep data | awk '{print $2}' | xargs kill -9 + +[17:06:31] vonng@vonng-mac /var/lib/pgsql +$ pg_ctl -D /var/lib/pgsql/data start +pg_ctl: another server might be running; trying to start server anyway +waiting for server to start....2018-01-25 17:07:27.063 CST [9762] LOG: listening on IPv6 address "::1", port 5432 +2018-01-25 17:07:27.063 CST [9762] LOG: listening on IPv4 address "127.0.0.1", port 5432 +2018-01-25 17:07:27.064 CST [9762] LOG: listening on Unix socket "/tmp/.s.PGSQL.5432" +2018-01-25 17:07:27.078 CST [9763] LOG: database system was interrupted; last known up at 2018-01-25 17:06:01 CST +2018-01-25 17:07:27.117 CST [9763] LOG: database system was not properly shut down; automatic recovery in progress +2018-01-25 17:07:27.120 CST [9763] LOG: redo starts at 0/2FB0160 +2018-01-25 17:07:27.722 CST [9763] LOG: invalid record length at 0/49CBE78: wanted 24, got 0 +2018-01-25 17:07:27.722 CST [9763] LOG: redo done at 0/49CBE50 +2018-01-25 17:07:27.722 CST [9763] LOG: last completed transaction was at log time 2018-01-25 17:06:30.158602+08 +2018-01-25 17:07:27.741 CST [9762] LOG: database system is ready to accept connections + done +server started +``` + +至此,WAL归档已经确认可以正常工作了。 + +### 3.4 制作基础备份 + +首先,查看当前WAL的位置: + +```bash +$ psql postgres -c 'SELECT pg_current_wal_lsn() as current, pg_current_wal_insert_lsn() as insert, pg_current_wal_flush_lsn() as flush;' + + current | insert | flush +-----------+-----------+----------- + 0/49CBF20 | 0/49CBF20 | 0/49CBF20 +``` + +使用`pg_basebackup`制作基础备份 + +```bash +psql postgres -c 'SELECT now();' +pg_basebackup -Fp -Pv -Xs -c fast -D /var/lib/pgsql/bkup + +# 常用选项 +-D : 必选项,基础备份的位置。 +-Fp : 备份格式: plain 普通文件 tar 归档文件 +-Pv : -P 启用进度报告 -v 启用详细输出 +-Xs : 在备份中包括备份期间产生的WAL日志 f:备份完后拉取 s:备份时流式传输 +-c : fast 立即执行Checkpoint而不是均摊IO spread:均摊IO +-R : 设置recovery.conf +``` + +制作基础备份时,会立即创建一个检查点使得所有脏数据页落盘。 + +```bash +$ pg_basebackup -Fp -Pv -Xs -c fast -D /var/lib/pgsql/bkup +pg_basebackup: initiating base backup, waiting for checkpoint to complete +pg_basebackup: checkpoint completed +pg_basebackup: write-ahead log start point: 0/5000028 on timeline 1 +pg_basebackup: starting background WAL receiver +45751/45751 kB (100%), 1/1 tablespace +pg_basebackup: write-ahead log end point: 0/50000F8 +pg_basebackup: waiting for background process to finish streaming ... +pg_basebackup: base backup completed +``` + + + +### 3.5 使用备份 + +#### 直接使用 + +最简单的使用方式,就是直接用`pg_ctl`启动它。 + +当`recovery.conf`不存在时,这样做会启动一个新的完整数据库实例,原原本本地保留了备份完成时的状态。数据库会并不会意识到自己是一个备份。而是以为自己上次没有正常关闭,应用`pg_wal`目录中自带的WAL进行修复,正常重启。 + +基础的全量备份可能每天或每周备份一次,要想恢复到最新的时刻,需要和WAL归档配合使用。 + +#### 使用WAL归档追赶进度 + +可以在备份中数据库下创建一个`recovery.conf`文件,并指定`restore_command`选项。这样的话,当使用`pg_ctl`启动这个数据目录时,postgres会依次拉取所需的WAL,直到没有了为止。 + +```bash +cat >> /var/lib/pgsql/bkup/recovery.conf <<- 'EOF' +restore_command = 'cp /var/lib/pgsql/wal/%f %p' +EOF +``` + +继续在原始主库中执行负载,这时候WAL的进度已经到了`0/9060CE0`,而制作备份的时候位置还在`0/5000028`。 + +启动备份之后,可以发现,备份数据库自动从归档文件夹拉取了5~8号WAL并应用。 + +```bash +$ pg_ctl start -D /var/lib/pgsql/bkup -o '-p 5433' +waiting for server to start....2018-01-25 17:35:35.001 CST [10862] LOG: listening on IPv6 address "::1", port 5433 +2018-01-25 17:35:35.001 CST [10862] LOG: listening on IPv4 address "127.0.0.1", port 5433 +2018-01-25 17:35:35.002 CST [10862] LOG: listening on Unix socket "/tmp/.s.PGSQL.5433" +2018-01-25 17:35:35.016 CST [10863] LOG: database system was interrupted; last known up at 2018-01-25 17:21:15 CST +2018-01-25 17:35:35.051 CST [10863] LOG: starting archive recovery +2018-01-25 17:35:35.063 CST [10863] LOG: restored log file "000000010000000000000005" from archive +2018-01-25 17:35:35.069 CST [10863] LOG: redo starts at 0/5000028 +2018-01-25 17:35:35.069 CST [10863] LOG: consistent recovery state reached at 0/50000F8 +2018-01-25 17:35:35.070 CST [10862] LOG: database system is ready to accept read only connections + done +server started +2018-01-25 17:35:35.081 CST [10863] LOG: restored log file "000000010000000000000006" from archive +$ 2018-01-25 17:35:35.924 CST [10863] LOG: restored log file "000000010000000000000007" from archive +2018-01-25 17:35:36.783 CST [10863] LOG: restored log file "000000010000000000000008" from archive +cp: /var/lib/pgsql/wal/000000010000000000000009: No such file or directory +2018-01-25 17:35:37.604 CST [10863] LOG: redo done at 0/8FFFF90 +2018-01-25 17:35:37.604 CST [10863] LOG: last completed transaction was at log time 2018-01-25 17:30:39.107943+08 +2018-01-25 17:35:37.614 CST [10863] LOG: restored log file "000000010000000000000008" from archive +cp: /var/lib/pgsql/wal/00000002.history: No such file or directory +2018-01-25 17:35:37.629 CST [10863] LOG: selected new timeline ID: 2 +cp: /var/lib/pgsql/wal/00000001.history: No such file or directory +2018-01-25 17:35:37.678 CST [10863] LOG: archive recovery complete +2018-01-25 17:35:37.783 CST [10862] LOG: database system is ready to accept connections +``` + +但是使用WAL归档的方式来恢复也有问题,例如查询主库与备库最新的数据记录,发现时间戳差了一秒。也就是说,主库还没有写完的WAL并没有被归档,因此也没有应用。 + +```bash +[17:37:22] vonng@vonng-mac /var/lib/pgsql +$ psql postgres -c 'SELECT max(ts) FROM foobar;' + max +---------------------------- + 2018-01-25 17:30:40.159684 +(1 row) + + +[17:37:42] vonng@vonng-mac /var/lib/pgsql +$ psql postgres -p 5433 -c 'SELECT max(ts) FROM foobar;' + max +---------------------------- + 2018-01-25 17:30:39.097167 +(1 row) +``` + +通常`archive_command, restore_command`主要用于紧急情况下的恢复,比如主库从库都挂了。因为还没有归档 + + + +### 3.6 指定进度 + +默认情况下,恢复将会一直恢复到 WAL 日志的末尾。下面的参数可以被用来指定一个更早的停止点。`recovery_target`、`recovery_target_name`、`recovery_target_time`和`recovery_target_xid`四个选项中最多只能使用一个,如果在配置文件中使用了多个,将使用最后一个。 + +上面四个恢复目标中,常用的是 `recovery_target_time`,用于指明将系统恢复到什么时间。 + +另外几个常用的选项包括: + +- `recovery_target_inclusive` (`boolean`) :是否包括目标点,默认为true +- `recovery_target_timeline` (`string`): 指定恢复到一个特定的时间线中。 +- `recovery_target_action` (`enum`):指定在达到恢复目标时服务器应该立刻采取的动作。 + - `pause`: 暂停恢复,默认选项,可通过`pg_wal_replay_resume`恢复。 + - `shutdown`: 自动关闭。 + - `promote`: 开始接受连接 + +例如在`2018-01-25 18:51:20` 创建了一个备份 + +```bash +$ psql postgres -c 'SELECT now();' + now +------------------------------ + 2018-01-25 18:51:20.34732+08 +(1 row) + + +[18:51:20] vonng@vonng-mac ~ +$ pg_basebackup -Fp -Pv -Xs -c fast -D /var/lib/pgsql/bkup +pg_basebackup: initiating base backup, waiting for checkpoint to complete +pg_basebackup: checkpoint completed +pg_basebackup: write-ahead log start point: 0/3000028 on timeline 1 +pg_basebackup: starting background WAL receiver +33007/33007 kB (100%), 1/1 tablespace +pg_basebackup: write-ahead log end point: 0/30000F8 +pg_basebackup: waiting for background process to finish streaming ... +pg_basebackup: base backup completed +``` + +之后运行了两分钟,到了`2018-01-25 18:53:05`我们发现有几条脏数据,于是从备份开始恢复,希望恢复到脏数据出现前一分钟的状态,例如`2018-01-25 18:52` + +可以这样配置 + +```bash +cat >> /var/lib/pgsql/bkup/recovery.conf <<- 'EOF' +restore_command = 'cp /var/lib/pgsql/wal/%f %p' +recovery_target_time = '2018-01-25 18:52:30' +recovery_target_action = 'promote' +EOF +``` + +当新的数据库实例完成恢复之后,可以看到它的状态确实回到了 18:52分,这正是我们期望的。 + +```bash +$ pg_ctl -D /var/lib/pgsql/bkup -o '-p 5433' start +waiting for server to start....2018-01-25 18:56:24.147 CST [13120] LOG: listening on IPv6 address "::1", port 5433 +2018-01-25 18:56:24.147 CST [13120] LOG: listening on IPv4 address "127.0.0.1", port 5433 +2018-01-25 18:56:24.148 CST [13120] LOG: listening on Unix socket "/tmp/.s.PGSQL.5433" +2018-01-25 18:56:24.162 CST [13121] LOG: database system was interrupted; last known up at 2018-01-25 18:51:22 CST +2018-01-25 18:56:24.197 CST [13121] LOG: starting point-in-time recovery to 2018-01-25 18:52:30+08 +2018-01-25 18:56:24.210 CST [13121] LOG: restored log file "000000010000000000000003" from archive +2018-01-25 18:56:24.215 CST [13121] LOG: redo starts at 0/3000028 +2018-01-25 18:56:24.215 CST [13121] LOG: consistent recovery state reached at 0/30000F8 +2018-01-25 18:56:24.216 CST [13120] LOG: database system is ready to accept read only connections + done +server started +2018-01-25 18:56:24.228 CST [13121] LOG: restored log file "000000010000000000000004" from archive +$ 2018-01-25 18:56:25.034 CST [13121] LOG: restored log file "000000010000000000000005" from archive +2018-01-25 18:56:25.853 CST [13121] LOG: restored log file "000000010000000000000006" from archive +2018-01-25 18:56:26.235 CST [13121] LOG: recovery stopping before commit of transaction 649, time 2018-01-25 18:52:30.492371+08 +2018-01-25 18:56:26.235 CST [13121] LOG: redo done at 0/67CFD40 +2018-01-25 18:56:26.235 CST [13121] LOG: last completed transaction was at log time 2018-01-25 18:52:29.425596+08 +cp: /var/lib/pgsql/wal/00000002.history: No such file or directory +2018-01-25 18:56:26.240 CST [13121] LOG: selected new timeline ID: 2 +cp: /var/lib/pgsql/wal/00000001.history: No such file or directory +2018-01-25 18:56:26.293 CST [13121] LOG: archive recovery complete +2018-01-25 18:56:26.401 CST [13120] LOG: database system is ready to accept connections +$ + +# query new server ,确实回到了18:52分 +$ psql postgres -p 5433 -c 'SELECT max(ts) FROM foobar;' + max +---------------------------- + 2018-01-25 18:52:29.413911 +(1 row) +``` + +### 3.7 时间线 + +每当归档文件恢复完成后,也就是服务器可以开始接受新的查询,写新的WAL的时候。会创建一个新的时间线用来区别新生成的WAL记录。WAL文件名由时间线和日志序号组成,因此新的时间线WAL不会覆盖老时间线的WAL。时间线主要用来解决复杂的恢复操作冲突,例如试想一个场景:刚才恢复到18:52分之后,新的服务器开始不断接受请求: + +```bash +psql postgres -c 'CREATE TABLE foobar(ts TIMESTAMP);' +for((i=0;i<1000;i++)) do + sleep 1 && \ + psql -p 5433 postgres -c 'INSERT INTO foobar SELECT now() FROM generate_series(1,10000)' && \ + psql -p 5433 postgres -c 'SELECT pg_current_wal_lsn() as current, pg_current_wal_insert_lsn() as insert, pg_current_wal_flush_lsn() as flush;' +done +``` + +可以看到,WAL归档目录中出现了两个`6`号WAL段文件,如果没有前面的时间线作为区分,WAL就会被覆盖。 + +```bash +$ ls -alh wal +total 262160 +drwxr-xr-x 12 vonng wheel 384B Jan 25 18:59 . +drwxr-xr-x 6 vonng wheel 192B Jan 25 18:51 .. +-rw------- 1 vonng wheel 16M Jan 25 18:51 000000010000000000000001 +-rw------- 1 vonng wheel 16M Jan 25 18:51 000000010000000000000002 +-rw------- 1 vonng wheel 16M Jan 25 18:51 000000010000000000000003 +-rw------- 1 vonng wheel 302B Jan 25 18:51 000000010000000000000003.00000028.backup +-rw------- 1 vonng wheel 16M Jan 25 18:51 000000010000000000000004 +-rw------- 1 vonng wheel 16M Jan 25 18:52 000000010000000000000005 +-rw------- 1 vonng wheel 16M Jan 25 18:52 000000010000000000000006 +-rw------- 1 vonng wheel 50B Jan 25 18:56 00000002.history +-rw------- 1 vonng wheel 16M Jan 25 18:58 000000020000000000000006 +-rw------- 1 vonng wheel 16M Jan 25 18:59 000000020000000000000007 +``` + +假设完成恢复之后又反悔了,则可以用基础备份通过指定`recovery_target_timeline = '1'` 再次恢复回第一次运行到18:53 时的状态。 + +### 3.8 其他注意事项 + +* 在Pg 10之前,哈希索引上的操作不会被记录在WAL中,需要在Slave上手工REINDEX。 +* 不要在创建基础备份的时候修改任何**模板数据库** +* 注意表空间会严格按照字面值记录其路径,如果使用了表空间,恢复时要非常小心。 + +​ + +## 4. 制作备机 + +通过主从(master-slave),可以同时提高可用性与可靠性。 + +- 主从读写分离提高性能:写请求落在Master上,通过WAL流复制传输到从库上,从库接受读请求。 +- 通过备份提高可靠性:当一台服务器故障时,可以立即由另一台顶上(promote slave or & make new slave) + +通常主从、副本、备机这些属于高可用的话题。但从另一个角度来讲,备机也是备份的一种。 + +#### 创建目录 + +```bash +sudo mkdir /var/lib/pgsql && sudo chown postgres:postgres /var/lib/pgsql/ +mkdir -p /var/lib/pgsql/master /var/lib/pgsql/slave /var/lib/pgsql/wal +``` + +#### 制作主库 + +```bash +pg_ctl -D /var/lib/pgsql/master init && pg_ctl -D /var/lib/pgsql/master start +``` + +#### 创建用户 + +创建备库需要一个具有`REPLICATION`权限的用户,这里在Master中创建`replication`用户 + +```bash +psql postgres -c 'CREATE USER replication REPLICATION;' +``` + +为了创建从库,需要一个具有`REPLICATION`权限的用户,并在`pg_hba`中允许访问,10中默认允许: + +```ini +local replication all trust +host replication all 127.0.0.1/32 trust +``` + +#### 制作备库 + +通过`pg_basebackup`创建一个slave实例。实际上是连接到Master实例,并复制一份数据目录到本地。 + +```bash +pg_basebackup -Fp -Pv -R -c fast -U replication -h localhost -D /var/lib/pgsql/slave +``` + +这里的关键是通过`-R` 选项,在备份的制作过程中自动将主机的连接信息填入`recovery.conf`,这样使用`pg_ctl `启动时,数据库会意识到自己是备机,并从主机自动拉取WAL追赶进度。 + +#### 启动从库 + +```bash +pg_ctl -D /var/lib/pgsql/slave -o "-p 5433" start +``` + +从库与主库的唯一区别在于,数据目录中多了一个`recovery.conf`文件。这个文件不仅仅可以用于标识从库的身份,而且在故障恢复时也需要用到。对于`pg_basebackup`构造的从库,它默认包含两个参数: + +```ini +standby_mode = 'on' +primary_conninfo = 'user=replication passfile=''/Users/vonng/.pgpass'' host=localhost port=5432 sslmode=prefer sslcompression=1 krbsrvname=postgres target_session_attrs=any' +``` + +`standby_mode`指明是否将PostgreSQL作为从库启动。 + +在备份时,`standby_mode`默认关闭,这样当所有的WAL拉取完毕后,就完成恢复,进入正常工作模式。 + +如果打开,那么数据库会意识到自己是备机,那么即使到达WAL末尾也不会停止,它会持续拉取主库的WAL,追赶主库的进度。 + +拉取WAL有两种办法,通过`primary_conninfo`流式复制拉取(9.0后的新特性,推荐,默认),或者通过`restore_command`来手工指明WAL的获取方式(老办法,恢复时使用)。 + +#### 查看状态 + +主库的所有从库可以通过系统视图`pg_stat_replication`查阅: + +```bash +$ psql postgres -tzxc 'SELECT * FROM pg_stat_replication;' +pid | 1947 +usesysid | 16384 +usename | replication +application_name | walreceiver +client_addr | ::1 +client_hostname | +client_port | 54124 +backend_start | 2018-01-25 13:24:57.029203+08 +backend_xmin | +state | streaming +sent_lsn | 0/5017F88 +write_lsn | 0/5017F88 +flush_lsn | 0/5017F88 +replay_lsn | 0/5017F88 +write_lag | +flush_lag | +replay_lag | +sync_priority | 0 +sync_state | async +``` + +检查主库和备库的状态可以使用函数`pg_is_in_recovery`,备库会处于恢复状态: + +```bash +$ psql postgres -Atzc 'SELECT pg_is_in_recovery()' && \ +psql postgres -p 5433 -Atzc 'SELECT pg_is_in_recovery()' +f +t +``` + +在主库中建表,从库也能看到。 + +```bash +psql postgres -c 'CREATE TABLE foobar(i INTEGER);' && psql postgres -p 5433 -c '\d' +``` + +在主库中插入数据,从库也能看到 + +```bash +psql postgres -c 'INSERT INTO foobar VALUES (1);' && \ +psql postgres -p 5433 -c 'SELECT * FROM foobar;' +``` + +现在主备已经配置就绪 \ No newline at end of file diff --git a/pg/pg-admin-config.md b/pg/pg-admin-config.md new file mode 100644 index 0000000..59945a6 --- /dev/null +++ b/pg/pg-admin-config.md @@ -0,0 +1,154 @@ +# Server Configuration + + + + +### 参数配置 + +* 查看配置的方法: + * 查看数据库目录下配置文件 + * 系统视图:`SELECT name,setting FROM pg_settings where name ~ 'xxx'';` + * 系统函数:`current_setting(setting_name [, missing_ok ])` + * SQL语句:`SHOW | ALL;` +* 修改配置的方法: + * 系统级修改:修改配置文件、执行`ALTER SYSTEM xxx`、启动时`-c`参数。 + * 数据库级别: `ALTER DATABASE` + * 会话级别:通过SET或`set_config(setting_name, new_value, false)`,更新pg_settings视图 + * 事务级别:通过SET或`set_config(setting_name, new_value, true)` + +* 生效配置的方法: + * 系统管理函数:`SELECT pg_reload_conf()` + * `pg_ctl reload`,或发送`SIGHUP` + * `/etc/init.d/postgresql-x.x reload`(el6) + * `systemctl reload service.postgresql-9.x` (el7) + + + +### 权限配置 + +* 在`postgresql.conf`中配置`listen_addresses`为`*`以允许外部连接。 +* 在`pg_hba.conf`中配置访问权限。hba是`Host based authentication` +* `pg_hba`的配置项为``构成的五元组,指明了: + * 什么样的连接方式:`local, host, hostssl, hostnossl` + * 什么样的数据库:`all, sameuser, samerole, replication, ` + * 什么样的用户:`all, , +` + * 什么样的地址:IP地址,CIDR地址,`0.0.0.0/0`表示所有机器。 + * 什么样的行为:`trust, reject, md5, password, ident, peer...` + + + +## 内核优化参数 + +```bash +optimize() { + if (( "$#" == 1 )); then + local datadir="$1"; shift + local mem="$(free \ + | awk '/Mem:/{print $2}')" + local swap="$(free \ + | awk '/Swap:/{print $2}')" + + cat > /etc/sysctl.conf <<- EOF + # Database kernel optimisation + fs.aio-max-nr = 1048576 + fs.file-max = 76724600 + kernel.sem = 4096 2147483647 2147483646 512000 + kernel.shmmax = $(( $mem * 1024 / 2 )) + kernel.shmall = $(( $mem / 5 )) + kernel.shmmni = 819200 + net.core.netdev_max_backlog = 10000 + net.core.rmem_default = 262144 + net.core.rmem_max = 4194304 + net.core.wmem_default = 262144 + net.core.wmem_max = 4194304 + net.core.somaxconn = 4096 + net.ipv4.tcp_max_syn_backlog = 4096 + net.ipv4.tcp_keepalive_intvl = 20 + net.ipv4.tcp_keepalive_probes = 3 + net.ipv4.tcp_keepalive_time = 60 + net.ipv4.tcp_mem = 8388608 12582912 16777216 + net.ipv4.tcp_fin_timeout = 5 + net.ipv4.tcp_synack_retries = 2 + net.ipv4.tcp_syncookies = 1 + net.ipv4.tcp_timestamps = 1 + net.ipv4.tcp_tw_recycle = 0 + net.ipv4.tcp_tw_reuse = 1 + net.ipv4.tcp_max_tw_buckets = 262144 + net.ipv4.tcp_rmem = 8192 87380 16777216 + net.ipv4.tcp_wmem = 8192 65536 16777216 + vm.dirty_background_bytes = 409600000 + net.ipv4.ip_local_port_range = 40000 65535 + vm.dirty_expire_centisecs = 6000 + vm.dirty_ratio = 80 + vm.dirty_writeback_centisecs = 50 + vm.extra_free_kbytes = 4096000 + vm.min_free_kbytes = 2097152 + vm.mmap_min_addr = 65536 + vm.swappiness = 0 + vm.overcommit_memory = 2 + vm.overcommit_ratio = $(( ( $mem - $swap ) * 100 / $mem )) + vm.zone_reclaim_mode = 0 + EOF + sysctl -p + + if ( ! type -f grubby &>/dev/null ); then + yum install -q -y grubby + fi + grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="numa=off transparent_hugepage=never" + + if [[ -x /opt/MegaRAID/MegaCli/MegaCli64 ]]; then + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp WB -LALL -aALL + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp ADRA -LALL -aALL + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp -DisDskCache -LALL -aALL + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp -Cached -LALL -aALL + fi + + if ( ! grep -q 'Database optimisation' /etc/rc.local ); then + cat >> /etc/rc.local <<- EOF + # Database optimisation + echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled + echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag + #blockdev --setra 16384 $(echo $(blkid | awk -F':' '$1!~"block"{print $1}')) + EOF + chmod +x /etc/rc.d/rc.local + fi + + cat > /etc/security/limits.d/postgresql.conf <<- EOF + postgres soft nproc 655360 + postgres hard nproc 655360 + postgres hard nofile 655360 + postgres soft nofile 655360 + postgres soft stack unlimited + postgres hard stack unlimited + postgres soft core unlimited + postgres hard core unlimited + postgres soft memlock 250000000 + postgres hard memlock 250000000 + EOF + cat > /etc/security/limits.d/pgbouncer.conf <<- EOF + pgbouncer soft nproc 655360 + pgbouncer hard nofile 655360 + pgbouncer soft nofile 655360 + pgbouncer soft stack unlimited + pgbouncer hard stack unlimited + pgbouncer soft core unlimited + pgbouncer hard core unlimited + pgbouncer soft memlock 250000000 + pgbouncer hard memlock 250000000 + EOF + cat > /etc/security/limits.d/pgpool.conf <<- EOF + pgpool soft nproc 655360 + pgpool hard nofile 655360 + pgpool soft nofile 655360 + pgpool soft stack unlimited + pgpool hard stack unlimited + pgpool soft core unlimited + pgpool hard core unlimited + pgpool soft memlock 250000000 + pgpool hard memlock 250000000 + EOF + fi +} + +``` + diff --git a/pg/pg-admin-ha.md b/pg/pg-admin-ha.md new file mode 100644 index 0000000..8306c75 --- /dev/null +++ b/pg/pg-admin-ha.md @@ -0,0 +1,207 @@ +--- +author: "Vonng" +description: "PostgreSQL 高可用" +categories: ["DBA"] +tags: ["PostgreSQL","HA"] +type: "post" +--- + + + +# PostgreSQL高可用 + +可以使多个数据库服务器协同工作,从而实现: + +* 高可用性(HA):若主服务器(master)失效,则允许从服务器(slave)快速接手它的任务 + +* 负载均衡(LB):允许多个服务器提供相同的数据 + + 只读的数据库服务器可以容易地协同服务。不幸的是,大部分数据库服务器收到的请求是读/写混合的,难以组合。 只读数据只需要在每台服务器上放置一次, 但对任意服务器的一次写动作,却必须被传播给所有的服务器, 才能保证一致性。 + +这种同步问题是服务器一起工作的最根本的困难。 因为没有单一解决方案能够消除该同步问题对所有用例的影响。有多种解决方案, 每一种方案都以一种不同的方式提出了这个问题, 并且对于一种特定的负载最小化了该问题所产生的影响。 + +某些方案通过只允许一台服务器修改数据来处理同步。 能修改数据的服务器被称为读/写、*主控*或*主要*服务器。 跟踪主控机中改变的服务器被称为*后备*或*从属*服务器。 如果一台后备服务器只有被提升为一台主控服务器后才能被连接, 它被称为一台*温后备*服务器, 而一台能够接受连接并且提供只读查询的后备服务器被称为一台 *热后备*服务器。 + +某些方案是同步的, 即一个数据修改事务只有到所有服务器都提交了该事务之后才被认为是提交成功。 这保证了一次故障转移不会丢失任何数据并且所有负载均衡的服务器将返回一致的结果 (不管哪台服务器被查询)。相反, 异步的方案允许在一次提交和它被传播到其他服务器之间有一些延迟, 这产生了切换到一个备份服务器时丢失某些事务的可能性, 并且负载均衡的服务器可能会返回略微陈旧的结果。当同步通信可能很慢时, 可以使用异步通信。 + +方案也可以按照它们的粒度进行分类。某些方案只能处理一整个数据库服务器, 而其他的允许在每个表或每个数据库的级别上进行控制。 + +在任何选择中,都必须考虑性能。通常在功能和性能之间都存在着权衡。例如, 在一个低速网络上的一种完全同步的方案可能使性能减少超过一半, 而一种异步的方案产生的性能影响可能是最小的。 + +本节的剩余部分列出了多种故障转移、复制和负载均衡方案。其中也有一个[术语表](http://www.postgres-r.org/documentation/terms)可用。 + + + +通过主从(master-slave),可以同时提高可用性与可靠性。 + +- 主从读写分离提高性能:写请求落在Master上,通过WAL流复制传输到从库上,从库接受读请求。 +- 通过备份提高可靠性:当一台服务器故障时,可以立即由另一台顶上(promote slave or & make new slave) + + + +## 准备环境 + +### 创建目录 + +```bash +sudo mkdir /var/lib/pgsql && sudo chown postgres:postgres /var/lib/pgsql/ +mkdir -p /var/lib/pgsql/master /var/lib/pgsql/slave /var/lib/pgsql/wal +``` + +### 制作主库 + +```bash +pg_ctl -D /var/lib/pgsql/master init && pg_ctl -D /var/lib/pgsql/master start +``` + +### 创建用户 + +创建备库需要一个具有`REPLICATION`权限的用户,这里在Master中创建`replication`用户 + +```bash +psql postgres -c 'CREATE USER replication REPLICATION;' +``` + +为了创建从库,需要一个具有`REPLICATION`权限的用户,并在`pg_hba`中允许访问,10中默认允许: + +```ini +local replication all trust +host replication all 127.0.0.1/32 trust +``` + +现在我们已经有了一个可用的主库。 + + + +## 制作备库 + +制作备库分为两步: + +- 有一个基础备份:拷贝数据目录,`pg_basebackup` +- 应用/追赶更新:拷贝并应用WAL,`primary_conninfo` 与`restore_command` + +### 拷贝数据库目录 + +通过`pg_basebackup`创建一个基础备份。基础备份实际上就是主库数据目录的一份物理拷贝,既可以放在安全的地方用于备用,也可以跑起来作为一个备库,分担主库的写压力,并提供冗余以提高可靠性。 + +```bash +pg_basebackup -Fp -Pv -R -c fast -U replication -h localhost -D /var/lib/pgsql/slave +``` + +启动从库,使用5433端口 + +```bash +pg_ctl -D /var/lib/pgsql/slave -o "-p 5433" start +``` + +从库与主库的唯一区别在于,数据目录中多了一个`recovery.conf`文件。这个文件不仅仅可以用于标识从库的身份,而且在故障恢复时也需要用到。对于`pg_basebackup`构造的从库,它默认包含两个参数: + +```ini +standby_mode = 'on' +primary_conninfo = 'user=replication passfile=''/Users/vonng/.pgpass'' host=localhost port=5432 sslmode=prefer sslcompression=1 krbsrvname=postgres target_session_attrs=any' +``` + +`standby_mode`指明是否将PostgreSQL作为从库启动。如果打开,那么从库会持续拉取WAL,即使已经到了WAL的最后位置也不会停止,实时保持和主库的同步。当制作备库时,需要设置`standby_mode=on` + +与此同时,还需要在`recovery.conf`中指明如何获取WAL。有两种方式: + +- 流式获取:通过`primary_conninfo`从一个主库流式拉取。这是9.0后的新特性,流式复制提供了更好的实时性,也更加便于使用。 +- 恢复命令:通过`restore_command`从指定位置不断复制并应用WAL日志,这是传统的方式,但实时性稍差(始终落后主库一个没写完的WAL文件)。但在主库挂了无法访问时,这种方式可以用于从归档的WAL中恢复。 + + + + + +## 检查状态 + +主库的所有从库可以通过系统视图`pg_stat_replication`查阅: + +```bash +$ psql postgres -tzxc 'SELECT * FROM pg_stat_replication;' +pid | 1947 +usesysid | 16384 +usename | replication +application_name | walreceiver +client_addr | ::1 +client_hostname | +client_port | 54124 +backend_start | 2018-01-25 13:24:57.029203+08 +backend_xmin | +state | streaming +sent_lsn | 0/5017F88 +write_lsn | 0/5017F88 +flush_lsn | 0/5017F88 +replay_lsn | 0/5017F88 +write_lag | +flush_lag | +replay_lag | +sync_priority | 0 +sync_state | async +``` + +检查主库和备库的状态可以使用函数`pg_is_in_recovery`,备库会处于恢复状态: + +```bash +$ psql postgres -Atzc 'SELECT pg_is_in_recovery()' && \ +psql postgres -p 5433 -Atzc 'SELECT pg_is_in_recovery()' +f +t +``` + +在主库中建表,从库也能看到。 + +```bash +psql postgres -c 'CREATE TABLE foobar(i INTEGER);' && psql postgres -p 5433 -c '\d' +``` + +在主库中插入数据,从库也能看到 + +```bash +psql postgres -c 'INSERT INTO foobar VALUES (1);' && \ +psql postgres -p 5433 -c 'SELECT * FROM foobar;' +``` + +现在主备已经配置就绪 + + + + + +## 制作备份 + + + + + +## 灾难恢复 + +在使用主备时,按照严重程度,灾难恢复分为三种情况: + +- 从库故障:重新制作一个从库。 +- 主库故障:晋升(promote)从库为主库,并通知应用方或修改连接池配置,可能导致短暂的不可用状态。 +- 主从都故障:利用基础备份和归档WAL制作新的主从。在此期间数据库不可用。 + + + +### 从库故障:制作备库 + +通常从库承载着只读流量,当从库故障时,读请求可以重新路由回主库。 + +但负载很大时,从库故障可能进一步导致主库压力过大发生故障,因此需要及时制作一个新的备库并启用。 + + + + + +### 主库故障:晋升从库 + +- 晋升主库为从库:`pg_ctl promote` +- 修复主库,将修复后的主库作为新的从库:`pg_rewind` +- 如果主库无法修复,可以立即制作新的从库:`pg_basebackup` + + + +### 主从故障 + +当主从都故障时,数据库进入不可用状态。可以通过基础备份与归档的WAL制作新的实例。 + diff --git a/pg/pg-admin-install.md b/pg/pg-admin-install.md new file mode 100644 index 0000000..ca89997 --- /dev/null +++ b/pg/pg-admin-install.md @@ -0,0 +1,924 @@ +--- +author: "Vonng" +description: "PostgreSQL安装方法" +categories: ["Dev"] +tags: ["PostgreSQL","Admin", "Install"] +type: "post" +--- + + + +# PostgreSQL Installation + + + +## 生产环境使用的方式 + +使用`pg_init.sh`脚本 + +通过`-P` 参数指定数据库角色名,`-V`指定数据库版本号,`-R`指定数据库的角色(master,slave) + +```bash +#!/bin/env bash + +# ######################################################################## +# PostgreSQL environment initialize program +# License: DBA +# Version: 1.1 +# Authors: panwenhang +# ######################################################################## + +declare -r PROGDIR="$(cd $(dirname $0) && pwd)" +declare -r PROGNAME="$(basename $0)" + +declare -x -r DIR_BASE='/export' +declare -x -r HOME='/home' + +export PATH=/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:$PATH + +# ########################################################## +# usage of this script +# ########################################################## +usage() { + cat <<- EOF + + Usage: $PROGNAME [options] + + You must execute this program with system superuser privilege (root)! + The product and dbversion must use together. + + OPTIONS: + -U, --superuser=username Database superuser + -P, --product=product product name + -B, --dbbase=dbbase base directory of postgresql + -R, --role=master master or slave + -V, --dbversion Database version + -h, --help usage of this program + + Example: + $PROGNAME -P test -V 9.6 + $PROGNAME -B /var/lib/pgsql/9.6 -V 9.6 + $PROGNAME -U dbsu -P test -R master -V 9.6 + + EOF +} + +# ########################################################## +# check execute user +# ########################################################## +check_exec_user() { + if [[ "$(whoami)" != "root" ]]; then + usage + exit -1 + fi +} + +# ########################################################## +# initialize superuser +# args: +# arg 1: superuser name +# ########################################################## +user_init() { + if (( "$#" == 1 )); then + local dbsu="$1"; shift + + if ( ! grep -q "$dbsu" /etc/group ); then + groupadd "$dbsu" + fi + + if ( ! grep -q "$dbsu" /etc/passwd ); then + useradd -d "$HOME"/"$dbsu" -g "$dbsu" "$dbsu" + fi + + if ( ! grep -q "$dbsu" /etc/passwd ) && ( ! grep -q "$dbsu" /etc/sudoers ); then + chmod u+w /etc/sudoers + echo "$dbsu ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + fi + + return 0 + else + return 1 + fi +} + +# ########################################################## +# initialize directory +# args: +# arg 1: superuser name +# arg 2: databse base directory +# ########################################################## +dir_init() { + if (( "$#" == 2 )); then + local dbsu="$1"; shift + local datadir="$1"; shift + + mkdir -p "$datadir"/{data,backup,rbackup,arcxlog,conf,scripts} + chown -R "$dbsu":"$dbsu" "$datadir" + chmod 0700 "$datadir"/data + + return 0 + else + return 1 + fi +} + +# ########################################################## +# install package of postgresql +# args: +# arg 1: postgresql version +# ########################################################## +pg_install() { + if (( "$#" == 1 )); then + local dbversion="$1"; shift + local major_version="${dbversion:0:3}" + local short_version="$(echo $dbversion \ + | awk -F'.' '{print $1$2}')" + local rpm_base='' + local os_release='' + + if ( grep -q 'CentOS release 6' /etc/redhat-release ); then + rpm_base="http://yum.postgresql.org/$major_version/redhat/rhel-6Server-$(uname -m)" + os_release="rhel6" + elif ( grep -q 'CentOS Linux release 7' /etc/redhat-release ); then + rpm_base="http://yum.postgresql.org/$major_version/redhat/rhel-7Server-$(uname -m)" + os_release="rhel7" + fi + + yum clean all && yum install -q -y epel-release + yum install -q -y tcl perl-ExtUtils-Embed libxml2 libxslt uuid readline lz4 nc + yum install -q -y "$rpm_base"/pgdg-centos"$short_version"-"$major_version"-1.noarch.rpm + yum install -q -y "$rpm_base"/pgdg-centos"$short_version"-"$major_version"-2.noarch.rpm + yum install -q -y "$rpm_base"/pgdg-centos"$short_version"-"$major_version"-3.noarch.rpm + + yum install -q -y postgresql"$short_version" postgresql"$short_version"-libs postgresql"$short_version"-server postgresql"$short_version"-contrib postgresql"$short_version"-devel postgresql"$short_version"-debuginfo + + yum install -q -y pgbouncer pgpool-II-"$short_version" pg_top"$short_version" postgis2_"$short_version" postgis2_"$short_version"-client pg_repack"$short_version" + + rm -f /usr/pgsql + ln -sf /usr/pgsql-"$major_version" /usr/pgsql + echo 'export PATH=/usr/pgsql/bin:$PATH' > /etc/profile.d/pgsql.sh + fi +} + +# ########################################################## +# install package of postgresql for custom +# args: +# arg 1: postgresql version +# ########################################################## +pg_install_custom() { + if (( "$#" == 1 )); then + local dbversion="$1"; shift + local major_version="${dbversion:0:3}" + local short_version="$(echo $dbversion \ + | awk -F'.' '{print $1$2}')" + local rpm_base='http://download.postgresql.com/packages/RPMS/' + local os_release='' + + if ( grep -q 'CentOS release 6' /etc/redhat-release ); then + os_release="el6" + elif ( grep -q 'CentOS Linux release 7' /etc/redhat-release ); then + os_release="el7" + fi + + if ( ! rpm --quiet -q postgresql-"$dbversion"-1."$os_release"."$(uname -m)" ); then + yum install -q -y tcl perl-ExtUtils-Embed libxml2 libxslt uuid readline + rpm -ivh --force "$rpm_base"/"$(uname -m)"/postgresql-"$dbversion"-1."$os_release"."$(uname -m)".rpm + fi + fi +} + +# ########################################################## +# postgresql shared xlog archive directory +# args: +# arg 1: postgresql base directory +# ########################################################## +shared_xlog() { + if (( "$#" == 1 )); then + local datadir="$1"; shift + yum install -y -q nfs-utils + echo "$datadir/arcxlog 10.191.0.0/16(rw)" > /etc/exports + service nfs start + fi +} + +# ########################################################## +# postgresql optimize +# args: +# arg 1: postgresql base directory +# ########################################################## +optimize() { + if (( "$#" == 1 )); then + local datadir="$1"; shift + local mem="$(free \ + | awk '/Mem:/{print $2}')" + local swap="$(free \ + | awk '/Swap:/{print $2}')" + + cat > /etc/sysctl.conf <<- EOF + # Database kernel optimisation + fs.aio-max-nr = 1048576 + fs.file-max = 76724600 + kernel.sem = 4096 2147483647 2147483646 512000 + kernel.shmmax = $(( $mem * 1024 / 2 )) + kernel.shmall = $(( $mem / 5 )) + kernel.shmmni = 819200 + net.core.netdev_max_backlog = 10000 + net.core.rmem_default = 262144 + net.core.rmem_max = 4194304 + net.core.wmem_default = 262144 + net.core.wmem_max = 4194304 + net.core.somaxconn = 4096 + net.ipv4.tcp_max_syn_backlog = 4096 + net.ipv4.tcp_keepalive_intvl = 20 + net.ipv4.tcp_keepalive_probes = 3 + net.ipv4.tcp_keepalive_time = 60 + net.ipv4.tcp_mem = 8388608 12582912 16777216 + net.ipv4.tcp_fin_timeout = 5 + net.ipv4.tcp_synack_retries = 2 + net.ipv4.tcp_syncookies = 1 + net.ipv4.tcp_timestamps = 1 + net.ipv4.tcp_tw_recycle = 0 + net.ipv4.tcp_tw_reuse = 1 + net.ipv4.tcp_max_tw_buckets = 262144 + net.ipv4.tcp_rmem = 8192 87380 16777216 + net.ipv4.tcp_wmem = 8192 65536 16777216 + vm.dirty_background_bytes = 409600000 + net.ipv4.ip_local_port_range = 40000 65535 + vm.dirty_expire_centisecs = 6000 + vm.dirty_ratio = 80 + vm.dirty_writeback_centisecs = 50 + vm.extra_free_kbytes = 4096000 + vm.min_free_kbytes = 2097152 + vm.mmap_min_addr = 65536 + vm.swappiness = 0 + vm.overcommit_memory = 2 + vm.overcommit_ratio = $(( ( $mem - $swap ) * 100 / $mem )) + vm.zone_reclaim_mode = 0 + EOF + sysctl -p + + if ( ! type -f grubby &>/dev/null ); then + yum install -q -y grubby + fi + grubby --update-kernel=/boot/vmlinuz-$(uname -r) --args="numa=off transparent_hugepage=never" + + if [[ -x /opt/MegaRAID/MegaCli/MegaCli64 ]]; then + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp WB -LALL -aALL + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp ADRA -LALL -aALL + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp -DisDskCache -LALL -aALL + /opt/MegaRAID/MegaCli/MegaCli64 -LDSetProp -Cached -LALL -aALL + fi + + if ( ! grep -q 'Database optimisation' /etc/rc.local ); then + cat >> /etc/rc.local <<- EOF + # Database optimisation + echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled + echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag + #blockdev --setra 16384 $(echo $(blkid | awk -F':' '$1!~"block"{print $1}')) + EOF + chmod +x /etc/rc.d/rc.local + fi + + cat > /etc/security/limits.d/postgresql.conf <<- EOF + postgres soft nproc 655360 + postgres hard nproc 655360 + postgres hard nofile 655360 + postgres soft nofile 655360 + postgres soft stack unlimited + postgres hard stack unlimited + postgres soft core unlimited + postgres hard core unlimited + postgres soft memlock 250000000 + postgres hard memlock 250000000 + EOF + cat > /etc/security/limits.d/pgbouncer.conf <<- EOF + pgbouncer soft nproc 655360 + pgbouncer hard nofile 655360 + pgbouncer soft nofile 655360 + pgbouncer soft stack unlimited + pgbouncer hard stack unlimited + pgbouncer soft core unlimited + pgbouncer hard core unlimited + pgbouncer soft memlock 250000000 + pgbouncer hard memlock 250000000 + EOF + cat > /etc/security/limits.d/pgpool.conf <<- EOF + pgpool soft nproc 655360 + pgpool hard nofile 655360 + pgpool soft nofile 655360 + pgpool soft stack unlimited + pgpool hard stack unlimited + pgpool soft core unlimited + pgpool hard core unlimited + pgpool soft memlock 250000000 + pgpool hard memlock 250000000 + EOF + fi +} + +# ########################################################## +# postgresql config file +# args: +# arg 1: postgresql superuser +# arg 2: postgresql base directory +# arg 3: postgresql short version +# ########################################################## +pg_conf_init() { + if (( "$#" == 3 )); then + local dbsu="$1"; shift + local datadir="$1"; shift + local short_version="$1"; shift + + cat > "$datadir"/conf/pg_hba.conf <<- EOF + host all $dbsu 0.0.0.0/0 reject + host monitor monitordb 0.0.0.0/0 reject + local all all md5 + host replication all 0.0.0.0/0 md5 + host all all 0.0.0.0/0 md5 + EOF + + cat > "$datadir"/conf/recovery.conf <<- EOF + standby_mode = 'on' + primary_conninfo = 'host=localhost port=5432 user=postgres password=password application_name=$(hostname)' + ###restore_command = '/bin/cp -n $datadir/arcxlog/%f %p' + ###restore_command = 'arcxlog=$datadir/arcxlog; /usr/bin/test -f \$arcxlog/\$(date +%Y%m%d)/%f.zip && unzip -o \$arcxlog/\$(date +%Y%m%d)/%f.zip' + ###restore_command = 'arcxlog=$datadir/arcxlog; /usr/bin/test -f \$arcxlog/\$(date +%Y%m%d)/%f.lz4 && lz4 -q -d \$arcxlog/\$(date +%Y%m%d)/%f.lz4 %p' + recovery_target_timeline = 'latest' + EOF + + chown -R "$dbsu":"$dbsu" "$datadir" + fi +} + +# ########################################################## +# postgresql initdb +# args: +# arg 1: postgresql base directory +# arg 2: postgresql superuser +# ########################################################## +pg_initdb() { + if (( "$#" == 2 )); then + local datadir="$1"; shift + local dbsu="$1"; shift + + chown -R "$dbsu":"$dbsu" "$datadir" + chmod 0700 "$datadir"/data + if [[ "$( ls $datadir/data | wc -l )" == "0" ]]; then + su - "$dbsu" sh -c "source /etc/profile; initdb -D $datadir/data" + su - "$dbsu" sh -c "/bin/cp -a $datadir/data/postgresql.conf $datadir/data/postgresql.conf.bak" + su - "$dbsu" sh -c "/bin/cp -a $datadir/conf/postgresql.conf $datadir/data/postgresql.conf" + su - "$dbsu" sh -c "/bin/cp -a $datadir/data/pg_hba.conf $datadir/data/pg_hba.conf.bak" + su - "$dbsu" sh -c "/bin/cp -a $datadir/conf/pg_hba.conf $datadir/data/pg_hba.conf" + fi + fi +} + +main() { + local product_name='test' + local dbtype='postgresql' + local db_version='9.6.0' + local major_version='9.6' + local short_version='96' + local superuser='postgres' + local dbbase="" + local role="slave" + + check_exec_user + + while (( "$#" >= 0 )); do + case "$1" in + -U|--superuser=*) + if [[ "$1" == "-U" ]]; then + shift + fi + superuser="${1##*=}" + shift + ;; + -P|--product=*) + if [[ "$1" == "-P" ]]; then + shift + fi + product_name="${1##*=}" + shift + ;; + -B|--dbbase=*) + if [[ "$1" == "-B" ]]; then + shift + fi + dbbase="${1##*=}" + shift + ;; + -R|--role=*) + if [[ "$1" == "-R" ]]; then + shift + fi + role="${1##*=}" + shift + ;; + -V|--dbversion=*) + if [[ "$1" == "-V" ]]; then + shift + fi + db_version="${1##*=}" + major_version="${db_version:0:3}" + short_version="$(echo $db_version | awk -F'.' '{print $1$2}')" + shift + ;; + -h|--help) + usage + exit + ;; + *) + break + ;; + esac + done + + dbtype="postgresql" + + if [[ -z "$dbbase" ]]; then + if [[ "$product_name" != "" ]] && [[ "$short_version" != "" ]]; then + dbbase="$DIR_BASE/$dbtype/${product_name}_${short_version}" + else + dbbase="$DIR_BASE/$dbtype" + fi + fi + + user_init "$superuser" + dir_init "$superuser" "$dbbase" + + pg_install "$db_version" + pg_conf_init "$superuser" "$dbbase" "$short_version" + + if [[ "$role" == "master" ]]; then + shared_xlog "$dbbase" + pg_initdb "$dbbase" "$superuser" + fi + + optimize "$dbbase" +} + +main "$@" + +``` + + + +## 从源码编译安装 + +```bash +./configure +make +su +make install +adduser postgres +mkdir /usr/local/pgsql/data +chown postgres /usr/local/pgsql/data +su - postgres +/usr/local/pgsql/bin/initdb -D /usr/local/pgsql/data +/usr/local/pgsql/bin/postgres -D /usr/local/pgsql/data >logfile 2>&1 & +/usr/local/pgsql/bin/createdb test +/usr/local/pgsql/bin/psql test +``` + + + + + +## 编译参数 + +# [16.4. 安装过程]() + +1. ​ + + **配置** + + 安装过程的第一步就是为你的系统配置源代码树并选择你喜欢的选项。这个工作是通过运行`configure`脚本实现的,对于默认安装,你只需要简单地输入: + + ``` + ./configure + ``` + + 该脚本将运行一些测试来决定一些系统相关的变量, 并检测你的操作系统的特殊设置,并且最后将在编译树中创建一些文件以记录它找到了什么。如果你想保持编译目录的独立,你也可以在一个源码树之外的目录中运行`configure` 。这个过程也被称为一个*VPATH*编译。做法如下: + + ``` + mkdir build_dir + cd build_dir + /path/to/source/tree/configure [options go here] + make + ``` + + ​ + + 默认设置将编译服务器和辅助程序,还有只需要 C 编译器的所有客户端程序和接口。默认时所有文件都将安装到`/usr/local/pgsql`。 + + 你可以通过给出下面的`configure`命令行选项中的一个或更多的选项来自定义编译和安装过程: + + ​ + + ​ + + - `--prefix=*PREFIX*` + + 把所有文件装在目录`*PREFIX*`中而不是`/usr/local/pgsql`中。 实际的文件会安装到数个子目录中;没有一个文件会直接安装到`*PREFIX*`目录里。如果你有特殊需要,你还可以用下面的选项自定义不同的子目录的位置。 不过,如果你把这些设置保留默认,那么安装将是可重定位的,意思是你可以在安装过后移动目录(`man`和`doc`位置不受此影响)。对于可重定位的安装,你可能需要使用`configure`的`--disable-rpath`选项。 还有,你需要告诉操作系统如何找到共享库。 + + - `--exec-prefix=*EXEC-PREFIX*` + + 你可以把体系相关的文件安装到一个不同的前缀下(`*EXEC-PREFIX*`),而不是`*PREFIX*`中设置的地方。 这样做可以比较方便地在不同主机之间共享体系相关的文件。 如果你省略这些,那么`*EXEC-PREFIX*`就会被设置为等于 `*PREFIX*`并且体系相关和体系无关的文件都会安装到同一棵目录树下,这也可能是你想要的。 + + - `--bindir=*DIRECTORY*` + + 为可执行程序指定目录。默认是`*EXEC-PREFIX*/bin`, 通常也就是`/usr/local/pgsql/bin`。 + + - `--sysconfdir=*DIRECTORY*` + + 用于各种各样配置文件的目录,默认为`*PREFIX*/etc`。 + + - `--libdir=*DIRECTORY*` + + 设置安装库和动态装载模块的目录。默认是`*EXEC-PREFIX*/lib`。 + + - `--includedir=*DIRECTORY*` + + C 和 C++ 头文件的目录。默认是`*PREFIX*/include`。 + + - `--datarootdir=*DIRECTORY*` + + 设置多种只读数据文件的根目录。这只为后面的某些选项设置默认值。默认值为`*PREFIX*/share`。 + + - `--datadir=*DIRECTORY*` + + 设置被安装的程序使用的只读数据文件的目录。默认值为`*DATAROOTDIR*`。注意这不会对你的数据库文件被放置的位置产生任何影响。 + + - `--localedir=*DIRECTORY*` + + 设置安装区域数据的目录,特别是消息翻译目录文件。默认值为`*DATAROOTDIR*/locale`。 + + - `--mandir=*DIRECTORY*` + + PostgreSQL自带的手册页将安装到这个目录,它们被安装在相应的`man*x*`子目录里。 默认是`*DATAROOTDIR*/man`。 + + - `--docdir=*DIRECTORY*` + + 设置安装文档文件的根目录,"man"页不包含在内。这只为后续选项设置默认值。这个选项的默认值为`*DATAROOTDIR*/doc/postgresql`。 + + - `--htmldir=*DIRECTORY*` + + PostgreSQL的HTML格式的文档将被安装在这个目录中。默认值为`*DATAROOTDIR*`。 + + ​ + + > **注意: **为了让PostgreSQL能够安装在一些共享的安装位置(例如`/usr/local/include`), 同时又不至于和系统其它部分产生名字空间干扰,我们特别做了一些处理。 首先,安装脚本会自动给`datadir`、`sysconfdir`和`docdir`后面附加上"`/postgresql`"字符串, 除非展开的完整路径名已经包含字符串"`postgres`"或者"`pgsql`"。 例如,如果你选择`/usr/local`作为前缀, 那么文档将安装在`/usr/local/doc/postgresql`,但如果前缀是`/opt/postgres`, 那么它将被放到`/opt/postgres/doc`。客户接口的公共 C 头文件安装到了`includedir`,并且是名字空间无关的。内部的头文件和服务器头文件都安装在`includedir`下的私有目录中。参考每种接口的文档获取关于如何访问头文件的信息。最后,如果合适,那么也会在`libdir`下创建一个私有的子目录用于动态可装载的模块。 + + ​ + + ​ + + ​ + + ​ + + - `--with-extra-version=*STRING*` + + 把`*STRING*`追加到 PostgreSQL 版本号。例如,你可以使用它来标记从未发布的 Git 快照或者包含定制补丁(带有一个如`git describe`标识符之类的额外版本号或者一个分发包发行号)创建的二进制文件。 + + - `--with-includes=*DIRECTORIES*` + + `*DIRECTORIES*`是一个冒号分隔的目录列表,这些目录将被加入编译器的头文件搜索列表中。 如果你有一些可选的包(例如 GNU Readline)安装在非标准位置, 你就必须使用这个选项,以及可能还有相应的 `--with-libraries`选项。例子:`--with-includes=/opt/gnu/include:/usr/sup/include`. + + - `--with-libraries=*DIRECTORIES*` + + `*DIRECTORIES*`是一个冒号分隔的目录列表,这些目录是用于查找库文件的。 如果你有一些包安装在非标准位置,你可能就需要使用这个选项(以及对应的`--with-includes`选项)。例子:`--with-libraries=/opt/gnu/lib:/usr/sup/lib`. + + - `--enable-nls[=*LANGUAGES*]` + + 打开本地语言支持(NLS),也就是以非英文显示程序消息的能力。`*LANGUAGES*`是一个空格分隔的语言代码列表, 表示你想支持的语言。例如`--enable-nls='de fr'` (你提供的列表和实际支持的列表之间的交集将会自动计算出来)。如果你没有声明一个列表,那么就会安装所有可用的翻译。要使用这个选项,你需要一个Gettext API 的实现。见上文。 + + - `--with-pgport=*NUMBER*` + + 把`*NUMBER*`设置为服务器和客户端的默认端口。默认是 5432。 这个端口可以在以后修改,不过如果你在这里声明,那么服务器和客户端将有相同的编译好了的默认值。这样会非常方便些。 通常选取一个非默认值的理由是你企图在同一台机器上运行多个PostgreSQL服务器。 + + - `--with-perl` + + 制作PL/Perl服务器端编程语言。 + + - `--with-python` + + 制作PL/Python服务器端编程语言。 + + - `--with-tcl` + + 制作PL/Tcl服务器编程语言。 + + - `--with-tclconfig=*DIRECTORY*` + + Tcl 安装文件`tclConfig.sh`,其中里面包含编译与 Tcl 接口的模块的配置信息。该文件通常可以自动地在一个众所周知的位置找到,但是如果你需要一个不同版本的 Tcl,你也可以指定可以找到它的目录。 + + - `--with-gssapi` + + 编译 GSSAPI 认证支持。在很多系统上,GSSAPI(通常是 Kerberos 安装的一部分)系统不会被安装在默认搜索位置(例如`/usr/include`、`/usr/lib`),因此你必须使用选项`--with-includes`和`--with-libraries`来配合该选项。`configure`将会检查所需的头文件和库以确保你的 GSSAPI 安装足以让配置继续下去。 + + - `--with-krb-srvnam=*NAME*` + + 默认的 Kerberos 服务主的名称(也被 GSSAPI 使用)。默认是`postgres`。通常没有理由改变这个值,除非你是一个 Windows 环境,这种情况下该名称必须被设置为大写形式`POSTGRES`。 + + - `--with-openssl` + + 编译SSL(加密)连接支持。这个选项需要安装OpenSSL包。`configure`将会检查所需的头文件和库以确保你的 OpenSSL安装足以让配置继续下去。 + + - `--with-pam` + + 编译PAM(可插拔认证模块)支持。 + + - `--with-bsd-auth` + + 编译 BSD 认证支持(BSD 认证框架目前只在 OpenBSD 上可用)。 + + - `--with-ldap` + + 为认证和连接参数查找编译LDAP支持(详见[第 32.17 节](http://www.postgres.cn/docs/9.6/libpq-ldap.html)和[第 20.3.7 节](http://www.postgres.cn/docs/9.6/auth-methods.html#AUTH-LDAP))。在 Unix 上,这需要安装OpenLDAP包。在 Windows 上将使用默认的WinLDAP库。`configure`将会检查所需的头文件和库以确保你的 OpenLDAP安装足以让配置继续下去。 + + - `--with-systemd` + + 编译对systemd 服务通知的支持。如果服务器是在systemd 机制下被启动,这可以提高集成度,否则不会有影响 ; see [第 18.3 节](http://www.postgres.cn/docs/9.6/server-start.html) for more information。要使用这个选项,必须安装libsystemd 以及相关的头文件。 + + - `--without-readline` + + 避免使用Readline库(以及libedit)。这个选项禁用了psql中的命令行编辑和历史, 因此我们不建议这么做。 + + - `--with-libedit-preferred` + + 更倾向于使用BSD许可证的libedit库而不是GPL许可证的Readline。这个选项只有在你同时安装了两个库时才有意义,在那种情况下默认会使用Readline。 + + - `--with-bonjour` + + 编译 Bonjour 支持。这要求你的操作系统支持 Bonjour。在 OS X 上建议使用。 + + - `--with-uuid=*LIBRARY*` + + 使用指定的 UUID 库编译 [uuid-ossp](http://www.postgres.cn/docs/9.6/uuid-ossp.html)模块(提供生成 UUID 的函数)。 `*LIBRARY*`必须是下列之一:`bsd`,用来使用 FreeBSD、NetBSD 和一些其他 BSD 衍生系统 中的 UUID 函数`e2fs`,用来使用`e2fsprogs`项目创建的 UUID 库, 这个库出现在大部分的 Linux 系统和 OS X 中,并且也能找到用于其他平台的 版本`ossp`,用来使用[OSSP UUID library](http://www.ossp.org/pkg/lib/uuid/) + + - `--with-ossp-uuid` + + `--with-uuid=ossp`的已废弃的等效选项。 + + - `--with-libxml` + + 编译 libxml (启用 SQL/XML 支持)。这个特性需要 Libxml 版本 2.6.23 及以上。Libxml 会安装一个程序`xml2-config`,它可以被用来检测所需的编译器和链接器选项。如果能找到,PostgreSQL 将自动使用它。要制定一个非常用的 libxml 安装位置,你可以设置环境变量`XML2_CONFIG`指向`xml2-config`程序所属的安装,或者使用选项`--with-includes`和`--with-libraries`。 + + - `--with-libxslt` + + 编译 [xml2](http://www.postgres.cn/docs/9.6/xml2.html)模块时使用 libxslt。xml2依赖这个库来执行XML的XSL转换。 + + - `--disable-integer-datetimes` + + 禁用对时间戳和间隔的64位存储支持,并且将 datetime 值存储为浮点数。浮点 datetime 存储在PostgreSQL 8.4之前是默认方式,但是现在已经被废弃,因为它对于`timestamp`值的全范围不支持毫秒精度。但是,基于整数的 datetime 存储要求64位整数类型。因此,当没有64位整数类型时,可以使用这个选项,或者在兼容为PostgreSQL之前版本开发的应用时使用。详见 [第 8.5 节](http://www.postgres.cn/docs/9.6/datatype-datetime.html)。 + + - `--disable-float4-byval` + + 禁用 float4 值的"传值",导致它们只能被"传引用"。这个选项会损失性能,但是在需要兼容使用 C 编写并使用"version 0"调用规范的老用户定义函数时可能需要这个选项。更好的长久解决方案是将任何这样的函数更新成使用"version 1"调用规范。 + + - `--disable-float8-byval` + + 禁用 float8 值的"传值",导致它们只能被"传引用"。这个选项会损失性能,但是在需要兼容使用 C 编写并使用"version 0"调用规范的老用户定义函数时可能需要这个选项。更好的长久解决方案是将任何这样的函数更新成使用"version 1"调用规范。注意这个选项并非只影响 float8,它还影响 int8 和某些相关类型如时间戳。在32位平台上,`--disable-float8-byval`是默认选项并且不允许选择`--enable-float8-byval`。 + + - `--with-segsize=*SEGSIZE*` + + 设置*段尺寸*,以 G 字节计。大型的表会被分解成多个操作系统文件,每一个的尺寸等于段尺寸。这避免了与操作系统对文件大小限制相关的问题。默认的段尺寸(1G字节)在所有支持的平台上都是安全的。如果你的操作系统有"largefile"支持(如今大部分都支持),你可以使用一个更大的段尺寸。这可以有助于在使用非常大的表时消耗的文件描述符数目。但是要当心不能选择一个超过你将使用的平台和文件系统所支持尺寸的值。你可能希望使用的其他工具(如tar)也可以对可用文件尺寸设限。如非绝对必要,我们推荐这个值应为2的幂。注意改变这个值需要一次 initdb。 + + - `--with-blocksize=*BLOCKSIZE*` + + 设置*块尺寸*,以 K 字节计。这是表内存储和I/O的单位。默认值(8K字节)适合于大多数情况,但是在特殊情况下可能其他值更有用。这个值必须是2的幂并且在 1 和 32 (K字节)之间。注意修改这个值需要一次 initdb。 + + - `--with-wal-segsize=*SEGSIZE*` + + 设置*WAL 段尺寸*,以 M 字节计。这是 WAL 日志中每一个独立文件的尺寸。调整这个值来控制传送 WAL 日志的粒度非常有用。默认尺寸为 16 M字节。这个值必须是2的幂并且在 1 到 64 (M字节)之间。注意修改这个值需要一次 initdb。 + + - `--with-wal-blocksize=*BLOCKSIZE*` + + 设置*WAL 块尺寸*,以 K 字节计。这是 WAL 日志存储和I/O的单位。默认值(8K 字节)适合于大多数情况,但是在特殊情况下其他值更好有用。这个值必须是2的幂并且在 1 到 64(K字节)之间。注意修改这个值需要一次 initdb。 + + - `--disable-spinlocks` + + 即便PostgreSQL对于该平台没有 CPU 自旋锁支持,也允许编译成功。自旋锁支持的缺乏会导致较差的性能,因此这个选项只有当编译终端或者通知你该平台缺乏自旋锁支持时才应被使用。如果在你的平台上要求使用该选项来编译PostgreSQL,请将此问题报告给PostgreSQL的开发者。 + + - `--disable-thread-safety` + + 禁用客户端库的线程安全性。这会阻止libpq和ECPG程序中的并发线程安全地控制它们私有的连接句柄。 + + - `--with-system-tzdata=*DIRECTORY*` + + PostgreSQL包含它自己的时区数据库,它被用于日期和时间操作。这个时区数据库实际上是和 IANA 时区数据库相兼容的,后者在很多操作系统如 FreeBSD、Linux和Solaris上都有提供,因此再次安装它可能是冗余的。当这个选项被使用时,将不会使用`*DIRECTORY*`中系统提供的时区数据库,而是使用包括在 PostgreSQL 源码发布中的时区数据库。`*DIRECTORY*`必须被指定为一个绝对路径。`/usr/share/zoneinfo`在某些操作系统上是一个很有可能的路径。注意安装例程将不会检测不匹配或错误的时区数据。如果你使用这个选项,建议你运行回归测试来验证你指定的时区数据能正常地工作在PostgreSQL中。这个选项主要针对那些很了解他们的目标操作系统的二进制包发布者。使用这个选项主要优点是不管何时当众多本地夏令时规则之一改变时, PostgreSQL 包不需要被升级。另一个优点是如果时区数据库文件在安装时不需要被编译, PostgreSQL 可以被更直接地交叉编译。 + + - `--without-zlib` + + 避免使用Zlib库。这样就禁用了pg_dump和 pg_restore中对压缩归档的支持。这个选项只适用于那些没有这个库的少见的系统。 + + - `--enable-debug` + + 把所有程序和库以带有调试符号的方式编译。这意味着你可以通过一个调试器运行程序来分析问题。 这样做显著增大了最后安装的可执行文件的大小,并且在非 GCC 的编译器上,这么做通常还要关闭编译器优化, 这些都导致速度的下降。但是,如果有这些符号的话,就可以非常有效地帮助定位可能发生问题的位置。目前,我们只是在你使用 GCC 的情况下才建议在生产安装中使用这个选项。但是如果你正在进行开发工作,或者正在使用 beta 版本,那么你就应该总是打开它。 + + - `--enable-coverage` + + 如果在使用 GCC,所有程序和库都会用代码覆盖率测试工具编译。在运行时,它们会在编译目录中生成代码覆盖率度量的文件。详见[第 31.5 节](http://www.postgres.cn/docs/9.6/regress-coverage.html)。这个选项只用于 GCC 以及做开发工作时。 + + - `--enable-profiling` + + 如果在使用 GCC,所有程序和库都被编译成可以进行性能分析。在后端退出时,将会创建一个子目录,其中包含用于性能分析的`gmon.out`文件。这个选项只用于 GCC 和做开发工作时。 + + - `--enable-cassert` + + 打开在服务器中的*assertion*检查, 它会检查许多"不可能发生"的条件。它对于代码开发的用途而言是无价之宝, 不过这些测试可能会显著地降低服务器的速度。并且,打开这个测试不会提高你的系统的稳定性! 这些断言检查并不是按照严重性分类的,因此一些相对无害的小故障也可能导致服务器重启 — 只要它触发了一次断言失败。 目前,我们不推荐在生产环境中使用这个选项,但是如果你在做开发或者在使用 beta 版本的时候应该打开它。 + + - `--enable-depend` + + 打开自动倚赖性跟踪。如果打开这个选项,那么制作文件(makefile)将设置为在任何头文件被修改的时候都将重新编译所有受影响的目标文件。 如果你在做开发的工作,那么这个选项很有用,但是如果你只是想编译一次并且安装,那么这就是浪费时间。 目前,这个选项只对 GCC 有用。 + + - `--enable-dtrace` + + 为PostgreSQL编译对动态跟踪工具 DTrace 的支持。 详见[第 28.5 节](http://www.postgres.cn/docs/9.6/dynamic-trace.html)。要指向`dtrace`程序,必须设置环境变量`DTRACE`。这通常是必需的,因为`dtrace`通常被安装在`/usr/sbin`中,该路径可能不在搜索路径中。`dtrace`程序的附加命令行选项可以在环境变量`DTRACEFLAGS`中指定。在 Solaris 上,要在一个64位二进制中包括 DTrace,你必须为 configure 指定`DTRACEFLAGS="-64"`。例如,使用 GCC 编译器:`./configure CC='gcc -m64' --enable-dtrace DTRACEFLAGS='-64' ...`使用 Sun 的编译器:`./configure CC='/opt/SUNWspro/bin/cc -xtarget=native64' --enable-dtrace DTRACEFLAGS='-64' ...` + + - `--enable-tap-tests` + + 启用 Perl TAP 工具进行测试。这要求安装了 Perl 以及 Perl 模块`IPC::Run`。 详见[第 31.4 节](http://www.postgres.cn/docs/9.6/regress-tap.html)。 + + ​ + + 如果你喜欢用那些和`configure`选取的不同的 C 编译器,那么你可以你的环境变量`CC`设置为你选择的程序。默认时,只要`gcc`可以使用,`configure`将选择它, 或者是该平台的默认(通常是`cc`)。类似地,你可以用`CFLAGS`变量覆盖默认编译器标志。 + + 你可以在`configure`命令行上指定环境变量, 例如: + + ``` + ./configure CC=/opt/bin/gcc CFLAGS='-O2 -pipe' + ``` + + ​ + + 下面是可以以这种方式设置的有效变量的列表: + + ​ + + ​ + + - `BISON` + + Bison程序 + + - `CC` + + C编译器 + + - `CFLAGS` + + 传递给 C 编译器的选项 + + - `CPP` + + C 预处理器 + + - `CPPFLAGS` + + 传递给 C 预处理器的选项 + + - `DTRACE` + + `dtrace`程序的位置 + + - `DTRACEFLAGS` + + 传递给`dtrace`程序的选项 + + - `FLEX` + + Flex程序 + + - `LDFLAGS` + + 链接可执行程序或共享库时使用的选项 + + - `LDFLAGS_EX` + + 只用于链接可执行程序的附加选项 + + - `LDFLAGS_SL` + + 只用于链接共享库的附加选项 + + - `MSGFMT` + + 用于本地语言支持的`msgfmt`程序 + + - `PERL` + + Perl 解释器的全路径。这将被用来决定编译 PL/Perl 时的依赖性。 + + - `PYTHON` + + Python 解释器的全路径。这将被用来决定编译 PL/Python 时的依赖性。另外这里指定的是 Python 2 还是 Python 3 (或者是隐式选择)决定了 PL/Python 语言的哪一种变种将成为可用的。详见 [第 44.1 节](http://www.postgres.cn/docs/9.6/plpython-python23.html)。 + + - `TCLSH` + + Tcl 解释器的全路径。这将被用来决定编译 PL/Tcl 时的依赖性,并且它将被替换到 Tcl 脚本中。 + + - `XML2_CONFIG` + + 用于定位 libxml 安装的`xml2-config`程序。 + + ​ + + > **注意: **在开发服务器内部代码时,我们推荐使用配置选项`--enable-cassert`(它会打开很多运行时错误检查)和`--enable-debug`(它会提高调试工具的有用性)。 + > + > 如果在使用 GCC,最好使用至少`-O1`的优化级别来编译,因为不使用优化(`-O0`)会禁用某些重要的编译器警告(例如使用未经初始化的变量)。但是,非零的优化级别会使调试更复杂,因为在编译好的代码中步进通常将不能和源代码行一一对应。如果你在尝试调试优化过的代码时觉得困惑,将感兴趣的特定文件使用`-O0`编译。一种简单的方式是传递一个选项给make:`make PROFILE=-O0 file.o`。 + +2. ​ + + **编译** + + 要开始编译,键入: + + ``` + make + ``` + + (一定要记得用GNU make)。依你的硬件而异,编译过程可能需要 5 分钟到半小时。显示的最后一行应该是: + + ``` + All of PostgreSQL successfully made. Ready to install. + ``` + + ​ + + 如果你希望编译所有能编译的东西,包括文档(HTML和手册页)以及附加模块(`contrib`),这样键入: + + ``` + make world + ``` + + 显示的最后一行应该是: + + ``` + PostgreSQL, contrib, and documentation successfully made. Ready to install. + ``` + + ​ + +3. **回归测试** + + 如果你想在安装文件前测试新编译的服务器, 那么你可以在这个时候运行回归测试。 回归测试是一个用于验证PostgreSQL在你的系统上是否按照开发人员设想的那样运行的测试套件。键入: + + ``` + make check + ``` + + (这条命令不能以 root 运行;请在非特权用户下运行该命令)。 (This won't work as root; do it as an unprivileged user.) [第 31 章](http://www.postgres.cn/docs/9.6/regress.html)包含关于如何解释测试结果的详细信息。你可以在以后的任何时间通过执行这条命令来运行这个测试。 + +4. ​ + + **安装文件** + + > **注意: **如果你正在升级一套现有的系统,请阅读 [第 18.6 节](http://www.postgres.cn/docs/9.6/upgrading.html) 其中有关于升级一个集簇的指导。 + + 要安装PostgreSQL,输入: + + ``` + make install + ``` + + 这条命令将把文件安装到在[步骤 1](http://www.postgres.cn/docs/9.6/install-procedure.html#CONFIGURE)中指定的目录。确保你有足够的权限向该区域写入。通常你需要用 root 权限做这一步。或者你也可以事先创建目标目录并且分派合适的权限。 + + 要安装文档(HTML和手册页),输入: + + ``` + make install-docs + ``` + + ​ + + 如果你按照上面的方法编译了所有东西,输入: + + ``` + make install-world + ``` + + 这也会安装文档。 + + 你可以使用`make install-strip`代替`make install`, 在安装可执行文件和库文件时把它们剥离。 这样将节约一些空间。如果你编译时带着调试支持,那么抽取将有效地删除调试支持, 因此我们应该只是在不再需要调试的时候做这些事情。 `install-strip`力图做一些合理的工作来节约空间, 但是它并不了解如何从可执行文件中抽取每个不需要的字节, 因此,如果你希望节约所有可能节约的磁盘空间,那么你可能需要手工做些处理。 + + 标准的安装只提供客户端应用开发和服务器端程序开发所需的所有头文件,例如用 C 写的定制函数或者数据类型(在PostgreSQL 8.0 之前,后者需要独立地执行一次`make install-all-headers`命令,不过现在这个步骤已经融合到标准的安装步骤中)。 + + **只安装客户端:. **如果你只想装客户应用和接口,那么你可以用下面的命令: + + ``` + make -C src/bin install + make -C src/include install + make -C src/interfaces install + make -C doc install + ``` + + `src/bin`中有一些服务器专用的二进制文件,但是它们很小。 + +**卸载:. **要撤销安装可以使用命令`make uninstall`。不过这样不会删除任何创建出来的目录。 + +**清理:. **在安装完成以后,你可以通过在源码树里面用命令`make clean`删除编译文件。 这样会保留`configure`程序生成的文件,这样以后你就可以用`make`命令重新编译所有东西。 要把源码树恢复为发布时的状态,可用`make distclean`命令。 如果你想从同一棵源码树上为多个不同平台制作,你就一定要运行这条命令并且为每个编译重新配置(另外一种方法是在每种平台上使用一套独立的编译树,这样源代码树就可以保留不被更改)。 + +如果你执行了一次制作,然后发现你的`configure`选项是错误的, 或者你修改了任何`configure`所探测的东西(例如,升级了软件), 那么在重新配置和编译之前运行一下`make distclean`是个好习惯。如果不这样做, 你修改的配置选项可能无法传播到所有需要变化的地方。 \ No newline at end of file diff --git a/pg/pg-admin-monitor.md b/pg/pg-admin-monitor.md new file mode 100644 index 0000000..88bd998 --- /dev/null +++ b/pg/pg-admin-monitor.md @@ -0,0 +1,198 @@ +# Monitor + +## 磁盘 + +### 磁盘写满问题 + +* 一个数据库管理员最重要的磁盘监控任务就是确保磁盘不会写满。 +* 一个写满了的数据磁盘可能不会导致数据的崩溃,但它肯定会让系统变得不可用。 +* 如果保存 WAL 文件的磁盘变满,Server会产生致命错误,可能会关闭。 +* 有些文件系统在快满的时候性能会急剧恶化,不要等到磁盘完全满时再行动。 +* 可以将WAL和数据目录放到不同的磁盘上。 +* 可以通过使用表空间(Tablespace)把一些数据库文件移到其他文件系统上去。 + +### 磁盘占用 + +* 每个表都有一个主要的堆磁盘文件,大多数数据都存储在其中。 +* 每个表和索引都存放在单独的磁盘文件里。若超过 1G 字节,则可能多于一个文件。 +* 如果一个表有很宽(>2KB)的列, 则存在一个TOAST文件之关联, 用于存储太宽的值。因为Pg里一个页大小8KB,而单个元组不允许跨页,TOAST字段长度用四字节整形表示,最高的两个比特位用于指示Toast,所以单个字段值长度不能超过1G。 + +### 监视磁盘 + +有三种方式监控磁盘空间: + +* 人工观察系统目录。`du`, `df -h` + +* 使用`oid2name`模块 + +* 使用`SQL`函数与系统视图。(推荐) + + 例如`pg_class`中的`RelPages`指明了对象使用的Page (8k block)的数目。可用于预估表大小。 + + +### 常用查询 + +```plsql +--查询表占用空间(估计) +-- 页面大小为8k,元组条数为估计值 +SELECT relname, relpages, reltuples, +pg_relation_filepath(oid), reltoastrelid +FROM pg_class where relname = 'messages'; + +-- 查看表索引大小 +SELECT c2.relname, c2.relpages +FROM pg_class c, pg_class c2, pg_index i +WHERE c.relname = 'messages' AND + c.oid = i.indrelid AND + c2.oid = i.indexrelid +ORDER BY c2.relname DESC; + +-- 查看表大小与索引大小 +SELECT + relname, + relpages, + reltuples, + pg_relation_filepath(oid), + pg_size_pretty(pg_relation_size(oid)) AS rel_size, + pg_size_pretty(pg_indexes_size(oid)) AS idx_size, + reltoastrelid +FROM pg_class +WHERE relname = 'poi'; +``` + + + +## 锁 + +未解决的锁可以通过系统视图 `pg_locks`查看 + + + +## Vacuum进度 + +目前只有Vacuum命令可以查看执行进度。 + +```bash +pg_stat_progress_vacuum +``` + + + + + +## 指标 + +### 进程监控 + +使用Unix工具,可以查看工作中的postgres进程 + +```bash +ps auxww | grep ^postgres +``` + +```bash +$ ps auxww | grep ^postgres +postgres 15551 0.0 0.1 57536 7132 pts/0 S 18:02 0:00 postgres -i +postgres 15554 0.0 0.0 57536 1184 ? Ss 18:02 0:00 postgres: writer process +postgres 15555 0.0 0.0 57536 916 ? Ss 18:02 0:00 postgres: checkpointer process +postgres 15556 0.0 0.0 57536 916 ? Ss 18:02 0:00 postgres: wal writer process +postgres 15557 0.0 0.0 58504 2244 ? Ss 18:02 0:00 postgres: autovacuum launcher process +postgres 15558 0.0 0.0 17512 1068 ? Ss 18:02 0:00 postgres: stats collector process +postgres 15582 0.0 0.0 58772 3080 ? Ss 18:04 0:00 postgres: joe runbug 127.0.0.1 idle +postgres 15606 0.0 0.0 58772 3052 ? Ss 18:07 0:00 postgres: tgl regression [local] SELECT waiting +postgres 15610 0.0 0.0 58772 3056 ? Ss 18:07 0:00 postgres: tgl regression [local] idle in transaction +``` + +例如,空连接会提示`[idle]`,`begin`的事务会提示`idle in transaction`,如果进程在执行某条命令,会显示在进程的名称之中。 + +其他实用的监控工具包括:`ps`,`top`,`iostat`,`vmstat` + + + +## 统计收集 + +### 相关选项 + +| name | type | default | comment | +| ---------------------------- | ---------- | ------------- | -------------------------------------- | +| `track_activities` | `boolean` | `true` | 启用对每个会话的当前执行命令的信息收集,还有命令开始执行的时间 | +| ` track_activity_query_size` | ` integer` | `1024` | 声明保留的字节数,以跟踪每个活动会话的当前执行命令 | +| ` track_counts` | `boolean` | `true` | 启用在数据库活动上的统计收集,Vacuum需要这个统计 | +| ` track_io_timing` | `boolean` | `false` | 启用对系统 I/O 调用的计时。 | +| ` track_functions` | `enum` | `none` | 指定`pl`只跟踪过程语言函数,指定`all`跟踪 SQL 和 C 语言函数 | +| ` stats_temp_directory` | `string` | `pg_stat_tmp` | 统计数据临时目录路径 | + +* 统计进程通过临时文件将统计数据传递给其他PostgreSQL进程 +* 临时的统计信息放在`pg_stat_tmp`目录下,这个目录可以通过RAMDISK获得更好的性能。 +* 当服务器关闭时,统计数据的拷贝会放在`pg_stat`目录下。 +* 当服务器启动恢复时,所有统计计数器会重置。 + + + +### 查看统计数据 + +* 统计数据并非实时更新的,每个服务进程只有在闲置前会更新统计计数。所以正在执行的查询和事务不影响计数。 +* 收集器本身每隔(PGSTAT_STAT_INTERVAL=500ms)才发送一次新的报告。所以除了 当前进程活动`track_activity`之外的统计指标都不是最新的。 +* 在事务中执行的统计查询,统计数据不会发生变化。使用`pg_stat_clear_snapshot()`来获取最新的快照。 + +### 系统视图名称 + +| 视图名称 | 描述 | +| ----------------------------- | ---------------------------------------- | +| `pg_stat_archiver` | 只有一行,显示有关 WAL 归档进程活动的统计信息。详见[pg_stat_archiver](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STAT-ARCHIVER-VIEW)。 | +| `pg_stat_bgwriter` | 只有一行,显示有关后台写进程的活动的统计信息。详见[pg_stat_bgwriter](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STAT-BGWRITER-VIEW)。 | +| `pg_stat_database` | 每个数据库一行,显示数据库范围的统计信息。详见[pg_stat_database](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STAT-DATABASE-VIEW)。 | +| `pg_stat_database_conflicts` | 每个数据库一行,显示数据库范围的统计信息, 这些信息的内容是关于由于与后备服务器的恢复过程 发生冲突而被取消的查询。详见[pg_stat_database_conflicts](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STAT-DATABASE-CONFLICTS-VIEW)。 | +| `pg_stat_all_tables` | 当前数据库中每个表一行,显示有关访问指定表的统计信息。详见[pg_stat_all_tables](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STAT-ALL-TABLES-VIEW)。 | +| `pg_stat_sys_tables` | 和`pg_stat_all_tables`一样,但只显示系统表。 | +| `pg_stat_user_tables` | 和`pg_stat_all_tables`一样,但只显示用户表。 | +| `pg_stat_xact_all_tables` | 和`pg_stat_all_tables`相似,但计数动作只在当前事务内发生(还*没有*被包括在`pg_stat_all_tables`和相关视图中)。用于生存和死亡行数量的列以及清理和分析动作在此视图中不出现。 | +| `pg_stat_xact_sys_tables` | 和`pg_stat_xact_all_tables`一样,但只显示系统表。 | +| `pg_stat_xact_user_tables` | 和`pg_stat_xact_all_tables`一样,但只显示用户表。 | +| `pg_stat_all_indexes` | 当前数据库中的每个索引一行,显示:表OID、索引OID、模式名、表名、索引名、 使用了该索引的索引扫描总数、索引扫描返回的索引记录数、使用该索引的简 单索引扫描抓取的活表(livetable)中数据行数。 当前数据库中的每个索引一行,显示与访问指定索引有关的统计信息。详见[pg_stat_all_indexes](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STAT-ALL-INDEXES-VIEW)。 | +| `pg_stat_sys_indexes` | 和`pg_stat_all_indexes`一样,但只显示系统表上的索引。 | +| `pg_stat_user_indexes` | 和`pg_stat_all_indexes`一样,但只显示用户表上的索引。 | +| `pg_statio_all_tables` | 当前数据库中每个表一行(包括TOAST表),显示:表OID、模式名、表名、 从该表中读取的磁盘块总数、缓冲区命中次数、该表上所有索引的磁盘块读取总数、 该表上所有索引的缓冲区命中总数、在该表的辅助TOAST表(如果存在)上的磁盘块读取总数、 在该表的辅助TOAST表(如果存在)上的缓冲区命中总数、TOAST表的索引的磁盘块读 取总数、TOAST表的索引的缓冲区命中总数。 当前数据库中的每个表一行,显示有关在指定表上 I/O 的统计信息。详见[pg_statio_all_tables](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STATIO-ALL-TABLES-VIEW)。 | +| `pg_statio_sys_tables` | 和`pg_statio_all_tables`一样,但只显示系统表。 | +| `pg_statio_user_tables` | 和`pg_statio_all_tables`一样,但只显示用户表。 | +| `pg_statio_all_indexes` | 当前数据库中每个索引一行,显示:表OID、索引OID、模式名、 表名、索引名、该索引的磁盘块读取总数、该索引的缓冲区命中总数。 当前数据库中的每个索引一行,显示与指定索引上的 I/O 有关的统计信息。详见[pg_statio_all_indexes](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STATIO-ALL-INDEXES-VIEW)。 | +| `pg_statio_sys_indexes` | 和`pg_statio_all_indexes`一样,但只显示系统表上的索引。 | +| `pg_statio_user_indexes` | 和`pg_statio_all_indexes`一样,但只显示用户表上的索引。 | +| `pg_statio_all_sequences` | 当前数据库中每个序列对象一行,显示:序列OID、模式名、序列名、序列的磁盘读取总数、序列的缓冲区命中总数。 当前数据库中的每个序列一行,显示与指定序列上的 I/O 有关的统计信息。详见[pg_statio_all_sequences](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STATIO-ALL-SEQUENCES-VIEW)。 | +| `pg_statio_sys_sequences` | 和`pg_statio_all_sequences`一样,但只显示系统序列(目前没有定义系统序列,因此这个视图总是为空)。 | +| `pg_statio_user_sequences` | 和`pg_statio_all_sequences`一样,但只显示用户序列。 | +| `pg_stat_user_functions` | 对于所有跟踪功能,函数的OID,模式,名称,数量 通话总时间,和自我的时间。自我时间是 在函数本身所花费的时间量,总时间包括 它调用函数所花费的时间。时间值以毫秒为单位。 每一个被跟踪的函数一行,显示与执行该函数有关的统计信息。详见[pg_stat_user_functions](http://www.postgres.cn/docs/9.6/monitoring-stats.html#PG-STAT-USER-FUNCTIONS-VIEW)。 | +| `pg_stat_xact_user_functions` | 和`pg_stat_user_functions`相似,但是只统计在当前事务期间的调用(还*没有*被包括在`pg_stat_user_functions`中)。 | +| `pg_stat_progress_vacuum` | 每个运行`VACUUM`的后端(包括自动清理工作者进程)一行,显示当前的进度。见[第 28.4.1 节](http://www.postgres.cn/docs/9.6/progress-reporting.html#VACUUM-PROGRESS-REPORTING)。 | + +### 常用统计语句 + +```plsql +-- 按照拉取数据条数降序,查看所有表。 +SELECT * FROM pg_stat_user_tables ORDER BY seq_tup_read DESC; + +-- 按照函数调用次数降序,查看所有存储过程调用情况 +SELECT * FROM pg_stat_user_functions ORDER BY calls DESC; + +-- 查看表使用的buffer +SELECT + relname, + count(*) AS buffers +FROM pg_class c + JOIN pg_buffercache b ON b.relfilenode = c.relfilenode + INNER JOIN pg_database d ON (b.reldatabase = d.oid AND d.datname = current_database()) +WHERE c.relname NOT LIKE 'pg%' +GROUP BY c.relname +ORDER BY 2 DESC; + +-- 检查表的脏页 +SELECT + relname, + b.isdirty +FROM pg_class c + JOIN pg_buffercache b ON b.relfilenode = c.relfilenode + INNER JOIN pg_database d ON (b.reldatabase = d.oid AND d.datname = current_database()) +WHERE c.relname NOT LIKE 'pg%' +ORDER BY 2 DESC; +``` + diff --git a/pg/pg-admin-pgbackrest.md b/pg/pg-admin-pgbackrest.md new file mode 100644 index 0000000..4d5a2b7 --- /dev/null +++ b/pg/pg-admin-pgbackrest.md @@ -0,0 +1,607 @@ +--- +author: "Vonng" +description: "PgBackRest中文文档" +categories: ["Ops"] +tags: ["PostgreSQL","pgBackrest"] +type: "post" +--- + +# pgBackRest文档 + +pgBackRest主页:http://pgbackrest.org + +pgBackRest旨在提供一个简单可靠,容易纵向扩展的PostgreSQL备份恢复系统。 + +pgBackRest并不依赖像tar和rsync这样的传统备份工具,而在内部实现所有备份功能,并使用自定义协议来与远程系统进行通信。 消除对tar和rsync的依赖可以更好地解决特定于数据库的备份问题。 自定义远程协议提供了更多的灵活性,并限制执行备份所需的连接类型,从而提高安全性。 + +pgBackRest v1.27是目前的稳定版本。 发行说明位于发行版页面上。 + +## 0. 特性 + +* 并行备份和恢复 + + 压缩通常是备份操作的瓶颈,但即使是现在已经很普及的多核服务器,大多数数据库备份解决方案仍然是单进程的。 pgBackRest通过并行处理解决了压缩瓶颈问题。利用多个核心进行压缩,即使在1Gb/s的链路上,也可以实现1TB /小时的原生吞吐量。更多的核心和更大的带宽将带来更高的吞吐量。 + +* 本地或远程操作 + + 自定义协议允许pgBackRest以最少的配置通过SSH进行本地或远程备份,恢复和归档。通过协议层也提供了查询PostgreSQL的接口,从而不需要对PostgreSQL进行远程访问,从而增强了安全性。 + +* 全量备份与增量备份 + + 支持全量备份,增量备份,以及差异备份。 pgBackRest不会受到rsync的时间分辨问题的影响,使得差异备份和增量备份完全安全。 + +* 备份轮换和归档过期 + + 可以为全量备份和增量备份设置保留策略,以创建覆盖任何时间范围的备份。 WAL归档可以设置为为所有的备份或仅最近的备份保留。在后一种情况下,在归档过程中会自动保证更老备份的一致性。 + +* 备份完整性 + + 每个文件在备份时都会计算校验和,并在还原过程中重新检查。完成文件复制后,备份会等待所有必须的WAL段进入存储库。存储库中的备份以与标准PostgreSQL集群(包括表空间)相同的格式存储。如果禁用压缩并启用硬链接,则可以在存储库中快照备份,并直接在快照上创建PostgreSQL集群。这对于以传统方式恢复很耗时的TB级数据库是有利的。所有操作都使用文件和目录级别fsync来确保持久性。 + + +* 页面校验和 + + PostgreSQL从9.3开始支持页面级校验和。如果启用页面校验和,pgBackRest将验证在备份过程中复制的每个文件的校验和。所有页面校验和在完整备份过程中均得到验证,在差异备份和增量备份过程中验证了已更改文件中的校验和。 + 验证失败不会停止备份过程,但会向控制台和文件日志输出具体的哪些页面验证失败的详细警告。 + + 此功能允许在包含有效数据副本的备份已过期之前及早检测到页级损坏。 + +* 备份恢复 + + 中止的备份可以从停止点恢复。已经复制的文件将与清单中的校验和进行比较,以确保完整性。由于此操作可以完全在备份服务器上进行,因此减少了数据库服务器上的负载,并节省了时间,因为校验和计算比压缩和重新传输数据要快。 + +* 流压缩和校验和 + + 无论存储库位于本地还是远程,压缩和校验和计算均在流中执行,而文件正在复制到存储库。 + 如果存储库位于备份服务器上,则在数据库服务器上执行压缩,并以压缩格式传输文件,并将其存储在备份服务器上。当禁用压缩时,利用较低级别的压缩来有效使用可用带宽,同时将CPU成本降至最低。 + +* 增量恢复 + + 清单包含备份中每个文件的校验和,以便在还原过程中可以使用这些校验和来加快处理速度。在增量恢复时,备份中不存在的任何文件将首先被删除,然后对其余文件执行校验和。与备份相匹配的文件将保留在原位,其余文件将照常恢复。并行处理可能会导致恢复时间大幅减少。 + +* 并行WAL归档 + + 包括专用的命令将WAL推送到归档并从归档中检索WAL。push命令会自动检测多次推送的WAL段,并在段相同时自动解除重复,否则会引发错误。 push和get命令都通过比较PostgreSQL版本和系统标识符来确保数据库和存储库匹配。这排除了错误配置WAL归档位置的可能性。 + 异步归档允许将传输转移到另一个并行压缩WAL段的进程,以实现最大的吞吐量。对于写入量非常高的数据库来说,这可能是一个关键功能。 + +* 表空间和链接支持 + + 完全支持表空间,并且还原表空间可以重映射到任何位置。也可以使用一个对开发恢复有用的命令将所有的表空间重新映射到一个位置。 + +* Amazon S3支持 + + pgBackRest存储库可以存储在Amazon S3上,以实现几乎无限的容量和保留。 + +* 加密 + + pgBackRest可以对存储库进行加密,以保护无论存储在何处的备份。 + +* 与PostgreSQL兼容> = 8.3 + + pgBackRest包含了对8.3以下版本的支持,因为旧版本的PostgreSQL仍然是经常使用的。 + + + +## 1. 简介 + +本用户指南旨在从头到尾按顺序进行,每一节依赖上一节。例如“备份”部分依赖“快速入门”部分中执行的设置。 + +尽管这些例子是针对Debian / Ubuntu和PostgreSQL 9.4的,但是将这个指南应用到任何Unix发行版和PostgreSQL版本上应该相当容易。请注意,由于Perl代码中的64位操作,目前只支持64位发行版。唯一的特定于操作系统的命令是创建,启动,停止和删除PostgreSQL集群的命令。尽管安装Perl库和可执行文件的位置可能有所不同,但任何Unix系统上的pgBackRest命令都是相同的。 + +PostgreSQL的配置信息和文档可以在PostgreSQL手册中找到。 + +本用户指南采用了一些新颖的方法来记录。从XML源生成文档时,每个命令都在虚拟机上运行。这意味着您可以高度自信地确保命令按照所呈现的顺序正确工作。捕获输出并在适当的时候显示在命令之下。如果输出不包括,那是因为它被认为是不相关的或者被认为是从叙述中分心的。 + +所有的命令都是作为非特权用户运行的,它对root用户和postgres用户都具有sudo权限。也可以直接以各自的用户身份运行这些命令而不用修改,在这种情况下,sudo命令可以被剥离。 + +## 2. 概念 + +### 2.1 备份 + +备份是数据库集群的一致副本,可以从硬件故障中恢复,执行时间点恢复或启动新的备用数据库。 + +* 全量备份(Full Backup) + + pgBackRest将数据库集簇的全部文件复制到备份服务器。数据库集簇的第一个备份总是全量备份。 + + pgBackRest总能从全量备份直接恢复。全量备份的一致性不依赖任何外部文件。 + +* 差异备份(Differential Backup) + + pgBackRest仅复制自上次全量备份以来,内容发生变更的数据库群集文件。恢复时,pgBackRest拷贝差异备份中的所有文件,以及之前一次全量备份中所有未发生变更的文件。差异备份的优点是它比全量备份需要更少的硬盘空间,缺点是差异备份的恢复依赖上一次全量备份的有效性。 + +* 增量备份(Incremental Backup) + + pgBackRest仅复制自上次备份(可能是另一个增量备份,差异备份或完全备份)以来发生更改的数据库群集文件。由于增量备份只包含自上次备份以来更改的那些文件,因此它们通常远远小于完全备份或差异备份。与差异备份一样,增量备份依赖于其他备份才能有效恢复增量备份。由于增量备份只包含自上次备份以来的文件,所有之前的增量备份都恢复到以前的差异,先前的差异备份和先前的完整备份必须全部有效才能执行增量备份的恢复。如果不存在差异备份,则以前的所有增量备份将恢复到之前的完整备份(必须存在),而完全备份本身必须有效才能恢复增量备份。 + +### 2.2 还原 + +还原是将备份复制到将作为实时数据库集群启动的系统的行为。还原需要备份文件和一个或多个WAL段才能正常工作。 + +#### 2.3 WAL + +WAL是PostgreSQL用来确保没有提交的更改丢失的机制。将事务顺序写入WAL,并且在将这些写入刷新到磁盘时认为事务被提交。之后,后台进程将更改写入主数据库集群文件(也称为堆)。在发生崩溃的情况下,重播WAL以使数据库保持一致。 + +WAL在概念上是无限的,但在实践中被分解成单独的16MB文件称为段。 WAL段按照命名约定`0000000100000A1E000000FE`,其中前8个十六进制数字表示时间线,接下来的16个数字是逻辑序列号(LSN)。 + +#### 2.4 加密 + +加密是将数据转换为无法识别的格式的过程,除非提供了适当的密码(也称为密码短语)。 + +pgBackRest将根据用户提供的密码加密存储库,从而防止未经授权访问存储库中的数据。 + + + +## 3. 安装 + +### short version + +```bash +# cent-os +sudo yum install -y pgbackrest + +# ubuntu +sudo apt-get install libdbd-pg-perl libio-socket-ssl-perl libxml-libxml-perl +``` + +### verbose version + +创建一个名为db-primary的新主机来包含演示群集并运行pgBackRest示例。 +如果已经安装了pgBackRest,最好确保没有安装先前的副本。取决于pgBackRest的版本可能已经安装在几个不同的位置。以下命令将删除所有先前版本的pgBackRest。 + +* db-primary⇒删除以前的pgBackRest安装 + +```bash +sudo rm -f /usr/bin/pgbackrest +sudo rm -f /usr/bin/pg_backrest +sudo rm -rf /usr/lib/perl5/BackRest +sudo rm -rf /usr/share/perl5/BackRest +sudo rm -rf /usr/lib/perl5/pgBackRest +sudo rm -rf /usr/share/perl5/pgBackRest +``` + +pgBackRest是用Perl编写的,默认包含在Debian / Ubuntu中。一些额外的模块也必须安装,但是它们可以作为标准包使用。 + +* db-primary⇒安装必需的Perl软件包 + +```bash +# cent-os +sudo yum install -y pgbackrest + +# ubuntu +sudo apt-get install libdbd-pg-perl libio-socket-ssl-perl libxml-libxml-perl +``` + +适用于pgBackRest的Debian / Ubuntu软件包位于apt.postgresql.org。如果没有为您的发行版/版本提供,则可以轻松下载源代码并手动安装。 + +* db-primary⇒下载pgBackRest的1.27版本 + +```bash +sudo wget -q -O - \ + https://github.com/pgbackrest/pgbackrest/archive/release/1.27.tar.gz | \ + sudo tar zx -C /root +``` + +* db-primary⇒安装pgBackRest + +```bash +sudo cp -r /root/pgbackrest-release-1.27/lib/pgBackRest \ + /usr/share/perl5 +sudo find /usr/share/perl5/pgBackRest -type f -exec chmod 644 {} + +sudo find /usr/share/perl5/pgBackRest -type d -exec chmod 755 {} + +sudo cp /root/pgbackrest-release-1.27/bin/pgbackrest /usr/bin/pgbackrest +sudo chmod 755 /usr/bin/pgbackrest +sudo mkdir -m 770 /var/log/pgbackrest +sudo chown postgres:postgres /var/log/pgbackrest +sudo touch /etc/pgbackrest.conf +sudo chmod 640 /etc/pgbackrest.conf +sudo chown postgres:postgres /etc/pgbackrest.conf +``` + +pgBackRest包含一个可选的伴随C库,可以增强性能并启用`checksum-page`选项和加密。预构建的软件包通常比手动构建C库更好,但为了完整性,下面给出了所需的步骤。根据分布情况,可能需要一些软件包,这里不一一列举。 + +* db-primary⇒构建并安装C库 + +```bash +sudo sh -c 'cd /root/pgbackrest-release-1.27/libc && \ + perl Makefile.PL INSTALLMAN1DIR=none INSTALLMAN3DIR=none' +sudo make -C /root/pgbackrest-release-1.27/libc test +sudo make -C /root/pgbackrest-release-1.27/libc install +``` + +现在pgBackRest应该正确安装了,但最好检查一下。如果任何依赖关系被遗漏,那么当你从命令行运行pgBackRest的时候你会得到一个错误。 + +* db-primary⇒确保安装正常 + +```bash +sudo -u postgres pgbackrest +pgBackRest 1.27 - General help + +Usage: + pgbackrest [options] [command] + +Commands: + archive-get Get a WAL segment from the archive. + archive-push Push a WAL segment to the archive. + backup Backup a database cluster. + check Check the configuration. + expire Expire backups that exceed retention. + help Get help. + info Retrieve information about backups. + restore Restore a database cluster. + stanza-create Create the required stanza data. + stanza-upgrade Upgrade a stanza. + start Allow pgBackRest processes to run. + stop Stop pgBackRest processes from running. + version Get version. + +Use 'pgbackrest help [command]' for more information. +``` + +### mac version + +在MacOS上安装可以按照之前的手动安装教程 + +```bash +# install homebrew & wget +/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" +brew install wget + +# install perl DB driver: Pg +perl -MCPAN -e 'install Bundle::DBD::Pg' + +# Download and unzip +wget https://github.com/pgbackrest/pgbackrest/archive/release/1.27.tar.gz + +# Copy to Perls lib +sudo cp -r ~/Downloads/pgbackrest-release-1.27/lib/pgBackRest /Library/Perl/5.18 +sudo find /Library/Perl/5.18/pgBackRest -type f -exec chmod 644 {} + +sudo find /Library/Perl/5.18/pgBackRest -type d -exec chmod 755 {} + + +# Copy binary to your path +sudo cp ~/Downloads/pgbackrest-release-1.27/bin/pgbackrest /usr/local/bin/ +sudo chmod 755 /usr/local/bin/pgbackrest + +# Make log dir & conf file. maybe you will change vonng to postgres +sudo mkdir -m 770 /var/log/pgbackrest && sudo touch /etc/pgbackrest.conf +sudo chmod 640 /etc/pgbackrest.conf +sudo chown vonng /etc/pgbackrest.conf /var/log/pgbackrest + +# Uninstall +# sudo rm -rf /usr/local/bin/pgbackrest /Library/Perl/5.18/pgBackRest /var/log/pgbackrest /etc/pgbackrest.conf +``` + + + + + +## 4. 快速入门 + +### 4.1 搭建测试数据库集群 + +创建示例群集是可选的,但强烈建议试一遍,尤其对于新用户,因为用户指南中的示例命令引用了示例群集。 示例假定演示群集正在默认端口(即5432)上运行。直到后面的部分才会启动群集,因为还有一些配置要做。 + +* db-primary⇒创建演示群集 + +```bash +# create database cluster +pg_ctl init -D /var/lib/pgsql/data + +# change listen address to * +sed -ie "s/^#listen_addresses = 'localhost'/listen_addresses = '*'/g" /var/lib/pgsql/data/postgresql.conf + +# change log prefix +sed -ie "s/^#log_line_prefix = '%m [%p] '/log_line_prefix = ''/g" /var/lib/pgsql/data/postgresql.conf + +``` + +默认情况下PostgreSQL只接受本地连接。本示例需要来自其他服务器的连接,将listen_addresses配置为在所有端口上侦听。如果有安全性要求,这样做可能是不合适的。 + +出于演示目的,log_line_prefix设置将被最低限度地配置。这使日志输出尽可能简短,以更好地说明重要的信息。 + +### 4.2 配置集群的备份单元(Stanza) + +一个备份单元是指 一组关于PostgreSQL数据库集簇的配置,它定义了数据库的位置,如何备份,归档选项等。大多数数据库服务器只有一个Postgres数据库集簇,因此只有一个备份单元,而备份服务器则对每一个需要备份的数据库集簇都有一个备份单元。 + +在主群集之后命名该节是诱人的,但是更好的名称描述群集中包含的数据库。由于节名称将用于主节点名称和所有副本,因此选择描述群集实际功能(例如app或dw)的名称(而不是本地群集名称(如main或prod))会更合适。 + +“Demo”这个名字可以准确地描述这个数据库集簇的目的,所以这里就这么用了。 + +`pgBackRest`需要知道PostgreSQL集簇的**数据目录**所在的位置。备份的时候PostgreSQL可以使用该目录,但恢复的时候PostgreSQL必须停机。备份期,提供给pgBackRest的值将与PostgreSQL运行的路径比较,如果它们不相等则备份将报错。确保`db-path`与`postgresql.conf`中的`data_directory`完全相同。 + +默认情况下,Debian / Ubuntu在/ var / lib / postgresql / [版本] / [集群]中存储集群,因此很容易确定数据目录的正确路径。 + +在创建`/etc/pgbackrest.conf`文件时,数据库所有者(通常是postgres)必须被授予读取权限。 + +* db-primary:`/etc/pgbackrest.conf`⇒配置PostgreSQL集群数据目录 + +```ini +[demo] +db-path=/var/lib/pgsql/data +``` + +pgBackRest配置文件遵循Windows INI约定。部分用括号中的文字表示,每个部分包含键/值对。以`#`开始的行被忽略,可以用作注释。 + +### 4.3 创建存储库 + +存储库是pgBackRest存储备份和归档WAL段的地方。 + +新备份很难提前估计需要多少空间。最好的办法是做一些备份,然后记录不同类型备份的大小(full / incr / diff),并测量每天产生的WAL数量。这将给你一个大致需要多少空间的概念。当然随着数据库的发展,需求可能会随着时间而变化。 + +对于这个演示,存储库将被存储在与PostgreSQL服务器相同的主机上。这是最简单的配置,在使用传统备份软件备份数据库主机的情况下非常有用。 + +* db-primary⇒创建pgBackRest存储库 + +```bash +sudo mkdir /var/lib/pgbackrest +sudo chmod 750 /var/lib/pgbackrest +sudo chown postgres:postgres /var/lib/pgbackrest +``` + +存储库路径必须配置,以便pgBackRest知道在哪里找到它。 + +* db-primary:`/etc/pgbackrest.conf` ⇒配置pgBackRest存储库路径 + +```ini +[demo] +db-path=/var/lib/postgresql/9.4/demo + +[global] +repo-path=/var/lib/pgbackrest +``` + + + +### 4.4 配置归档 + +备份正在运行的PostgreSQL集群需要启用WAL归档。请注意,即使没有对群集进行明确写入,在备份过程中也会创建至少一个WAL段。 + +* db-primary:`/var/lib/pgsql/data/postgresql.conf`⇒ 配置存档设置 + +```ini +archive_command = 'pgbackrest --stanza=demo archive-push %p' +archive_mode = on +listen_addresses = '*' +log_line_prefix = '' +max_wal_senders = 3 +wal_level = hot_standby +``` + +wal_level设置必须至少设置为`archive`,但`hot_standby`和`logical`也适用于备份。 在PostgreSQL 10中,相应的wal_level是`replica`。将wal_level设置为hot_standy并增加max_wal_senders是一个好主意,即使您当前没有运行热备用数据库也是一个好主意,因为这样可以在不重新启动主群集的情况下添加它们。在进行这些更改之后和执行备份之前,必须重新启动PostgreSQL群集。 + + + +### 4.5 保留配置(retention) + +pgBackRest会根据保留配置对备份进行过期处理。 + +* db-primary: `/etc/pgbackrest.conf` ⇒ 配置为保留两个全量备份 + +```ini +[demo] +db-path=/var/lib/postgresql/9.4/demo + +[global] +repo-path=/var/lib/pgbackrest + +retention-full=2 +``` + +更多关于保留的信息可以在`Retention`一节找到。 + + + +### 4.6 配置存储库加密 + +该节创建命令必须在仓库位于初始化节的主机上运行。建议的检查命令后运行节创建,确保归档和备份的配置是否正确。 + +* db-primary: `/etc/pgbackrest.conf` ⇒ 配置pgBackRest存储库加密 + +```ini +[demo] +db-path=/var/lib/postgresql/9.4/demo + +[global] +repo-cipher-pass=zWaf6XtpjIVZC5444yXB+cgFDFl7MxGlgkZSaoPvTGirhPygu4jOKOXf9LO4vjfO +repo-cipher-type=aes-256-cbc +repo-path=/var/lib/pgbackrest +retention-full=2 +``` + +一旦存储库(repository)配置完成且备份单元创建并检查完毕,存储库加密设置便不能更改。 + + + +### 4.7 创建存储单元 + +`stanza-create`命令必须在仓库位于初始化节的主机上运行。建议在`stanza-create`命令之后运行`check`命令,确保归档和备份的配置是否正确。 + +* db-primary ⇒ 创建存储单元并检查配置 + +```bash +postgres$ pgbackrest --stanza=demo --log-level-console=info stanza-create + +P00 INFO: stanza-create command begin 1.27: --db1-path=/var/lib/postgresql/9.4/demo --log-level-console=info --no-log-timestamp --repo-cipher-pass= --repo-cipher-type=aes-256-cbc --repo-path=/var/lib/pgbackrest --stanza=demo + +P00 INFO: stanza-create command end: completed successfully +``` + + + +``` +1. Install + + $ sudo yum install -y pgbackrest + + +2. configuration + + 1) pgbackrest.conf + + $ sudo vim /etc/pgbackrest.conf + [global] + repo-cipher-pass=O8lotSfiXYSYomc9BQ0UzgM9PgXoyNo1t3c0UmiM7M26rOETVNawbsW7BYn+I9es + repo-cipher-type=aes-256-cbc + repo-path=/var/backups + retention-full=2 + retention-diff=2 + retention-archive=2 + start-fast=y + stop-auto=y + archive-copy=y + + [global:archive-push] + archive-async=y + process-max=4 + + [test] + db-path=/var/lib/pgsql/9.5/data + process-max=10 + + 2) postgresql.conf + + $ sudo vim /var/lib/pgsql/9.5/data/postgresql.conf + archive_command = '/usr/bin/pgbackrest --stanza=test archive-push %p' + +3. Initial + + $ sudo chown -R postgres:postgres /var/backups/ + $ sudo -u postgres pgbackrest --stanza=test --log-level-console=info stanza-create + 2018-01-04 11:38:21.082 P00 INFO: stanza-create command begin 1.27: --db1-path=/var/lib/pgsql/9.5/data --log-level-console=info --repo-cipher-pass= --repo-cipher-type=aes-256-cbc --repo-path=/var/backups --stanza=test + 2018-01-04 11:38:21.533 P00 INFO: stanza-create command end: completed successfully + $ sudo service postgresql-9.5 reload + + $ sudo -u postgres pgbackrest --stanza=test --log-level-console=info info + stanza: test + status: error (no valid backups) + + db (current) + wal archive min/max (9.5-1): 0000000500041CFD000000BE / 0000000500041CFD000000BE + +4. Backup + + $ sudo -u postgres pgbackrest --stanza=test --log-level-console=info --type=full backup + 2018-01-04 16:24:57.329 P00 INFO: backup command begin 1.27: --archive-copy --db1-path=/var/lib/pgsql/9.5/data --log-level-console=info --process-max=40 --repo-cipher-pass= --repo-cipher-type=aes- + 256-cbc --repo-path=/var/backups --retention-archive=2 --retention-diff=2 --retention-full=2 --stanza=test --start-fast --stop-auto --type=full + 2018-01-04 16:24:58.192 P00 INFO: execute exclusive pg_start_backup() with label "pgBackRest backup started at 2018-01-04 16:24:57": backup begins after the requested immediate checkpoint completes + 2018-01-04 16:24:58.495 P00 INFO: backup start archive = 0000000500041CFD000000C0, lsn = 41CFD/C0000060 + 2018-01-04 16:26:04.863 P34 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.83 (1GB, 0%) checksum ab17fdd9f70652a0de55fd0da5d2b6b1f48de490 + 2018-01-04 16:26:04.923 P35 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.82 (1GB, 0%) checksum 5acba8d0eb70dcdc64199201ee3999743e747699 + 2018-01-04 16:26:05.208 P37 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.80 (1GB, 0%) checksum 74e2f876d8e7d68ab29624d53d33b0c6cb078382 + 2018-01-04 16:26:06.973 P30 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.87 (1GB, 1%) checksum b6d6884724178476ee24a9a1a812e8941d4da396 + 2018-01-04 16:26:09.434 P24 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.92 (1GB, 1%) checksum c5e6232171e0a7cadc7fc57f459a7bc75c2955d8 + 2018-01-04 16:26:09.860 P40 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.78 (1GB, 1%) checksum 95d94b1bac488592677f7942b85ab5cc2a39bf62 + 2018-01-04 16:26:10.708 P33 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.84 (1GB, 2%) checksum 32e8c83f9bdc5934552f54ee59841f1877b04f69 + 2018-01-04 16:26:11.035 P28 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.89 (1GB, 2%) checksum aa7bee244d2d2c49b56bc9b2e0b9bf36f2bcc227 + 2018-01-04 16:26:11.239 P17 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.99 (1GB, 2%) checksum 218bcecf7da2230363926ca00d719011a6c27467 + 2018-01-04 16:26:11.383 P18 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.98 (1GB, 2%) checksum 38744d27867017dfadb6b520b6c0034daca67481 + ... + 2018-01-04 16:34:07.782 P32 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016471.184 (852.7MB, 98%) checksum 92990e159b0436d5a6843d21b2d888b636e246cf + 2018-01-04 16:34:07.935 P10 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016468.100 (1GB, 98%) checksum d9e0009447a5ef068ce214239f1c999cc5251462 + 2018-01-04 16:34:10.212 P35 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016476.3 (569.6MB, 98%) checksum d02e6efed6cea3005e1342d9d6a8e27afa5239d7 + 2018-01-04 16:34:12.289 P20 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016468.10 (1GB, 98%) checksum 1a99468cd18e9399ade9ddc446eb21f1c4a1f137 + 2018-01-04 16:34:13.270 P03 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016468.1 (1GB, 99%) checksum c0ddb80d5f1be83aa4557777ad05adb7cbc47e72 + 2018-01-04 16:34:13.792 P38 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016468 (1GB, 99%) checksum 767a2e0d21063b92b9cebc735fbb0e3c7332218d + 2018-01-04 16:34:18.446 P26 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016473.3 (863.9MB, 99%) checksum 87ba54690ea418c2ddd1d488c56fa164ebda5042 + 2018-01-04 16:34:23.551 P13 INFO: backup file /var/lib/pgsql/9.5/data/base/16384/3072016475.7 (895.4MB, 100%) checksum a2693bfdc84940c82b7d77a13b752e33448bb008 + 2018-01-04 16:34:23.648 P00 INFO: full backup size = 341.5GB + 2018-01-04 16:34:23.649 P00 INFO: execute exclusive pg_stop_backup() and wait for all WAL segments to archive + 2018-01-04 16:34:37.774 P00 INFO: backup stop archive = 0000000500041CFD000000C0, lsn = 41CFD/C0000168 + 2018-01-04 16:34:39.648 P00 INFO: new backup label = 20180104-162457F + 2018-01-04 16:34:41.004 P00 INFO: backup command end: completed successfully + 2018-01-04 16:34:41.005 P00 INFO: expire command begin 1.27: --log-level-console=info --repo-cipher-pass= --repo-cipher-type=aes-256-cbc --repo-path=/var/backups --retention-archive=2 --retention-diff=2 --retention-full=2 --stanza=test + 2018-01-04 16:34:41.028 P00 INFO: full backup total < 2 - using oldest full backup for 9.5-1 archive retention + 2018-01-04 16:34:41.034 P00 INFO: expire command end: completed successfully + + $ sudo -u postgres pgbackrest --stanza=test --log-level-console=info info + stanza: test + status: ok + + db (current) + wal archive min/max (9.5-1): 0000000500041CFD000000C0 / 0000000500041CFD000000C0 + + full backup: 20180104-162457F + timestamp start/stop: 2018-01-04 16:24:57 / 2018-01-04 16:34:38 + wal start/stop: 0000000500041CFD000000C0 / 0000000500041CFD000000C0 + database size: 341.5GB, backup size: 341.5GB + repository size: 153.6GB, repository backup size: 153.6GB + + +5. restore + + $ sudo vim /etc/pgbackrest.conf + db-path=/export/pgdata + $ sudo mkdir /export/pgdata + $ sudo chown -R postgres:postgres /export/pgdata/ + $ sudo chmod 0700 /export/pgdata/ + $ sudo -u postgres pgbackrest --stanza=test --log-level-console=info --delta --set=20180104-162457F --type=time "--target=2018-01-04 16:34:38" restore + 2018-01-04 17:04:23.170 P00 INFO: restore command begin 1.27: --db1-path=/export/pgdata --delta --log-level-console=info --process-max=40 --repo-cipher-pass= --repo-cipher-type=aes-256-cbc --repo- + path=/var/backups --set=20180104-162457F --stanza=test "--target=2018-01-04 16:34:38" --type=time + WARN: --delta or --force specified but unable to find 'PG_VERSION' or 'backup.manifest' in '/export/pgdata' to confirm that this is a valid $PGDATA directory. --delta and --force have been disabled and if an + y files exist in the destination directories the restore will be aborted. + 2018-01-04 17:04:23.313 P00 INFO: restore backup set 20180104-162457F + 2018-01-04 17:04:23.935 P00 INFO: remap $PGDATA directory to /export/pgdata + 2018-01-04 17:05:09.626 P01 INFO: restore file /export/pgdata/base/16384/3072016476.2 (1GB, 0%) checksum be1145405b8bcfa57c3f1fd8d0a78eee3ed2df21 + 2018-01-04 17:05:09.627 P04 INFO: restore file /export/pgdata/base/16384/3072016475.6 (1GB, 0%) checksum d2bc51d5b58dea3d14869244cd5a23345dbc4ffb + 2018-01-04 17:05:09.627 P27 INFO: restore file /export/pgdata/base/16384/3072016471.9 (1GB, 0%) checksum 94cbf743143baffac0b1baf41e60d4ed99ab910f + 2018-01-04 17:05:09.627 P37 INFO: restore file /export/pgdata/base/16384/3072016471.80 (1GB, 1%) checksum 74e2f876d8e7d68ab29624d53d33b0c6cb078382 + 2018-01-04 17:05:09.627 P38 INFO: restore file /export/pgdata/base/16384/3072016471.8 (1GB, 1%) checksum 5f0edd85543c9640d2c6cf73257165e621a6b295 + 2018-01-04 17:05:09.652 P02 INFO: restore file /export/pgdata/base/16384/3072016476.1 (1GB, 1%) checksum 3e262262b106bdc42c9fe17ebdf62bc4ab2e8166 + ... + 2018-01-04 17:09:15.415 P34 INFO: restore file /export/pgdata/base/1/13142 (0B, 100%) + 2018-01-04 17:09:15.415 P35 INFO: restore file /export/pgdata/base/1/13137 (0B, 100%) + 2018-01-04 17:09:15.415 P36 INFO: restore file /export/pgdata/base/1/13132 (0B, 100%) + 2018-01-04 17:09:15.415 P37 INFO: restore file /export/pgdata/base/1/13127 (0B, 100%) + 2018-01-04 17:09:15.418 P00 INFO: write /export/pgdata/recovery.conf + 2018-01-04 17:09:15.950 P00 INFO: restore global/pg_control (performed last to ensure aborted restores cannot be started) + 2018-01-04 17:09:16.588 P00 INFO: restore command end: completed successfully + + $ sudo vim /export/pgdata/postgresql.conf + port = 5433 + $ sudo -u postgres /usr/pgsql-9.5/bin/pg_ctl -D /export/pgdata/ start + server starting + < 2018-01-04 17:13:47.361 CST >LOG: redirecting log output to logging collector process + < 2018-01-04 17:13:47.361 CST >HINT: Future log output will appear in directory "pg_log". + + $ sudo -u postgres psql -p5433 + psql (9.5.10) + Type "help" for help. + + postgres=# \q + +6. archive_command and restore_command + 1) on master + $ sudo vim /var/lib/pgsql/9.5/data/postgresql.conf + archive_command = '/usr/bin/pgbackrest --stanza=test archive-push %p' + $ sudo service postgresql-9.5 reload + $ sudo yum install -y -q nfs-utils + $ sudo echo "/var/backups 10.191.0.0/16(rw)" > /etc/exports + $ sudo service nfs start + + 2) on slave + $ sudo mount -o v3 master_ip:/var/backups /var/backups + $ sudo vim /etc/pgbackrest.conf + [global] + repo-cipher-pass=O8lotSfiXYSYomc9BQ0UzgM9PgXoyNo1t3c0UmiM7M26rOETVNawbsW7BYn+I9es + repo-cipher-type=aes-256-cbc + repo-path=/var/backups + retention-full=2 + retention-diff=2 + retention-archive=2 + start-fast=y + stop-auto=y + archive-copy=y + + [global:archive-push] + archive-async=y + process-max=4 + + [test] + db-path=/var/lib/pgsql/9.5/data + process-max=10 + + $ sudo vim /var/lib/pgsql/9.5/data/recovery.conf + restore_command = '/usr/bin/pgbackrest --stanza=test archive-get %f "%p"' + + +``` diff --git a/pg/pg-admin-setup.md b/pg/pg-admin-setup.md new file mode 100644 index 0000000..7ec04b1 --- /dev/null +++ b/pg/pg-admin-setup.md @@ -0,0 +1,77 @@ +# Setup + + + +## 1. PostgreSQL用户账户 + +* 使用一个独立的用户下运行PostgreSQL,不与其他程序共享。 +* 默认用户是`postgres` +* 使用`adduser`或`useradd`添加用户。 + + + + +## 2. 创建数据库组 + +使用数据库前要在磁盘上初始化一个数据库存储区域。称之为一个*数据库集簇(database cluster)*,SQL标准术语为*目录集簇 catelog cluster*。一个数据库集簇被一个Pg Server实例管理,是多个数据库的集合。 + +在初始化之后,一个数据库集簇将包含一个名为`postgres`的默认数据库,一个名为`template1`的模板数据库。 + +### 数据目录 + +从文件系统的视角看,一个数据库集簇是一个单一目录,所有数据都将被存储在其中。称之为*数据目录(data directory)*或*数据区域(data area)*。 + +#### 位置选择 + +* 数据目录没有默认的位置,通常使用`/usr/local/pgsql/data`或`/var/lib/pgsql/data`。 +* 通常数据库目录名为`data`,用户`postgres`至少需要拥有该目录及父目录的写权限。 +* 不要使用挂载点直接作为数据目录,以免一堆权限问题。 +* 不要软挂载NFS,有各种烂问题。 + +#### 初始化 + +使用`initdb`,通过`-D`选项或环境变量`PGDATA`指定数据目录的位置: + +``` +$ initdb -D /usr/local/pgsql/data +``` + +另一种替代方案是`pg_ctl`,实际上调用了`initdb` + +``` +$ pg_ctl -D /usr/local/pgsql/data initdb +``` + +* 如果目标目录不存在,将会创建。 +* 如果目标目录已存在或没有父目录的写入权限,将会失败 +* 初始化会回收其他用户的访问权限。 +* 使用`-W`选项为超级用户设置一个初始密码。 +* 默认使用系统的`Locale`和字符集编码。使用`--locale`指明区域,使用`-E`指明编码 + + + + +## 3. 启动数据库服务器 + +* 服务器程序是`postgres`,位于`$PGHOME/bin/postgres`。 +* 至少需要通过`-D`或`PGDATA`提供数据目录的地址,否则启动失败。 + +```bash +# 前台启动 +postgres -D /var/lib/pgsql/data +# 后台启动 +postgres -D /var/lib/pgsql/data >logfile 2>&1 & +``` + +* 更好的方式是通过`pg_ctl`来启动服务 + +```bash +pg_ctl -l logfile +``` + +* 日志文件自己指定存放位置,使用分割工具做log rotate +* 启动Server时会在数据目录写入`postmaster.pid`文件。 + +### 开机自动启动 + +* `contrib/start-scripts` 提供了操作系统相关的开机自动启动脚本,需要root权限安装。 \ No newline at end of file diff --git a/pg/pg-admin-wal.md b/pg/pg-admin-wal.md new file mode 100644 index 0000000..c3662df --- /dev/null +++ b/pg/pg-admin-wal.md @@ -0,0 +1,121 @@ +--- +author: "Vonng" +description: "PostgreSQL WAL与检查点" +categories: ["DBA"] +tags: ["PostgreSQL","WAL", "Internal"] +type: "post" +--- + + + +# PostgreSQL WAL & Checkpoint + +数据库需要保证两个基本的特性:**可靠性**与**可用性**。通俗来讲: + +可靠性就是:出了故障,既不会丢数据,也不会弄脏数据。 + +可用性就是:保证足够的读写性能,出了故障后,能够快速恢复服务。 + +朴素的数据库实现有两个选项:在内存中修改数据页,或者将事物变更直接写入磁盘。但这产生了一个两难困境: + +* 内存支持随机读写,因此在性能上表现强悍,然而作为易失性存储,一旦故障就会丢数据。 +* 硬盘恰恰相反,随机读写表现糟糕,但在故障时数据要可靠的多。 + +内存可用性强可靠性差,硬盘可用性差但可靠性强,如何解决这一对矛盾,让内存与硬盘取长补短,就是生产级数据库需要考虑的问题了。 + + + +## 0x1 核心思想 + +硬盘的随机写入性能很糟糕,但顺序写入的性能却非常可观。即使是SSD也符合这一规律,因为一次写入的擦除单位是Block(通常是几M),而操作系统的写入单元是Page(通常约4k)。如果每次事物提交都要直接将脏数据页落盘,性能表现肯定不会可观。但如果采用另一种方式,将**数据的变更**而不是**变更后的最新数据本身**落盘,就可以将随机写入变为顺序写入,从而极大地提高磁盘写入效率。 + +于是,预写式日志(WAL,Write Ahead Log) 出现了,所谓日志,在最朴素的意义上来讲,就是一个Append-Only的数据文件,记录了操作的内容。只要保留了WAL,数据库就是可靠的,可以恢复的。从一个给定的状态,例如空数据库开始,回放所有的操作日志到当前的时间点,就可以恢复出当前数据库应有的状态。与此同时,如果日志已经落盘确保了可靠性,数据页就不需要在每次提交时落盘了。数据页的读写可以完全在内存进行,从而提供强悍的性能支持。 + +**但可用性不仅仅包括足够的性能,当发生故障时能够快速恢复也是可用性要求的一部分**。考虑最极端的情况,从数据库创建之初所有数据页就在内存里一直飘着,只有操作日志落了盘。现在数据库运行了一整年,突然崩溃了,这时候要想恢复就需要重放一整年的操作日志,也许需要几个小时,也许需要好几天。对于生产环境,这是无法接受的,检查点(Checkpoint)解决了这个问题。 + +检查点(Checkpoint)类似于游戏中存档的概念,远古时期的很多游戏没有存档,一旦Game Over就要重头再来。后来的游戏有了记忆和存档,当挑战Boss失败时,只要读取最近的存档,就可以避免从头开始。 + +数据库中的检查点代表这样一种操作,在某一个检查点时,所有脏数据页会写回到磁盘中,使得磁盘和内存中的数据保持一致。这样当故障恢复时,只需要从该检查点开始回放操作日志即可。 + +例如,每个整点执行一次检查点,存档一次,那么当故障时,只需要从本小时开始的检查点开始回放WAL,就可以完成恢复。同时,检查点还有一个好处是,当数据页落盘之后,在这个检查点之前的WAL日志就可以不用了。对于高负载数据库,例如每小时产生TB级别WAL的数据库,使用检查点能够极大地减少恢复的时间和磁盘的用量。 + +通过检查点和预写式日志,数据库可以同时保证高度的可靠性和可用性。 + + + +## 0x2 WAL概述 + +预写式日志(WAL)是保证数据完整性的一种标准方法。对其详尽的描述几乎可以在所有(如果不是全部)有关事务处理的书中找到。简单来说,WAL的中心概念是数据文件(存储着表和索引)的修改必须在这些动作被日志记录之后才被写入,即在描述这些改变的日志记录被刷到持久存储以后。如果我们遵循这种过程,我们不需要在每个事务提交时刷写数据页面到磁盘,因为我们知道在发生崩溃时可以使用日志来恢复数据库:任何还没有被应用到数据页面的改变可以根据其日志记录重做(这是前滚恢复,也被称为REDO)。 + +使用WAL可以显著降低磁盘的写次数,因为只有日志文件需要被刷出到磁盘以保证事务被提交,而被事务改变的每一个数据文件则不必被刷出。**日志文件被按照顺序写入,因此同步日志的代价要远低于刷写数据页面的代价**。在处理很多影响数据存储不同部分的小事务的服务器上这一点尤其明显。此外,当服务器在处理很多小的并行事务时,日志文件的一个`fsync`可以提交很多事务。 + +##异步提交 + +*异步提交*是一个允许事务能更快完成的选项,代价是在数据库崩溃时最近的事务会丢失。在很多应用中这是一个可接受的交换。 + +如前一节所述,事务提交通常是*同步的*:服务器等到事务的WAL记录被刷写到持久存储之后才向客户端返回成功指示。因此客户端可以确保那些报告已被提交的事务确会被保存,即便随后马上发生了一次服务器崩溃。但是,对于短事务来说这种延迟是其总执行时间的主要部分。选择异步提交模式意味着服务器将在事务被逻辑上提交后立刻返回成功,而此时由它生成的WAL记录还没有被真正地写到磁盘上。这将为小型事务的生产力产生显著地提升。 + +异步提交会带来数据丢失的风险。在向客户端报告事务完成到事务真正被提交(即能保证服务器崩溃时它也不会被丢失)之间有一个短的时间窗口。因此如果客户端将会做一些要求其事务被记住的外部动作,就不应该用异步提交。例如,一个银行肯定不会使用异步提交事务来记录一台ATM的现金分发。但是在很多情境中不需要这种强的保证,例如事件日志。 + +使用异步提交带来的风险是数据丢失,而不是数据损坏。如果数据库可能崩溃,它会通过重放WAL到被刷写的最后一个记录来进行恢复。数据库将因此被恢复到一个自身一致状态,但是任何还没有被刷写到磁盘的事务将不会反映在该状态中。因此其影响就是丢失了最后的少量事务。由于事务按照提交顺序被重放,所以不会出现任何不一致性 — 例如一个事务B按照前面一个事务A的效果来进行修改,则不会出现A的效果丢失而B的效果被保留的情况。 + +用户可以选择每一个事务的提交模式,这样可以有同步提交和异步提交的事务并行运行。这允许我们灵活地在性能和事务持久性之间进行权衡。提交模式由用户可设置的参数[synchronous_commit](http://www.postgres.cn/docs/9.6/runtime-config-wal.html#GUC-SYNCHRONOUS-COMMIT)控制,它可以使用任何一种修改配置参数的方法进行设置。一个事务真正使用的提交模式取决于当事务提交开始时`synchronous_commit`的值。 + +特定的实用命令,如`DROP TABLE`,被强制按照同步提交而不考虑`synchronous_commit`的设定。这是为了确保服务器文件系统和数据库逻辑状态之间的一致性。支持两阶段提交的命令页总是同步提交的,如`PREPARE TRANSACTION`。 + +如果数据库在异步提交和事务WAL记录写入之间的风险窗口期间崩溃,在该事务期间所作的修改*将*丢失。风险窗口的持续时间是有限制的,因为一个后台进程("WAL写进程")每[wal_writer_delay](http://www.postgres.cn/docs/9.6/runtime-config-wal.html#GUC-WAL-WRITER-DELAY)毫秒会把未写入的WAL记录刷写到磁盘。风险窗口实际的最大持续时间是`wal_writer_delay`的3倍,因为WAL写进程被设计成倾向于在忙时一次写入所有页面。 + +一个立刻关闭等同于一次服务器崩溃,因此也将会导致未刷写的异步提交丢失。 + +异步提交提供的行为与配置[fsync](http://www.postgres.cn/docs/9.6/runtime-config-wal.html#GUC-FSYNC) = off不同。`fsync`是一个服务器范围的设置,它将会影响所有事务的行为。它禁用了PostgreSQL中所有尝试同步写入到数据库不同部分的逻辑,并且因此一次系统崩溃(即,一个硬件或操作系统崩溃,不是PostgreSQL本身的失败)可能造成数据库状态的任意损坏。在很多情境中,带来大部分性能提升的异步提交可以通过关闭`fsync`来获得,而且不会带来数据损坏的风险。 + +[commit_delay](http://www.postgres.cn/docs/9.6/runtime-config-wal.html#GUC-COMMIT-DELAY)也看起来很像异步提交,但它实际上是一种同步提交方法(事实上,`commit_delay`在异步提交时被忽略)。`commit_delay`会使事务在刷写WAL到磁盘之前有一个延迟,它期望由一个这样的事务所执行的刷写能够也服务于其他同时提交的事务。该设置可以被看成是一种时间窗口,在其期间事务可以参与到一次单一的刷写中,这种方式用于在多个事务之间摊销刷写的开销。 + + + +## 0x3 查看WAL状态 + + + + + + + +## 0x5 流复制 + + + +## 0x6 Checkpoint + + + +## 相关命令 + +- `CHECKPOINT` : 强制一个事务日志检查点 + +一个检查点是事务日志序列中的一个点,在该点上所有数据文件 都已经被更新为反映日志中的信息。所有数据文件将被刷写到磁盘。 检查点期间发生的细节可见[第 30.4 节](http://www.postgres.cn/docs/9.6/wal-configuration.html)。 + +`CHECKPOINT`命令在发出时强制一个 立即的检查点,而不用等待由系统规划的常规检查点(由 [第 19.5.2 节](http://www.postgres.cn/docs/9.6/runtime-config-wal.html#RUNTIME-CONFIG-WAL-CHECKPOINTS)中的设置控制)。 `CHECKPOINT`不是用来在普通操作中 使用的命令。 + +如果在恢复期间执行,`CHECKPOINT` 命令将强制一个重启点(见[第 30.4 节](http://www.postgres.cn/docs/9.6/wal-configuration.html)) 而不是写一个新检查点。 + +只有超级用户能够调用`CHECKPOINT`。 + + + + + +## WAL相关视图与函数 + +``` + +``` + + + + + +## Checkpoint相关参数 + + + diff --git a/pg/pg-sql-lock.md b/pg/pg-sql-lock.md new file mode 100644 index 0000000..359f348 --- /dev/null +++ b/pg/pg-sql-lock.md @@ -0,0 +1,306 @@ +--- +author: "Vonng" +description: "PostgreSQL中锁的类型,加锁的方法" +categories: ["Dev"] +tags: ["PostgreSQL","SQL", "Lock"] +type: "post" +--- + +# PostgreSQL的锁 + +## 显式加锁 + +通常锁会在相应命令执行中自动获取,但也可以手动加锁。 + +使用LOCK命令加锁 + +```sql +LOCK [ TABLE ] [ ONLY ] name [ * ] [, ...] [ IN lockmode MODE ] [ NOWAIT ] +``` + +### 描述 + +* 显式锁表必须在一个事务中进行,在事务外锁表会报错。 +* `LOCK TABLE`只能获取表锁,默认会等待冲突的锁被释放。 +* 命令一旦获取到锁, 会被在当前事务中一直持有。没有`UNLOCK TABLE`,锁总是在事务结束时被释放。 +* 指定`NOWAIT`选项时,如果命令不能立刻获得锁就会中止并报错。 +* 指定`ONLY`选项,则继承于该表的子表不会自动加锁。 + +### 实践 + +例如,在迁移数据时,希望在dump和restore的期间,禁止对表的写入,此时可以使用Exclusive显式锁表。 + +```sql +BEGIN; +LOCK TABLE messages IN EXCLUSIVE MODE; +-- DO Something +COMMIT +``` + +如果不是使用`sql`而是使用`pg_dump`时,可以采用曲线救国的方式,开启后台进程来锁表。如下例所示: + +```bash +function lock_message(){ + declare -i partition_idx=$1 + pgurl=$(get_src_url) + pipe=/tmp/mpl${partition_idx} + catpid=/tmp/mplpid${partition_idx} + + mkfifo ${pipe} + cat > ${pipe} & + echo $! > ${catpid} + cat ${pipe} | psql ${pgurl} & + lock_statement="BEGIN;LOCK TABLE rel_8192_${partition_idx}.messages IN EXCLUSIVE MODE;" + echo $lock_statement > ${pipe} +} + + +function unlock_message(){ + declare -i partition_idx=$1 + pipe=/tmp/mpl${partition_idx} + catpid=/tmp/mplpid${partition_idx} + + echo "ROLLBACK;\q" > ${pipe} + cat ${catpid} | xargs kill + rm -rf ${pipe} ${catpid} + ps aux | grep "cat\ /tmp*" | grep -v "grep" | awk '{print $2}' | xargs kill + echo "UNLOCK rel_8192_${partition_idx}.message" +} + + +function handle_message(){ + lock_message ${partition_idx} + psql ${dst_url} -c "TRUNCATE messages;" + pg_dump ${src_url} -a -t messages | psql ${dst_url} + unlock_message ${partition_idx} +} +``` + +在为引用表的命令自动获取锁时, PostgreSQL总是尽可能使用最不严格的 锁模式。提供`LOCK TABLE`是用于想要更严格 的锁定的情况。例如,假设一个应用运行一个`READ COMMITTED` 隔离级别的事务, 并且需要确保一个表中的数据在该事务的期间保持稳定。要实现这个目的, 必须在查询之前在表上获得`SHARE`锁模式。这将阻止并发的 数据更改并且确保该表的后续读操作会看到已提交数据的一个稳定视图, 因为`SHARE`锁模式与写入者所要求的 `ROW EXCLUSIVE`锁有冲突,并且你的 `LOCK TABLE *name* IN SHARE MODE` 语句将等待,直到任何并发持有`ROW EXCLUSIVE`模式锁的持有者提交或者回滚。因此,一旦得到锁, 就不会有未提交的写入还没有解决。更进一步,在释放该锁之前,任何 人都不能开始。 + +要在运行在`REPEATABLE READ`或`SERIALIZABLE` 隔离级别的事务中得到类似的效果,你必须在执行任何 `SELECT`或者数据修改语句之前执行 `LOCK TABLE`语句。一个 `REPEATABLE READ`或者`SERIALIZABLE`事务的 数据视图将在它的第一个`SELECT`或者数据修改语句开始 时被冻结。在该事务中稍后的一个`LOCK TABLE`仍将阻止并发写 — 但它不会确保该事务读到的东西对应于最新的已提交值。 + +如果一个此类事务正要修改表中的数据,那么它应该使用 `SHARE ROW EXCLUSIVE`锁模式来取代 `SHARE`模式。这会保证一次只有一个此类事务运行。如果 不用这种模式,死锁就可能出现:两个事务可能都要求 `SHARE`模式,并且都不能获得 `ROW EXCLUSIVE`模式来真正地执行它们的更新(注意一个 事务所拥有的锁不会冲突,因此一个事务可以在它持有`SHARE` 模式时获得`ROW EXCLUSIVE`模式 — 但是如果有其他 人持有`SHARE`模式时则不能)。为了避免死锁,确保所有的 事务在同样的对象上以相同的顺序获得锁,并且如果在一个对象上涉及多 种锁模式,事务应该总是首先获得最严格的那种模式。 + + + +## 参数 + +- `*name*` + + 要锁定的一个现有表的名称(可以是模式限定的)。如果在表名前指定了 `ONLY`,只有该表会被锁定。如果没有指定了 `ONLY`,该表和它所有的后代表(如果有)都会被锁定。可选 地,在表名后指定`*`来显式地表示把后代表包括在内。命令`LOCK TABLE a, b;`等效于 `LOCK TABLE a; LOCK TABLE b;`。这些表会被按照在 `LOCK TABLE`中指定的顺序一个一个 被锁定。 + +- `*lockmode*` + + 锁模式指定这个锁和哪些锁冲突。锁模式在 [第 13.3 节](http://www.postgres.cn/docs/9.6/explicit-locking.html)中描述。如果没有指定锁模式,那儿将使用最严格的模式`ACCESS EXCLUSIVE`。 + +- `NOWAIT` + + 指定`LOCK TABLE`不等待任何冲突锁被释放: 如果所指定的锁不能立即获得,那么事务就会中止。 + +## 注解 + +`LOCK TABLE ... IN ACCESS SHARE MODE`要求目标表上的 `SELECT`特权。`LOCK TABLE ... IN ROW EXCLUSIVE MODE`要求目标表上的`INSERT`、`UPDATE`、 `DELETE`或`TRUNCATE`特权。所有其他形式的 `LOCK`要求表级`UPDATE`、`DELETE`或`TRUNCATE`特权。 + +`LOCK TABLE`在一个事务块外部没有用处:锁将只保持到语句 完成。因此如果在一个事务块外部使用了`LOCK`, PostgreSQL会报告一个错误。使用 [BEGIN](http://www.postgres.cn/docs/9.6/sql-begin.html)和[COMMIT](http://www.postgres.cn/docs/9.6/sql-commit.html)(或者 [ROLLBACK](http://www.postgres.cn/docs/9.6/sql-rollback.html))定义一个事务块。 + +`LOCK TABLE`只处理表级锁,因此涉及到 `ROW`的模式名称在这里都是不当的。这些模式名称应该通常 被解读为用户在被锁定表中获取行级锁的意向。还有, `ROW EXCLUSIVE`模式是一个可共享的表锁。记住就 `LOCK TABLE`而言,所有的锁模式都具有相同的语义, 只有模式的冲突规则有所不同。关于如何获取一个真正的行级锁的信息, 请见`SELECT`参考文档中的 [第 13.3.2 节](http://www.postgres.cn/docs/9.6/explicit-locking.html#LOCKING-ROWS)和[*锁定子句*](http://www.postgres.cn/docs/9.6/sql-select.html#SQL-FOR-UPDATE-SHARE)。 + +## 示例 + +在将要向一个外键表中执行插入时在主键表上获得一个 `SHARE`锁: + +``` +BEGIN WORK; +LOCK TABLE films IN SHARE MODE; +SELECT id FROM films + WHERE name = 'Star Wars: Episode I - The Phantom Menace'; +-- 如果记录没有被返回就做 ROLLBACK +INSERT INTO films_user_comments VALUES + (_id_, 'GREAT! I was waiting for it for so long!'); +COMMIT WORK; +``` + +在将要执行一次删除操作前在主键表上取一个 `SHARE ROW EXCLUSIVE`锁: + +``` +BEGIN WORK; +LOCK TABLE films IN SHARE ROW EXCLUSIVE MODE; +DELETE FROM films_user_comments WHERE id IN + (SELECT id FROM films WHERE rating < 5); +DELETE FROM films WHERE rating < 5; +COMMIT WORK; +``` + +## 兼容性 + +在 SQL 标准中没有`LOCK TABLE`,SQL 标准中使用 `SET TRANSACTION`指定事务上的并发层次。 PostgreSQL也支持这样做,详见 [SET TRANSACTION](http://www.postgres.cn/docs/9.6/sql-set-transaction.html)。 + + + + + + + +## 锁的种类:表、行、页 + +​ PostgreSQL提供了多种锁模式用于控制对表中数据的并发访问。 这些模式可以用于在MVCC无法给出期望行为的情境中由应用控制的锁。 同样,大多数PostgreSQL命令会自动要求恰当的锁以保证被引用的表在命令的执行过程中 不会以一种不兼容的方式删除或修改(例如,`TRUNCATE`无法安全地与同一表中上的其他操作并发地执行,因此它在表上获得一个排他锁来强制这种行为)。 + +要检查在一个数据库服务器中当前未解除的锁列表,可以使用[`pg_locks`](http://www.postgres.cn/docs/9.6/view-pg-locks.html)系统视图。 有关监控锁管理器子系统状态的更多信息,请参考[第 28 章](http://www.postgres.cn/docs/9.6/monitoring.html)。 + + + +### 表锁 + +下面的列表显示了可用的锁模式和PostgreSQL自动使用它们的场合。 你也可以用[LOCK](http://www.postgres.cn/docs/9.6/sql-lock.html)命令显式获得这些锁。请记住所有这些锁模式都是表级锁,即使它们的名字包含"row"单词(这些名称是历史遗产)。 在一定程度上,这些名字反应了每种锁模式的典型用法 — 但是语意却都是一样的。 两种锁模式之间真正的区别是它们有着不同的冲突锁模式集合(参考[表 13-2](http://www.postgres.cn/docs/9.6/explicit-locking.html#TABLE-LOCK-COMPATIBILITY))。 两个事务在同一时刻不能在同一个表上持有属于相互冲突模式的锁(但是,一个事务决不会和自身冲突。例如,它可以在同一个表上获得`ACCESS EXCLUSIVE`锁然后接着获取`ACCESS SHARE`锁)。非冲突锁模式可以由许多事务同时持有。 请特别注意有些锁模式是自冲突的(例如,在一个时刻`ACCESS EXCLUSIVE`锁不能被多于一个事务持有)而其他锁模式不是自冲突的(例如,`ACCESS SHARE`锁可以被多个事务持有)。 + +**表级锁模式** + +- `ACCESS SHARE` + + 只与`ACCESS EXCLUSIVE`锁模式冲突。`SELECT`命令在被引用的表上获得一个这种模式的锁。通常,任何只*读取*表而不修改它的查询都将获得这种锁模式。 + +- `ROW SHARE` + + 与`EXCLUSIVE`和`ACCESS EXCLUSIVE`锁模式冲突。`SELECT FOR UPDATE`和`SELECT FOR SHARE`命令在目标表上取得一个这种模式的锁 (加上在被引用但没有选择`FOR UPDATE/FOR SHARE`的任何其他表上的`ACCESS SHARE`锁)。 + +- `ROW EXCLUSIVE` + + 与`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`和`ACCESS EXCLUSIVE`锁模式冲突。命令`UPDATE`、`DELETE`和`INSERT`在目标表上取得这种锁模式(加上在任何其他被引用表上的`ACCESS SHARE`锁)。通常,这种锁模式将被任何*修改表中数据*的命令取得。 + +- `SHARE UPDATE EXCLUSIVE` + + 与`SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`和`ACCESS EXCLUSIVE`锁模式冲突。这种模式保护一个表不受并发模式改变和`VACUUM`运行的影响。由`VACUUM`(不带`FULL`)、`ANALYZE`、`CREATE INDEX CONCURRENTLY`和`ALTER TABLE VALIDATE`以及其他`ALTER TABLE`的变体获得。 + +- `SHARE` + + 与`ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`和`ACCESS EXCLUSIVE`锁模式冲突。这种模式保护一个表不受并发数据改变的影响。由`CREATE INDEX`(不带`CONCURRENTLY`)取得。 + +- `SHARE ROW EXCLUSIVE` + + 与`ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`和`ACCESS EXCLUSIVE`锁模式冲突。这种模式保护一个表不受并发数据修改所影响,并且是自排他的,这样在一个时刻只能有一个会话持有它。由`CREATE TRIGGER`和很多 `ALTER TABLE`的很多形式所获得(见 [ALTER TABLE](http://www.postgres.cn/docs/9.6/sql-altertable.html))。 + +- `EXCLUSIVE` + + 与`ROW SHARE`、`ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`和`ACCESS EXCLUSIVE`锁模式冲突。这种模式只允许并发的`ACCESS SHARE`锁,即只有来自于表的读操作可以与一个持有该锁模式的事务并行处理。由`REFRESH MATERIALIZED VIEW CONCURRENTLY`获得。 + +- `ACCESS EXCLUSIVE` + + 与所有模式的锁冲突(`ACCESS SHARE`、`ROW SHARE`、`ROW EXCLUSIVE`、`SHARE UPDATE EXCLUSIVE`、`SHARE`、`SHARE ROW EXCLUSIVE`、`EXCLUSIVE`和`ACCESS EXCLUSIVE`)。这种模式保证持有者是访问该表的唯一事务。由`ALTER TABLE`、`DROP TABLE`、`TRUNCATE`、`REINDEX`、`CLUSTER`、`VACUUM FULL`和`REFRESH MATERIALIZED VIEW`(不带`CONCURRENTLY`)命令获取。`ALTER TABLE`的很多形式也在这个层面上获得锁(见[ALTER TABLE](http://www.postgres.cn/docs/9.6/sql-altertable.html))。这也是未显式指定模式的`LOCK TABLE`命令的默认锁模式。 + +> **提示: **只有一个`ACCESS EXCLUSIVE`锁阻塞一个`SELECT`(不带`FOR UPDATE/SHARE`)语句。 + +一旦被获取,一个锁通常将被持有直到事务结束。 但是如果在建立保存点之后才获得锁,那么在回滚到这个保存点的时候将立即释放该锁。 这与`ROLLBACK`取消保存点之后所有的影响的原则保持一致。 同样的原则也适用于在PL/pgSQL异常块中获得的锁:一个跳出块的错误将释放在块中获得的锁。 + +**表 13-2. 冲突的锁模式** + +| 请求的锁模式 | 当前的锁模式 | | | | | | | | +| ---------------------- | --------- | ------------- | ---------------------- | ----- | ------------------- | --------- | ---------------- | ---- | +| ACCESS SHARE | ROW SHARE | ROW EXCLUSIVE | SHARE UPDATE EXCLUSIVE | SHARE | SHARE ROW EXCLUSIVE | EXCLUSIVE | ACCESS EXCLUSIVE | | +| ACCESS SHARE | | | | | | | | X | +| ROW SHARE | | | | | | | X | X | +| ROW EXCLUSIVE | | | | | X | X | X | X | +| SHARE UPDATE EXCLUSIVE | | | | X | X | X | X | X | +| SHARE | | | X | X | | X | X | X | +| SHARE ROW EXCLUSIVE | | | X | X | X | X | X | X | +| EXCLUSIVE | | X | X | X | X | X | X | X | +| ACCESS EXCLUSIVE | X | X | X | X | X | X | X | X | + +### 行锁 + +除了表级锁以外,还有行级锁,在下文列出了行级锁以及在哪些情境下PostgreSQL会自动使用它们。行级锁的完整冲突表请见[表 13-3](http://www.postgres.cn/docs/9.6/explicit-locking.html#ROW-LOCK-COMPATIBILITY)。注意一个事务可能会在相同的行上保持冲突的锁,甚至是在不同的子事务中。但是除此之外,两个事务永远不可能在相同的行上持有冲突的锁。行级锁不影响数据查询,它们只阻塞对同一行的*写入者和加锁者*。 + +**行级锁模式** + +- `FOR UPDATE` + + `FOR UPDATE`会导致由`SELECT`语句检索到的行被锁定,就好像它们要被更新。这可以阻止它们被其他事务锁定、修改或者删除,一直到当前事务结束。也就是说其他尝试`UPDATE`、`DELETE`、`SELECT FOR UPDATE`、`SELECT FOR NO KEY UPDATE`、`SELECT FOR SHARE`或者`SELECT FOR KEY SHARE`这些行的事务将被阻塞,直到当前事务结束。反过来,`SELECT FOR UPDATE`将等待已经在相同行上运行以上这些命令的并发事务,并且接着锁定并且返回被更新的行(或者没有行,因为行可能已被删除)。不过,在一个`REPEATABLE READ`或`SERIALIZABLE`事务中,如果一个要被锁定的行在事务开始后被更改,将会抛出一个错误。进一步的讨论请见[第 13.4 节](http://www.postgres.cn/docs/9.6/applevel-consistency.html)。任何在一行上的`DELETE`命令也会获得`FOR UPDATE`锁模式,在某些列上修改值的`UPDATE`也会获得该锁模式。当前`UPDATE`情况中被考虑的列集合是那些具有能用于外键的唯一索引的列(所以部分索引和表达式索引不被考虑),但是这种要求未来有可能会改变。 + +- `FOR NO KEY UPDATE` + + 行为与`FOR UPDATE`类似,不过获得的锁较弱:这种锁将不会阻塞尝试在相同行上获得锁的`SELECT FOR KEY SHARE`命令。任何不获取`FOR UPDATE`锁的`UPDATE`也会获得这种锁模式。 + +- `FOR SHARE` + + 行为与`FOR NO KEY UPDATE`类似,不过它在每个检索到的行上获得一个共享锁而不是排他锁。一个共享锁会阻塞其他事务在这些行上执行`UPDATE`、`DELETE`、`SELECT FOR UPDATE`或者`SELECT FOR NO KEY UPDATE`,但是它不会阻止它们执行`SELECT FOR SHARE`或者`SELECT FOR KEY SHARE`。 + +- `FOR KEY SHARE` + + 行为与`FOR SHARE`类似,不过锁较弱:`SELECT FOR UPDATE`会被阻塞,但是`SELECT FOR NO KEY UPDATE`不会被阻塞。一个键共享锁会阻塞其他事务执行修改键值的`DELETE`或者`UPDATE`,但不会阻塞其他`UPDATE`,也不会阻止`SELECT FOR NO KEY UPDATE`、`SELECT FOR SHARE`或者`SELECT FOR KEY SHARE`。 + +PostgreSQL不会在内存里保存任何关于已修改行的信息,因此对一次锁定的行数没有限制。 不过,锁住一行会导致一次磁盘写,例如, `SELECT FOR UPDATE`将修改选中的行以标记它们被锁住,并且因此会导致磁盘写入。 + +**表 13-3. 冲突的行级锁** + +| 要求的锁模式 | 当前的锁模式 | | | | +| ----------------- | --------- | ----------------- | ---------- | ---- | +| FOR KEY SHARE | FOR SHARE | FOR NO KEY UPDATE | FOR UPDATE | | +| FOR KEY SHARE | | | | X | +| FOR SHARE | | | X | X | +| FOR NO KEY UPDATE | | X | X | X | +| FOR UPDATE | X | X | X | X | + +### 页锁 + +除了表级别和行级别的锁以外,页面级别的共享/排他锁被用来控制对共享缓冲池中表页面的读/写。 这些锁在行被抓取或者更新后马上被释放。应用开发者通常不需要关心页级锁,我们在这里提到它们只是为了完整。 + + + +## 死锁 + +显式锁定的使用可能会增加*死锁*的可能性,死锁是指两个(或多个)事务相互持有对方想要的锁。例如,如果事务 1 在表 A 上获得一个排他锁,同时试图获取一个在表 B 上的排他锁, 而事务 2 已经持有表 B 的排他锁,同时却正在请求表 A 上的一个排他锁,那么两个事务就都不能进行下去。PostgreSQL能够自动检测到死锁情况并且会通过中断其中一个事务从而允许其它事务完成来解决这个问题(具体哪个事务会被中断是很难预测的,而且也不应该依靠这样的预测)。 + +要注意死锁也可能会作为行级锁的结果而发生(并且因此,它们即使在没有使用显式锁定的情况下也会发生)。考虑如下情况,两个并发事务在修改一个表。第一个事务执行: + +``` +UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111; +``` + +这样就在指定帐号的行上获得了一个行级锁。然后,第二个事务执行: + +``` +UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222; +UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111; +``` + +第一个`UPDATE`语句成功地在指定行上获得了一个行级锁,因此它成功更新了该行。 但是第二个`UPDATE`语句发现它试图更新的行已经被锁住了,因此它等待持有该锁的事务结束。事务二现在就在等待事务一结束,然后再继续执行。现在,事务一执行: + +``` +UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222; +``` + +事务一试图在指定行上获得一个行级锁,但是它得不到:事务二已经持有了这样的锁。所以它要等待事务二完成。因此,事务一被事务二阻塞,而事务二也被事务一阻塞:一个死锁。 PostgreSQL将检测这样的情况并中断其中一个事务。 + +防止死锁的最好方法通常是保证所有使用一个数据库的应用都以一致的顺序在多个对象上获得锁。在上面的例子里,如果两个事务以同样的顺序更新那些行,那么就不会发生死锁。 我们也应该保证一个事务中在一个对象上获得的第一个锁是该对象需要的最严格的锁模式。如果我们无法提前验证这些,那么可以通过重试因死锁而中断的事务来即时处理死锁。 + +只要没有检测到死锁情况,寻求一个表级或行级锁的事务将无限等待冲突锁被释放。这意味着一个应用长时间保持事务开启不是什么好事(例如等待用户输入)。 + + + +## 咨询锁 + +PostgreSQL提供了一种方法创建由应用定义其含义的锁。这种锁被称为*咨询锁*,因为系统并不强迫其使用 — 而是由应用来保证其正确的使用。咨询锁可用于 MVCC 模型不适用的锁定策略。例如,咨询锁的一种常用用法是模拟所谓"平面文件"数据管理系统典型的悲观锁策略。虽然一个存储在表中的标志可以被用于相同目的,但咨询锁更快、可以避免表膨胀并且会由服务器在会话结束时自动清理。 + +有两种方法在PostgreSQL中获取一个咨询锁:在会话级别或在事务级别。一旦在会话级别获得了咨询锁,它将被保持直到被显式释放或会话结束。不同于标准锁请求,会话级咨询锁请求不尊重事务语义:在一个后来被回滚的事务中得到的锁在回滚后仍然被保持,并且同样即使调用它的事务后来失败一个解锁也是有效的。一个锁在它所属的进程中可以被获取多次;对于每一个完成的锁请求必须有一个相应的解锁请求,直至锁被真正释放。在另一方面,事务级锁请求的行为更像普通锁请求:在事务结束时会自动释放它们,并且没有显式的解锁操作。这种行为通常比会话级别的行为更方便,因为它使用一个咨询锁的时间更短。对于同一咨询锁标识符的会话级别和事务级别的锁请求按照期望将彼此阻塞。如果一个会话已经持有了一个给定的咨询锁,由它发出的附加请求将总是成功,即使有其他会话在等待该锁;不管现有的锁和新请求是处在会话级别还是事务级别,这种说法都是真的。 + +和所有PostgreSQL中的锁一样,当前被任何会话所持有的咨询锁的完整列表可以在[`pg_locks`](http://www.postgres.cn/docs/9.6/view-pg-locks.html)系统视图中找到。 + +咨询锁和普通锁都被存储在一个共享内存池中,它的尺寸由[max_locks_per_transaction](http://www.postgres.cn/docs/9.6/runtime-config-locks.html#GUC-MAX-LOCKS-PER-TRANSACTION)和[max_connections](http://www.postgres.cn/docs/9.6/runtime-config-connection.html#GUC-MAX-CONNECTIONS)配置变量定义。 必须当心不要耗尽这些内存,否则服务器将不能再授予任何锁。这对服务器可以授予的咨询锁数量设置了一个上限,根据服务器的配置不同,这个限制通常是数万到数十万。 + +在使用咨询锁方法的特定情况下,特别是查询中涉及显式排序和`LIMIT`子句时,由于 SQL 表达式被计算的顺序,必须小心控制锁的获取。例如: + +``` +SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok +SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- danger! +SELECT pg_advisory_lock(q.id) FROM +( + SELECT id FROM foo WHERE id > 12345 LIMIT 100 +) q; -- ok +``` + +在上述查询中,第二种形式是危险的,因为不能保证在锁定函数被执行之前应用`LIMIT`。这可能导致获得某些应用不期望的锁,并因此在会话结束之前无法释放。 从应用的角度来看,这样的锁将被挂起,虽然它们仍然在`pg_locks`中可见。 + +提供的操作咨询锁函数在[第 9.26.10 节](http://www.postgres.cn/docs/9.6/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS)中描述。 + + + diff --git a/pg/pg-tune-autovacuum.md b/pg/pg-tune-autovacuum.md new file mode 100644 index 0000000..59fd556 --- /dev/null +++ b/pg/pg-tune-autovacuum.md @@ -0,0 +1,141 @@ +# Autovacuum Tuning Basics [译] + +原文:https://blog.2ndquadrant.com/autovacuum-tuning-basics/#PostgreSQL%20Performance%20Tuning + +几个星期前,我介绍了调优检查点的基础知识,在那篇文章中,我还提到了性能问题的第二个常见原因是autovacuum(根据我们在邮件列表和我们的客户支持下看到的)。所以让我跟随这个帖子关于自动调谐的基础知识。我将非常简要地解释必要的理论(死元组,膨胀和autovacuum如何处理它),但是这篇博文的主要重点是调优 - 那里有什么配置选项,经验法则等等。 + +### 死元组 + +首先,让我们简单地解释一下“死亡元组”和“膨胀”(如果你想得到更详细的解释,可以阅读Joe Nelson的文章,详细讨论这个)。 + +当您在PostgreSQL中执行DELETE时,行(又名元组)不会立即从数据文件中删除。相反,它只能通过在行首部中设置xmax字段来标记为已删除。对于UPDATE,也可以看作PostgreSQL中的DELETE + INSERT。 + +这是PostgreSQL MVCC背后的基本思想之一,因为它允许更大的并发性,只需在不同的进程之间进行最小程度的锁定。这个MVCC实现的缺点当然是它留下了被删除的元组,甚至在所有可能看到这些版本的事务完成之后。 + +如果不清理,这些“死元组”(对于任何事务实际上是不可见的)将永远停留在数据文件中,浪费磁盘空间,而对于有许多DELETE和UPDATE的表,死元组可能会占用绝大部分磁盘空间。当然,这些死元组也会被索引引用,进一步增加了浪费的磁盘空间。这就是我们在PostgreSQL中所谓的“膨胀”。当然,查询必须处理的数据越多(即使99%的数据立即被“抛弃”),查询就越慢。 + +### Vacuum和AutoVacuum + +回收死元组占用的空间的最直接的方法是(通过手动运行VACUUM命令)。这个维护命令将扫描表并从表和索引中删除死元组 - 它通常不会将磁盘空间返回给操作系统,但会使其可用于新行。 + +注意:`VACUUM FULL`会回收空间并将其返回给操作系统,但是有一些缺点。首先它获得对表的**独占锁(Access Exclusive)**,阻止所有操作(包括SELECT)。其次,它本质上创建了表的副本,使所需的磁盘空间增加了一倍,所以当磁盘空间已经不足时不太实际。 + +`VACUUM`的麻烦在于它完全是手动操作 - 只有在您决定运行时才会发生,而不是在需要时才会发生。你可以把它放到cron中,每5分钟在所有的表上运行一次,但是大部分运行的机会并不会真正清理任何东西,唯一的影响是CPU和I / O在系统上的使用率会更高。或者你可能每天晚上只运行一次,在这种情况下,你可能会累积更多你想要的死元组。 + +这是我们使用**自动清理(AutoVacuum)**的主要目的;根据需要进行清理,以控制浪费空间的数量。数据库确实知道随着时间的推移生成了多少个死元组(每个事务都报告它删除和更新的元组的数量),当表累积了一定数量的死元组时,可以触发清理工作(默认情况下是20%表,我们将会看到)。所以在繁忙的时候会更频繁地执行,而当数据库大部分空闲的时候则更少。 + +### autoanalyze + +清理死元组不是自动清理的唯一任务。它还负责更新优化程序在规划查询时使用的数据分布统计信息。您可以通过运行ANALYZE来手动收集这些数据,但是会遇到与VACUUM类似的问题 - 您可能会经常运行或不经常运行。 + +解决方案也是类似的 - 数据库可以观察表中有多少行更改,并自动运行ANALYZE。 + +注意:ANALYZE的开销要大一点,因为虽然VACUUM的成本与死元组的数量成正比(开销很小,当死元组数量很少时/没有),ANALYZE必须在每次执行时从头开始重建统计数据。另一方面,如果你不是经常运行它,那么不好的计划选择的代价可能同样严重。 + +为了简洁起见,我会在后面的文章中大部分忽略这个自动清理任务 - 配置与清理非常相似,并且遵循大致相同的推理。 + +### 监控 + +在进行任何调整之前,您需要能够收集相关数据,否则您怎么能说您需要进行任何调整,或评估配置更改的影响? + +换句话说,你应该有一些基本的监控,从数据库中收集指标。为了清理,你至少要看这些值: + + +* `pg_stat_all_tables.n_dead_tup` :每个表中的死元组数(用户表和系统目录) +* `(n_dead_tup / n_live_tup)` : 每个表中死/活元组的比率 +* `(pg_class.relpages / pg_class.reltuples)`: 每行空格 + + +如果您已经部署了监控系统(应该的),那么您很有可能已经收集了这些指标。总体目标是获得稳定的行为,这些指标没有突然或显着的变化。 + +还有一个方便的pgstattuple扩展,允许您对表和索引执行分析,包括计算可用空间量,死元组等。 + +### 调整目标 + +在查看实际配置参数之前,让我们简要讨论一下高级调优目标,即更改参数时要实现的目标: + +清理死元组 - 保持合理低的磁盘空间量,而不是浪费不合理的磁盘空间量,防止索引膨胀并保持查询速度。 +最大限度地减少清理的影响 - 不要经常执行清理工作,因为这会浪费资源(CPU,I / O和RAM),并可能会严重影响性能。 + +也就是说,你需要找到适当的平衡 - 经常运行可能会不够经常运行。天平在很大程度上取决于您所管理的数据量,您正在处理的工作负载类型(DELETE / UPDATE数量)。 + +`postgresql.conf`中的大多数默认值是相当保守的,原因有二。首先,根据当时常见的资源(CPU,RAM,...),默认值是几年前决定的。其次,我们希望默认配置可以在任何地方工作,包括树莓派(Raspberry Pi)或小型VPS服务器等小型机器。对于许多部署(特别是较小的部署和/或处理大部分读取工作负载),默认的配置参数将工作得很好。 + +随着数据库大小和/或写入数量的增加,问题开始出现。典型的问题是清理不经常发生,当发生这种情况时会大大地干扰性能,因为它必须处理大量的垃圾。如果这些情况你应该遵循这个简单的规则: + +* 如果它造成了损失,你不会经常这样做。 + + +* 也就是说,调整参数,使得清理更频繁,每次处理更少量的死元组。 + +注意:人们有时遵循不同的规则 - 如果伤害了,不要这样做。 - 完全禁用自动清理。除非你真的(真的)知道你在做什么,并且有定期的清理脚本,否则请不要这样做。否则,你正在画一个角落,而不是性能有些下降,你将不得不面对严重退化的表现,甚至可能是停机。 + +所以,现在我们知道我们想要通过调谐实现什么,让我们看看配置参数... + +### 阈值和比例因子 + +当然,你可能会调整的第一件事情是当清理被触发,这是受两个参数的影响: + +autovacuum_vacuum_threshold = 50 +autovacuum_vacuum_scale_factor = 0.2 +并且每当无用元组的数目(你可以看到pg_stat_all_tables.n_dead_tup)超过 + +阈值+ pg_class.reltuples * scale_factor + +该表将被视为需要清理。该公式基本上说,高达20%的表可能是清理前的死元组(50行的阈值是为了防止对小表进行非常频繁的清理)。 + +默认比例因子适用于中小型的表格,但对于非常大型的表格来说效果并不理想 - 在10GB的表格上,这大概是2GB的死元组,而在1TB的表格上则是200GB。 + +这是一个积累了大量死元组的例子,并且一次处理所有的元组,这将会受到伤害。根据之前提到的规则,解决方案是通过显着降低比例因子来更频繁地执行此操作,甚至可能是这样: + +autovacuum_vacuum_scale_factor = 0.01 + +这将限制仅减少到表格的1%。另一种解决方法是完全放弃比例因子,仅使用阈值 + +autovacuum_vacuum_scale_factor = 0 +autovacuum_vacuum_threshold = 10000 + +这应该在产生10000个死元组之后触发清理。 + +一个麻烦的问题是postgresql.conf中的这些变化会影响到所有表(实际上是整个集群),并且可能会对小型表(包括系统目录)的清理产生不利影响。 + +当小桌子被更频繁地清理时,最简单的解决办法就是完全忽略这个问题。小桌子的清理将是相当便宜的,并改善工人数量 + +还没有提到的一个配置选项是autovacuum_max_workers,那是什么意思?那么,清理不会发生在一个自动清理过程中,但是数据库可以启动到实际清理不同数据库/表的autovacuum_max_workers进程。 + +这很有用,因为例如你不想停止清理小表,直到完成一个大表的清理(这可能需要相当多的时间,因为节流)。 + +麻烦的是用户假定工作人员的数量与可能发生的清理量成正比。如果将自动清扫工人的数量增加到6人,那么与默认的3人相比,工作量肯定会增加一倍,对吧? + +那么,不。几段前面描述的成本上限是全球性的,所有的汽车真空工人都是共同的。每个工作进程只获得总成本限制的1 / autovacuum_max_workers,所以增加工作者数量只会使他们变慢。 + +这有点像高速公路 - 车辆数量增加一倍,但速度减半,只能让你每小时到达目的地的人数相同。 + +所以如果你的数据库的清理跟不上用户的活动,那么增加工作者的数量并不是一个解决方案,除非你也调整了其他的参数。 + +### 按表限流 + +实际上,当我说成本限制是全局的,所有的自动清理Worker共同分担的时候,我一直在说谎。与缩放因子和阈值类似,可以设置每个表的成本限制和延迟: + +```sql +ALTER TABLE t SET (autovacuum_vacuum_cost_limit = 1000); +ALTER TABLE t SET (autovacuum_vacuum_cost_delay = 10); +``` + +全球成本计算中不包括处理这些表的工人,而是独立地进行限制。 + +这给了你相当多的灵活性和权力,但不要忘记 - 拥有巨大的权力是很大的责任! + +在实践中,我们几乎从不使用这个功能,有两个基本的原因。首先,您通常希望在后台清理上使用单个全局限制。其次,多个工人有时被扼杀在一起,有时独立地使监督和分析系统的行为变得更加困难。 + +### 概要 + +所以这就是你调整自动清理的方法。如果我必须把它归纳成几条基本规则,那就是这五条: + +* 说真的,不要禁用autovacuum,除非你真的知道你在做什么。 +* 在繁忙的数据库上(做大量更新和删除),特别是大数据库,你可能应该减小比例因子,这样更频繁地进行清理。 +* 在合理的硬件(良好的存储,多核心)上,你应该增加节流参数,以便清理能够跟上。 +* 单独增加autovacuum_max_workers在大多数情况下并不会真的有帮助。你会得到更多的进程变慢。 +* 你可以使用ALTER TABLE来设置每个表的参数,但是如果你真的需要的话,可以考虑一下。这使得系统更复杂,更难以检查。 + +我原本包括几个部分,解释什么时候autovacuum没有真正的工作,以及如何检测它们(以及什么是最好的解决方案),但博客文章已经太长了,所以我会在几天后分开发布。 \ No newline at end of file diff --git a/pg/pg-tune-checkpoint.md b/pg/pg-tune-checkpoint.md new file mode 100644 index 0000000..a07403b --- /dev/null +++ b/pg/pg-tune-checkpoint.md @@ -0,0 +1,151 @@ +# Checkpoints Tuning Basics [译] + +原文地址:https://blog.2ndquadrant.com/basics-of-tuning-checkpoints/ + +在进行不重复的写操作的系统上,调优检查点对于获得良好性能至关重要。然而,检查点是我们经常在社区邮件列表和我们客户的性能调整评估过程中发现混淆和配置问题的领域之一。 (另外一个是autovacuum,前几天由Citus的Joe Nelson讨论过。)那么让我来引导你通过检查点 - 他们做什么以及如何在PostgreSQL中调整它们。 + +## 什么是检查点? + +PostgreSQL是依赖于预写日志(WAL)的数据库之一 - 所有的改变都会先写入一个日志(一系列更改),然后再写入数据文件。这提供了持久性,因为在发生崩溃的情况下,数据库可能会使用WAL来执行恢复 - 从WAL读取更改并将其重新应用于数据文件。 + +虽然这可能会使写入量增加一倍,但实际上可能会提高性能。用户只需等待WAL(刷新到磁盘),而数据文件只在内存中修改,然后在后台刷新。这很好,因为虽然WAL写入本质上是连续的,但写入数据文件通常是随机的。 + +假设系统崩溃,数据库需要执行恢复。最简单的方法是从头开始,从头开始重放整个WAL。最后,我们应该得到一个完整和正确的数据库。缺点当然是需要保留和重放整个WAL。我们经常处理的数据库不是非常大(比如几百GB),但是每天产生几个TB的WAL。所以想象在运行一年的数据库上,需要多少磁盘空间来保持所有的WAL,以及在恢复过程中需要多少时间来重放。 + +但是如果数据库保证在给定的WAL位置(wal_lsn)处的所有数据变更都已经落盘。那么就可以在恢复期间确定这个位置,并仅重播WAL的剩余部分,从而显着减少恢复时间。而且它也可以移除“已知的安全点”之前的WAL。 + +这正是检查点的目的 - 确保WAL在某个时间点之前不再需要进行恢复,从而减少磁盘空间要求和恢复时间。 + +*注意:如果你碰巧是一个玩家,你可能对检查点的概念很熟悉 - 你的角色通过了游戏中的某个点,如果你没有击败下一个老板或掉入一个湖泊热熔岩,你从最后一点,而不是从一开始就开始。让我们看看如何在PostgreSQL中实现这一点;-)* + +还有另外一个极端 :非常频繁的检查点(例如每隔一秒钟)。这样可以只保留微量的WAL,恢复极快(不得不重播只有微小的WAL量)。但它也会从异步写入数据页面退化为同步写入,严重影响用户(例如增加COMMIT延迟,减少吞吐量)。 + +所以在实践中通常希望检查点不要频繁到影响用户,但又足够频繁以限制恢复和磁盘空间需求的时间。 + + + + + +## 触发检查点 + +有三到四个原因可以触发检查点: + +* 直接执行CHECKPOINT命令 +* 执行需要检查点的命令(例如,pg_start_backup,CREATE DATABASE或pg_ctl stop | restart等) +* 达到自上次检查点以来的配置时间量 +* 从上一个检查点(又名“用完WAL”或“填充WAL”)生成配置的WAL量 + +前两点在这里相当不重要 - 那些是罕见的,手动触发的事件。这篇博文是关于如何配置其他两个事件,影响常规定期检查点。 + +这些时间/大小限制是使用两个配置选项设置的: + + + +* `checkpoint_timeout = 5min` +* `max_wal_size = 1GB`(在PostgreSQL 9.5之前是`checkpoint_segments`) + +使用这些(默认)值,PostgreSQL将每5分钟触发一次CHECKPOINT,或者在WAL增长到磁盘上大约1GB之后。 + +注意:max_wal_size是总WAL大小的软限制,这有两个结果。首先,数据库将尽量不超过它,但是被允许,因此在分区上保留足够的空闲空间并监视它。其次,这不是一个“每个检查点”的限制 - 由于扩展检查点(稍后解释),WAL配额被分为2-3个检查点。因此,根据checkpoint_completion_target,使用max_wal_size,数据库将在写入300-500 MB的WAL之后启动CHECKPOINT。 + +默认值相当低,就像示例配置文件中的大多数其他默认值一样,即使在像Raspberry Pi这样的小型系统上也可以工作。 + +但是,如何确定您的系统的良好价值?我们的目标不是过分频繁地或不经常地检查点,而我们的调整“最佳实践”包括两个步骤: + +* 选择一个“合理”的`checkpoint_timeout`值 +* 设置`max_wal_size`足够高,很少到达 + +很难说checkpoint_timeout的“合理”值是多少,因为它取决于恢复时间目标(RTO),即什么是可接受的最大恢复持续时间。 + +但是,这有点棘手,因为checkpoint_timeout是生成WAL需要多长时间的限制,而不是直接在恢复时间上。而遗憾的是,无法确切说明需要多长时间才能恢复。 WAL通常由多个进程(运行DML)生成,而恢复则由单个进程执行(这种限制大多是固有的,不可能很快消失)。这不仅会影响本地恢复,还会影响到流式复制到备用数据库。当然,本地恢复通常在重启后立即发生,当文件系统缓存很冷时。 + +但一般来说,默认值(5分钟)是相当低的,30分钟到1小时之间的值是相当普遍的。 PostgreSQL 9.6甚至将最大值增加到1天(所以有些黑客认为这是一个好的主意)。由于整页写入,低值也可能导致写入放大(我不打算在这里讨论)。 + +假设我们已经决定使用30分钟。 + +``` +checkpoint_timeout = 30min +``` + +现在我们需要估计数据库在30分钟内产生多少WAL,以便我们可以使用它来获得max_wal_size。 有几种方法可以确定生成多少WAL: + +* 使用`pg_current_xlog_insert_location()` (10之后是`pg_current_wal_insert_lsn`)查看实际的WAL位置(基本上在文件中偏移),并计算每30分钟测量的位置之间的差异。 +* 启用`log_checkpoints = on`,然后从服务器日志中提取信息(每个完成的检查点都会有详细的统计信息,包括WAL的数量)。 +* 使用来自pg_stat_bgwriter的数据,其中也包含关于检查点数量的信息(可以结合当前max_wal_size值的知识)。 + +例如,我们使用第一种方法。 在运行pgbench的测试机上,我看到: + +```bash +postgres=# SELECT pg_current_xlog_insert_location(); + pg_current_xlog_insert_location +--------------------------------- + 3D/B4020A58 +(1 row) + +... after 5 minutes ... + +postgres=# SELECT pg_current_xlog_insert_location(); + pg_current_xlog_insert_location +--------------------------------- + 3E/2203E0F8 +(1 row) + +postgres=# SELECT pg_xlog_location_diff('3E/2203E0F8', '3D/B4020A58'); + pg_xlog_location_diff +----------------------- + 1845614240 +(1 row) +``` + +这表明在5分钟内,数据库生成了〜1.8GB的WAL,因此`checkpoint_timeout = 30min`将会是约10GB的WAL。不过如前所述,max_wal_size是2 - 3个检查点的配额,所以max_wal_size = 30GB(3 x 10GB)似乎是正确的。 + +其他方法使用不同的数据来源,但想法是相同的。 + +## 传播检查点 + +我建议你需要调整`checkpoint_timeout`和`max_wal_size`,但我并没有说出全部的真相。还有另一个参数叫做`checkpoint_completion_target`。但要调整它,你需要了解“传播检查点”是什么意思。 + +但在CHECKPOINT期间,数据库需要执行以下三个基本步骤: + +* 识别共享缓冲区中的所有脏(已修改)块 +* 将所有这些缓冲区写入磁盘(或者写入文件系统缓存) +* `fsync`将所有修改后的文件保存到磁盘 + +只有当所有这些步骤完成后,检查点才算完成。您可以尽可能快地完成这些步骤,即一次性写入所有脏缓冲区,然后在文件上调用fsync,实际上这就是直到PostgreSQL 8.2一直做的事情。但是由于填充文件系统缓存、喂饱设备会导致I/O延迟,并影响用户会话。 + +为了解决这个问题,PostgreSQL 8.3引入了“扩展检查点”的概念,而不是一次写入所有的数据,写入的时间很长。这使得操作系统有时间在后台刷新脏数据,使得最终的fsync开销更小。 + +这些写入是基于进入下一个检查点的进度而被限制的, 数据库知道下一个检查点还剩多少时间/WAL空间,并计算出应当写出多少个缓冲区。然而数据库一直拖到最后时刻才写入。这意味着最后一批写入仍然在文件系统缓存中,使得最终的fsync()调用(在开始下一个检查点之前发出)再次开销巨大。 + +所以数据库需要留出足够的时间,以便脏数据在后台刷新到磁盘 - 而页缓存(Linux文件系统缓存)的到期通常是由时间驱动的,特别是通过这个内核参数: + +`vm.dirty_expire_centisecs = 3000` + +这表示数据在30秒后过期(默认情况下)。 + +注意:当涉及到内核参数时,调整`vm.dirty_background_bytes`很重要。在具有大量内存的系统上,默认值太高,从而使内核在文件系统缓存中积累了大量脏数据。内核通常决定一次刷新它们,减少扩展检查点的好处。 + +现在回到`checkpoint_completion_target = 0.5`。这个配置参数表示如果所有的写操作都完成的话,到下一个检查点有多远。例如,假设检查点仅由checkpoint_timeout = 5min触发,数据库将会限制写入操作,以便在2.5分钟后完成最后一次写入操作。然后操作系统又有2.5分钟将数据刷新到磁盘,这样5分钟后发出的fsync呼叫廉价又快捷。 + +当然,离开系统2.5分钟可能看起来过多,考虑到到期超时只有30秒。您可能会增加`checkpoint_completion_target`例如到0.85这将使系统大约45秒,比它需要的30秒多一点。这并不是推荐的,因为在密集写入的情况下,检查点可能比5分钟之后更早地被max_wal_size触发,使操作系统少于30秒。 + +但是,处理写入密集型工作负载的系统不太可能运行更高的checkpoint_timeouts值,使默认的completion_target值肯定太低。例如,如果将超时设置为30分钟,则会强制数据库在前15分钟内完成所有写入(以写入速率的两倍),然后闲置15分钟。 + +相反,您可以尝试使用此公式粗略设置checkpoint_completion_target + +`(checkpoint_timeout - 2min)/ checkpoint_timeout` + +其中30分钟约为0.93。有时候建议不要超过0.9--这可能是好的,你不可能观察到这两个值之间的任何显着差异。 (当使用非常高的checkpoint_timeout值时,这可能会改变,PostgreSQL 9.6现在可以达到1天)。 + + + +## 总结 + +所以现在你应该知道检查点的目的是什么,也是调整它们的基础。总结一下: + +* 大多数检查点应该是基于时间的,即由checkpoint_timeout触发 + * 性能(不频繁的检查点)和恢复所需的时间(频繁的检查点) + * 15-30分钟之间的数值是最常见的,上升到1h也不会有什么不好的,9.6甚至能设置为1天。 +* 在决定超时之后,通过估计WAL的数量来选择`max_wal_size` +* 设置`checkpoint_completion_target`,以便内核有足够的时间将数据刷新到磁盘(但不是非常多) +* 还要调整`vm.dirty_background_bytes`来防止内核在页面缓存中积累大量脏数据 \ No newline at end of file diff --git a/pg/pg-tune-memory.md b/pg/pg-tune-memory.md new file mode 100644 index 0000000..4823a27 --- /dev/null +++ b/pg/pg-tune-memory.md @@ -0,0 +1,23 @@ +# PostgreSQL Memory Tunning + +— effective_cache_size(integer) + +设置计划程序关于可用于单个查询的磁盘高速缓存的有效大小的假设。 这是估算使用指数成本的因素; 更高的值使得更有可能使用索引扫描,更低的值使得更有可能使用顺序扫描。 当设置这个参数时,你应该考虑PostgreSQL的共享缓冲区和内核磁盘缓存中将用于PostgreSQL数据文件的部分。 另外,考虑到不同表上的并发查询的预期数量,因为它们将不得不共享可用空间。 此参数对PostgreSQL分配的共享内存大小没有影响,也不会保留内核磁盘缓存; 它仅用于估计目的。 系统也不会假定数据在查询之间保留在磁盘缓存中。 默认值是4千兆字节(4GB)。 + +25% ~ 40% mem + + + +`shared_buffers` (`integer`) + +设置数据库服务器将使用的共享内存缓冲区量。默认通常是 128 兆字节(`128MB`),但是如果你的内核设置不支持(在initdb时决定),那么可以会更少。这个设置必须至少为 128 千字节(`BLCKSZ`的非默认值将改变最小值)。不过为了更好的性能,通常会使用明显高于最小值的设置。 + +如果有一个专用的 1GB 或更多内存的数据库服务器, 一个合理的`shared_buffers`开始值是系统内存的 25%。 即使很大的`shared_buffers`有效, 也会造成一些工作负载, 但因为PostgreSQL同样依赖操作系统的高速缓冲区, 将`shared_buffers`设置为超过 40% 的RAM不太可能比一个小点值工作得更好。 为了能把对写大量新的或改变的数据的处理分布在一个较长的时间段内, `shared_buffers`更大的 设置通常要求对`max_wal_size`也做相应增加。 + +如果系统内存小于 1GB,一个较小的 RAM 百分数是合适的,这样可以为操作系统留下足够的空间。 同时,在 Windows 上,`shared_buffers`设置得较大也不一定有效。你会发现保持相对低的设置并且更多使用操作系统高速缓存会得到更好的结果。Windows 上可用的`shared_buffers`值通常是从 64MB 到 512 MB。 + + + +`effective_cache_size` (`integer`) + +设置规划器对一个单一查询可用的有效磁盘缓冲区尺寸的假设。这个参数会被考虑在使用一个索引的代价估计中,更高的数值会使得索引扫描更可能被使用,更低的数值会使得顺序扫描更可能被使用。在设置这个参数时,你还应该考虑PostgreSQL的共享缓冲区以及将被用于PostgreSQL数据文件的内核磁盘缓冲区。另外,还要考虑预计在不同表上的并发查询数目,因为它们必须共享可用的空间。这个参数对PostgreSQL分配的共享内存尺寸没有影响,它也不会保留内核磁盘缓冲,它只用于估计的目的。系统也不会假设在查询之间数据会保留在磁盘缓冲中。默认值是 4吉字节(`4GB`)。 \ No newline at end of file