观察衍生数据状态
衍⽣数据集是写路径和读路径相遇的地⽅。它代表了在写⼊时需要完成的⼯作量与在读取时需要完成的⼯作量之间的权衡。
物化视图和缓存
不常⻅的查询仍然可以从⾛索引。这通常被称为常⻅查询的缓存(cache),尽管我们也可以称之为物化视图(materialized view),因为当新⽂档出现,且需要被包含在这些常⻅查询的搜索结果之中时,这些索引就需要更新。
有状态,可离线的客户端
当我们摆脱⽆状态客户端与中央数据库交互的假设,并转向在终端⽤户设备上维护状态时,这就开启了新世界的⼤⻔。特别是,我们可以将设备上的状态视为服务器状态的缓存。屏幕上的像素是客户端应⽤中模型对象的物化视图;模型对象是远程数据中⼼的本地状态副本
将状态变更推送给客户端
这些设备有时会离线,并在此期间⽆法收到服务器状态变更的任何通知。但是我们已经解决了这个问题:在“消费者偏移量”中,我们讨论了基于⽇志的消息代理的消费者能在失败或断开连接后重连,并确保它不会错过掉线期间任何到达的消息。同样的技术适⽤于单个⽤户,每个设备都是⼀个⼩事件流的⼩⼩订阅者。
端到端的事件流
允许服务器将状态变更事件,推送到客户端的事件管道中,是⾮常⾃然的。因此,状态变化可以通过端到端(end-to-end)的写路径流动:从⼀个设备上的交互触发状态变更开始,经由事件⽇志,并穿过⼏个衍⽣数据系统与流处理器,⼀直到另⼀台设备上的⽤户界⾯,⽽有⼈正在观察⽤户界⾯上的状态变化。这些状态变化能以相当低的延迟传播 —— ⽐如说,在⼀秒内从⼀端到另⼀端。
读也是事件
多分区数据处理
将事情做正确
我们希望构建可靠且正确的应⽤(即使⾯对各种故障,程序的语义也能被很好地定义与理解)。约四⼗年来,原⼦性,隔离性和持久性(第7章)等事务特性⼀直是构建正确应⽤的⾸选⼯具。然⽽这些地基没有看上去那么牢固:例如弱隔离级别带来的困惑可以佐证。
为数据库使用端到端的参数
虽然不变性很有⽤,但它本身并⾮万灵药。让我们来看⼀个可能发⽣的,⾮常微妙的数据损坏案例。
正好执行一次操作
最有效的⽅法之⼀是使操作幂等(idempotent)(参阅“幂等性”);即确保它⽆论是执⾏⼀次还是执⾏多次都具有相同的效果。但是,将不是天⽣幂等的操作变为幂等的操作需要⼀些额外的努⼒与关注:你可能需要维护⼀些额外的元数据(例如更新了值的操作ID集合),并在从⼀个节点故障切换⾄另⼀个节点时做好防护。
抑制重复
在这种情况下,可能会向⽤户显示错误消息,⽽他们可能会⼿动重试。 Web浏览器警告说,“你确定要再次提交这个表单吗?” —— ⽤户选“是”,因为他们希望操作发⽣。 (Post/Redirect/Get模式【54】可以避免在正常操作中出现此警告消息,但POST请求超时就没办法了。)从Web服务器的⻆度来看,重试是⼀个独⽴的请求,⽽从数据库的⻆度来看,这是⼀个独⽴的事务。通常的除重机制⽆济于事。
操作标识符
除了抑制重复的请求之外,例12-2中的请求表表现得就像⼀种事件⽇志,提示向着事件溯源的⽅向(参阅“事件溯源”)。更新账户余额事实上不必与插⼊事件发⽣在同⼀个事务中,因为它们是冗余的,⽽能由下游消费者从请求事件中衍⽣出来 —— 只要该事件被恰好处理⼀次,这⼜⼀次可以使⽤请求ID来强制执⾏。
端到端的原则
尽管低层级的功能(TCP复制抑制,以太⽹校验和,WiFi加密)⽆法单独提供所需的端到端功能,但它们仍然很有⽤,因为它们能降低较⾼层级出现问题的可能性。例如,如果我们没有TCP来将数据包排成正确的顺序,那么HTTP请求通常就会被搅烂。我们只需要记住,低级别的可靠性功能本身并不⾜以确保端到端的正确性。
在数据系统中应用端到端思考
强制约束
唯一性约束需要达成共识
唯⼀性检查可以通过对唯⼀性字段分区做横向扩展。例如,如果需要通过请求ID确保唯⼀性,你可以确保所有具有相同请求ID的请求都被路由到同⼀分区。如果你需要让⽤户名是唯⼀的,则可以按⽤户名的散列值做分区。
基于日志消息传递中的唯一性
多分区请求处理
及时性与完整行
- 及时性 及时性意味着确保⽤户观察到系统的最新状态。
- 完整性 完整性意味着没有损坏;即没有数据丢失,并且没有⽭盾或错误的数据。
数据流系统的正确性
另⼀⽅⾯,对于在本章中讨论的基于事件的数据流系统⽽⾔,它们的⼀个有趣特性就是将及时性与完整性分开。在异步处理事件流时不能保证及时性,除⾮你显式构建⼀个在返回之前明确等待特定消息到达的消费者。但完整性实际上才是流处理系统的核⼼。