From b258b24067a7a8f01f8192ab34d5f9735af36e2e Mon Sep 17 00:00:00 2001 From: Gang Yin Date: Tue, 24 Aug 2021 09:22:32 +0800 Subject: [PATCH] translation updates for chapter 12 --- ch12.md | 178 ++++++++++++++++++++++++-------------------------- zh-tw/ch12.md | 176 ++++++++++++++++++++++++------------------------- 2 files changed, 173 insertions(+), 181 deletions(-) diff --git a/ch12.md b/ch12.md index a5faa22..9720bae 100644 --- a/ch12.md +++ b/ch12.md @@ -50,9 +50,9 @@ #### 衍生数据与分布式事务 -​ 保持不同数据系统彼此一致的经典方法涉及分布式事务,如“[原子提交与两阶段提交(2PC)](ch9.md#原子提交与两阶段提交(2PC))”中所述。与分布式事务相比,使用衍生数据系统的方法如何? +​ 保持不同数据系统彼此一致的经典方法涉及分布式事务,如“[原子提交与两阶段提交](ch9.md#原子提交与两阶段提交)”中所述。与分布式事务相比,使用衍生数据系统的方法如何? -​ 在抽象层面,它们通过不同的方式达到类似的目标。分布式事务通过**锁**进行互斥来决定写入的顺序(请参阅“[两阶段锁定(2PL)](ch7.md#两阶段锁定(2PL))”),而CDC和事件溯源使用日志进行排序。分布式事务使用原子提交来确保变更只生效一次,而基于日志的系统通常基于**确定性重试**和**幂等性**。 +​ 在抽象层面,它们通过不同的方式达到类似的目标。分布式事务通过**锁**进行互斥来决定写入的顺序(请参阅“[两阶段锁定](ch7.md#两阶段锁定)”),而CDC和事件溯源使用日志进行排序。分布式事务使用原子提交来确保变更只生效一次,而基于日志的系统通常基于**确定性重试**和**幂等性**。 ​ 最大的不同之处在于事务系统通常提供[线性一致性](ch9.md#线性一致性),这包含着有用的保证,例如[读己之写](ch5.md#读己之写)。另一方面,衍生数据系统通常是异步更新的,因此它们默认不会提供相同的时序保证。 @@ -412,25 +412,25 @@ ​ 对于只读取数据的无状态服务,出问题也没什么大不了的:你可以修复该错误并重启服务,而一切都恢复正常。像数据库这样的有状态系统就没那么简单了:它们被设计为永远记住事物(或多或少),所以如果出现问题,这种(错误的)效果也将潜在地永远持续下去,这意味着它们需要更仔细的思考【50】。 -​ 我们希望构建可靠且**正确**的应用(即使面对各种故障,程序的语义也能被很好地定义与理解)。约四十年来,原子性,隔离性和持久性([第七章](ch7.md))等事务特性一直是构建正确应用的首选工具。然而这些地基没有看上去那么牢固:例如弱隔离级别带来的困惑可以佐证(请参阅“[弱隔离级别](ch7.md#弱隔离级别)”)。 +​ 我们希望构建可靠且**正确**的应用(即使面对各种故障,程序的语义也能被很好地定义与理解)。约四十年来,原子性、隔离性和持久性([第七章](ch7.md))等事务特性一直是构建正确应用的首选工具。然而这些地基没有看上去那么牢固:例如弱隔离级别带来的困惑可以佐证(请参阅“[弱隔离级别](ch7.md#弱隔离级别)”)。 -​ 事务在某些领域被完全抛弃,并被提供更好性能与可伸缩性的模型取代,但更复杂的语义(例如,请参阅“[无领导者复制](ch5.md#无领导者复制)”)。**一致性(Consistency)** 经常被谈起,但其定义并不明确(“[一致性](ch5.md#一致性)”和[第九章](ch9.md))。有些人断言我们应当为了高可用而“拥抱弱一致性”,但却对这些概念实际上意味着什么缺乏清晰的认识。 +​ 事务在某些领域被完全抛弃,并被提供更好性能与可伸缩性的模型取代,但后者有更复杂的语义(例如,请参阅“[无主复制](ch5.md#无主复制)”)。**一致性(Consistency)** 经常被谈起,但其定义并不明确(请参阅“[一致性](ch7.md#一致性)”和[第九章](ch9.md))。有些人断言我们应当为了高可用而“拥抱弱一致性”,但却对这些概念实际上意味着什么缺乏清晰的认识。 ​ 对于如此重要的话题,我们的理解,以及我们的工程方法却是惊人地薄弱。例如,确定在特定事务隔离等级或复制配置下运行特定应用是否安全是非常困难的【51,52】。通常简单的解决方案似乎在低并发性的情况下工作正常,并且没有错误,但在要求更高的情况下却会出现许多微妙的错误。 -​ 例如,凯尔金斯伯里(Kyle Kingsbury)的杰普森(Jepsen)实验【53】标出了一些产品声称的安全保证与其在网络问题与崩溃时的实际行为之间的明显差异。即使像数据库这样的基础设施产品没有问题,应用代码仍然需要正确使用它们提供的功能才行,如果配置很难理解,这是很容易出错的(在这种情况下指的是弱隔离级别,法定人数配置等)。 +​ 例如,Kyle Kingsbury的Jepsen实验【53】标出了一些产品声称的安全保证与其在网络问题与崩溃时的实际行为之间的明显差异。即使像数据库这样的基础设施产品没有问题,应用代码仍然需要正确使用它们提供的功能才行,如果配置很难理解,这是很容易出错的(在这种情况下指的是弱隔离级别,法定人数配置等)。 -​ 如果你的应用可以容忍偶尔的崩溃,以及以不可预料的方式损坏或丢失数据,那生活就要简单得多,而你可能只要双手合十念阿弥陀佛,期望佛祖能保佑最好的结果。另一方面,如果你需要更强的正确性保证,那么可序列化与原子提交就是久经考验的方法,但它们是有代价的:它们通常只在单个数据中心中工作(排除地理散布式架构),并限制了系统能够实现的规模与容错特性。 +​ 如果你的应用可以容忍偶尔的崩溃,以及以不可预料的方式损坏或丢失数据,那生活就要简单得多,而你可能只要双手合十念阿弥陀佛,期望佛祖能保佑最好的结果。另一方面,如果你需要更强的正确性保证,那么可串行化与原子提交就是久经考验的方法,但它们是有代价的:它们通常只在单个数据中心中工作(这就排除了地理位置分散的架构),并限制了系统能够实现的规模与容错特性。 -​ 虽然传统的事务方法并没有走远,但我也相信在使应用正确而灵活地处理错误方面上,事务并不是最后的遗言。在本节中,我将提出一些在数据流架构中考量正确性的方式。 +​ 虽然传统的事务方法并没有走远,但我也相信在使应用正确而灵活地处理错误方面上,事务也不是最后一个可以谈的。在本节中,我将提出一些在数据流架构中考量正确性的方式。 -### 数据库端到端的争论 +### 数据库的端到端原则 -​ 应用仅仅是使用具有相对较强安全属性的数据系统(例如可序列化的事务),并不意味着就可以保证没有数据丢失或损坏。例如,如果某个应用有个Bug,导致它写入不正确的数据,或者从数据库中删除数据,那么可序列化的事务也救不了你。 +​ 仅仅因为一个应用程序使用了具有相对较强安全属性的数据系统(例如可串行化的事务),并不意味着就可以保证没有数据丢失或损坏。例如,如果某个应用有个Bug,导致它写入不正确的数据,或者从数据库中删除数据,那么可串行化的事务也救不了你。 -​ 这个例子可能看起来很无聊,但值得认真对待:应用会出Bug,而人也会犯错误。我在“[状态,流与不可变性](ch11.md#状态,流与不可变性)”中使用了这个例子来支持不可变和仅追加的数据,阉割掉错误代码摧毁良好数据的能力,能让从错误中恢复更为容易。 +​ 这个例子可能看起来很无聊,但值得认真对待:应用会出Bug,而人也会犯错误。我在“[状态、流和不变性](ch11.md#状态、流和不变性)”中使用了这个例子来支持不可变和仅追加的数据,阉割掉错误代码摧毁良好数据的能力,能让从错误中恢复更为容易。 -​ 虽然不变性很有用,但它本身并非万灵药。让我们来看一个可能发生的,非常微妙的数据损坏案例。 +​ 虽然不变性很有用,但它本身并非万灵药。让我们来看一个可能发生的、非常微妙的数据损坏案例。 #### 正好执行一次操作 @@ -438,13 +438,13 @@ ​ 处理两次是数据损坏的一种形式:为同样的服务向客户收费两次(收费太多)或增长计数器两次(夸大指标)都不是我们想要的。在这种情况下,恰好一次意味着安排计算,使得最终效果与没有发生错误的情况一样,即使操作实际上因为某种错误而重试。我们先前讨论过实现这一目标的几种方法。 -​ 最有效的方法之一是使操作**幂等(idempotent)**(请参阅“[幂等性](ch11.md#幂等性)”);即确保它无论是执行一次还是执行多次都具有相同的效果。但是,将不是天生幂等的操作变为幂等的操作需要一些额外的努力与关注:你可能需要维护一些额外的元数据(例如更新了值的操作ID集合),并在从一个节点故障切换至另一个节点时做好防护(请参阅的“[领导与锁定](ch9.md#领导与锁定)”)。 +​ 最有效的方法之一是使操作**幂等(idempotent)**(请参阅“[幂等性](ch11.md#幂等性)”);即确保它无论是执行一次还是执行多次都具有相同的效果。但是,将不是天生幂等的操作变为幂等的操作需要一些额外的努力与关注:你可能需要维护一些额外的元数据(例如更新了值的操作ID集合),并在从一个节点故障切换至另一个节点时做好防护(请参阅“[领导者和锁](ch8.md#领导者和锁)”)。 #### 抑制重复 -​ 除了流处理之外,其他许多地方也需要抑制重复的模式。例如,TCP使用数据包上的序列号,在接收方将它们正确排序。并确定网络上是否有数据包丢失或重复。任何丢失的数据包都会被重新传输,而在将数据交付应用前,TCP协议栈会移除任何重复数据包。 +​ 除了流处理之外,其他许多地方也需要抑制重复的模式。例如,TCP使用了数据包上的序列号,以便接收方可以将它们正确排序,并确定网络上是否有数据包丢失或重复。在将数据交付应用前,TCP协议栈会重新传输任何丢失的数据包,也会移除任何重复的数据包。 -​ 但是,这种重复抑制仅适用于单条TCP连接的场景中。假设TCP连接是一个客户端与数据库的连接,并且它正在执行[例12-1]()中的事务。在许多数据库中,事务是绑定在客户端连接上的(如果客户端发送了多个查询,数据库就知道它们属于同一个事务,因为它们是在同一个TCP连接上发送的)。如果客户端在发送`COMMIT`之后但在从数据库服务器收到响应之前遇到网络中断与连接超时,客户端是不知道事务是否已经被提交的([图8-1](img/fig8-1.png))。 +​ 但是,这种重复抑制仅适用于单条TCP连接的场景中。假设TCP连接是一个客户端与数据库的连接,并且它正在执行[例12-1]()中的事务。在许多数据库中,事务是绑定在客户端连接上的(如果客户端发送了多个查询,数据库就知道它们属于同一个事务,因为它们是在同一个TCP连接上发送的)。如果客户端在发送`COMMIT`之后并在从数据库服务器收到响应之前遇到网络中断与连接超时,客户端是不知道事务是否已经被提交的([图8-1](img/fig8-1.png))。 **例12-1 资金从一个账户到另一个账户的非幂等转移** @@ -455,17 +455,17 @@ BEGIN TRANSACTION; COMMIT; ``` -​ 客户端可以重连到数据库并重试事务,但现在现在处于TCP重复抑制的范围之外了。因为[例12-1]()中的事务不是幂等的,可能会发生转了\$22而不是期望的\$11。因此,尽管[例12-1]()是一个事务原子性的标准样例,但它实际上并不正确,而真正的银行并不会这样办事【3】。 +​ 客户端可以重连到数据库并重试事务,但现在已经处于TCP重复抑制的范围之外了。因为[例12-1]()中的事务不是幂等的,可能会发生转了\$22而不是期望的\$11。因此,尽管[例12-1]()是一个事务原子性的标准样例,但它实际上并不正确,而真正的银行并不会这样办事【3】。 -​ 两阶段提交(请参阅“[原子提交与两阶段提交(2PC)](ch9.md#原子提交与两阶段提交(2PC))”)协议会破坏TCP连接与事务之间的1:1映射,因为它们必须在故障后允许事务协调器重连到数据库,告诉数据库将存疑事务提交还是中止。这足以确保事务只被恰好执行一次吗?不幸的是,并不能。 +​ 两阶段提交(请参阅“[原子提交与两阶段提交](ch9.md#原子提交与两阶段提交)”)协议会破坏TCP连接与事务之间的1:1映射,因为它们必须在故障后允许事务协调器重连到数据库,告诉数据库将存疑事务提交还是中止。这足以确保事务只被恰好执行一次吗?不幸的是,并不能。 ​ 即使我们可以抑制数据库客户端与服务器之间的重复事务,我们仍然需要担心终端用户设备与应用服务器之间的网络。例如,如果终端用户的客户端是Web浏览器,则它可能会使用HTTP POST请求向服务器提交指令。也许用户正处于一个信号微弱的蜂窝数据网络连接中,它们成功地发送了POST,但却在能够从服务器接收响应之前没了信号。 -​ 在这种情况下,可能会向用户显示错误消息,而他们可能会手动重试。 Web浏览器警告说,“你确定要再次提交这个表单吗?” —— 用户选“是”,因为他们希望操作发生。 (Post/Redirect/Get模式【54】可以避免在正常操作中出现此警告消息,但POST请求超时就没办法了。)从Web服务器的角度来看,重试是一个独立的请求,而从数据库的角度来看,这是一个独立的事务。通常的除重机制无济于事。 +​ 在这种情况下,可能会向用户显示错误消息,而他们可能会手动重试。 Web浏览器警告说,“你确定要再次提交这个表单吗?” —— 用户选“是”,因为他们希望操作发生。 (Post/Redirect/Get模式【54】可以避免在正常操作中出现此警告消息,但POST请求超时就没办法了。)从Web服务器的角度来看,重试是一个独立的请求;从数据库的角度来看,这是一个独立的事务。通常的除重机制无济于事。 #### 操作标识符 -​ 要在通过几跳的网络通信上使操作具有幂等性,仅仅依赖数据库提供的事务机制是不够的 —— 你需要考虑**端到端(end-to-end)** 的请求流。 +​ 要在通过几跳的网络通信上使操作具有幂等性,仅仅依赖数据库提供的事务机制是不够的 —— 你需要考虑**端到端(end-to-end)**的请求流。 ​ 例如,你可以为操作生成一个唯一的标识符(例如UUID),并将其作为隐藏表单字段包含在客户端应用中,或通过计算所有表单相关字段的散列来生成操作ID 【3】。如果Web浏览器提交了两次POST请求,这两个请求将具有相同的操作ID。然后,你可以将该操作ID一路传递到数据库,并检查你是否曾经使用给定的ID执行过一个操作,如[例12-2]()中所示。 **例12-2 使用唯一ID来抑制重复请求** @@ -474,41 +474,42 @@ COMMIT; ALTER TABLE requests ADD UNIQUE (request_id); BEGIN TRANSACTION; - INSERT INTO requests(request_id, from_account, to_account, amount) - VALUES('0286FDB8-D7E1-423F-B40B-792B3608036C', 4321, 1234, 11.00); + INSERT INTO requests + (request_id, from_account, to_account, amount) + VALUES('0286FDB8-D7E1-423F-B40B-792B3608036C', 4321, 1234, 11.00); UPDATE accounts SET balance = balance + 11.00 WHERE account_id = 1234; UPDATE accounts SET balance = balance - 11.00 WHERE account_id = 4321; COMMIT; ``` -​ [例12-2]()依赖于`request_id`列上的唯一约束。如果一个事务尝试插入一个已经存在的ID,那么`INSERT`失败,事务被中止,使其无法生效两次。即使在较弱的隔离级别下,关系数据库也能正确地维护唯一性约束(而在“[写入偏斜与幻读](ch7.md#写入偏斜与幻读)”中讨论过,应用级别的**检查-然后-插入**可能会在不可序列化的隔离下失败)。 +​ [例12-2]()依赖于`request_id`列上的唯一约束。如果一个事务尝试插入一个已经存在的ID,那么`INSERT`失败,事务被中止,使其无法生效两次。即使在较弱的隔离级别下,关系数据库也能正确地维护唯一性约束(而在“[写入偏斜与幻读](ch7.md#写入偏斜与幻读)”中讨论过,应用级别的**检查-然后-插入**可能会在不可串行化的隔离下失败)。 -​ 除了抑制重复的请求之外,[例12-2]()中的请求表表现得就像一种事件日志,提示向着事件溯源的方向(请参阅“[事件溯源](ch11.md#事件溯源)”)。更新账户余额事实上不必与插入事件发生在同一个事务中,因为它们是冗余的,而能由下游消费者从请求事件中衍生出来 —— 只要该事件被恰好处理一次,这又一次可以使用请求ID来强制执行。 +​ 除了抑制重复的请求之外,[例12-2]()中的请求表表现得就像一种事件日志,暗示着事件溯源的想法(请参阅“[事件溯源](ch11.md#事件溯源)”)。更新账户余额事实上不必与插入事件发生在同一个事务中,因为它们是冗余的,而能由下游消费者从请求事件中衍生出来 —— 只要该事件被恰好处理一次,这又一次可以使用请求ID来强制执行。 -**端到端的原则** +#### 端到端原则 -​ 抑制重复事务的这种情况只是一个更普遍的原则的一个例子,这个原则被称为**端到端的原则(end-to-end argument)**,它在1984年由Saltzer,Reed和Clark阐述【55】: +​ 抑制重复事务的这种情况只是一个更普遍的原则的一个例子,这个原则被称为**端到端原则(end-to-end argument)**,它在1984年由Saltzer,Reed和Clark阐述【55】: -> ​ 只有在通信系统两端应用的知识与帮助下,所讨论的功能才能完全地正确地实现。因而将这种被质疑的功能作为通信系统本身的功能是不可能的。 (有时,通信系统可以提供这种功能的不完备版本,可能有助于提高性能) +> ​ 只有在通信系统两端应用的知识与帮助下,所讨论的功能才能完全地正确地实现。因而将这种被质疑的功能作为通信系统本身的功能是不可能的。 (有时,通信系统可以提供这种功能的不完备版本,可能有助于提高性能。) > ​ 在我们的例子中**所讨论的功能**是重复抑制。我们看到TCP在TCP连接层次抑制了重复的数据包,一些流处理器在消息处理层次提供了所谓的恰好一次语义,但这些都无法阻止当一个请求超时时,用户亲自提交重复的请求。TCP,数据库事务,以及流处理器本身并不能完全排除这些重复。解决这个问题需要一个端到端的解决方案:从终端用户的客户端一路传递到数据库的事务标识符。 -​ 端到端参数也适用于检查数据的完整性:以太网,TCP和TLS中内置的校验和可以检测网络中数据包的损坏情况,但是它们无法检测到由连接两端发送/接收软件中Bug导致的损坏。或数据存储所在磁盘上的损坏。如果你想捕获数据所有可能的损坏来源,你也需要端到端的校验和。 +​ 端到端原则也适用于检查数据的完整性:以太网,TCP和TLS中内置的校验和可以检测网络中数据包的损坏情况,但是它们无法检测到由连接两端发送/接收软件中Bug导致的损坏。或数据存储所在磁盘上的损坏。如果你想捕获数据所有可能的损坏来源,你也需要端到端的校验和。 ​ 类似的原则也适用于加密【55】:家庭WiFi网络上的密码可以防止人们窃听你的WiFi流量,但无法阻止互联网上其他地方攻击者的窥探;客户端与服务器之间的TLS/SSL可以阻挡网络攻击者,但无法阻止恶意服务器。只有端到端的加密和认证可以防止所有这些事情。 -​ 尽管低层级的功能(TCP复制抑制,以太网校验和,WiFi加密)无法单独提供所需的端到端功能,但它们仍然很有用,因为它们能降低较高层级出现问题的可能性。例如,如果我们没有TCP来将数据包排成正确的顺序,那么HTTP请求通常就会被搅烂。我们只需要记住,低级别的可靠性功能本身并不足以确保端到端的正确性。 +​ 尽管低层级的功能(TCP重复抑制,以太网校验和,WiFi加密)无法单独提供所需的端到端功能,但它们仍然很有用,因为它们能降低较高层级出现问题的可能性。例如,如果我们没有TCP来将数据包排成正确的顺序,那么HTTP请求通常就会被搅烂。我们只需要记住,低级别的可靠性功能本身并不足以确保端到端的正确性。 #### 在数据系统中应用端到端思考 -​ 这将我带回最初的论点·:仅仅因为应用使用了提供相对较强安全属性的数据系统,例如可序列化的事务,并不意味着应用的数据就不会丢失或损坏了。应用本身也需要采取端到端的措施,例如除重。 +​ 这将我带回最初的论点:仅仅因为应用使用了提供相对较强安全属性的数据系统,例如可串行化的事务,并不意味着应用的数据就不会丢失或损坏了。应用本身也需要采取端到端的措施,例如除重。 ​ 这实在是一个遗憾,因为容错机制很难弄好。低层级的可靠机制(比如TCP中的那些)运行的相当好,因而剩下的高层级错误基本很少出现。如果能将这些剩下的高层级容错机制打包成抽象,而应用不需要再去操心,那该多好呀 —— 但恐怕我们还没有找到这一正确的抽象。 ​ 长期以来,事务被认为是一个很好的抽象,我相信它们确实是很有用的。正如[第七章](ch7.md)导言中所讨论的,它们将各种可能的问题(并发写入,违背约束,崩溃,网络中断,磁盘故障)合并为两种可能结果:提交或中止。这是对编程模型而言是一种巨大的简化,但恐怕这还不够。 -​ 事务是代价高昂的,当涉及异构存储技术时尤为甚(请参阅的“[实践中的分布式事务](ch9.md#实践中的分布式事务)”)。我们拒绝使用分布式事务是因为它开销太大,结果我们最后不得不在应用代码中重新实现容错机制。正如本书中大量的例子所示,对并发性与部分失败的推理是困难且违反直觉的,所以我怀疑大多数应用级别的机制都不能正确工作,最终结果是数据丢失或损坏。 +​ 事务是代价高昂的,当涉及异构存储技术时尤为甚(请参阅“[实践中的分布式事务](ch9.md#实践中的分布式事务)”)。我们拒绝使用分布式事务是因为它开销太大,结果我们最后不得不在应用代码中重新实现容错机制。正如本书中大量的例子所示,对并发性与部分失败的推理是困难且违反直觉的,所以我怀疑大多数应用级别的机制都不能正确工作,最终结果是数据丢失或损坏。 ​ 出于这些原因,我认为探索对容错的抽象是很有价值的。它使提供应用特定的端到端的正确性属性变得更简单,而且还能在大规模分布式环境中提供良好的性能与运维特性。 @@ -518,7 +519,7 @@ COMMIT; ​ 我们先来特别关注一下**唯一性约束** —— 例如我们在[例12-2]()中所依赖的约束。在“[约束和唯一性保证](ch9.md#约束和唯一性保证)”中,我们看到了几个其他需要强制实施唯一性的应用功能例子:用户名或电子邮件地址必须唯一标识用户,文件存储服务不能包含多个重名文件,两个人不能在航班或剧院预订同一个座位。 -​ 其他类型的约束也非常类似:例如,确保帐户余额永远不会变为负数,你就不会超卖库存;或者会议室没有重复的预订。执行唯一性约束的技术通常也可以用于这些约束。 +​ 其他类型的约束也非常类似:例如,确保帐户余额永远不会变为负数,确保不会超卖库存,或者会议室没有重复的预订。执行唯一性约束的技术通常也可以用于这些约束。 #### 唯一性约束需要达成共识 @@ -528,20 +529,19 @@ COMMIT; ​ 唯一性检查可以通过对唯一性字段分区做横向伸缩。例如,如果需要通过请求ID确保唯一性(如[例12-2]()所示),你可以确保所有具有相同请求ID的请求都被路由到同一分区(请参阅[第六章](ch6.md))。如果你需要让用户名是唯一的,则可以按用户名的散列值做分区。 -​ 但异步多主复制排除在外,因为可能会发生不同主库同时接受冲突写操作的情况,因而这些值不再是唯一的(请参阅“[实现可线性化系统](ch9.md#实现可线性化系统)”)。如果你想立刻拒绝任何违背约束的写入,同步协调是无法避免的【56】。 +​ 但异步多主复制排除在外,因为可能会发生不同主库同时接受冲突写操作的情况,因而这些值不再是唯一的(请参阅“[实现线性一致的系统](ch9.md#实现线性一致的系统)”)。如果你想立刻拒绝任何违背约束的写入,同步协调是无法避免的【56】。 #### 基于日志消息传递中的唯一性 ​ 日志确保所有消费者以相同的顺序看见消息 —— 这种保证在形式上被称为**全序广播(total order boardcast)** 并且等价于共识(请参阅“[全序广播](ch9.md#全序广播)”)。在使用基于日志的消息传递的分拆数据库方法中,我们可以使用非常类似的方法来执行唯一性约束。 -​ 流处理器在单个线程上依次消费单个日志分区中的所有消息(请参阅“[与传统消息传递相比的日志](ch11.md#与传统消息传递相比的日志)”)。因此,如果日志是按有待确保唯一的值做的分区,则流处理器可以无歧义地,确定性地决定几个冲突操作中的哪一个先到达。例如,在多个用户尝试宣告相同用户名的情况下【57】: +​ 流处理器在单个线程上依次消费单个日志分区中的所有消息(请参阅“[日志与传统的消息传递相比](ch11.md#日志与传统的消息传递相比)”)。因此,如果日志是按需要确保唯一的值做的分区,则流处理器可以无歧义地、确定性地决定几个冲突操作中的哪一个先到达。例如,在多个用户尝试宣告相同用户名的情况下【57】: 1. 每个对用户名的请求都被编码为一条消息,并追加到按用户名散列值确定的分区。 2. 流处理器依序读取日志中的请求,并使用本地数据库来追踪哪些用户名已经被占用了。对于所有申请可用用户名的请求,它都会记录该用户名,并向输出流发送一条成功消息。对于所有申请已占用用户名的请求,它都会向输出流发送一条拒绝消息。 3. 请求用户名的客户端监视输出流,等待与其请求相对应的成功或拒绝消息。 - -该算法基本上与“[使用全序广播实现线性一致的存储](ch9.md#使用全序广播实现线性一致的存储)”中的算法相同。它可以简单地通过增加分区数伸缩至较大的请求吞吐量,因为每个分区可以被独立处理。 +该算法基本上与“[使用全序广播实现线性一致的存储](ch9.md#使用全序广播实现线性一致的存储)”中的算法相同。它可以简单地通过增加分区数伸缩至较大的请求吞吐量,因为每个分区都可以被独立处理。 ​ 该方法不仅适用于唯一性约束,而且适用于许多其他类型的约束。其基本原理是,任何可能冲突的写入都会路由到相同的分区并按顺序处理。正如“[什么是冲突?](ch5.md#什么是冲突?)”与“[写入偏斜与幻读](ch7.md#写入偏斜与幻读)”中所述,冲突的定义可能取决于应用,但流处理器可以使用任意逻辑来验证请求。这个想法与Bayou在90年代开创的方法类似【58】。 @@ -549,7 +549,7 @@ COMMIT; ​ 当涉及多个分区时,确保操作以原子方式执行且同时满足约束就变得很有趣了。在[例12-2]()中,可能有三个分区:一个包含请求ID,一个包含收款人账户,另一个包含付款人账户。没有理由把这三种东西放入同一个分区,因为它们都是相互独立的。 -​ 在数据库的传统方法中,执行此事务需要跨全部三个分区进行原子提交,这实质上是将该事务嵌入一个全序,就这些分区上的所有其他事务而言。而这样就要求跨分区协调,不同的分区无法再独立地进行处理,因此吞吐量可能会受到影响。 +​ 在数据库的传统方法中,执行此事务需要跨全部三个分区进行原子提交,就这些分区上的所有其他事务而言,这实质上是将该事务嵌入一个全序。而这样就要求跨分区协调,不同的分区无法再独立地进行处理,因此吞吐量很可能会受到影响。 但事实证明,使用分区日志可以达到等价的正确性而无需原子提交: @@ -563,7 +563,7 @@ COMMIT; ​ 如果你想确保付款人的帐户不会因此次转账而透支,则可以使用一个额外的流处理器来维护账户余额并校验事务(按付款人账户分区),只有有效的事务会被记录在步骤1中的请求日志中。 -​ 通过将多分区事务分解为两个不同分区方式的阶段,并使用端到端的请求ID,我们实现了同样的正确性属性(每个请求对付款人与收款人都恰好生效一次),即使在出现故障,且没有使用原子提交协议的情况下依然如此。使用多个不同分区方式的阶段与我们在“[多分区数据处理](#多分区数据处理)”中讨论的想法类似(请参阅“[并发控制](ch11.md#并发控制)”)。 +​ 通过将多分区事务分解为两个不同分区方式的阶段,并使用端到端的请求ID,我们实现了同样的正确性属性(每个请求对付款人与收款人都恰好生效一次),即使在出现故障,且没有使用原子提交协议的情况下依然如此。使用多个不同分区阶段的想法与我们在“[多分区数据处理](#多分区数据处理)”中讨论的想法类似(也请参阅“[并发控制](ch11.md#并发控制)”)。 ### 及时性与完整性 @@ -583,17 +583,16 @@ COMMIT; ***完整性(Integrity)*** -​ 完整性意味着没有损坏;即没有数据丢失,并且没有矛盾或错误的数据。尤其是如果某些衍生数据集是作为底层数据之上的视图而维护的(请参阅“[从事件日志导出当前状态](ch11.md#从事件日志导出当前状态)”),这种衍生必须是正确的。例如,数据库索引必须正确地反映数据库的内容 —— 缺失某些记录的索引并不是很有用。 - -​ 如果完整性被违背,这种不一致是永久的:在大多数情况下,等待与重试并不能修复数据库损坏。相反的是,需要显式地检查与修复。在ACID事务的上下文中(请参阅“[ACID的涵义](ch7.md#ACID的涵义)”),一致性通常被理解为某种特定于应用的完整性概念。原子性和持久性是保持完整性的重要工具。 +​ 完整性意味着没有损坏;即没有数据丢失,并且没有矛盾或错误的数据。尤其是如果某些衍生数据集是作为底层数据之上的视图而维护的(请参阅“[从事件日志中派生出当前状态](ch11.md#从事件日志中派生出当前状态)”),这种衍生必须是正确的。例如,数据库索引必须正确地反映数据库的内容 —— 缺失某些记录的索引并不是很有用。 +​ 如果完整性被违背,这种不一致是永久的:在大多数情况下,等待与重试并不能修复数据库损坏。相反的是,需要显式地检查与修复。在ACID事务的上下文中(请参阅“[ACID的含义](ch7.md#ACID的含义)”),一致性通常被理解为某种特定于应用的完整性概念。原子性和持久性是保持完整性的重要工具。 ​ 口号形式:违反及时性,“最终一致性”;违反完整性,“永无一致性”。 ​ 我断言在大多数应用中,完整性比及时性重要得多。违反及时性可能令人困惑与讨厌,但违反完整性的结果可能是灾难性的。 -​ 例如在你的信用卡对账单上,如果某一笔过去24小时内完成的交易尚未出现并不令人奇怪 —— 这些系统有一定的滞后是正常的。我们知道银行是异步核算与敲定交易的,而这里的及时性也并不是非常重要【3】。但果当期对账单余额与上期对账单余额加交易总额对不上(求和错误),或者出现一比向你收费但未向商家付款的交易(消失的钱),那实在是太糟糕了。这样的问题就违背了系统的完整性。 +​ 例如在你的信用卡对账单上,如果某一笔过去24小时内完成的交易尚未出现并不令人奇怪 —— 这些系统有一定的滞后是正常的。我们知道银行是异步核算与敲定交易的,这里的及时性并不是非常重要【3】。但果当期对账单余额与上期对账单余额加交易总额对不上(求和错误),或者出现一笔向你收费但未向商家付款的交易(消失的钱),那实在是太糟糕了。这样的问题就违背了系统的完整性。 #### 数据流系统的正确性 @@ -601,13 +600,13 @@ COMMIT; ​ 另一方面,对于在本章中讨论的基于事件的数据流系统而言,它们的一个有趣特性就是将及时性与完整性分开。在异步处理事件流时不能保证及时性,除非你显式构建一个在返回之前明确等待特定消息到达的消费者。但完整性实际上才是流处理系统的核心。 -​ **恰好一次**或**等效一次**语义(请参阅“[容错](ch11.md#容错)”)是一种保持完整性的机制。如果事件丢失或者生效两次,就有可能违背数据系统的完整性。因此在面对故障时,容错消息传递与重复抑制(例如,幂等操作)对于维护数据系统的完整性是很重要的。 +​ **恰好一次**或**等效一次**语义(请参阅“[容错](ch11.md#容错)”)是一种保持完整性的机制。如果事件丢失或者生效两次,就有可能违背数据系统的完整性。因此在出现故障时,容错消息传递与重复抑制(例如,幂等操作)对于维护数据系统的完整性是很重要的。 -​ 正如我们在上一节看到的那样,可靠的流处理系统可以在无需分布式事务与原子提交协议的情况下保持完整性,这意味着它们能潜在地实现好得多的性能与运维稳健性,在达到类似正确性的前提下。为了达成这种正确性,我们组合使用了多种机制: +​ 正如我们在上一节看到的那样,可靠的流处理系统可以在无需分布式事务与原子提交协议的情况下保持完整性,这意味着它们有潜力达到与后者相当的正确性,同时还具备好得多的性能与运维稳健性。为了达成这种正确性,我们组合使用了多种机制: * 将写入操作的内容表示为单条消息,从而可以轻松地被原子写入 —— 与事件溯源搭配效果拔群(请参阅“[事件溯源](ch11.md#事件溯源)”)。 -* 使用与存储过程类似的确定性衍生函数,从这一消息中衍生出所有其他的状态变更(请参阅“[真的串行执行](ch7.md#真的串行执行)”和“[作为衍生函数的应用代码](ch11.md#作为衍生函数的应用代码)”) -* 将客户端生成的请求ID传递通过所有的处理层次,从而启用端到端除重,带来幂等性。 +* 使用与存储过程类似的确定性衍生函数,从这一消息中衍生出所有其他的状态变更(请参阅“[真的串行执行](ch7.md#真的串行执行)”和“[应用代码作为衍生函数](ch12.md#应用代码作为衍生函数)”) +* 将客户端生成的请求ID传递通过所有的处理层次,从而允许端到端的除重,带来幂等性。 * 使消息不可变,并允许衍生数据能随时被重新处理,这使从错误中恢复更加容易(请参阅“[不可变事件的优点](ch11.md#不可变事件的优点)”) 这种机制组合在我看来,是未来构建容错应用的一个非常有前景的方向。 @@ -618,33 +617,31 @@ COMMIT; 然而另一个需要了解的事实是,许多真实世界的应用实际上可以摆脱这种形式,接受弱得多的唯一性: -* 如果两个人同时注册了相同的用户名或预订了相同的座位,你可以发送其中一个发消息道歉,并要求他们选择一个不同的用户名。这种纠正错误的变化被称为**补偿性事务(compensating transaction)**【59,60】。 -* 如果客户订购的物品多于仓库中的物品,你可以下单补仓,并为延误向客户道歉,向他们提供折扣。实际上,这么说吧,如果在叉车在仓库中轧过了你的货物,剩下的货物比你想象的要少,那么你也是得这么做【61】。因此,既然道歉工作流无论如何已经成为你商业过程中的一部分了,那么对库存物品数目添加线性一致的约束可能就没必要了。 +* 如果两个人同时注册了相同的用户名或预订了相同的座位,你可以给其中一个人发消息道歉,并要求他们选择一个不同的用户名。这种纠正错误的变化被称为**补偿性事务(compensating transaction)**【59,60】。 +* 如果客户订购的物品多于仓库中的物品,你可以下单补仓,并为延误向客户道歉,向他们提供折扣。实际上,这么说吧,如果叉车在仓库中轧过了你的货物,剩下的货物比你想象的要少,那么你也是得这么做【61】。因此,既然道歉工作流无论如何已经成为你商业过程中的一部分了,那么对库存物品数目添加线性一致的约束可能就没必要了。 * 与之类似,许多航空公司都会超卖机票,打着一些旅客可能会错过航班的算盘;许多旅馆也会超卖客房,抱着部分客人可能会取消预订的期望。在这些情况下,出于商业原因而故意违反了“一人一座”的约束;当需求超过供给的情况出现时,就会进入补偿流程(退款、升级舱位/房型、提供隔壁酒店的免费的房间)。即使没有超卖,为了应对由恶劣天气或员工罢工导致的航班取消,你还是需要道歉与补偿流程 —— 从这些问题中恢复仅仅是商业活动的正常组成部分。 * 如果有人从账户超额取款,银行可以向他们收取透支费用,并要求他们偿还欠款。通过限制每天的提款总额,银行的风险是有限的。 +​ 在许多商业场景中,临时违背约束并稍后通过道歉来修复,实际上是可以接受的。道歉的成本各不相同,但通常很低(以金钱或名声来算):你无法撤回已发送的电子邮件,但可以发送一封后续电子邮件进行更正。如果你不小心向信用卡收取了两次费用,则可以将其中一项收费退款,而代价仅仅是手续费,也许还有客户的投诉。尽管一旦ATM吐了钱,你无法直接取回,但原则上如果账户透支而客户拒不支付,你可以派催收员收回欠款。 - -​ 在许多商业场景中,临时违背约束并稍后通过道歉来修复,实际上是可以接受的。道歉的成本各不相同,但通常很低(以金钱或名声来算):你无法撤回已发送的电子邮件,但可以发送一封后续电子邮件进行更正。如果你不小心向信用卡收取了两次费用,则可以将其中一项收费退款,而代价仅仅是手续费,也许还有客户的投诉……。尽管一旦ATM吐了钱,你无法直接取回,但原则上如果账户透支而客户拒不支付,你可以派催收员收回欠款…。 - -​ 道歉的成本是否能接受是一个商业决策。如果可以接受的话,在写入数据之前检查所有约束的传统模型反而会带来不必要的限制,而线性一致性的约束也不是必须的。乐观写入,事后检查可能是一种合理的选择。你仍然可以在做一些挽回成本高昂的事情前确保验证发生,但这并不意味着写入数据之前必须先进行验证。 +​ 道歉的成本是否能接受是一个商业决策。如果可以接受的话,在写入数据之前检查所有约束的传统模型反而会带来不必要的限制,而线性一致性的约束也不是必须的。乐观写入,事后检查可能是一种合理的选择。你仍然可以在做一些挽回成本高昂的事情前确保有相关的验证,但这并不意味着写入数据之前必须先进行验证。 ​ 这些应用**确实**需要完整性:你不会希望丢失预订信息,或者由于借方贷方不匹配导致资金消失。但是它们在执行约束时**并不需要**及时性:如果你销售的货物多于仓库中的库存,可以在事后道歉后并弥补问题。这种做法与我们在“[处理写入冲突](ch5.md#处理写入冲突)”中讨论的冲突解决方法类似。 #### 无协调数据系统 -我们现在做了两个有趣的观察: +我们现在已经做了两个有趣的观察: -1. 数据流系统可以维持衍生数据的完整性保证,而无需原子提交,线性一致性,或者同步跨分区协调。 +1. 数据流系统可以维持衍生数据的完整性保证,而无需原子提交、线性一致性或者同步的跨分区协调。 2. 虽然严格的唯一性约束要求及时性和协调,但许多应用实际上可以接受宽松的约束:只要整个过程保持完整性,这些约束可能会被临时违反并在稍后被修复。 -总之这些观察意味着,数据流系统可以为许多应用提供无需协调的数据管理服务,且仍能给出很强的完整性保证。这种**无协调(coordination-avoiding)** 的数据系统有着很大的吸引力:比起需要执行同步协调的系统,它们能达到更好的性能与更强的容错能力【56】。 +总之这些观察意味着,数据流系统可以为许多应用提供无需协调的数据管理服务,且仍能给出很强的完整性保证。这种**无协调(coordination-avoiding)**的数据系统有着很大的吸引力:比起需要执行同步协调的系统,它们能达到更好的性能与更强的容错能力【56】。 -​ 例如,这种系统可以使用多领导者配置运维,跨越多个数据中心,在区域间异步复制。任何一个数据中心都可以持续独立运行,因为不需要同步的跨区域协调。这样的系统时效性保证会很弱 —— 如果不引入协调它是不可能是线性一致的 —— 但它仍然可以提供有力的完整性保证。 +​ 例如,这种系统可以使用多领导者配置运维,跨越多个数据中心,在区域间异步复制。任何一个数据中心都可以持续独立运行,因为不需要同步的跨区域协调。这样的系统的及时性保证会很弱 —— 如果不引入协调它是不可能是线性一致的 —— 但它仍然可以提供有力的完整性保证。 -​ 在这种情况下,可序列化事务作为维护衍生状态的一部分仍然是有用的,但它们可以在小范围内运行,在那里它们工作得很好【8】。异构分布式事务(如XA事务)(请参阅“[实践中的分布式事务](ch9.md#实践中的分布式事务)”)不是必需的。同步协调仍然可以在需要的地方引入(例如在无法恢复的操作之前强制执行严格的约束),但是如果只是应用的一小部分地方需要它,没必要让所有操作都付出协调的代价。【43】。 +​ 在这种情况下,可串行化事务作为维护衍生状态的一部分仍然是有用的,但它们只能在小范围内运行,在那里它们工作得很好【8】。异构分布式事务(如XA事务)(请参阅“[实践中的分布式事务](ch9.md#实践中的分布式事务)”)不是必需的。同步协调仍然可以在需要的地方引入(例如在无法恢复的操作之前强制执行严格的约束),但是如果只是应用的一小部分地方需要它,没必要让所有操作都付出协调的代价。【43】。 -​ 另一种审视协调与约束的角度是:它们减少了由于不一致而必须做出的道歉数量,但也可能会降低系统的性能和可用性,从而可能增加由于宕机中断而需要做出的道歉数量。你不可能将道歉数量减少到零,但可以根据自己的需求寻找最佳平衡点 —— 既不存在太多不一致性,又不存在太多可用性问题的最佳选择。 +​ 另一种审视协调与约束的角度是:它们减少了由于不一致而必须做出的道歉数量,但也可能会降低系统的性能和可用性,从而可能增加由于宕机中断而需要做出的道歉数量。你不可能将道歉数量减少到零,但可以根据自己的需求寻找最佳平衡点 —— 既不存在太多不一致性,又不存在太多可用性问题。 ### 信任但验证 @@ -652,7 +649,7 @@ COMMIT; ​ 这些假设是相当合理的,因为大多数时候它们都是成立的,如果我们不得不经常担心计算机出错,那么基本上寸步难行。在传统上,系统模型采用二元方法处理故障:我们假设有些事情可能会发生,而其他事情**永远**不会发生。实际上,这更像是一个概率问题:有些事情更有可能,其他事情不太可能。问题在于违反我们假设的情况是否经常发生,以至于我们可能在实践中遇到它们。 -​ 我们已经看到,数据可能会在尚未落盘时损坏(请参阅“[复制与持久性](ch5.md#复制与持久性)”),而网络上的数据损坏有时可能规避了TCP校验和(请参阅“[弱谎言形式](ch8.md#弱谎言形式)” )。也许我们应当更关注这些事情? +​ 我们已经看到,数据可能会在尚未落盘时损坏(请参阅“[复制与持久性](ch7.md#复制与持久性)”),而网络上的数据损坏有时可能规避了TCP校验和(请参阅“[弱谎言形式](ch8.md#弱谎言形式)” )。也许我们应当更关注这些事情? ​ 我过去所从事的一个应用收集了来自客户端的崩溃报告,我们收到的一些报告,只有在这些设备内存中出现了随机位翻转才解释的通。这看起来不太可能,但是如果有足够多的设备运行你的软件,那么即使再不可能发生的事也确实会发生。除了由于硬件故障或辐射导致的随机存储器损坏之外,一些病态的存储器访问模式甚至可以在没有故障的存储器中翻转位【62】 —— 一种可用于破坏操作系统安全机制的效应【63】(这种技术被称为**Rowhammer**)。一旦你仔细观察,硬件并不是看上去那样完美的抽象。 @@ -660,7 +657,7 @@ COMMIT; #### 维护完整性,尽管软件有Bug -​ 除了这些硬件问题之外,总是存在软件Bug的风险,这些错误不会被较低层次的网络,内存或文件系统校验和所捕获。即使广泛使用的数据库软件也有Bug:即使像MySQL与PostgreSQL这样稳健、考虑充分、久经实战考验,多年以来被许多人充分测试过的软件,就我个人所见也有Bug:比如MySQL未能正确维护唯一约束【65】,以及PostgreSQL的可序列化隔离等级存在特定的写偏差异常【66】。对于更不成熟的软件来说,情况可能要糟糕的多。 +​ 除了这些硬件问题之外,总是存在软件Bug的风险,这些错误不会被较低层次的网络、内存或文件系统校验和所捕获。即使广泛使用的数据库软件也有Bug:即使像MySQL与PostgreSQL这样稳健、口碑良好、多年来被许多人充分测试过的软件,就我个人所见也有Bug,比如MySQL未能正确维护唯一约束【65】,以及PostgreSQL的可串行化隔离等级存在特定的写偏斜异常【66】。对于不那么成熟的软件来说,情况可能要糟糕得多。 ​ 尽管在仔细设计,测试,以及审查上做出很多努力,但Bug仍然会在不知不觉中产生。尽管它们很少,而且最终会被发现并被修复,但总会有那么一段时间,这些Bug可能会损坏数据。 @@ -672,7 +669,7 @@ COMMIT; ​ 由于硬件和软件并不总是符合我们的理想,所以数据损坏似乎早晚不可避免。因此,我们至少应该有办法查明数据是否已经损坏,以便我们能够修复它,并尝试追查错误的来源。检查数据完整性称为**审计(auditing)**。 -​ 如“[不可变事件的优点](ch11.md#不可变事件的优点)”一节中所述,审计不仅仅适用于财务应用程序。不过,可审计性在财务中是非常非常重要的。这种错误发生之后,需要能被检测与解决,我们都知道所有人都会认为这是合理需求。 +​ 如“[不可变事件的优点](ch11.md#不可变事件的优点)”一节中所述,审计不仅仅适用于财务应用程序。不过,可审计性在财务中是非常非常重要的,因为每个人都知道错误总会发生,我们也都认为能够检测和解决问题是合理的需求。 ​ 成熟的系统同样倾向于考虑不太可能的事情出错的可能性,并管理这种风险。例如,HDFS和Amazon S3等大规模存储系统并不完全信任磁盘:它们运行后台进程持续回读文件,并将其与其他副本进行比较,并将文件从一个磁盘移动到另一个,以便降低静默损坏的风险【67】。 @@ -688,7 +685,7 @@ COMMIT; #### 为可审计性而设计 -​ 如果一个事务在一个数据库中改变了多个对象,在这一事实发生后,很难说清这个事务到底意味着什么。即使你捕获了事务日志(请参阅“[变更数据捕获](ch11.md#变更数据捕获)”),各种表中的插入,更新和删除操作并不一定能清楚地表明**为什么**要执行这些变更。决定这些变更的是应用逻辑中的调用,而这一应用逻辑稍纵即逝,无法重现。 +​ 如果一个事务在一个数据库中改变了多个对象,在这一事实发生后,很难说清这个事务到底意味着什么。即使你捕获了事务日志(请参阅“[变更数据捕获](ch11.md#变更数据捕获)”),各种表中的插入、更新和删除操作并不一定能清楚地表明**为什么**要执行这些变更。决定这些变更的是应用逻辑中的调用,而这一应用逻辑稍纵即逝,无法重现。 ​ 相比之下,基于事件的系统可以提供更好的可审计性。在事件溯源方法中,系统的用户输入被表示为一个单一不可变事件,而任何其导致的状态变更都衍生自该事件。衍生可以实现为具有确定性与可重复性,因而相同的事件日志通过相同版本的衍生代码时,会导致相同的状态变更。 @@ -700,26 +697,25 @@ COMMIT; ​ 如果我们不能完全相信系统的每个组件都不会损坏 —— 每一个硬件都没缺陷,每一个软件都没有Bug —— 那我们至少必须定期检查数据的完整性。如果我们不检查,我们就不能发现损坏,直到无可挽回地导致对下游的破坏时,那时候再去追踪问题就要难得多,且代价也要高的多。 -​ 检查数据系统的完整性,最好是以端到端的方式进行(请参阅“[数据库端到端的争论](#数据库的端到端的争论)”):我们能在完整性检查中涵盖的系统越多,某些处理阶中出现不被察觉损坏的几率就越小。如果我们能检查整个衍生数据管道端到端的正确性,那么沿着这一路径的任何磁盘,网络,服务,以及算法的正确性检查都隐含在其中了。 +​ 检查数据系统的完整性,最好是以端到端的方式进行(请参阅“[数据库的端到端原则](#数据库的端到端原则)”):我们能在完整性检查中涵盖的系统越多,某些处理阶中出现不被察觉损坏的几率就越小。如果我们能检查整个衍生数据管道端到端的正确性,那么沿着这一路径的任何磁盘、网络、服务以及算法的正确性检查都隐含在其中了。 ​ 持续的端到端完整性检查可以不断提高你对系统正确性的信心,从而使你能更快地进步【70】。与自动化测试一样,审计提高了快速发现错误的可能性,从而降低了系统变更或新存储技术可能导致损失的风险。如果你不害怕进行变更,就可以更好地充分演化一个应用,使其满足不断变化的需求。 #### 用于可审计数据系统的工具 -​ 目前,将可审计性作为顶层关注点的数据系统并不多。一些应用实现了自己的审计机制,例如将所有变更记录到单独的审计表中,但是确保审计日志与数据库状态的完整性仍然是很困难的。可以通过定期使用硬件安全模块对事务日志进行签名来防止篡改,但这无法保证正确的事务一开始就能进入到日志中。 +​ 目前,将可审计性作为顶层关注点的数据系统并不多。一些应用实现了自己的审计机制,例如将所有变更记录到单独的审计表中,但是确保审计日志与数据库状态的完整性仍然是很困难的。可以定期使用硬件安全模块对事务日志进行签名来防止篡改,但这无法保证正确的事务一开始就能进入到日志中。 ​ 使用密码学工具来证明系统的完整性是十分有趣的,这种方式对于宽泛的硬件与软件问题,甚至是潜在的恶意行为都很稳健有效。加密货币,区块链,以及诸如比特币,以太坊,Ripple,Stellar的分布式账本技术已经迅速出现在这一领域【71,72,73】。 ​ 我没有资格评论这些技术用于货币,或者合同商定机制的价值。但从数据系统的角度来看,它们包含了一些有趣的想法。实质上,它们是分布式数据库,具有数据模型与事务机制,而不同副本可以由互不信任的组织托管。副本不断检查其他副本的完整性,并使用共识协议对应当执行的事务达成一致。 -​ 我对这些技术的拜占庭容错方面有些怀疑(请参阅“[拜占庭故障](ch8.md#拜占庭故障)”),而且我发现**工作证明(proof of work)** 技术非常浪费(比如,比特币挖矿)。比特币的交易吞吐量相当低,尽管是出于政治与经济原因而非技术上的原因。不过,完整性检查的方面是很有趣的。 +​ 我对这些技术的拜占庭容错方面有些怀疑(请参阅“[拜占庭故障](ch8.md#拜占庭故障)”),而且我发现**工作证明(proof of work)** 技术非常浪费(比如,比特币挖矿)。比特币的交易吞吐量相当低,尽管更多是出于政治与经济原因而非技术上的原因。不过,完整性检查的方面是很有趣的。 -​ 密码学审计与完整性检查通常依赖**默克尔树(Merkle tree)**【74】,这是一颗散列值的树,能够用于高效地证明一条记录出现在一个数据集中(以及其他一些特性)。除了炒作的沸沸扬扬的加密货币之外,**证书透明性(certificate transparency)** 也是一种依赖Merkle树的安全技术,用来检查TLS/SSL证书的有效性【75,76】。 +​ 密码学审计与完整性检查通常依赖**默克尔树(Merkle tree)**【74】,这是一颗散列值的树,能够用于高效地证明一条记录出现在一个数据集中(以及其他一些特性)。除了炒作的沸沸扬扬的加密货币之外,**证书透明性(certificate transparency)**也是一种依赖Merkle树的安全技术,用来检查TLS/SSL证书的有效性【75,76】。 ​ 我可以想象,那些在证书透明度与分布式账本中使用的完整性检查和审计算法,将会在通用数据系统中得到越来越广泛的应用。要使得这些算法对于没有密码学审计的系统同样可伸缩,并尽可能降低性能损失还需要一些工作。 但我认为这是一个值得关注的有趣领域。 - ## 做正确的事情 ​ 在本书的最后部分,我想退后一步。在本书中,我们考察了各种不同的数据系统架构,评价了它们的优点与缺点,并探讨了构建可靠,可伸缩,可维护应用的技术。但是,我们忽略了讨论中一个重要而基础的部分,现在我想补充一下。 @@ -730,7 +726,7 @@ COMMIT; ​ 软件开发越来越多地涉及重要的道德抉择。有一些指导原则可以帮助软件工程师解决这些问题,例如ACM的软件工程道德规范与专业实践【77】,但实践中很少会讨论这些,更不用说应用与强制执行了。因此,工程师和产品经理有时会对隐私与产品潜在的负面后果抱有非常傲慢的态度【78,79,80】。 -​ 技术本身并无好坏之分 —— 关键在于它被如何使用,以及它如何影响人们。这对枪械这样的武器,这是成立的,而搜索引擎这样的软件系统与之类似。我认为,软件工程师仅仅专注于技术而忽视其后果是不够的:道德责任也是我们的责任。对道德推理很困难,但它太重要了,我们无法忽视。 +​ 技术本身并无好坏之分 —— 关键在于它被如何使用,以及它如何影响人们。这对枪械这样的武器是成立的,而搜索引擎这样的软件系统与之类似。我认为,软件工程师仅仅专注于技术而忽视其后果是不够的:道德责任也是我们的责任。对道德推理很困难,但它太重要了,我们无法忽视。 ### 预测性分析 @@ -742,11 +738,11 @@ COMMIT; #### 偏见与歧视 -​ 算法做出的决定不一定比人类更好或更差。每个人都可能有偏见,即使他们主动抗拒这一点;而歧视性做法也可能已经在文化上被制度化了。人们希望根据数据做出决定,而不是通过人的主观评价与直觉,希望这样能更加公平,并给予传统体制中经常被忽视的人更好的机会。【83】。 +​ 算法做出的决定不一定比人类更好或更差。每个人都可能有偏见,即使他们主动抗拒这一点;而歧视性做法也可能已经在文化上被制度化了。人们希望根据数据做出决定,而不是通过人的主观评价与直觉,希望这样能更加公平,并给予传统体制中经常被忽视的人更好的机会【83】。 ​ 当我们开发预测性分析系统时,不是仅仅用软件通过一系列IF ELSE规则将人类的决策过程自动化,那些规则本身甚至都是从数据中推断出来的。但这些系统学到的模式是个黑盒:即使数据中存在一些相关性,我们可能也压根不知道为什么。如果算法的输入中存在系统性的偏见,则系统很有可能会在输出中学习并放大这种偏见【84】。 -​ 在许多国家,反歧视法律禁止按种族,年龄,性别,性取向,残疾,或信仰等受保护的特征区分对待不同的人。其他的个人特征可能是允许用于分析的,但是如果这些特征与受保护的特征存在关联,又会发生什么?例如在种族隔离地区中,一个人的邮政编码,甚至是他们的IP地址,都是很强的种族指示物。这样的话,相信一种算法可以以某种方式将有偏数据作为输入,并产生公平和公正的输出【85】似乎是很荒谬的。然而这种观点似乎常常潜伏在数据驱动型决策的支持者中,这种态度被讽刺为“在处理偏差上,机器学习与洗钱类似”(machine learning is like money laundering for bias)【86】。 +​ 在许多国家,反歧视法律禁止按种族,年龄,性别,性取向,残疾,或信仰等受保护的特征区分对待不同的人。其他的个人特征可能是允许用于分析的,但是如果这些特征与受保护的特征存在关联,又会发生什么?例如在种族隔离地区中,一个人的邮政编码,甚至是他们的IP地址,都是很强的种族指示物。这样的话,相信一种算法可以以某种方式将有偏见的数据作为输入,并产生公平和公正的输出【85】似乎是很荒谬的。然而这种观点似乎常常潜伏在数据驱动型决策的支持者中,这种态度被讽刺为“在处理偏差上,机器学习与洗钱类似”(machine learning is like money laundering for bias)【86】。 ​ 预测性分析系统只是基于过去进行推断;如果过去是歧视性的,它们就会将这种歧视归纳为规律。如果我们希望未来比过去更好,那么就需要道德想象力,而这是只有人类才能提供的东西【87】。数据与模型应该是我们的工具,而不是我们的主人。 @@ -762,7 +758,7 @@ COMMIT; ​ 盲目相信数据决策至高无上,这不仅仅是一种妄想,而是有切实危险的。随着数据驱动的决策变得越来越普遍,我们需要弄清楚,如何使算法更负责任且更加透明,如何避免加强现有的偏见,以及如何在它们不可避免地出错时加以修复。 -​ 我们还需要想清楚,如何避免数据被用于害人,如何认识数据的积极潜力。例如,分析可以揭示人们生活的财务特点与社会特点。一方面,这种权力可以用来将援助与支持集中在帮助那些最需要援助的人身上。另一方面,它有时会被掠夺性企业用于识别弱势群体,并向其兜售高风险产品,比如高利贷,智商税与莆田医院【87,90】[^译注i]。 +​ 我们还需要想清楚,如何避免数据被用于害人,如何认识数据的积极潜力。例如,分析可以揭示人们生活的财务特点与社会特点。一方面,这种权力可以用来将援助与支持集中在帮助那些最需要援助的人身上。另一方面,它有时会被掠夺性企业用于识别弱势群体,并向其兜售高风险产品,比如高利贷和没有价值的大学文凭【87,90】。 #### 反馈循环 @@ -770,15 +766,15 @@ COMMIT; ​ 当预测性分析影响人们的生活时,自我强化的反馈循环会导致非常有害的问题。例如,考虑雇主使用信用分来评估候选人的例子。你可能是一个信用分不错的好员工,但因不可抗力的意外而陷入财务困境。由于不能按期付账单,你的信用分会受到影响,进而导致找到工作更为困难。失业使你陷入贫困,这进一步恶化了你的分数,使你更难找到工作【87】。在数据与数学严谨性的伪装背后,隐藏的是由恶毒假设导致的恶性循环。 -​ 我们无法预测这种反馈循环何时发生。然而通过对整个系统(不仅仅是计算机化的部分,而且还有与之互动的人)进行整体思考,许多后果是可以够预测的 —— 一种称为**系统思维(systems thinkin)** 的方法【92】。我们可以尝试理解数据分析系统如何响应不同的行为,结构或特性。该系统是否加强和增大了人们之间现有的差异(例如,损不足以奉有余,富者愈富,贫者愈贫),还是试图与不公作斗争?而且即使有着最好的动机,我们也必须当心意想不到的后果。 +​ 我们无法预测这种反馈循环何时发生。然而通过对整个系统(不仅仅是计算机化的部分,而且还有与之互动的人)进行整体思考,许多后果是可以够预测的 —— 一种称为**系统思维(systems thinking)**的方法【92】。我们可以尝试理解数据分析系统如何响应不同的行为,结构或特性。该系统是否加强和增大了人们之间现有的差异(例如,损不足以奉有余,富者愈富,贫者愈贫),还是试图与不公作斗争?而且即使有着最好的动机,我们也必须当心意想不到的后果。 ### 隐私和追踪 -​ 除了预测性分析 —— 即使用数据来做出关于人的自动决策 —— 数据收集本身也存在道德问题。收集数据的组织,与被收集数据的人之间,到底属于什么关系? +​ 除了预测性分析 —— 使用数据来做出关于人的自动决策 —— 数据收集本身也存在道德问题。收集数据的组织,与被收集数据的人之间,到底属于什么关系? ​ 当系统只存储用户明确输入的数据时,是因为用户希望系统以特定方式存储和处理这些数据,**系统是在为用户提供服务**:用户就是客户。但是,当用户的活动被跟踪并记录,作为他们正在做的其他事情的副作用时,这种关系就没有那么清晰了。该服务不再仅仅完成用户想要它要做的事情,而是服务于它自己的利益,而这可能与用户的利益相冲突。 -​ 追踪用户行为数据对于许多面向用户的在线服务而言,变得越来越重要:追踪用户点击了哪些搜索结果有助于提高搜索结果的排名;推荐“喜欢X的人也喜欢Y”,可以帮助用户发现实用有趣的东西; A/B测试和用户流量分析有助于改善用户界面。这些功能需要一定量的用户行为跟踪,而用户也可以从中受益。 +​ 追踪用户行为数据对于许多面向用户的在线服务而言,变得越来越重要:追踪用户点击了哪些搜索结果有助于改善搜索结果的排名;推荐“喜欢X的人也喜欢Y”,可以帮助用户发现实用有趣的东西; A/B测试和用户流量分析有助于改善用户界面。这些功能需要一定量的用户行为跟踪,而用户也可以从中受益。 ​ 但不同公司有着不同的商业模式,追踪并未止步于此。如果服务是通过广告盈利的,那么广告主才是真正的客户,而用户的利益则屈居其次。跟踪的数据会变得更详细,分析变得更深入,数据会保留很长时间,以便为每个人建立详细画像,用于营销。 @@ -786,9 +782,9 @@ COMMIT; #### 监视 -​ 让我们做一个思想实验,尝试用**监视(surveillance)** 一词替换**数据(data)**,再看看常见的短语是不是听起来还那么漂亮【93】。比如:“在我们的监视驱动的组织中,我们收集实时监视流并将它们存储在我们的监视仓库中。我们的监视科学家使用高级分析和监视处理来获得新的见解。“ +​ 让我们做一个思想实验,尝试用**监视(surveillance)** 一词替换**数据(data)**,再看看常见的短语是不是听起来还那么漂亮【93】。比如:“在我们的监视驱动的组织中,我们收集实时监视流并将它们存储在我们的监视仓库中。我们的监视科学家使用高级分析和监视处理来获得新的见解。” -​ 对于本书《设计监控密集型应用》而言,这个思想实验是罕见的争议性内容,但我认为需要激烈的言辞来强调这一点。在我们尝试制造软件“吞噬世界”的过程中【94】,我们已经建立了世界上迄今为止所见过的最伟大的大规模监视基础设施。我们正朝着万物互联迈进,我们正在迅速走近这样一个世界:每个有人居住的空间至少包含一个带互联网连接的麦克风,以智能手机,智能电视,语音控制助理设备,婴儿监视器甚至儿童玩具的形式存在,并使用基于云的语音识别。这些设备中的很多都有着可怕的安全记录【95】。 +​ 对于本书《设计监控密集型应用》而言,这个思想实验是罕见的争议性内容,但我认为需要激烈的言辞来强调这一点。在我们尝试制造软件“吞噬世界”的过程中【94】,我们已经建立了世界上迄今为止所见过的最伟大的大规模监视基础设施。我们正朝着万物互联迈进,我们正在迅速走近这样一个世界:每个有人居住的空间至少包含一个带互联网连接的麦克风,以智能手机、智能电视、语音控制助理设备、婴儿监视器甚至儿童玩具的形式存在,并使用基于云的语音识别。这些设备中的很多都有着可怕的安全记录【95】。 ​ 即使是最为极权与专制的政权,可能也只会想着在每个房间装一个麦克风,并强迫每个人始终携带能够追踪其位置与动向的设备。然而,我们显然是自愿地,甚至热情地投身于这个全域监视的世界。不同之处在于,数据是由公司,而不是由政府机构收集的【96】。 @@ -798,7 +794,7 @@ COMMIT; #### 同意与选择的自由 -​ 我们可能会断言用户是自愿选择使用服务的,尽管服务会跟踪其活动,而且他们已经同意了服务条款与隐私政策,因此他们同意数据收集。我们甚至可以声称,用户在用所提供的数据来**换取**有价值的服务,并且为了提供服务,追踪是必要的。毫无疑问,社交网络,搜索引擎,以及各种其他免费的在线服务对于用户来说都是有价值的,但是这个说法却存在问题。 +​ 我们可能会断言用户是自愿选择使用了会跟踪其活动的服务,而且他们已经同意了服务条款与隐私政策,因此他们同意数据收集。我们甚至可以声称,用户在用所提供的数据来**换取**有价值的服务,并且为了提供服务,追踪是必要的。毫无疑问,社交网络、搜索引擎以及各种其他免费的在线服务对于用户来说都是有价值的,但是这个说法却存在问题。 ​ 用户几乎不知道他们提供给我们的是什么数据,哪些数据被放进了数据库,数据又是怎样被保留与处理的 —— 大多数隐私政策都是模棱两可的,忽悠用户而不敢打开天窗说亮话。如果用户不了解他们的数据会发生什么,就无法给出任何有意义的同意。有时来自一个用户的数据还会提到一些关于其他人的事,而其他那些人既不是该服务的用户,也没有同意任何条款。我们在本书这一部分中讨论的衍生数据集 —— 来自整个用户群的数据,加上行为追踪与外部数据源 —— 就恰好是用户无法(在真正意义上)理解的数据类型。 @@ -806,11 +802,11 @@ COMMIT; ​ 对于不同意监视的用户,唯一真正管用的备选项,就是简单地不使用服务。但这个选择也不是真正自由的:如果一项服务如此受欢迎,以至于“被大多数人认为是基本社会参与的必要条件”【99】,那么指望人们选择退出这项服务是不合理的 —— 使用它**事实上(de facto)** 是强制性的。例如,在大多数西方社会群体中,携带智能手机,使用Facebook进行社交,以及使用Google查找信息已成为常态。特别是当一项服务具有网络效应时,人们选择**不**使用会产生社会成本。 -​ 因为跟踪用户而拒绝使用服务,这只是少数人才拥有的权力,他们有足够的时间与知识来了解隐私政策,并承受的起代价:错过社会参与,以及使用服务可能带来的专业机会。对于那些处境不太好的人而言,并没有真正意义上的选择:监控是不可避免的。 +​ 因为一个服务会跟踪用户而拒绝使用它,这只是少数人才拥有的权力,他们有足够的时间与知识来了解隐私政策,并承受得起代价:错过社会参与,以及使用服务可能带来的专业机会。对于那些处境不太好的人而言,并没有真正意义上的选择:监控是不可避免的。 #### 隐私与数据使用 -​ 有时候,人们声称“隐私已死”,理由是有些用户愿意把各种关于他们生活的事情发布到社交媒体上,有时是平凡俗套,但有时是高度私密的。但这种说法是错误的,而且是对**隐私(privacy)** 一词的误解。 +​ 有时候,人们声称“隐私已死”,理由是有些用户愿意把各种关于他们生活的事情发布到社交媒体上,有时是平凡俗套,但有时是高度私密的。但这种说法是错误的,而且是对**隐私(privacy)**一词的误解。 ​ 拥有隐私并不意味着保密一切东西;它意味着拥有选择向谁展示哪些东西的自由,要公开什么,以及要保密什么。**隐私权是一项决定权**:在从保密到透明的光谱上,隐私使得每个人都能决定自己想要在什么地方位于光谱上的哪个位置【99】。这是一个人自由与自主的重要方面。 @@ -818,13 +814,13 @@ COMMIT; ​ 这些公司反过来选择保密这些监视结果,因为揭露这些会令人毛骨悚然,并损害它们的商业模式(比其他公司更了解人)。用户的私密信息只会间接地披露,例如针对特定人群定向投放广告的工具(比如那些患有特定疾病的人群)。 -​ 即使特定用户无法从特定广告定向的人群中以个体的形式区分出来,但他们已经失去了披露一些私密信息的能动性,例如他们是否患有某种疾病。决定向谁透露什么并不是由个体按照自己的喜好决定的,是由**公司**,以利润最大化为目标来行使隐私权的。 +​ 即使特定用户无法从特定广告定向的人群中以个体的形式区分出来,但他们已经失去了披露一些私密信息的能动性,例如他们是否患有某种疾病。决定向谁透露什么并不是由个体按照自己的喜好决定的,而是由**公司**,以利润最大化为目标来行使隐私权的。 -​ 许多公司都有一个目标,不要让人**感觉到**毛骨悚然 —— 先不说它们收集数据实际上是多么具有侵犯性,让我们先关注用户感知的管理。这些用户感受经常被管理的很糟糕:例如,在事实上可能正确的一些东西,但如果会触发痛苦的回忆,用户可能并不希望被提醒【100】。对于任何类型的数据,我们都应当考虑它出错、不可取、不合时宜的可能性,并且需要建立处理这些失效的机制。无论是“不可取”还是“不合时宜”,当然都是由人的判断决定的;除非我们明确地将算法编码设计为尊重人类的需求,否则算法会无视这些概念。作为这些系统的工程师,我们必须保持谦卑,充分规划,接受这些失效。 +​ 许多公司都有一个目标,不要让人**感觉到**毛骨悚然 —— 先不说它们收集数据实际上是多么具有侵犯性,让我们先关注对用户感受的管理。这些用户感受经常被管理得很糟糕:例如,在事实上可能正确的一些东西,如果会触发痛苦的回忆,用户可能并不希望被提醒【100】。对于任何类型的数据,我们都应当考虑它出错、不可取、不合时宜的可能性,并且需要建立处理这些失效的机制。无论是“不可取”还是“不合时宜”,当然都是由人的判断决定的;除非我们明确地将算法编码设计为尊重人类的需求,否则算法会无视这些概念。作为这些系统的工程师,我们必须保持谦卑,充分规划,接受这些失效。 ​ 允许在线服务的用户控制其隐私设置,例如控制其他用户可以看到哪些东西,是将一些控制交还给用户的第一步。但无论怎么设置,服务本身仍然可以不受限制地访问数据,并能以隐私策略允许的任何方式自由使用它。即使服务承诺不会将数据出售给第三方,它通常会授予自己不受限制的权利,以便在内部处理与分析数据,而且往往比用户公开可见的部分要深入的多。 -​ 这种从个体到公司的大规模隐私权转移在历史上是史无前例的【99】。监控一直存在,但它过去是昂贵的,手动的,不是可伸缩的,自动化的。信任关系始终存在,例如患者与其医生之间,或被告与其律师之间 —— 但在这些情况下,数据的使用严格受到道德,法律和监管限制的约束。互联网服务使得在未经有意义的同意下收集大量敏感信息变得容易得多,而且无需用户理解他们的私人数据到底发生了什么。 +​ 这种从个体到公司的大规模隐私权转移在历史上是史无前例的【99】。监控一直存在,但它过去是昂贵的、手动的,不是可伸缩的、自动化的。信任关系一直存在,例如患者与其医生之间,或被告与其律师之间 —— 但在这些情况下,数据的使用严格受到道德,法律和监管限制的约束。互联网服务使得在未经有意义的同意下收集大量敏感信息变得容易得多,而且无需用户理解他们的私人数据到底发生了什么。 #### 数据资产与权力 @@ -832,9 +828,9 @@ COMMIT; ​ 更准确的看法恰恰相反:从经济的角度来看,如果定向广告是服务的金主,那么关于人的行为数据就是服务的核心资产。在这种情况下,用户与之交互的应用仅仅是一种诱骗用户将更多的个人信息提供给监控基础设施的手段【99】。在线服务中经常表现出的令人愉悦的人类创造力与社会关系,十分讽刺地被数据提取机器所滥用。 -​ 个人数据是珍贵资产的说法因为数据中介的存在得到支持,这是阴影中的秘密行业,购买,聚合,分析,推断,以及转售私密个人数据,主要用于市场营销【90】。初创公司按照它们的用户数量,“眼球数”,—— 即它们的监视能力来估值。 +​ 个人数据是珍贵资产的说法因为数据中介的存在得到支持,这是阴影中的秘密行业,购买、聚合、分析、推断以及转售私密个人数据,主要用于市场营销【90】。初创公司按照它们的用户数量,“眼球数”,—— 即它们的监视能力来估值。 -​ 因为数据很有价值,所以很多人都想要它。当然,公司也想要它 —— 这就是为什么它们一开始就收集数据的原因。但政府也想获得它:通过秘密交易,胁迫,法律强制,或者只是窃取【101】。当公司破产时,收集到的个人数据就是被出售的资产之一。而且数据安全很难保护,因此经常发生令人难堪的泄漏事件【102】。 +​ 因为数据很有价值,所以很多人都想要它。当然,公司也想要它 —— 这就是为什么它们一开始就收集数据的原因。但政府也想获得它:通过秘密交易、胁迫、法律强制或者只是窃取【101】。当公司破产时,收集到的个人数据就是被出售的资产之一。而且数据安全很难保护,因此经常发生令人难堪的泄漏事件【102】。 ​ 这些观察已经导致批评者声称,数据不仅仅是一种资产,而且是一种“有毒资产”【101】,或者至少是“有害物质”【103】。即使我们认为自己有能力阻止数据滥用,但每当我们收集数据时,我们都需要平衡收益以及这些数据落入恶人手中的风险:计算机系统可能会被犯罪分子或敌国特务渗透,数据可能会被内鬼泄露,公司可能会落入不择手段的管理层手中,而这些管理者有着迥然不同的价值观,或者国家可能被能毫无愧色迫使我们交出数据的政权所接管。 @@ -846,9 +842,9 @@ COMMIT; ​ 工业革命是通过重大的技术与农业进步实现的,它带来了持续的经济增长,长期的生活水平显著提高。然而它也带来了一些严重的问题:空气污染(由于烟雾和化学过程)和水污染(工业垃圾和人类垃圾)是可怖的。工厂老板生活在纷奢之中,而城市工人经常居住在非常糟糕的住房中,并且在恶劣的条件下长时间工作。童工很常见,甚至包括矿井中危险而低薪的工作。 -​ 制定了保护措施花费了很长的时间,例如环境保护条例,工作场所安全条例,宣布使用童工非法,以及食品卫生检查。毫无疑问,生产成本增加了,因为工厂再也不能把废物倒入河流,销售污染的食物,或者剥削工人。但是整个社会都从中受益良多,我们中很少会有人想回到这些管制条例之前的日子【87】。 +​ 制定保护措施花费了很长的时间,例如环境保护条例、工作场所安全条例、宣布使用童工非法以及食品卫生检查。毫无疑问,生产成本增加了,因为工厂再也不能把废物倒入河流、销售污染的食物或者剥削工人。但是整个社会都从中受益良多,我们中很少会有人想回到这些管制条例之前的日子【87】。 -​ 就像工业革命有着黑暗面需要应对一样,我们转向信息时代的过程中,也有需要应对与解决的重大问题。我相信数据的收集与使用就是其中一个问题。用布鲁斯·施奈尔的话来说【96】: +​ 就像工业革命有着黑暗面需要应对一样,我们转向信息时代的过程中,也有需要应对与解决的重大问题。我相信数据的收集与使用就是其中一个问题。用Bruce Schneier的话来说【96】: > ​ 数据是信息时代的污染问题,保护隐私是环境挑战。几乎所有的电脑都能生产信息。它堆积在周围,开始溃烂。我们如何处理它 —— 我们如何控制它,以及如何摆脱它 —— 是信息经济健康发展的核心议题。正如我们今天回顾工业时代的早期年代,并想知道我们的祖先在忙于建设工业世界的过程时怎么能忽略污染问题;我们的孙辈在回望信息时代的早期年代时,将会就我们如何应对数据收集和滥用的挑战来评断我们。 > @@ -856,29 +852,29 @@ COMMIT; #### 立法与自律 -​ 数据保护法可能有助于维护个人的权利。例如,1995年的“欧洲数据保护指示”规定,个人数据必须“为特定的,明确的和合法的目的收集,而不是以与这些目的不相符的方式进一步处理”,并且数据必须“就收集的目的而言适当,相关,不过分。“【107】。 +​ 数据保护法可能有助于维护个人的权利。例如,1995年的“欧洲数据保护指示”规定,个人数据必须“为特定的、明确的和合法的目的收集,而不是以与这些目的不相符的方式进一步处理”,并且数据必须“就收集的目的而言适当、相关、不过分。“【107】。 ​ 但是,这个立法在今天的互联网环境下是否有效还是有疑问的【108】。这些规则直接否定了大数据的哲学,即最大限度地收集数据,将其与其他数据集结合起来进行试验和探索,以便产生新的洞察。探索意味着将数据用于未曾预期的目的,这与用户同意的“特定和明确”目的相反(如果我们可以有意义地表示同意的话)【109】。更新的规章正在制定中【89】。 ​ 那些收集了大量有关人的数据的公司反对监管,认为这是创新的负担与阻碍。在某种程度上,这种反对是有道理的。例如,分享医疗数据时,存在明显的隐私风险,但也有潜在的机遇:如果数据分析能够帮助我们实现更好的诊断或找到更好的治疗方法,能够阻止多少人的死亡【110】?过度监管可能会阻止这种突破。在这种潜在机会与风险之间找出平衡是很困难的【105】。 -​ 从根本上说,我认为我们需要科技行业在个人数据方面的文化转变。我们应该停止将用户视作待优化的指标数据,并记住他们是值得尊重,有尊严和能动性的人。我们应当在数据收集和实际处理中自我约束,以建立和维持依赖我们软件的人们的信任【111】。我们应当将教育终端用户视为己任,告诉他们我们是如何使用他们的数据的,而不是将他们蒙在鼓里。 +​ 从根本上说,我认为我们需要科技行业在个人数据方面的文化转变。我们应该停止将用户视作待优化的指标数据,并记住他们是值得尊重、有尊严和能动性的人。我们应当在数据收集和实际处理中自我约束,以建立和维持依赖我们软件的人们的信任【111】。我们应当将教育终端用户视为己任,告诉他们我们是如何使用他们的数据的,而不是将他们蒙在鼓里。 -​ 我们应该允许每个人保留自己的隐私 —— 即,对自己数据的控制 —— 而不是通过监视来窃取这种控制权。我们控制自己数据的个体权利就像是国家公园的自然环境:如果我们不去明确地保护它,关心它,它就会被破坏。这将是公地的悲剧,我们都会因此而变得更糟。无所不在的监视并非不可避免的 —— 我们现在仍然能阻止它。 +​ 我们应该允许每个人保留自己的隐私 —— 即,对自己数据的控制 —— 而不是通过监视来窃取这种控制权。我们控制自己数据的个体权利就像是国家公园的自然环境:如果我们不去明确地保护它、关心它,它就会被破坏。这将是公地的悲剧,我们都会因此而变得更糟。无所不在的监视并非不可避免的 —— 我们现在仍然能阻止它。 -​ 我们究竟能做到哪一步,是一个开放的问题。首先,我们不应该永久保留数据,而是一旦不再需要就立即清除数据【111,112】。清除数据与不变性的想法背道而驰(请参阅“[不变性的局限性](ch11.md#不变性的局限性)”),但这是可以解决该问题。我所看到的一种很有前景的方法是通过加密协议来实施访问控制,而不仅仅是通过策略【113,114】。总的来说,文化与态度的改变是必要的。 +​ 我们究竟能做到哪一步,是一个开放的问题。首先,我们不应该永久保留数据,而是一旦不再需要就立即清除数据【111,112】。清除数据与不变性的想法背道而驰(请参阅“[不变性的局限性](ch11.md#不变性的局限性)”),但这是可以解决的问题。我所看到的一种很有前景的方法是通过加密协议来实施访问控制,而不仅仅是通过策略【113,114】。总的来说,文化与态度的改变是必要的。 ## 本章小结 -​ 在本章中,我们讨论了设计数据系统的新方式,而且也包括了我的个人观点,以及对未来的猜测。我们从这样一种观察开始:没有单种工具能高效服务所有可能的用例,因此应用必须组合使用几种不同的软件才能实现其目标。我们讨论了如何使用批处理与事件流来解决这一**数据集成(data integration)** 问题,以便让数据变更在不同系统之间流动。 +​ 在本章中,我们讨论了设计数据系统的新方式,而且也包括了我的个人观点,以及对未来的猜测。我们从这样一种观察开始:没有单种工具能高效服务所有可能的用例,因此应用必须组合使用几种不同的软件才能实现其目标。我们讨论了如何使用批处理与事件流来解决这一**数据集成(data integration)**问题,以便让数据变更在不同系统之间流动。 ​ 在这种方法中,某些系统被指定为记录系统,而其他数据则通过转换衍生自记录系统。通过这种方式,我们可以维护索引,物化视图,机器学习模型,统计摘要等等。通过使这些衍生和转换操作异步且松散耦合,能够防止一个区域中的问题扩散到系统中不相关部分,从而增加整个系统的稳健性与容错性。 ​ 将数据流表示为从一个数据集到另一个数据集的转换也有助于演化应用程序:如果你想变更其中一个处理步骤,例如变更索引或缓存的结构,则可以在整个输入数据集上重新运行新的转换代码,以便重新衍生输出。同样,出现问题时,你也可以修复代码并重新处理数据以便恢复。 -​ 这些过程与数据库内部已经完成的过程非常类似,因此我们将数据流应用的概念重新改写为,**分拆(unbundling)** 数据库组件,并通过组合这些松散耦合的组件来构建应用程序。 +​ 这些过程与数据库内部已经完成的过程非常类似,因此我们将数据流应用的概念重新改写为,**分拆(unbundling)**数据库组件,并通过组合这些松散耦合的组件来构建应用程序。 ​ 衍生状态可以通过观察底层数据的变更来更新。此外,衍生状态本身可以进一步被下游消费者观察。我们甚至可以将这种数据流一路传送至显示数据的终端用户设备,从而构建可动态更新以反映数据变更,并在离线时能继续工作的用户界面。 @@ -886,7 +882,7 @@ COMMIT; ​ 通过围绕数据流构建应用,并异步检查约束,我们可以避免绝大多数的协调工作,创建保证完整性且性能仍然表现良好的系统,即使在地理散布的情况下与出现故障时亦然。然后,我们对使用审计来验证数据完整性,以及损坏检测进行了一些讨论。 -​ 最后,我们退后一步,审视了构建数据密集型应用的一些道德问题。我们看到,虽然数据可以用来做好事,但它也可能造成很大伤害:作出严重影响人们生活的决定却难以申诉,导致歧视与剥削,监视常态化,曝光私密信息。我们也冒着数据被泄露的风险,并且可能会发现,即使是善意地使用数据也可能会导致意想不到的后果。 +​ 最后,我们退后一步,审视了构建数据密集型应用的一些道德问题。我们看到,虽然数据可以用来做好事,但它也可能造成很大伤害:作出严重影响人们生活的决定却难以申诉,导致歧视与剥削、监视常态化、曝光私密信息。我们也冒着数据被泄露的风险,并且可能会发现,即使是善意地使用数据也可能会导致意想不到的后果。 ​ 由于软件和数据对世界产生了如此巨大的影响,我们工程师们必须牢记,我们有责任为我们想要的那种世界而努力:一个尊重人们,尊重人性的世界。我希望我们能够一起为实现这一目标而努力。 diff --git a/zh-tw/ch12.md b/zh-tw/ch12.md index b3f892b..f99d640 100644 --- a/zh-tw/ch12.md +++ b/zh-tw/ch12.md @@ -50,9 +50,9 @@ #### 衍生資料與分散式事務 -​ 保持不同資料系統彼此一致的經典方法涉及分散式事務,如“[原子提交與兩階段提交(2PC)](ch9.md#原子提交與兩階段提交(2PC))”中所述。與分散式事務相比,使用衍生資料系統的方法如何? +​ 保持不同資料系統彼此一致的經典方法涉及分散式事務,如“[原子提交與兩階段提交](ch9.md#原子提交與兩階段提交)”中所述。與分散式事務相比,使用衍生資料系統的方法如何? -​ 在抽象層面,它們透過不同的方式達到類似的目標。分散式事務透過**鎖**進行互斥來決定寫入的順序(請參閱“[兩階段鎖定(2PL)](ch7.md#兩階段鎖定(2PL))”),而CDC和事件溯源使用日誌進行排序。分散式事務使用原子提交來確保變更只生效一次,而基於日誌的系統通常基於**確定性重試**和**冪等性**。 +​ 在抽象層面,它們透過不同的方式達到類似的目標。分散式事務透過**鎖**進行互斥來決定寫入的順序(請參閱“[兩階段鎖定](ch7.md#兩階段鎖定)”),而CDC和事件溯源使用日誌進行排序。分散式事務使用原子提交來確保變更只生效一次,而基於日誌的系統通常基於**確定性重試**和**冪等性**。 ​ 最大的不同之處在於事務系統通常提供[線性一致性](ch9.md#線性一致性),這包含著有用的保證,例如[讀己之寫](ch5.md#讀己之寫)。另一方面,衍生資料系統通常是非同步更新的,因此它們預設不會提供相同的時序保證。 @@ -412,25 +412,25 @@ ​ 對於只讀取資料的無狀態服務,出問題也沒什麼大不了的:你可以修復該錯誤並重啟服務,而一切都恢復正常。像資料庫這樣的有狀態系統就沒那麼簡單了:它們被設計為永遠記住事物(或多或少),所以如果出現問題,這種(錯誤的)效果也將潛在地永遠持續下去,這意味著它們需要更仔細的思考【50】。 -​ 我們希望構建可靠且**正確**的應用(即使面對各種故障,程式的語義也能被很好地定義與理解)。約四十年來,原子性,隔離性和永續性([第七章](ch7.md))等事務特性一直是構建正確應用的首選工具。然而這些地基沒有看上去那麼牢固:例如弱隔離級別帶來的困惑可以佐證(請參閱“[弱隔離級別](ch7.md#弱隔離級別)”)。 +​ 我們希望構建可靠且**正確**的應用(即使面對各種故障,程式的語義也能被很好地定義與理解)。約四十年來,原子性、隔離性和永續性([第七章](ch7.md))等事務特性一直是構建正確應用的首選工具。然而這些地基沒有看上去那麼牢固:例如弱隔離級別帶來的困惑可以佐證(請參閱“[弱隔離級別](ch7.md#弱隔離級別)”)。 -​ 事務在某些領域被完全拋棄,並被提供更好效能與可伸縮性的模型取代,但更復雜的語義(例如,請參閱“[無領導者複製](ch5.md#無領導者複製)”)。**一致性(Consistency)** 經常被談起,但其定義並不明確(“[一致性](ch5.md#一致性)”和[第九章](ch9.md))。有些人斷言我們應當為了高可用而“擁抱弱一致性”,但卻對這些概念實際上意味著什麼缺乏清晰的認識。 +​ 事務在某些領域被完全拋棄,並被提供更好效能與可伸縮性的模型取代,但後者有更復雜的語義(例如,請參閱“[無主複製](ch5.md#無主複製)”)。**一致性(Consistency)** 經常被談起,但其定義並不明確(請參閱“[一致性](ch7.md#一致性)”和[第九章](ch9.md))。有些人斷言我們應當為了高可用而“擁抱弱一致性”,但卻對這些概念實際上意味著什麼缺乏清晰的認識。 ​ 對於如此重要的話題,我們的理解,以及我們的工程方法卻是驚人地薄弱。例如,確定在特定事務隔離等級或複製配置下執行特定應用是否安全是非常困難的【51,52】。通常簡單的解決方案似乎在低併發性的情況下工作正常,並且沒有錯誤,但在要求更高的情況下卻會出現許多微妙的錯誤。 -​ 例如,凱爾金斯伯裡(Kyle Kingsbury)的傑普森(Jepsen)實驗【53】標出了一些產品聲稱的安全保證與其在網路問題與崩潰時的實際行為之間的明顯差異。即使像資料庫這樣的基礎設施產品沒有問題,應用程式碼仍然需要正確使用它們提供的功能才行,如果配置很難理解,這是很容易出錯的(在這種情況下指的是弱隔離級別,法定人數配置等)。 +​ 例如,Kyle Kingsbury的Jepsen實驗【53】標出了一些產品聲稱的安全保證與其在網路問題與崩潰時的實際行為之間的明顯差異。即使像資料庫這樣的基礎設施產品沒有問題,應用程式碼仍然需要正確使用它們提供的功能才行,如果配置很難理解,這是很容易出錯的(在這種情況下指的是弱隔離級別,法定人數配置等)。 -​ 如果你的應用可以容忍偶爾的崩潰,以及以不可預料的方式損壞或丟失資料,那生活就要簡單得多,而你可能只要雙手合十念阿彌陀佛,期望佛祖能保佑最好的結果。另一方面,如果你需要更強的正確性保證,那麼可序列化與原子提交就是久經考驗的方法,但它們是有代價的:它們通常只在單個數據中心中工作(排除地理散佈式架構),並限制了系統能夠實現的規模與容錯特性。 +​ 如果你的應用可以容忍偶爾的崩潰,以及以不可預料的方式損壞或丟失資料,那生活就要簡單得多,而你可能只要雙手合十念阿彌陀佛,期望佛祖能保佑最好的結果。另一方面,如果你需要更強的正確性保證,那麼可序列化與原子提交就是久經考驗的方法,但它們是有代價的:它們通常只在單個數據中心中工作(這就排除了地理位置分散的架構),並限制了系統能夠實現的規模與容錯特性。 -​ 雖然傳統的事務方法並沒有走遠,但我也相信在使應用正確而靈活地處理錯誤方面上,事務並不是最後的遺言。在本節中,我將提出一些在資料流架構中考量正確性的方式。 +​ 雖然傳統的事務方法並沒有走遠,但我也相信在使應用正確而靈活地處理錯誤方面上,事務也不是最後一個可以談的。在本節中,我將提出一些在資料流架構中考量正確性的方式。 -### 資料庫端到端的爭論 +### 資料庫的端到端原則 -​ 應用僅僅是使用具有相對較強安全屬性的資料系統(例如可序列化的事務),並不意味著就可以保證沒有資料丟失或損壞。例如,如果某個應用有個Bug,導致它寫入不正確的資料,或者從資料庫中刪除資料,那麼可序列化的事務也救不了你。 +​ 僅僅因為一個應用程式使用了具有相對較強安全屬性的資料系統(例如可序列化的事務),並不意味著就可以保證沒有資料丟失或損壞。例如,如果某個應用有個Bug,導致它寫入不正確的資料,或者從資料庫中刪除資料,那麼可序列化的事務也救不了你。 -​ 這個例子可能看起來很無聊,但值得認真對待:應用會出Bug,而人也會犯錯誤。我在“[狀態,流與不可變性](ch11.md#狀態,流與不可變性)”中使用了這個例子來支援不可變和僅追加的資料,閹割掉錯誤程式碼摧毀良好資料的能力,能讓從錯誤中恢復更為容易。 +​ 這個例子可能看起來很無聊,但值得認真對待:應用會出Bug,而人也會犯錯誤。我在“[狀態、流和不變性](ch11.md#狀態、流和不變性)”中使用了這個例子來支援不可變和僅追加的資料,閹割掉錯誤程式碼摧毀良好資料的能力,能讓從錯誤中恢復更為容易。 -​ 雖然不變性很有用,但它本身並非萬靈藥。讓我們來看一個可能發生的,非常微妙的資料損壞案例。 +​ 雖然不變性很有用,但它本身並非萬靈藥。讓我們來看一個可能發生的、非常微妙的資料損壞案例。 #### 正好執行一次操作 @@ -438,13 +438,13 @@ ​ 處理兩次是資料損壞的一種形式:為同樣的服務向客戶收費兩次(收費太多)或增長計數器兩次(誇大指標)都不是我們想要的。在這種情況下,恰好一次意味著安排計算,使得最終效果與沒有發生錯誤的情況一樣,即使操作實際上因為某種錯誤而重試。我們先前討論過實現這一目標的幾種方法。 -​ 最有效的方法之一是使操作**冪等(idempotent)**(請參閱“[冪等性](ch11.md#冪等性)”);即確保它無論是執行一次還是執行多次都具有相同的效果。但是,將不是天生冪等的操作變為冪等的操作需要一些額外的努力與關注:你可能需要維護一些額外的元資料(例如更新了值的操作ID集合),並在從一個節點故障切換至另一個節點時做好防護(請參閱的“[領導與鎖定](ch9.md#領導與鎖定)”)。 +​ 最有效的方法之一是使操作**冪等(idempotent)**(請參閱“[冪等性](ch11.md#冪等性)”);即確保它無論是執行一次還是執行多次都具有相同的效果。但是,將不是天生冪等的操作變為冪等的操作需要一些額外的努力與關注:你可能需要維護一些額外的元資料(例如更新了值的操作ID集合),並在從一個節點故障切換至另一個節點時做好防護(請參閱“[領導者和鎖](ch8.md#領導者和鎖)”)。 #### 抑制重複 -​ 除了流處理之外,其他許多地方也需要抑制重複的模式。例如,TCP使用資料包上的序列號,在接收方將它們正確排序。並確定網路上是否有資料包丟失或重複。任何丟失的資料包都會被重新傳輸,而在將資料交付應用前,TCP協議棧會移除任何重複資料包。 +​ 除了流處理之外,其他許多地方也需要抑制重複的模式。例如,TCP使用了資料包上的序列號,以便接收方可以將它們正確排序,並確定網路上是否有資料包丟失或重複。在將資料交付應用前,TCP協議棧會重新傳輸任何丟失的資料包,也會移除任何重複的資料包。 -​ 但是,這種重複抑制僅適用於單條TCP連線的場景中。假設TCP連線是一個客戶端與資料庫的連線,並且它正在執行[例12-1]()中的事務。在許多資料庫中,事務是繫結在客戶端連線上的(如果客戶端傳送了多個查詢,資料庫就知道它們屬於同一個事務,因為它們是在同一個TCP連線上傳送的)。如果客戶端在傳送`COMMIT`之後但在從資料庫伺服器收到響應之前遇到網路中斷與連線超時,客戶端是不知道事務是否已經被提交的([圖8-1](../img/fig8-1.png))。 +​ 但是,這種重複抑制僅適用於單條TCP連線的場景中。假設TCP連線是一個客戶端與資料庫的連線,並且它正在執行[例12-1]()中的事務。在許多資料庫中,事務是繫結在客戶端連線上的(如果客戶端傳送了多個查詢,資料庫就知道它們屬於同一個事務,因為它們是在同一個TCP連線上傳送的)。如果客戶端在傳送`COMMIT`之後並在從資料庫伺服器收到響應之前遇到網路中斷與連線超時,客戶端是不知道事務是否已經被提交的([圖8-1](../img/fig8-1.png))。 **例12-1 資金從一個賬戶到另一個賬戶的非冪等轉移** @@ -455,17 +455,17 @@ BEGIN TRANSACTION; COMMIT; ``` -​ 客戶端可以重連到資料庫並重試事務,但現在現在處於TCP重複抑制的範圍之外了。因為[例12-1]()中的事務不是冪等的,可能會發生轉了\$22而不是期望的\$11。因此,儘管[例12-1]()是一個事務原子性的標準樣例,但它實際上並不正確,而真正的銀行並不會這樣辦事【3】。 +​ 客戶端可以重連到資料庫並重試事務,但現在已經處於TCP重複抑制的範圍之外了。因為[例12-1]()中的事務不是冪等的,可能會發生轉了\$22而不是期望的\$11。因此,儘管[例12-1]()是一個事務原子性的標準樣例,但它實際上並不正確,而真正的銀行並不會這樣辦事【3】。 -​ 兩階段提交(請參閱“[原子提交與兩階段提交(2PC)](ch9.md#原子提交與兩階段提交(2PC))”)協議會破壞TCP連線與事務之間的1:1對映,因為它們必須在故障後允許事務協調器重連到資料庫,告訴資料庫將存疑事務提交還是中止。這足以確保事務只被恰好執行一次嗎?不幸的是,並不能。 +​ 兩階段提交(請參閱“[原子提交與兩階段提交](ch9.md#原子提交與兩階段提交)”)協議會破壞TCP連線與事務之間的1:1對映,因為它們必須在故障後允許事務協調器重連到資料庫,告訴資料庫將存疑事務提交還是中止。這足以確保事務只被恰好執行一次嗎?不幸的是,並不能。 ​ 即使我們可以抑制資料庫客戶端與伺服器之間的重複事務,我們仍然需要擔心終端使用者裝置與應用伺服器之間的網路。例如,如果終端使用者的客戶端是Web瀏覽器,則它可能會使用HTTP POST請求向伺服器提交指令。也許使用者正處於一個訊號微弱的蜂窩資料網路連線中,它們成功地傳送了POST,但卻在能夠從伺服器接收響應之前沒了訊號。 -​ 在這種情況下,可能會向用戶顯示錯誤訊息,而他們可能會手動重試。 Web瀏覽器警告說,“你確定要再次提交這個表單嗎?” —— 使用者選“是”,因為他們希望操作發生。 (Post/Redirect/Get模式【54】可以避免在正常操作中出現此警告訊息,但POST請求超時就沒辦法了。)從Web伺服器的角度來看,重試是一個獨立的請求,而從資料庫的角度來看,這是一個獨立的事務。通常的除重機制無濟於事。 +​ 在這種情況下,可能會向用戶顯示錯誤訊息,而他們可能會手動重試。 Web瀏覽器警告說,“你確定要再次提交這個表單嗎?” —— 使用者選“是”,因為他們希望操作發生。 (Post/Redirect/Get模式【54】可以避免在正常操作中出現此警告訊息,但POST請求超時就沒辦法了。)從Web伺服器的角度來看,重試是一個獨立的請求;從資料庫的角度來看,這是一個獨立的事務。通常的除重機制無濟於事。 #### 操作識別符號 -​ 要在通過幾跳的網路通訊上使操作具有冪等性,僅僅依賴資料庫提供的事務機制是不夠的 —— 你需要考慮**端到端(end-to-end)** 的請求流。 +​ 要在通過幾跳的網路通訊上使操作具有冪等性,僅僅依賴資料庫提供的事務機制是不夠的 —— 你需要考慮**端到端(end-to-end)**的請求流。 ​ 例如,你可以為操作生成一個唯一的識別符號(例如UUID),並將其作為隱藏表單欄位包含在客戶端應用中,或透過計算所有表單相關欄位的雜湊來生成操作ID 【3】。如果Web瀏覽器提交了兩次POST請求,這兩個請求將具有相同的操作ID。然後,你可以將該操作ID一路傳遞到資料庫,並檢查你是否曾經使用給定的ID執行過一個操作,如[例12-2]()中所示。 **例12-2 使用唯一ID來抑制重複請求** @@ -474,8 +474,9 @@ COMMIT; ALTER TABLE requests ADD UNIQUE (request_id); BEGIN TRANSACTION; - INSERT INTO requests(request_id, from_account, to_account, amount) - VALUES('0286FDB8-D7E1-423F-B40B-792B3608036C', 4321, 1234, 11.00); + INSERT INTO requests + (request_id, from_account, to_account, amount) + VALUES('0286FDB8-D7E1-423F-B40B-792B3608036C', 4321, 1234, 11.00); UPDATE accounts SET balance = balance + 11.00 WHERE account_id = 1234; UPDATE accounts SET balance = balance - 11.00 WHERE account_id = 4321; COMMIT; @@ -483,32 +484,32 @@ COMMIT; ​ [例12-2]()依賴於`request_id`列上的唯一約束。如果一個事務嘗試插入一個已經存在的ID,那麼`INSERT`失敗,事務被中止,使其無法生效兩次。即使在較弱的隔離級別下,關係資料庫也能正確地維護唯一性約束(而在“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中討論過,應用級別的**檢查-然後-插入**可能會在不可序列化的隔離下失敗)。 -​ 除了抑制重複的請求之外,[例12-2]()中的請求表表現得就像一種事件日誌,提示向著事件溯源的方向(請參閱“[事件溯源](ch11.md#事件溯源)”)。更新賬戶餘額事實上不必與插入事件發生在同一個事務中,因為它們是冗餘的,而能由下游消費者從請求事件中衍生出來 —— 只要該事件被恰好處理一次,這又一次可以使用請求ID來強制執行。 +​ 除了抑制重複的請求之外,[例12-2]()中的請求表表現得就像一種事件日誌,暗示著事件溯源的想法(請參閱“[事件溯源](ch11.md#事件溯源)”)。更新賬戶餘額事實上不必與插入事件發生在同一個事務中,因為它們是冗餘的,而能由下游消費者從請求事件中衍生出來 —— 只要該事件被恰好處理一次,這又一次可以使用請求ID來強制執行。 -**端到端的原則** +#### 端到端原則 -​ 抑制重複事務的這種情況只是一個更普遍的原則的一個例子,這個原則被稱為**端到端的原則(end-to-end argument)**,它在1984年由Saltzer,Reed和Clark闡述【55】: +​ 抑制重複事務的這種情況只是一個更普遍的原則的一個例子,這個原則被稱為**端到端原則(end-to-end argument)**,它在1984年由Saltzer,Reed和Clark闡述【55】: -> ​ 只有在通訊系統兩端應用的知識與幫助下,所討論的功能才能完全地正確地實現。因而將這種被質疑的功能作為通訊系統本身的功能是不可能的。 (有時,通訊系統可以提供這種功能的不完備版本,可能有助於提高效能) +> ​ 只有在通訊系統兩端應用的知識與幫助下,所討論的功能才能完全地正確地實現。因而將這種被質疑的功能作為通訊系統本身的功能是不可能的。 (有時,通訊系統可以提供這種功能的不完備版本,可能有助於提高效能。) > ​ 在我們的例子中**所討論的功能**是重複抑制。我們看到TCP在TCP連線層次抑制了重複的資料包,一些流處理器在訊息處理層次提供了所謂的恰好一次語義,但這些都無法阻止當一個請求超時時,使用者親自提交重複的請求。TCP,資料庫事務,以及流處理器本身並不能完全排除這些重複。解決這個問題需要一個端到端的解決方案:從終端使用者的客戶端一路傳遞到資料庫的事務識別符號。 -​ 端到端引數也適用於檢查資料的完整性:乙太網,TCP和TLS中內建的校驗和可以檢測網路中資料包的損壞情況,但是它們無法檢測到由連線兩端傳送/接收軟體中Bug導致的損壞。或資料儲存所在磁碟上的損壞。如果你想捕獲資料所有可能的損壞來源,你也需要端到端的校驗和。 +​ 端到端原則也適用於檢查資料的完整性:乙太網,TCP和TLS中內建的校驗和可以檢測網路中資料包的損壞情況,但是它們無法檢測到由連線兩端傳送/接收軟體中Bug導致的損壞。或資料儲存所在磁碟上的損壞。如果你想捕獲資料所有可能的損壞來源,你也需要端到端的校驗和。 ​ 類似的原則也適用於加密【55】:家庭WiFi網路上的密碼可以防止人們竊聽你的WiFi流量,但無法阻止網際網路上其他地方攻擊者的窺探;客戶端與伺服器之間的TLS/SSL可以阻擋網路攻擊者,但無法阻止惡意伺服器。只有端到端的加密和認證可以防止所有這些事情。 -​ 儘管低層級的功能(TCP複製抑制,乙太網校驗和,WiFi加密)無法單獨提供所需的端到端功能,但它們仍然很有用,因為它們能降低較高層級出現問題的可能性。例如,如果我們沒有TCP來將資料包排成正確的順序,那麼HTTP請求通常就會被攪爛。我們只需要記住,低級別的可靠性功能本身並不足以確保端到端的正確性。 +​ 儘管低層級的功能(TCP重複抑制,乙太網校驗和,WiFi加密)無法單獨提供所需的端到端功能,但它們仍然很有用,因為它們能降低較高層級出現問題的可能性。例如,如果我們沒有TCP來將資料包排成正確的順序,那麼HTTP請求通常就會被攪爛。我們只需要記住,低級別的可靠性功能本身並不足以確保端到端的正確性。 #### 在資料系統中應用端到端思考 -​ 這將我帶回最初的論點·:僅僅因為應用使用了提供相對較強安全屬性的資料系統,例如可序列化的事務,並不意味著應用的資料就不會丟失或損壞了。應用本身也需要採取端到端的措施,例如除重。 +​ 這將我帶回最初的論點:僅僅因為應用使用了提供相對較強安全屬性的資料系統,例如可序列化的事務,並不意味著應用的資料就不會丟失或損壞了。應用本身也需要採取端到端的措施,例如除重。 ​ 這實在是一個遺憾,因為容錯機制很難弄好。低層級的可靠機制(比如TCP中的那些)執行的相當好,因而剩下的高層級錯誤基本很少出現。如果能將這些剩下的高層級容錯機制打包成抽象,而應用不需要再去操心,那該多好呀 —— 但恐怕我們還沒有找到這一正確的抽象。 ​ 長期以來,事務被認為是一個很好的抽象,我相信它們確實是很有用的。正如[第七章](ch7.md)導言中所討論的,它們將各種可能的問題(併發寫入,違背約束,崩潰,網路中斷,磁碟故障)合併為兩種可能結果:提交或中止。這是對程式設計模型而言是一種巨大的簡化,但恐怕這還不夠。 -​ 事務是代價高昂的,當涉及異構儲存技術時尤為甚(請參閱的“[實踐中的分散式事務](ch9.md#實踐中的分散式事務)”)。我們拒絕使用分散式事務是因為它開銷太大,結果我們最後不得不在應用程式碼中重新實現容錯機制。正如本書中大量的例子所示,對併發性與部分失敗的推理是困難且違反直覺的,所以我懷疑大多數應用級別的機制都不能正確工作,最終結果是資料丟失或損壞。 +​ 事務是代價高昂的,當涉及異構儲存技術時尤為甚(請參閱“[實踐中的分散式事務](ch9.md#實踐中的分散式事務)”)。我們拒絕使用分散式事務是因為它開銷太大,結果我們最後不得不在應用程式碼中重新實現容錯機制。正如本書中大量的例子所示,對併發性與部分失敗的推理是困難且違反直覺的,所以我懷疑大多數應用級別的機制都不能正確工作,最終結果是資料丟失或損壞。 ​ 出於這些原因,我認為探索對容錯的抽象是很有價值的。它使提供應用特定的端到端的正確性屬性變得更簡單,而且還能在大規模分散式環境中提供良好的效能與運維特性。 @@ -518,7 +519,7 @@ COMMIT; ​ 我們先來特別關注一下**唯一性約束** —— 例如我們在[例12-2]()中所依賴的約束。在“[約束和唯一性保證](ch9.md#約束和唯一性保證)”中,我們看到了幾個其他需要強制實施唯一性的應用功能例子:使用者名稱或電子郵件地址必須唯一標識使用者,檔案儲存服務不能包含多個重名檔案,兩個人不能在航班或劇院預訂同一個座位。 -​ 其他型別的約束也非常類似:例如,確保帳戶餘額永遠不會變為負數,你就不會超賣庫存;或者會議室沒有重複的預訂。執行唯一性約束的技術通常也可以用於這些約束。 +​ 其他型別的約束也非常類似:例如,確保帳戶餘額永遠不會變為負數,確保不會超賣庫存,或者會議室沒有重複的預訂。執行唯一性約束的技術通常也可以用於這些約束。 #### 唯一性約束需要達成共識 @@ -528,20 +529,19 @@ COMMIT; ​ 唯一性檢查可以透過對唯一性欄位分割槽做橫向伸縮。例如,如果需要透過請求ID確保唯一性(如[例12-2]()所示),你可以確保所有具有相同請求ID的請求都被路由到同一分割槽(請參閱[第六章](ch6.md))。如果你需要讓使用者名稱是唯一的,則可以按使用者名稱的雜湊值做分割槽。 -​ 但非同步多主複製排除在外,因為可能會發生不同主庫同時接受衝突寫操作的情況,因而這些值不再是唯一的(請參閱“[實現可線性化系統](ch9.md#實現可線性化系統)”)。如果你想立刻拒絕任何違背約束的寫入,同步協調是無法避免的【56】。 +​ 但非同步多主複製排除在外,因為可能會發生不同主庫同時接受衝突寫操作的情況,因而這些值不再是唯一的(請參閱“[實現線性一致的系統](ch9.md#實現線性一致的系統)”)。如果你想立刻拒絕任何違背約束的寫入,同步協調是無法避免的【56】。 #### 基於日誌訊息傳遞中的唯一性 ​ 日誌確保所有消費者以相同的順序看見訊息 —— 這種保證在形式上被稱為**全序廣播(total order boardcast)** 並且等價於共識(請參閱“[全序廣播](ch9.md#全序廣播)”)。在使用基於日誌的訊息傳遞的分拆資料庫方法中,我們可以使用非常類似的方法來執行唯一性約束。 -​ 流處理器在單個執行緒上依次消費單個日誌分割槽中的所有訊息(請參閱“[與傳統訊息傳遞相比的日誌](ch11.md#與傳統訊息傳遞相比的日誌)”)。因此,如果日誌是按有待確保唯一的值做的分割槽,則流處理器可以無歧義地,確定性地決定幾個衝突操作中的哪一個先到達。例如,在多個使用者嘗試宣告相同使用者名稱的情況下【57】: +​ 流處理器在單個執行緒上依次消費單個日誌分割槽中的所有訊息(請參閱“[日誌與傳統的訊息傳遞相比](ch11.md#日誌與傳統的訊息傳遞相比)”)。因此,如果日誌是按需要確保唯一的值做的分割槽,則流處理器可以無歧義地、確定性地決定幾個衝突操作中的哪一個先到達。例如,在多個使用者嘗試宣告相同使用者名稱的情況下【57】: 1. 每個對使用者名稱的請求都被編碼為一條訊息,並追加到按使用者名稱雜湊值確定的分割槽。 2. 流處理器依序讀取日誌中的請求,並使用本地資料庫來追蹤哪些使用者名稱已經被佔用了。對於所有申請可用使用者名稱的請求,它都會記錄該使用者名稱,並向輸出流傳送一條成功訊息。對於所有申請已佔用使用者名稱的請求,它都會向輸出流傳送一條拒絕訊息。 3. 請求使用者名稱的客戶端監視輸出流,等待與其請求相對應的成功或拒絕訊息。 - -該演算法基本上與“[使用全序廣播實現線性一致的儲存](ch9.md#使用全序廣播實現線性一致的儲存)”中的演算法相同。它可以簡單地透過增加分割槽數伸縮至較大的請求吞吐量,因為每個分割槽可以被獨立處理。 +該演算法基本上與“[使用全序廣播實現線性一致的儲存](ch9.md#使用全序廣播實現線性一致的儲存)”中的演算法相同。它可以簡單地透過增加分割槽數伸縮至較大的請求吞吐量,因為每個分割槽都可以被獨立處理。 ​ 該方法不僅適用於唯一性約束,而且適用於許多其他型別的約束。其基本原理是,任何可能衝突的寫入都會路由到相同的分割槽並按順序處理。正如“[什麼是衝突?](ch5.md#什麼是衝突?)”與“[寫入偏斜與幻讀](ch7.md#寫入偏斜與幻讀)”中所述,衝突的定義可能取決於應用,但流處理器可以使用任意邏輯來驗證請求。這個想法與Bayou在90年代開創的方法類似【58】。 @@ -549,7 +549,7 @@ COMMIT; ​ 當涉及多個分割槽時,確保操作以原子方式執行且同時滿足約束就變得很有趣了。在[例12-2]()中,可能有三個分割槽:一個包含請求ID,一個包含收款人賬戶,另一個包含付款人賬戶。沒有理由把這三種東西放入同一個分割槽,因為它們都是相互獨立的。 -​ 在資料庫的傳統方法中,執行此事務需要跨全部三個分割槽進行原子提交,這實質上是將該事務嵌入一個全序,就這些分割槽上的所有其他事務而言。而這樣就要求跨分割槽協調,不同的分割槽無法再獨立地進行處理,因此吞吐量可能會受到影響。 +​ 在資料庫的傳統方法中,執行此事務需要跨全部三個分割槽進行原子提交,就這些分割槽上的所有其他事務而言,這實質上是將該事務嵌入一個全序。而這樣就要求跨分割槽協調,不同的分割槽無法再獨立地進行處理,因此吞吐量很可能會受到影響。 但事實證明,使用分割槽日誌可以達到等價的正確性而無需原子提交: @@ -563,7 +563,7 @@ COMMIT; ​ 如果你想確保付款人的帳戶不會因此次轉賬而透支,則可以使用一個額外的流處理器來維護賬戶餘額並校驗事務(按付款人賬戶分割槽),只有有效的事務會被記錄在步驟1中的請求日誌中。 -​ 透過將多分割槽事務分解為兩個不同分割槽方式的階段,並使用端到端的請求ID,我們實現了同樣的正確性屬性(每個請求對付款人與收款人都恰好生效一次),即使在出現故障,且沒有使用原子提交協議的情況下依然如此。使用多個不同分割槽方式的階段與我們在“[多分割槽資料處理](#多分割槽資料處理)”中討論的想法類似(請參閱“[併發控制](ch11.md#併發控制)”)。 +​ 透過將多分割槽事務分解為兩個不同分割槽方式的階段,並使用端到端的請求ID,我們實現了同樣的正確性屬性(每個請求對付款人與收款人都恰好生效一次),即使在出現故障,且沒有使用原子提交協議的情況下依然如此。使用多個不同分割槽階段的想法與我們在“[多分割槽資料處理](#多分割槽資料處理)”中討論的想法類似(也請參閱“[併發控制](ch11.md#併發控制)”)。 ### 及時性與完整性 @@ -583,17 +583,16 @@ COMMIT; ***完整性(Integrity)*** -​ 完整性意味著沒有損壞;即沒有資料丟失,並且沒有矛盾或錯誤的資料。尤其是如果某些衍生資料集是作為底層資料之上的檢視而維護的(請參閱“[從事件日誌匯出當前狀態](ch11.md#從事件日誌匯出當前狀態)”),這種衍生必須是正確的。例如,資料庫索引必須正確地反映資料庫的內容 —— 缺失某些記錄的索引並不是很有用。 - -​ 如果完整性被違背,這種不一致是永久的:在大多數情況下,等待與重試並不能修復資料庫損壞。相反的是,需要顯式地檢查與修復。在ACID事務的上下文中(請參閱“[ACID的涵義](ch7.md#ACID的涵義)”),一致性通常被理解為某種特定於應用的完整性概念。原子性和永續性是保持完整性的重要工具。 +​ 完整性意味著沒有損壞;即沒有資料丟失,並且沒有矛盾或錯誤的資料。尤其是如果某些衍生資料集是作為底層資料之上的檢視而維護的(請參閱“[從事件日誌中派生出當前狀態](ch11.md#從事件日誌中派生出當前狀態)”),這種衍生必須是正確的。例如,資料庫索引必須正確地反映資料庫的內容 —— 缺失某些記錄的索引並不是很有用。 +​ 如果完整性被違背,這種不一致是永久的:在大多數情況下,等待與重試並不能修復資料庫損壞。相反的是,需要顯式地檢查與修復。在ACID事務的上下文中(請參閱“[ACID的含義](ch7.md#ACID的含義)”),一致性通常被理解為某種特定於應用的完整性概念。原子性和永續性是保持完整性的重要工具。 ​ 口號形式:違反及時性,“最終一致性”;違反完整性,“永無一致性”。 ​ 我斷言在大多數應用中,完整性比及時性重要得多。違反及時性可能令人困惑與討厭,但違反完整性的結果可能是災難性的。 -​ 例如在你的信用卡對賬單上,如果某一筆過去24小時內完成的交易尚未出現並不令人奇怪 —— 這些系統有一定的滯後是正常的。我們知道銀行是非同步核算與敲定交易的,而這裡的及時性也並不是非常重要【3】。但果當期對賬單餘額與上期對賬單餘額加交易總額對不上(求和錯誤),或者出現一比向你收費但未向商家付款的交易(消失的錢),那實在是太糟糕了。這樣的問題就違背了系統的完整性。 +​ 例如在你的信用卡對賬單上,如果某一筆過去24小時內完成的交易尚未出現並不令人奇怪 —— 這些系統有一定的滯後是正常的。我們知道銀行是非同步核算與敲定交易的,這裡的及時性並不是非常重要【3】。但果當期對賬單餘額與上期對賬單餘額加交易總額對不上(求和錯誤),或者出現一筆向你收費但未向商家付款的交易(消失的錢),那實在是太糟糕了。這樣的問題就違背了系統的完整性。 #### 資料流系統的正確性 @@ -601,13 +600,13 @@ COMMIT; ​ 另一方面,對於在本章中討論的基於事件的資料流系統而言,它們的一個有趣特性就是將及時性與完整性分開。在非同步處理事件流時不能保證及時性,除非你顯式構建一個在返回之前明確等待特定訊息到達的消費者。但完整性實際上才是流處理系統的核心。 -​ **恰好一次**或**等效一次**語義(請參閱“[容錯](ch11.md#容錯)”)是一種保持完整性的機制。如果事件丟失或者生效兩次,就有可能違背資料系統的完整性。因此在面對故障時,容錯訊息傳遞與重複抑制(例如,冪等操作)對於維護資料系統的完整性是很重要的。 +​ **恰好一次**或**等效一次**語義(請參閱“[容錯](ch11.md#容錯)”)是一種保持完整性的機制。如果事件丟失或者生效兩次,就有可能違背資料系統的完整性。因此在出現故障時,容錯訊息傳遞與重複抑制(例如,冪等操作)對於維護資料系統的完整性是很重要的。 -​ 正如我們在上一節看到的那樣,可靠的流處理系統可以在無需分散式事務與原子提交協議的情況下保持完整性,這意味著它們能潛在地實現好得多的效能與運維穩健性,在達到類似正確性的前提下。為了達成這種正確性,我們組合使用了多種機制: +​ 正如我們在上一節看到的那樣,可靠的流處理系統可以在無需分散式事務與原子提交協議的情況下保持完整性,這意味著它們有潛力達到與後者相當的正確性,同時還具備好得多的效能與運維穩健性。為了達成這種正確性,我們組合使用了多種機制: * 將寫入操作的內容表示為單條訊息,從而可以輕鬆地被原子寫入 —— 與事件溯源搭配效果拔群(請參閱“[事件溯源](ch11.md#事件溯源)”)。 -* 使用與儲存過程類似的確定性衍生函式,從這一訊息中衍生出所有其他的狀態變更(請參閱“[真的序列執行](ch7.md#真的序列執行)”和“[作為衍生函式的應用程式碼](ch11.md#作為衍生函式的應用程式碼)”) -* 將客戶端生成的請求ID傳遞透過所有的處理層次,從而啟用端到端除重,帶來冪等性。 +* 使用與儲存過程類似的確定性衍生函式,從這一訊息中衍生出所有其他的狀態變更(請參閱“[真的序列執行](ch7.md#真的序列執行)”和“[應用程式碼作為衍生函式](ch12.md#應用程式碼作為衍生函式)”) +* 將客戶端生成的請求ID傳遞透過所有的處理層次,從而允許端到端的除重,帶來冪等性。 * 使訊息不可變,並允許衍生資料能隨時被重新處理,這使從錯誤中恢復更加容易(請參閱“[不可變事件的優點](ch11.md#不可變事件的優點)”) 這種機制組合在我看來,是未來構建容錯應用的一個非常有前景的方向。 @@ -618,33 +617,31 @@ COMMIT; 然而另一個需要了解的事實是,許多真實世界的應用實際上可以擺脫這種形式,接受弱得多的唯一性: -* 如果兩個人同時註冊了相同的使用者名稱或預訂了相同的座位,你可以傳送其中一個發訊息道歉,並要求他們選擇一個不同的使用者名稱。這種糾正錯誤的變化被稱為**補償性事務(compensating transaction)**【59,60】。 -* 如果客戶訂購的物品多於倉庫中的物品,你可以下單補倉,併為延誤向客戶道歉,向他們提供折扣。實際上,這麼說吧,如果在叉車在倉庫中軋過了你的貨物,剩下的貨物比你想象的要少,那麼你也是得這麼做【61】。因此,既然道歉工作流無論如何已經成為你商業過程中的一部分了,那麼對庫存物品數目新增線性一致的約束可能就沒必要了。 +* 如果兩個人同時註冊了相同的使用者名稱或預訂了相同的座位,你可以給其中一個人發訊息道歉,並要求他們選擇一個不同的使用者名稱。這種糾正錯誤的變化被稱為**補償性事務(compensating transaction)**【59,60】。 +* 如果客戶訂購的物品多於倉庫中的物品,你可以下單補倉,併為延誤向客戶道歉,向他們提供折扣。實際上,這麼說吧,如果叉車在倉庫中軋過了你的貨物,剩下的貨物比你想象的要少,那麼你也是得這麼做【61】。因此,既然道歉工作流無論如何已經成為你商業過程中的一部分了,那麼對庫存物品數目新增線性一致的約束可能就沒必要了。 * 與之類似,許多航空公司都會超賣機票,打著一些旅客可能會錯過航班的算盤;許多旅館也會超賣客房,抱著部分客人可能會取消預訂的期望。在這些情況下,出於商業原因而故意違反了“一人一座”的約束;當需求超過供給的情況出現時,就會進入補償流程(退款、升級艙位/房型、提供隔壁酒店的免費的房間)。即使沒有超賣,為了應對由惡劣天氣或員工罷工導致的航班取消,你還是需要道歉與補償流程 —— 從這些問題中恢復僅僅是商業活動的正常組成部分。 * 如果有人從賬戶超額取款,銀行可以向他們收取透支費用,並要求他們償還欠款。透過限制每天的提款總額,銀行的風險是有限的。 +​ 在許多商業場景中,臨時違背約束並稍後透過道歉來修復,實際上是可以接受的。道歉的成本各不相同,但通常很低(以金錢或名聲來算):你無法撤回已傳送的電子郵件,但可以傳送一封后續電子郵件進行更正。如果你不小心向信用卡收取了兩次費用,則可以將其中一項收費退款,而代價僅僅是手續費,也許還有客戶的投訴。儘管一旦ATM吐了錢,你無法直接取回,但原則上如果賬戶透支而客戶拒不支付,你可以派催收員收回欠款。 - -​ 在許多商業場景中,臨時違背約束並稍後透過道歉來修復,實際上是可以接受的。道歉的成本各不相同,但通常很低(以金錢或名聲來算):你無法撤回已傳送的電子郵件,但可以傳送一封后續電子郵件進行更正。如果你不小心向信用卡收取了兩次費用,則可以將其中一項收費退款,而代價僅僅是手續費,也許還有客戶的投訴……。儘管一旦ATM吐了錢,你無法直接取回,但原則上如果賬戶透支而客戶拒不支付,你可以派催收員收回欠款…。 - -​ 道歉的成本是否能接受是一個商業決策。如果可以接受的話,在寫入資料之前檢查所有約束的傳統模型反而會帶來不必要的限制,而線性一致性的約束也不是必須的。樂觀寫入,事後檢查可能是一種合理的選擇。你仍然可以在做一些挽回成本高昂的事情前確保驗證發生,但這並不意味著寫入資料之前必須先進行驗證。 +​ 道歉的成本是否能接受是一個商業決策。如果可以接受的話,在寫入資料之前檢查所有約束的傳統模型反而會帶來不必要的限制,而線性一致性的約束也不是必須的。樂觀寫入,事後檢查可能是一種合理的選擇。你仍然可以在做一些挽回成本高昂的事情前確保有相關的驗證,但這並不意味著寫入資料之前必須先進行驗證。 ​ 這些應用**確實**需要完整性:你不會希望丟失預訂資訊,或者由於借方貸方不匹配導致資金消失。但是它們在執行約束時**並不需要**及時性:如果你銷售的貨物多於倉庫中的庫存,可以在事後道歉後並彌補問題。這種做法與我們在“[處理寫入衝突](ch5.md#處理寫入衝突)”中討論的衝突解決方法類似。 #### 無協調資料系統 -我們現在做了兩個有趣的觀察: +我們現在已經做了兩個有趣的觀察: -1. 資料流系統可以維持衍生資料的完整性保證,而無需原子提交,線性一致性,或者同步跨分割槽協調。 +1. 資料流系統可以維持衍生資料的完整性保證,而無需原子提交、線性一致性或者同步的跨分割槽協調。 2. 雖然嚴格的唯一性約束要求及時性和協調,但許多應用實際上可以接受寬鬆的約束:只要整個過程保持完整性,這些約束可能會被臨時違反並在稍後被修復。 -總之這些觀察意味著,資料流系統可以為許多應用提供無需協調的資料管理服務,且仍能給出很強的完整性保證。這種**無協調(coordination-avoiding)** 的資料系統有著很大的吸引力:比起需要執行同步協調的系統,它們能達到更好的效能與更強的容錯能力【56】。 +總之這些觀察意味著,資料流系統可以為許多應用提供無需協調的資料管理服務,且仍能給出很強的完整性保證。這種**無協調(coordination-avoiding)**的資料系統有著很大的吸引力:比起需要執行同步協調的系統,它們能達到更好的效能與更強的容錯能力【56】。 -​ 例如,這種系統可以使用多領導者配置運維,跨越多個數據中心,在區域間非同步複製。任何一個數據中心都可以持續獨立執行,因為不需要同步的跨區域協調。這樣的系統時效性保證會很弱 —— 如果不引入協調它是不可能是線性一致的 —— 但它仍然可以提供有力的完整性保證。 +​ 例如,這種系統可以使用多領導者配置運維,跨越多個數據中心,在區域間非同步複製。任何一個數據中心都可以持續獨立執行,因為不需要同步的跨區域協調。這樣的系統的及時性保證會很弱 —— 如果不引入協調它是不可能是線性一致的 —— 但它仍然可以提供有力的完整性保證。 -​ 在這種情況下,可序列化事務作為維護衍生狀態的一部分仍然是有用的,但它們可以在小範圍內執行,在那裡它們工作得很好【8】。異構分散式事務(如XA事務)(請參閱“[實踐中的分散式事務](ch9.md#實踐中的分散式事務)”)不是必需的。同步協調仍然可以在需要的地方引入(例如在無法恢復的操作之前強制執行嚴格的約束),但是如果只是應用的一小部分地方需要它,沒必要讓所有操作都付出協調的代價。【43】。 +​ 在這種情況下,可序列化事務作為維護衍生狀態的一部分仍然是有用的,但它們只能在小範圍內執行,在那裡它們工作得很好【8】。異構分散式事務(如XA事務)(請參閱“[實踐中的分散式事務](ch9.md#實踐中的分散式事務)”)不是必需的。同步協調仍然可以在需要的地方引入(例如在無法恢復的操作之前強制執行嚴格的約束),但是如果只是應用的一小部分地方需要它,沒必要讓所有操作都付出協調的代價。【43】。 -​ 另一種審視協調與約束的角度是:它們減少了由於不一致而必須做出的道歉數量,但也可能會降低系統的效能和可用性,從而可能增加由於宕機中斷而需要做出的道歉數量。你不可能將道歉數量減少到零,但可以根據自己的需求尋找最佳平衡點 —— 既不存在太多不一致性,又不存在太多可用性問題的最佳選擇。 +​ 另一種審視協調與約束的角度是:它們減少了由於不一致而必須做出的道歉數量,但也可能會降低系統的效能和可用性,從而可能增加由於宕機中斷而需要做出的道歉數量。你不可能將道歉數量減少到零,但可以根據自己的需求尋找最佳平衡點 —— 既不存在太多不一致性,又不存在太多可用性問題。 ### 信任但驗證 @@ -652,7 +649,7 @@ COMMIT; ​ 這些假設是相當合理的,因為大多數時候它們都是成立的,如果我們不得不經常擔心計算機出錯,那麼基本上寸步難行。在傳統上,系統模型採用二元方法處理故障:我們假設有些事情可能會發生,而其他事情**永遠**不會發生。實際上,這更像是一個概率問題:有些事情更有可能,其他事情不太可能。問題在於違反我們假設的情況是否經常發生,以至於我們可能在實踐中遇到它們。 -​ 我們已經看到,資料可能會在尚未落盤時損壞(請參閱“[複製與永續性](ch5.md#複製與永續性)”),而網路上的資料損壞有時可能規避了TCP校驗和(請參閱“[弱謊言形式](ch8.md#弱謊言形式)” )。也許我們應當更關注這些事情? +​ 我們已經看到,資料可能會在尚未落盤時損壞(請參閱“[複製與永續性](ch7.md#複製與永續性)”),而網路上的資料損壞有時可能規避了TCP校驗和(請參閱“[弱謊言形式](ch8.md#弱謊言形式)” )。也許我們應當更關注這些事情? ​ 我過去所從事的一個應用收集了來自客戶端的崩潰報告,我們收到的一些報告,只有在這些裝置記憶體中出現了隨機位翻轉才解釋的通。這看起來不太可能,但是如果有足夠多的裝置執行你的軟體,那麼即使再不可能發生的事也確實會發生。除了由於硬體故障或輻射導致的隨機儲存器損壞之外,一些病態的儲存器訪問模式甚至可以在沒有故障的儲存器中翻轉位【62】 —— 一種可用於破壞作業系統安全機制的效應【63】(這種技術被稱為**Rowhammer**)。一旦你仔細觀察,硬體並不是看上去那樣完美的抽象。 @@ -660,7 +657,7 @@ COMMIT; #### 維護完整性,儘管軟體有Bug -​ 除了這些硬體問題之外,總是存在軟體Bug的風險,這些錯誤不會被較低層次的網路,記憶體或檔案系統校驗和所捕獲。即使廣泛使用的資料庫軟體也有Bug:即使像MySQL與PostgreSQL這樣穩健、考慮充分、久經實戰考驗,多年以來被許多人充分測試過的軟體,就我個人所見也有Bug:比如MySQL未能正確維護唯一約束【65】,以及PostgreSQL的可序列化隔離等級存在特定的寫偏差異常【66】。對於更不成熟的軟體來說,情況可能要糟糕的多。 +​ 除了這些硬體問題之外,總是存在軟體Bug的風險,這些錯誤不會被較低層次的網路、記憶體或檔案系統校驗和所捕獲。即使廣泛使用的資料庫軟體也有Bug:即使像MySQL與PostgreSQL這樣穩健、口碑良好、多年來被許多人充分測試過的軟體,就我個人所見也有Bug,比如MySQL未能正確維護唯一約束【65】,以及PostgreSQL的可序列化隔離等級存在特定的寫偏斜異常【66】。對於不那麼成熟的軟體來說,情況可能要糟糕得多。 ​ 儘管在仔細設計,測試,以及審查上做出很多努力,但Bug仍然會在不知不覺中產生。儘管它們很少,而且最終會被發現並被修復,但總會有那麼一段時間,這些Bug可能會損壞資料。 @@ -672,7 +669,7 @@ COMMIT; ​ 由於硬體和軟體並不總是符合我們的理想,所以資料損壞似乎早晚不可避免。因此,我們至少應該有辦法查明資料是否已經損壞,以便我們能夠修復它,並嘗試追查錯誤的來源。檢查資料完整性稱為**審計(auditing)**。 -​ 如“[不可變事件的優點](ch11.md#不可變事件的優點)”一節中所述,審計不僅僅適用於財務應用程式。不過,可審計性在財務中是非常非常重要的。這種錯誤發生之後,需要能被檢測與解決,我們都知道所有人都會認為這是合理需求。 +​ 如“[不可變事件的優點](ch11.md#不可變事件的優點)”一節中所述,審計不僅僅適用於財務應用程式。不過,可審計性在財務中是非常非常重要的,因為每個人都知道錯誤總會發生,我們也都認為能夠檢測和解決問題是合理的需求。 ​ 成熟的系統同樣傾向於考慮不太可能的事情出錯的可能性,並管理這種風險。例如,HDFS和Amazon S3等大規模儲存系統並不完全信任磁碟:它們執行後臺程序持續回讀檔案,並將其與其他副本進行比較,並將檔案從一個磁碟移動到另一個,以便降低靜默損壞的風險【67】。 @@ -688,7 +685,7 @@ COMMIT; #### 為可審計性而設計 -​ 如果一個事務在一個數據庫中改變了多個物件,在這一事實發生後,很難說清這個事務到底意味著什麼。即使你捕獲了事務日誌(請參閱“[變更資料捕獲](ch11.md#變更資料捕獲)”),各種表中的插入,更新和刪除操作並不一定能清楚地表明**為什麼**要執行這些變更。決定這些變更的是應用邏輯中的呼叫,而這一應用邏輯稍縱即逝,無法重現。 +​ 如果一個事務在一個數據庫中改變了多個物件,在這一事實發生後,很難說清這個事務到底意味著什麼。即使你捕獲了事務日誌(請參閱“[變更資料捕獲](ch11.md#變更資料捕獲)”),各種表中的插入、更新和刪除操作並不一定能清楚地表明**為什麼**要執行這些變更。決定這些變更的是應用邏輯中的呼叫,而這一應用邏輯稍縱即逝,無法重現。 ​ 相比之下,基於事件的系統可以提供更好的可審計性。在事件溯源方法中,系統的使用者輸入被表示為一個單一不可變事件,而任何其導致的狀態變更都衍生自該事件。衍生可以實現為具有確定性與可重複性,因而相同的事件日誌透過相同版本的衍生程式碼時,會導致相同的狀態變更。 @@ -700,26 +697,25 @@ COMMIT; ​ 如果我們不能完全相信系統的每個元件都不會損壞 —— 每一個硬體都沒缺陷,每一個軟體都沒有Bug —— 那我們至少必須定期檢查資料的完整性。如果我們不檢查,我們就不能發現損壞,直到無可挽回地導致對下游的破壞時,那時候再去追蹤問題就要難得多,且代價也要高的多。 -​ 檢查資料系統的完整性,最好是以端到端的方式進行(請參閱“[資料庫端到端的爭論](#資料庫的端到端的爭論)”):我們能在完整性檢查中涵蓋的系統越多,某些處理階中出現不被察覺損壞的機率就越小。如果我們能檢查整個衍生資料管道端到端的正確性,那麼沿著這一路徑的任何磁碟,網路,服務,以及演算法的正確性檢查都隱含在其中了。 +​ 檢查資料系統的完整性,最好是以端到端的方式進行(請參閱“[資料庫的端到端原則](#資料庫的端到端原則)”):我們能在完整性檢查中涵蓋的系統越多,某些處理階中出現不被察覺損壞的機率就越小。如果我們能檢查整個衍生資料管道端到端的正確性,那麼沿著這一路徑的任何磁碟、網路、服務以及演算法的正確性檢查都隱含在其中了。 ​ 持續的端到端完整性檢查可以不斷提高你對系統正確性的信心,從而使你能更快地進步【70】。與自動化測試一樣,審計提高了快速發現錯誤的可能性,從而降低了系統變更或新儲存技術可能導致損失的風險。如果你不害怕進行變更,就可以更好地充分演化一個應用,使其滿足不斷變化的需求。 #### 用於可審計資料系統的工具 -​ 目前,將可審計性作為頂層關注點的資料系統並不多。一些應用實現了自己的審計機制,例如將所有變更記錄到單獨的審計表中,但是確保審計日誌與資料庫狀態的完整性仍然是很困難的。可以透過定期使用硬體安全模組對事務日誌進行簽名來防止篡改,但這無法保證正確的事務一開始就能進入到日誌中。 +​ 目前,將可審計性作為頂層關注點的資料系統並不多。一些應用實現了自己的審計機制,例如將所有變更記錄到單獨的審計表中,但是確保審計日誌與資料庫狀態的完整性仍然是很困難的。可以定期使用硬體安全模組對事務日誌進行簽名來防止篡改,但這無法保證正確的事務一開始就能進入到日誌中。 ​ 使用密碼學工具來證明系統的完整性是十分有趣的,這種方式對於寬泛的硬體與軟體問題,甚至是潛在的惡意行為都很穩健有效。加密貨幣,區塊鏈,以及諸如比特幣,以太坊,Ripple,Stellar的分散式賬本技術已經迅速出現在這一領域【71,72,73】。 ​ 我沒有資格評論這些技術用於貨幣,或者合同商定機制的價值。但從資料系統的角度來看,它們包含了一些有趣的想法。實質上,它們是分散式資料庫,具有資料模型與事務機制,而不同副本可以由互不信任的組織託管。副本不斷檢查其他副本的完整性,並使用共識協議對應當執行的事務達成一致。 -​ 我對這些技術的拜占庭容錯方面有些懷疑(請參閱“[拜占庭故障](ch8.md#拜占庭故障)”),而且我發現**工作證明(proof of work)** 技術非常浪費(比如,比特幣挖礦)。比特幣的交易吞吐量相當低,儘管是出於政治與經濟原因而非技術上的原因。不過,完整性檢查的方面是很有趣的。 +​ 我對這些技術的拜占庭容錯方面有些懷疑(請參閱“[拜占庭故障](ch8.md#拜占庭故障)”),而且我發現**工作證明(proof of work)** 技術非常浪費(比如,比特幣挖礦)。比特幣的交易吞吐量相當低,儘管更多是出於政治與經濟原因而非技術上的原因。不過,完整性檢查的方面是很有趣的。 -​ 密碼學審計與完整性檢查通常依賴**默克爾樹(Merkle tree)**【74】,這是一顆雜湊值的樹,能夠用於高效地證明一條記錄出現在一個數據集中(以及其他一些特性)。除了炒作的沸沸揚揚的加密貨幣之外,**證書透明性(certificate transparency)** 也是一種依賴Merkle樹的安全技術,用來檢查TLS/SSL證書的有效性【75,76】。 +​ 密碼學審計與完整性檢查通常依賴**默克爾樹(Merkle tree)**【74】,這是一顆雜湊值的樹,能夠用於高效地證明一條記錄出現在一個數據集中(以及其他一些特性)。除了炒作的沸沸揚揚的加密貨幣之外,**證書透明性(certificate transparency)**也是一種依賴Merkle樹的安全技術,用來檢查TLS/SSL證書的有效性【75,76】。 ​ 我可以想象,那些在證書透明度與分散式賬本中使用的完整性檢查和審計演算法,將會在通用資料系統中得到越來越廣泛的應用。要使得這些演算法對於沒有密碼學審計的系統同樣可伸縮,並儘可能降低效能損失還需要一些工作。 但我認為這是一個值得關注的有趣領域。 - ## 做正確的事情 ​ 在本書的最後部分,我想退後一步。在本書中,我們考察了各種不同的資料系統架構,評價了它們的優點與缺點,並探討了構建可靠,可伸縮,可維護應用的技術。但是,我們忽略了討論中一個重要而基礎的部分,現在我想補充一下。 @@ -730,7 +726,7 @@ COMMIT; ​ 軟體開發越來越多地涉及重要的道德抉擇。有一些指導原則可以幫助軟體工程師解決這些問題,例如ACM的軟體工程道德規範與專業實踐【77】,但實踐中很少會討論這些,更不用說應用與強制執行了。因此,工程師和產品經理有時會對隱私與產品潛在的負面後果抱有非常傲慢的態度【78,79,80】。 -​ 技術本身並無好壞之分 —— 關鍵在於它被如何使用,以及它如何影響人們。這對槍械這樣的武器,這是成立的,而搜尋引擎這樣的軟體系統與之類似。我認為,軟體工程師僅僅專注於技術而忽視其後果是不夠的:道德責任也是我們的責任。對道德推理很困難,但它太重要了,我們無法忽視。 +​ 技術本身並無好壞之分 —— 關鍵在於它被如何使用,以及它如何影響人們。這對槍械這樣的武器是成立的,而搜尋引擎這樣的軟體系統與之類似。我認為,軟體工程師僅僅專注於技術而忽視其後果是不夠的:道德責任也是我們的責任。對道德推理很困難,但它太重要了,我們無法忽視。 ### 預測性分析 @@ -742,11 +738,11 @@ COMMIT; #### 偏見與歧視 -​ 演算法做出的決定不一定比人類更好或更差。每個人都可能有偏見,即使他們主動抗拒這一點;而歧視性做法也可能已經在文化上被制度化了。人們希望根據資料做出決定,而不是透過人的主觀評價與直覺,希望這樣能更加公平,並給予傳統體制中經常被忽視的人更好的機會。【83】。 +​ 演算法做出的決定不一定比人類更好或更差。每個人都可能有偏見,即使他們主動抗拒這一點;而歧視性做法也可能已經在文化上被制度化了。人們希望根據資料做出決定,而不是透過人的主觀評價與直覺,希望這樣能更加公平,並給予傳統體制中經常被忽視的人更好的機會【83】。 ​ 當我們開發預測性分析系統時,不是僅僅用軟體透過一系列IF ELSE規則將人類的決策過程自動化,那些規則本身甚至都是從資料中推斷出來的。但這些系統學到的模式是個黑盒:即使資料中存在一些相關性,我們可能也壓根不知道為什麼。如果演算法的輸入中存在系統性的偏見,則系統很有可能會在輸出中學習並放大這種偏見【84】。 -​ 在許多國家,反歧視法律禁止按種族,年齡,性別,性取向,殘疾,或信仰等受保護的特徵區分對待不同的人。其他的個人特徵可能是允許用於分析的,但是如果這些特徵與受保護的特徵存在關聯,又會發生什麼?例如在種族隔離地區中,一個人的郵政編碼,甚至是他們的IP地址,都是很強的種族指示物。這樣的話,相信一種演算法可以以某種方式將有偏資料作為輸入,併產生公平和公正的輸出【85】似乎是很荒謬的。然而這種觀點似乎常常潛伏在資料驅動型決策的支持者中,這種態度被諷刺為“在處理偏差上,機器學習與洗錢類似”(machine learning is like money laundering for bias)【86】。 +​ 在許多國家,反歧視法律禁止按種族,年齡,性別,性取向,殘疾,或信仰等受保護的特徵區分對待不同的人。其他的個人特徵可能是允許用於分析的,但是如果這些特徵與受保護的特徵存在關聯,又會發生什麼?例如在種族隔離地區中,一個人的郵政編碼,甚至是他們的IP地址,都是很強的種族指示物。這樣的話,相信一種演算法可以以某種方式將有偏見的資料作為輸入,併產生公平和公正的輸出【85】似乎是很荒謬的。然而這種觀點似乎常常潛伏在資料驅動型決策的支持者中,這種態度被諷刺為“在處理偏差上,機器學習與洗錢類似”(machine learning is like money laundering for bias)【86】。 ​ 預測性分析系統只是基於過去進行推斷;如果過去是歧視性的,它們就會將這種歧視歸納為規律。如果我們希望未來比過去更好,那麼就需要道德想象力,而這是隻有人類才能提供的東西【87】。資料與模型應該是我們的工具,而不是我們的主人。 @@ -762,7 +758,7 @@ COMMIT; ​ 盲目相信資料決策至高無上,這不僅僅是一種妄想,而是有切實危險的。隨著資料驅動的決策變得越來越普遍,我們需要弄清楚,如何使演算法更負責任且更加透明,如何避免加強現有的偏見,以及如何在它們不可避免地出錯時加以修復。 -​ 我們還需要想清楚,如何避免資料被用於害人,如何認識資料的積極潛力。例如,分析可以揭示人們生活的財務特點與社會特點。一方面,這種權力可以用來將援助與支援集中在幫助那些最需要援助的人身上。另一方面,它有時會被掠奪性企業用於識別弱勢群體,並向其兜售高風險產品,比如高利貸,智商稅與莆田醫院【87,90】[^譯註i]。 +​ 我們還需要想清楚,如何避免資料被用於害人,如何認識資料的積極潛力。例如,分析可以揭示人們生活的財務特點與社會特點。一方面,這種權力可以用來將援助與支援集中在幫助那些最需要援助的人身上。另一方面,它有時會被掠奪性企業用於識別弱勢群體,並向其兜售高風險產品,比如高利貸和沒有價值的大學文憑【87,90】。 #### 反饋迴圈 @@ -770,15 +766,15 @@ COMMIT; ​ 當預測性分析影響人們的生活時,自我強化的反饋迴圈會導致非常有害的問題。例如,考慮僱主使用信用分來評估候選人的例子。你可能是一個信用分不錯的好員工,但因不可抗力的意外而陷入財務困境。由於不能按期付賬單,你的信用分會受到影響,進而導致找到工作更為困難。失業使你陷入貧困,這進一步惡化了你的分數,使你更難找到工作【87】。在資料與數學嚴謹性的偽裝背後,隱藏的是由惡毒假設導致的惡性迴圈。 -​ 我們無法預測這種反饋迴圈何時發生。然而透過對整個系統(不僅僅是計算機化的部分,而且還有與之互動的人)進行整體思考,許多後果是可以夠預測的 —— 一種稱為**系統思維(systems thinkin)** 的方法【92】。我們可以嘗試理解資料分析系統如何響應不同的行為,結構或特性。該系統是否加強和增大了人們之間現有的差異(例如,損不足以奉有餘,富者愈富,貧者愈貧),還是試圖與不公作鬥爭?而且即使有著最好的動機,我們也必須當心意想不到的後果。 +​ 我們無法預測這種反饋迴圈何時發生。然而透過對整個系統(不僅僅是計算機化的部分,而且還有與之互動的人)進行整體思考,許多後果是可以夠預測的 —— 一種稱為**系統思維(systems thinking)**的方法【92】。我們可以嘗試理解資料分析系統如何響應不同的行為,結構或特性。該系統是否加強和增大了人們之間現有的差異(例如,損不足以奉有餘,富者愈富,貧者愈貧),還是試圖與不公作鬥爭?而且即使有著最好的動機,我們也必須當心意想不到的後果。 ### 隱私和追蹤 -​ 除了預測性分析 —— 即使用資料來做出關於人的自動決策 —— 資料收集本身也存在道德問題。收集資料的組織,與被收集資料的人之間,到底屬於什麼關係? +​ 除了預測性分析 —— 使用資料來做出關於人的自動決策 —— 資料收集本身也存在道德問題。收集資料的組織,與被收集資料的人之間,到底屬於什麼關係? ​ 當系統只儲存使用者明確輸入的資料時,是因為使用者希望系統以特定方式儲存和處理這些資料,**系統是在為使用者提供服務**:使用者就是客戶。但是,當用戶的活動被跟蹤並記錄,作為他們正在做的其他事情的副作用時,這種關係就沒有那麼清晰了。該服務不再僅僅完成使用者想要它要做的事情,而是服務於它自己的利益,而這可能與使用者的利益相沖突。 -​ 追蹤使用者行為資料對於許多面向用戶的線上服務而言,變得越來越重要:追蹤使用者點選了哪些搜尋結果有助於提高搜尋結果的排名;推薦“喜歡X的人也喜歡Y”,可以幫助使用者發現實用有趣的東西; A/B測試和使用者流量分析有助於改善使用者介面。這些功能需要一定量的使用者行為跟蹤,而使用者也可以從中受益。 +​ 追蹤使用者行為資料對於許多面向用戶的線上服務而言,變得越來越重要:追蹤使用者點選了哪些搜尋結果有助於改善搜尋結果的排名;推薦“喜歡X的人也喜歡Y”,可以幫助使用者發現實用有趣的東西; A/B測試和使用者流量分析有助於改善使用者介面。這些功能需要一定量的使用者行為跟蹤,而使用者也可以從中受益。 ​ 但不同公司有著不同的商業模式,追蹤並未止步於此。如果服務是透過廣告盈利的,那麼廣告主才是真正的客戶,而使用者的利益則屈居其次。跟蹤的資料會變得更詳細,分析變得更深入,資料會保留很長時間,以便為每個人建立詳細畫像,用於營銷。 @@ -786,9 +782,9 @@ COMMIT; #### 監視 -​ 讓我們做一個思想實驗,嘗試用**監視(surveillance)** 一詞替換**資料(data)**,再看看常見的短語是不是聽起來還那麼漂亮【93】。比如:“在我們的監視驅動的組織中,我們收集實時監視流並將它們儲存在我們的監視倉庫中。我們的監視科學家使用高階分析和監視處理來獲得新的見解。“ +​ 讓我們做一個思想實驗,嘗試用**監視(surveillance)** 一詞替換**資料(data)**,再看看常見的短語是不是聽起來還那麼漂亮【93】。比如:“在我們的監視驅動的組織中,我們收集實時監視流並將它們儲存在我們的監視倉庫中。我們的監視科學家使用高階分析和監視處理來獲得新的見解。” -​ 對於本書《設計監控密集型應用》而言,這個思想實驗是罕見的爭議性內容,但我認為需要激烈的言辭來強調這一點。在我們嘗試製造軟體“吞噬世界”的過程中【94】,我們已經建立了世界上迄今為止所見過的最偉大的大規模監視基礎設施。我們正朝著萬物互聯邁進,我們正在迅速走近這樣一個世界:每個有人居住的空間至少包含一個帶網際網路連線的麥克風,以智慧手機,智慧電視,語音控制助理裝置,嬰兒監視器甚至兒童玩具的形式存在,並使用基於雲的語音識別。這些裝置中的很多都有著可怕的安全記錄【95】。 +​ 對於本書《設計監控密集型應用》而言,這個思想實驗是罕見的爭議性內容,但我認為需要激烈的言辭來強調這一點。在我們嘗試製造軟體“吞噬世界”的過程中【94】,我們已經建立了世界上迄今為止所見過的最偉大的大規模監視基礎設施。我們正朝著萬物互聯邁進,我們正在迅速走近這樣一個世界:每個有人居住的空間至少包含一個帶網際網路連線的麥克風,以智慧手機、智慧電視、語音控制助理裝置、嬰兒監視器甚至兒童玩具的形式存在,並使用基於雲的語音識別。這些裝置中的很多都有著可怕的安全記錄【95】。 ​ 即使是最為極權與專制的政權,可能也只會想著在每個房間裝一個麥克風,並強迫每個人始終攜帶能夠追蹤其位置與動向的裝置。然而,我們顯然是自願地,甚至熱情地投身於這個全域監視的世界。不同之處在於,資料是由公司,而不是由政府機構收集的【96】。 @@ -798,7 +794,7 @@ COMMIT; #### 同意與選擇的自由 -​ 我們可能會斷言使用者是自願選擇使用服務的,儘管服務會跟蹤其活動,而且他們已經同意了服務條款與隱私政策,因此他們同意資料收集。我們甚至可以聲稱,使用者在用所提供的資料來**換取**有價值的服務,並且為了提供服務,追蹤是必要的。毫無疑問,社交網路,搜尋引擎,以及各種其他免費的線上服務對於使用者來說都是有價值的,但是這個說法卻存在問題。 +​ 我們可能會斷言使用者是自願選擇使用了會跟蹤其活動的服務,而且他們已經同意了服務條款與隱私政策,因此他們同意資料收集。我們甚至可以聲稱,使用者在用所提供的資料來**換取**有價值的服務,並且為了提供服務,追蹤是必要的。毫無疑問,社交網路、搜尋引擎以及各種其他免費的線上服務對於使用者來說都是有價值的,但是這個說法卻存在問題。 ​ 使用者幾乎不知道他們提供給我們的是什麼資料,哪些資料被放進了資料庫,資料又是怎樣被保留與處理的 —— 大多數隱私政策都是模稜兩可的,忽悠使用者而不敢開啟天窗說亮話。如果使用者不瞭解他們的資料會發生什麼,就無法給出任何有意義的同意。有時來自一個使用者的資料還會提到一些關於其他人的事,而其他那些人既不是該服務的使用者,也沒有同意任何條款。我們在本書這一部分中討論的衍生資料集 —— 來自整個使用者群的資料,加上行為追蹤與外部資料來源 —— 就恰好是使用者無法(在真正意義上)理解的資料型別。 @@ -806,11 +802,11 @@ COMMIT; ​ 對於不同意監視的使用者,唯一真正管用的備選項,就是簡單地不使用服務。但這個選擇也不是真正自由的:如果一項服務如此受歡迎,以至於“被大多數人認為是基本社會參與的必要條件”【99】,那麼指望人們選擇退出這項服務是不合理的 —— 使用它**事實上(de facto)** 是強制性的。例如,在大多數西方社會群體中,攜帶智慧手機,使用Facebook進行社交,以及使用Google查詢資訊已成為常態。特別是當一項服務具有網路效應時,人們選擇**不**使用會產生社會成本。 -​ 因為跟蹤使用者而拒絕使用服務,這只是少數人才擁有的權力,他們有足夠的時間與知識來了解隱私政策,並承受的起代價:錯過社會參與,以及使用服務可能帶來的專業機會。對於那些處境不太好的人而言,並沒有真正意義上的選擇:監控是不可避免的。 +​ 因為一個服務會跟蹤使用者而拒絕使用它,這只是少數人才擁有的權力,他們有足夠的時間與知識來了解隱私政策,並承受得起代價:錯過社會參與,以及使用服務可能帶來的專業機會。對於那些處境不太好的人而言,並沒有真正意義上的選擇:監控是不可避免的。 #### 隱私與資料使用 -​ 有時候,人們聲稱“隱私已死”,理由是有些使用者願意把各種關於他們生活的事情釋出到社交媒體上,有時是平凡俗套,但有時是高度私密的。但這種說法是錯誤的,而且是對**隱私(privacy)** 一詞的誤解。 +​ 有時候,人們聲稱“隱私已死”,理由是有些使用者願意把各種關於他們生活的事情釋出到社交媒體上,有時是平凡俗套,但有時是高度私密的。但這種說法是錯誤的,而且是對**隱私(privacy)**一詞的誤解。 ​ 擁有隱私並不意味著保密一切東西;它意味著擁有選擇向誰展示哪些東西的自由,要公開什麼,以及要保密什麼。**隱私權是一項決定權**:在從保密到透明的光譜上,隱私使得每個人都能決定自己想要在什麼地方位於光譜上的哪個位置【99】。這是一個人自由與自主的重要方面。 @@ -818,13 +814,13 @@ COMMIT; ​ 這些公司反過來選擇保密這些監視結果,因為揭露這些會令人毛骨悚然,並損害它們的商業模式(比其他公司更瞭解人)。使用者的私密資訊只會間接地披露,例如針對特定人群定向投放廣告的工具(比如那些患有特定疾病的人群)。 -​ 即使特定使用者無法從特定廣告定向的人群中以個體的形式區分出來,但他們已經失去了披露一些私密資訊的能動性,例如他們是否患有某種疾病。決定向誰透露什麼並不是由個體按照自己的喜好決定的,是由**公司**,以利潤最大化為目標來行使隱私權的。 +​ 即使特定使用者無法從特定廣告定向的人群中以個體的形式區分出來,但他們已經失去了披露一些私密資訊的能動性,例如他們是否患有某種疾病。決定向誰透露什麼並不是由個體按照自己的喜好決定的,而是由**公司**,以利潤最大化為目標來行使隱私權的。 -​ 許多公司都有一個目標,不要讓人**感覺到**毛骨悚然 —— 先不說它們收集資料實際上是多麼具有侵犯性,讓我們先關注使用者感知的管理。這些使用者感受經常被管理的很糟糕:例如,在事實上可能正確的一些東西,但如果會觸發痛苦的回憶,使用者可能並不希望被提醒【100】。對於任何型別的資料,我們都應當考慮它出錯、不可取、不合時宜的可能性,並且需要建立處理這些失效的機制。無論是“不可取”還是“不合時宜”,當然都是由人的判斷決定的;除非我們明確地將演算法編碼設計為尊重人類的需求,否則演算法會無視這些概念。作為這些系統的工程師,我們必須保持謙卑,充分規劃,接受這些失效。 +​ 許多公司都有一個目標,不要讓人**感覺到**毛骨悚然 —— 先不說它們收集資料實際上是多麼具有侵犯性,讓我們先關注對使用者感受的管理。這些使用者感受經常被管理得很糟糕:例如,在事實上可能正確的一些東西,如果會觸發痛苦的回憶,使用者可能並不希望被提醒【100】。對於任何型別的資料,我們都應當考慮它出錯、不可取、不合時宜的可能性,並且需要建立處理這些失效的機制。無論是“不可取”還是“不合時宜”,當然都是由人的判斷決定的;除非我們明確地將演算法編碼設計為尊重人類的需求,否則演算法會無視這些概念。作為這些系統的工程師,我們必須保持謙卑,充分規劃,接受這些失效。 ​ 允許線上服務的使用者控制其隱私設定,例如控制其他使用者可以看到哪些東西,是將一些控制交還給使用者的第一步。但無論怎麼設定,服務本身仍然可以不受限制地訪問資料,並能以隱私策略允許的任何方式自由使用它。即使服務承諾不會將資料出售給第三方,它通常會授予自己不受限制的權利,以便在內部處理與分析資料,而且往往比使用者公開可見的部分要深入的多。 -​ 這種從個體到公司的大規模隱私權轉移在歷史上是史無前例的【99】。監控一直存在,但它過去是昂貴的,手動的,不是可伸縮的,自動化的。信任關係始終存在,例如患者與其醫生之間,或被告與其律師之間 —— 但在這些情況下,資料的使用嚴格受到道德,法律和監管限制的約束。網際網路服務使得在未經有意義的同意下收集大量敏感資訊變得容易得多,而且無需使用者理解他們的私人資料到底發生了什麼。 +​ 這種從個體到公司的大規模隱私權轉移在歷史上是史無前例的【99】。監控一直存在,但它過去是昂貴的、手動的,不是可伸縮的、自動化的。信任關係一直存在,例如患者與其醫生之間,或被告與其律師之間 —— 但在這些情況下,資料的使用嚴格受到道德,法律和監管限制的約束。網際網路服務使得在未經有意義的同意下收集大量敏感資訊變得容易得多,而且無需使用者理解他們的私人資料到底發生了什麼。 #### 資料資產與權力 @@ -832,9 +828,9 @@ COMMIT; ​ 更準確的看法恰恰相反:從經濟的角度來看,如果定向廣告是服務的金主,那麼關於人的行為資料就是服務的核心資產。在這種情況下,使用者與之互動的應用僅僅是一種誘騙使用者將更多的個人資訊提供給監控基礎設施的手段【99】。線上服務中經常表現出的令人愉悅的人類創造力與社會關係,十分諷刺地被資料提取機器所濫用。 -​ 個人資料是珍貴資產的說法因為資料中介的存在得到支援,這是陰影中的祕密行業,購買,聚合,分析,推斷,以及轉售私密個人資料,主要用於市場營銷【90】。初創公司按照它們的使用者數量,“眼球數”,—— 即它們的監視能力來估值。 +​ 個人資料是珍貴資產的說法因為資料中介的存在得到支援,這是陰影中的祕密行業,購買、聚合、分析、推斷以及轉售私密個人資料,主要用於市場營銷【90】。初創公司按照它們的使用者數量,“眼球數”,—— 即它們的監視能力來估值。 -​ 因為資料很有價值,所以很多人都想要它。當然,公司也想要它 —— 這就是為什麼它們一開始就收集資料的原因。但政府也想獲得它:透過祕密交易,脅迫,法律強制,或者只是竊取【101】。當公司破產時,收集到的個人資料就是被出售的資產之一。而且資料安全很難保護,因此經常發生令人難堪的洩漏事件【102】。 +​ 因為資料很有價值,所以很多人都想要它。當然,公司也想要它 —— 這就是為什麼它們一開始就收集資料的原因。但政府也想獲得它:透過祕密交易、脅迫、法律強制或者只是竊取【101】。當公司破產時,收集到的個人資料就是被出售的資產之一。而且資料安全很難保護,因此經常發生令人難堪的洩漏事件【102】。 ​ 這些觀察已經導致批評者聲稱,資料不僅僅是一種資產,而且是一種“有毒資產”【101】,或者至少是“有害物質”【103】。即使我們認為自己有能力阻止資料濫用,但每當我們收集資料時,我們都需要平衡收益以及這些資料落入惡人手中的風險:計算機系統可能會被犯罪分子或敵國特務滲透,資料可能會被內鬼洩露,公司可能會落入不擇手段的管理層手中,而這些管理者有著迥然不同的價值觀,或者國家可能被能毫無愧色迫使我們交出資料的政權所接管。 @@ -846,9 +842,9 @@ COMMIT; ​ 工業革命是透過重大的技術與農業進步實現的,它帶來了持續的經濟增長,長期的生活水平顯著提高。然而它也帶來了一些嚴重的問題:空氣汙染(由於煙霧和化學過程)和水汙染(工業垃圾和人類垃圾)是可怖的。工廠老闆生活在紛奢之中,而城市工人經常居住在非常糟糕的住房中,並且在惡劣的條件下長時間工作。童工很常見,甚至包括礦井中危險而低薪的工作。 -​ 制定了保護措施花費了很長的時間,例如環境保護條例,工作場所安全條例,宣佈使用童工非法,以及食品衛生檢查。毫無疑問,生產成本增加了,因為工廠再也不能把廢物倒入河流,銷售汙染的食物,或者剝削工人。但是整個社會都從中受益良多,我們中很少會有人想回到這些管制條例之前的日子【87】。 +​ 制定保護措施花費了很長的時間,例如環境保護條例、工作場所安全條例、宣佈使用童工非法以及食品衛生檢查。毫無疑問,生產成本增加了,因為工廠再也不能把廢物倒入河流、銷售汙染的食物或者剝削工人。但是整個社會都從中受益良多,我們中很少會有人想回到這些管制條例之前的日子【87】。 -​ 就像工業革命有著黑暗面需要應對一樣,我們轉向資訊時代的過程中,也有需要應對與解決的重大問題。我相信資料的收集與使用就是其中一個問題。用布魯斯·施奈爾的話來說【96】: +​ 就像工業革命有著黑暗面需要應對一樣,我們轉向資訊時代的過程中,也有需要應對與解決的重大問題。我相信資料的收集與使用就是其中一個問題。用Bruce Schneier的話來說【96】: > ​ 資料是資訊時代的汙染問題,保護隱私是環境挑戰。幾乎所有的電腦都能生產資訊。它堆積在周圍,開始潰爛。我們如何處理它 —— 我們如何控制它,以及如何擺脫它 —— 是資訊經濟健康發展的核心議題。正如我們今天回顧工業時代的早期年代,並想知道我們的祖先在忙於建設工業世界的過程時怎麼能忽略汙染問題;我們的孫輩在回望資訊時代的早期年代時,將會就我們如何應對資料收集和濫用的挑戰來評斷我們。 > @@ -856,29 +852,29 @@ COMMIT; #### 立法與自律 -​ 資料保護法可能有助於維護個人的權利。例如,1995年的“歐洲資料保護指示”規定,個人資料必須“為特定的,明確的和合法的目的收集,而不是以與這些目的不相符的方式進一步處理”,並且資料必須“就收集的目的而言適當,相關,不過分。“【107】。 +​ 資料保護法可能有助於維護個人的權利。例如,1995年的“歐洲資料保護指示”規定,個人資料必須“為特定的、明確的和合法的目的收集,而不是以與這些目的不相符的方式進一步處理”,並且資料必須“就收集的目的而言適當、相關、不過分。“【107】。 ​ 但是,這個立法在今天的網際網路環境下是否有效還是有疑問的【108】。這些規則直接否定了大資料的哲學,即最大限度地收集資料,將其與其他資料集結合起來進行試驗和探索,以便產生新的洞察。探索意味著將資料用於未曾預期的目的,這與使用者同意的“特定和明確”目的相反(如果我們可以有意義地表示同意的話)【109】。更新的規章正在制定中【89】。 ​ 那些收集了大量有關人的資料的公司反對監管,認為這是創新的負擔與阻礙。在某種程度上,這種反對是有道理的。例如,分享醫療資料時,存在明顯的隱私風險,但也有潛在的機遇:如果資料分析能夠幫助我們實現更好的診斷或找到更好的治療方法,能夠阻止多少人的死亡【110】?過度監管可能會阻止這種突破。在這種潛在機會與風險之間找出平衡是很困難的【105】。 -​ 從根本上說,我認為我們需要科技行業在個人資料方面的文化轉變。我們應該停止將使用者視作待最佳化的指標資料,並記住他們是值得尊重,有尊嚴和能動性的人。我們應當在資料收集和實際處理中自我約束,以建立和維持依賴我們軟體的人們的信任【111】。我們應當將教育終端使用者視為己任,告訴他們我們是如何使用他們的資料的,而不是將他們矇在鼓裡。 +​ 從根本上說,我認為我們需要科技行業在個人資料方面的文化轉變。我們應該停止將使用者視作待最佳化的指標資料,並記住他們是值得尊重、有尊嚴和能動性的人。我們應當在資料收集和實際處理中自我約束,以建立和維持依賴我們軟體的人們的信任【111】。我們應當將教育終端使用者視為己任,告訴他們我們是如何使用他們的資料的,而不是將他們矇在鼓裡。 -​ 我們應該允許每個人保留自己的隱私 —— 即,對自己資料的控制 —— 而不是透過監視來竊取這種控制權。我們控制自己資料的個體權利就像是國家公園的自然環境:如果我們不去明確地保護它,關心它,它就會被破壞。這將是公地的悲劇,我們都會因此而變得更糟。無所不在的監視並非不可避免的 —— 我們現在仍然能阻止它。 +​ 我們應該允許每個人保留自己的隱私 —— 即,對自己資料的控制 —— 而不是透過監視來竊取這種控制權。我們控制自己資料的個體權利就像是國家公園的自然環境:如果我們不去明確地保護它、關心它,它就會被破壞。這將是公地的悲劇,我們都會因此而變得更糟。無所不在的監視並非不可避免的 —— 我們現在仍然能阻止它。 -​ 我們究竟能做到哪一步,是一個開放的問題。首先,我們不應該永久保留資料,而是一旦不再需要就立即清除資料【111,112】。清除資料與不變性的想法背道而馳(請參閱“[不變性的侷限性](ch11.md#不變性的侷限性)”),但這是可以解決該問題。我所看到的一種很有前景的方法是透過加密協議來實施訪問控制,而不僅僅是透過策略【113,114】。總的來說,文化與態度的改變是必要的。 +​ 我們究竟能做到哪一步,是一個開放的問題。首先,我們不應該永久保留資料,而是一旦不再需要就立即清除資料【111,112】。清除資料與不變性的想法背道而馳(請參閱“[不變性的侷限性](ch11.md#不變性的侷限性)”),但這是可以解決的問題。我所看到的一種很有前景的方法是透過加密協議來實施訪問控制,而不僅僅是透過策略【113,114】。總的來說,文化與態度的改變是必要的。 ## 本章小結 -​ 在本章中,我們討論了設計資料系統的新方式,而且也包括了我的個人觀點,以及對未來的猜測。我們從這樣一種觀察開始:沒有單種工具能高效服務所有可能的用例,因此應用必須組合使用幾種不同的軟體才能實現其目標。我們討論瞭如何使用批處理與事件流來解決這一**資料整合(data integration)** 問題,以便讓資料變更在不同系統之間流動。 +​ 在本章中,我們討論了設計資料系統的新方式,而且也包括了我的個人觀點,以及對未來的猜測。我們從這樣一種觀察開始:沒有單種工具能高效服務所有可能的用例,因此應用必須組合使用幾種不同的軟體才能實現其目標。我們討論瞭如何使用批處理與事件流來解決這一**資料整合(data integration)**問題,以便讓資料變更在不同系統之間流動。 ​ 在這種方法中,某些系統被指定為記錄系統,而其他資料則透過轉換衍生自記錄系統。透過這種方式,我們可以維護索引,物化檢視,機器學習模型,統計摘要等等。透過使這些衍生和轉換操作非同步且鬆散耦合,能夠防止一個區域中的問題擴散到系統中不相關部分,從而增加整個系統的穩健性與容錯性。 ​ 將資料流表示為從一個數據集到另一個數據集的轉換也有助於演化應用程式:如果你想變更其中一個處理步驟,例如變更索引或快取的結構,則可以在整個輸入資料集上重新執行新的轉換程式碼,以便重新衍生輸出。同樣,出現問題時,你也可以修復程式碼並重新處理資料以便恢復。 -​ 這些過程與資料庫內部已經完成的過程非常類似,因此我們將資料流應用的概念重新改寫為,**分拆(unbundling)** 資料庫元件,並透過組合這些鬆散耦合的元件來構建應用程式。 +​ 這些過程與資料庫內部已經完成的過程非常類似,因此我們將資料流應用的概念重新改寫為,**分拆(unbundling)**資料庫元件,並透過組合這些鬆散耦合的元件來構建應用程式。 ​ 衍生狀態可以透過觀察底層資料的變更來更新。此外,衍生狀態本身可以進一步被下游消費者觀察。我們甚至可以將這種資料流一路傳送至顯示資料的終端使用者裝置,從而構建可動態更新以反映資料變更,並在離線時能繼續工作的使用者介面。 @@ -886,7 +882,7 @@ COMMIT; ​ 透過圍繞資料流構建應用,並非同步檢查約束,我們可以避免絕大多數的協調工作,建立保證完整性且效能仍然表現良好的系統,即使在地理散佈的情況下與出現故障時亦然。然後,我們對使用審計來驗證資料完整性,以及損壞檢測進行了一些討論。 -​ 最後,我們退後一步,審視了構建資料密集型應用的一些道德問題。我們看到,雖然資料可以用來做好事,但它也可能造成很大傷害:作出嚴重影響人們生活的決定卻難以申訴,導致歧視與剝削,監視常態化,曝光私密資訊。我們也冒著資料被洩露的風險,並且可能會發現,即使是善意地使用資料也可能會導致意想不到的後果。 +​ 最後,我們退後一步,審視了構建資料密集型應用的一些道德問題。我們看到,雖然資料可以用來做好事,但它也可能造成很大傷害:作出嚴重影響人們生活的決定卻難以申訴,導致歧視與剝削、監視常態化、曝光私密資訊。我們也冒著資料被洩露的風險,並且可能會發現,即使是善意地使用資料也可能會導致意想不到的後果。 ​ 由於軟體和資料對世界產生了如此巨大的影響,我們工程師們必須牢記,我們有責任為我們想要的那種世界而努力:一個尊重人們,尊重人性的世界。我希望我們能夠一起為實現這一目標而努力。