# Spanner-关于程序员使用 NoSQL 规模的 SQL 语义构建应用程序

> 原文： [http://highscalability.com/blog/2012/10/22/spanner-its-about-programmers-building-apps-using-sql-semant.html](http://highscalability.com/blog/2012/10/22/spanner-its-about-programmers-building-apps-using-sql-semant.html)

![](img/e618cf11a7e558cbccc35dc3c895b625.png)很多人似乎都非常讨厌 [NewSQL](http://blogs.the451group.com/information_management/2011/04/06/what-we-talk-about-when-we-talk-about-newsql/) 这个词，或者几乎是这个词的任何新造词，但是在看过高级软件工作人员 Alex Lloyd 之后 Google 工程师，就 Building Spanner  进行了精彩的演讲 [ ，这是最适合 Spanner 的术语。](http://vimeo.com/43759726)

Spanner 将 OldSQL 的 SQL +事务模型包装在全球分布式 NoSQL 系统的重新设计的骨骼上。 在我看来，这是 NewSQL。

由于 Spanner 不是 BigTable 的近亲，因此 NoSQL 组件应该不足为奇。 Spanner 负责在任意数量的地理分布数据中心内跨越数百万台计算机。 令人惊讶的是，OldSQL 是如何被接受的。 Alex 在 HotStorage 会议上发表的 [](http://static.usenix.org/event/hotstorage11/tech/slides/lloyd.pdf)早期演讲中，采用 OldSQL 的原因是希望使程序员更轻松，更快速地构建应用程序 。 主要思想似乎很熟悉：

*   小型复杂数据库与庞大，可扩展，简单的数据库之间存在错误的二分法。 我们可以拥有功能并对其进行扩展。
*   复杂性可以保留，可以放到某个地方，因此，如果不在数据库中，则将其推送给开发人员。
*   将复杂性降低到堆栈中，以便开发人员可以专注于构建功能而不是数据库而不是基础结构。
*   创建快速发展的应用团队的关键：ACID 交易； 全局可序列化； 编写第一步交易，而不是十步工作流程； 编写查询而不是代码循环； 加盟 没有用户定义的冲突解决功能； 标准化同步； 即付即用，获得可预期的性能所需的费用。

Spanner 并非以成为 NewSQL 明星为目标。 Spanner 最初是 BigTable 克隆，带有分布式文件系统隐喻。 然后，Spanner 演变为全局 ProtocolBuf 容器。 最终，Spanner 受到 Google 内部客户的推动，以变得与关系和应用程序程序员更加友好。

显然，在 Google 内部使用 [Dremel](http://highscalability.com/blog/2010/8/4/dremel-interactive-analysis-of-web-scale-datasets-data-as-a.html) 已向开发人员表明，可以大规模使用 OLAP 和 SQL，他们希望 OLTP 应用具有相同的易用性和上市时间。 看来 Google 有很多应用程序可供开发，程序员不喜欢处理在最终一致的系统之上生产可靠产品的现实世界中的复杂性。

诀窍是弄清楚如何使 SQL 真正大规模地工作。 为了表明我们仍处于编程经验阶段的深度，该过程甚至花费了 Google 五年的开发时间。 亚历克斯说，真正的工作实际上是建立一个复杂的，可靠的分布式系统。 那是很难正确的部分。

在谈论原子钟等所有内容时，您可能会感到系统中存在魔力。 您可以进行巨大的跨表，跨数百万条记录的数据中心事务处理而不会受到任何损失。 那是不对的。 Spanner 是 OLTP 系统。 它使用两阶段提交，因此长时间的大型更新仍将锁定和阻止，程序员仍处于进入和退出的困境。 想法是这些限制值得程序员提高生产力，并且确实出现的任何瓶颈都可以逐案处理。 通过演讲，我逐渐感觉到，Spanner 的域中将包含诸如 pub-sub 之类的专用应用程序域。 尽管事务方面可能是常规事务，但除了所有的全局重新分区魔术在幕后透明进行之外，它们的事务时间戳方法在读取路径上确实具有很多不错的功能。

为说明每个 Paxos 组很难扩展到大量副本的困难，Alex 转向了水文隐喻：

> 您可以将 Spanner 分区用作一种有序的 pub-sub 方案，在该方案中，您在某个分区的所有位置都有只读副本，并且您正试图使用​​它来以有序方式将数据分配给很多 不同的数据中心。 这带来了不同的挑战。 如果您没有足够的带宽访问那些数据中心的某些子集，该怎么办？ 您不希望数据在领导者中缓冲太久。 如果您将其溢出到磁盘上，那么当带宽可用时，您就不会招致搜索损失。 它变得像水文学。 您需要将所有这些数据在不同的时间发送到不同的位置，并且希望在变化的条件下使所有流平稳地移动。 平稳地意味着更少的服务器重启，意味着更好的延迟尾部，意味着更好的编程模型。

这也许是我最喜欢的部分。 我只是喜欢数据流过的图像，就像水滴从数百万台机器和网络中滴落，暂时聚集在内存和磁盘的洞穴中，总是分裂，总是重组，总是不断变化，总是不断进步，是巨大的不断流动的一部分 [数据周期](http://en.wikipedia.org/wiki/Water_cycle)永不丢失。 太棒了。

如果您有机会观看视频，我强烈建议您这样做，这的确很好，根本没有什么毛病。 关于在分布式事务中使用时钟的部分做得特别好。 但是，如果您时间紧缺，可以参考以下内容：

*   5 年的努力。
*   NoSQL 规模的 SQL 语义。
*   试图获得一个看起来像单个巨型 MySQL 的抽象。
*   关系数据库是一个非常熟悉的生产环境，可以在其中建立应用程序。
*   Spanner 是存在证明，可以将关系数据库扩展到全局分布式存储系统。
*   编写应用时无需考虑事务语义。 正确无误。 然后，您返回并优化一些高事务写入，这些优化将真正获得回报。
*   希望向应用程序开发人员提供真正直接的语义。 应用程序开发人员应考虑业务逻辑而不是并发性。
*   他们这样做的方式是通过构建具有绝对绝对误差的时钟。 然后将它们与时间戳分配集成在并发控制中：
    *   时间戳的总顺序遵循事务的部分顺序。 如果交易 A 在交易 B 之前发生，我们知道交易 A 的时间戳小于交易 B 的时间戳。
    *   这意味着对所有内容进行有效的可序列化查询。 您可以将跨越数十个数据中心的 PB 级数据库的总和提高到一分钱。 可能需要一段时间才能获取所有数据。 但是您可以期望答案是正确的。
*   BigTable 早期的 NoSQL 数据库。 论文于 2006 年问世。
*   MegaStore 建立在 BigTable 之上。 它添加了 Paxos 同步层和更丰富的数据模型。 论文在 2011 年问世。
*   底层架构中的 Spanner 很像 BigTable。 更新会附加到数据中心本地分布式文件系统中的日志中。 定期将它们压缩为不可变的 b 树（SSTables），并定期将这些 SSTable 合并在一起。 Leveldb 是一个开源版本。
*   开发人员仍必须考虑如何对数据进行分区以提高效率，但开发人员应尽可能地专注于业务逻辑。
*   目标不是数据重新分区的停机时间。 **Google 所做的一切都与数据移动息息相关，因为在数据中心之间移动用户和重新分片是一项持续的后台活动。** **因为这是一个连续的过程，所以各种并发性错误不断出现**。 事务有助于正确地分配其连续的分区流逻辑。 在进行交易之前，他们有很多错误，这就是为什么花了 5 年的时间的一部分。
*   希望程序员减少在设计过程中早期做出的分区决策的束缚。
*   希望获得 Megastore 最成功的部分：
    *   处理大规模数据中心中断，而不会给用户带来明显影响。
    *   处理单元内部较小的中断，微中断。 示例：底层平板电脑因过载或损坏而中断，仅一个机架上的电源就中断了，等等。
    *   用户可能会在计时器触发时看到延迟增加，并且他们移至另一个数据中心，但看不到对事务语义的影响。
*   但是 Megastore 有一些问题：
    *   数据库被划分为一堆实体组。 实体组是他们自己的交易域，您不能跨实体组进行交易。
    *   它使用乐观并发。 当副本之间的传播间隔为 50 毫秒，而您正在执行同步复制时，写入将至少花费 50 毫秒，这将为乐观并发故障创建一个很大的漏洞窗口。
    *   将分层系统整合为单个集成系统的好处。 与 Megastore 和 Bigtable 之间的接口相比，Spanner 与物理存储之间的接口要丰富和优化。
*   向 SQL 的文化转变
    *   基于 SQL 的分析系统 Dremel 在 Google 上进行了许多 SQL 转换，因为它可以将查询的语义向下推送到存储系统中，并让其确定该怎么做。
    *   这种文化根深蒂固，您可以扩展，也可以使用 SQL。 Dremel 表明，对于分析，您可以同时拥有。 Spanner 显示您可以同时使用 OLTP。
*   由于业务问题，人们要求简化地理分区。 例如，将用户数据从一个区域移动到另一区域。
    *   法律问题
    *   产品增长意味着您想要尽可能高效地将多少人放到
*   扳手的数据模型
    *   并不总是具有关系模型。 与 Jeff Dean 在 2009 年提出的内容完全不同。
    *   带有单个用户关系表的示例，每个用户都有一个名称和家庭区域。
    *   User 数据库可以分为几个分区。 美国的一个只读分区，欧洲的另一个分区。 大型数据库将具有数百万个分区。 分区 1 在美国将具有三个副本。 另一个分区在欧洲具有三个副本。 美国有一个只读副本。 这很有用，因此您可以在欧洲拥有一个书面仲裁，这意味着您永远不会阻塞跨大西洋的 RPC 调用，但是在美国，尽管它可能有些陈旧，但您仍然可以查询所有数据。
    *   主细节层次结构在物理上被聚在一起。 在分布式系统上，更为重要的是记录将存储在其他服务器上。
    *   关系抽象的底层是程序员如何将密钥手动编码到 Bigtable 中。 每个表格单元格都只有一个条目，例如：

        *   @ 10 部分是时间戳。
        *   在 Bob 记录开始之前，Alice 的订单将与 Alice 一起存储。
*   并发
    *   在较高级别，Spanner 使用两阶段锁定和快照隔离的组合。
    *   并未尝试创建疯狂的新模型。 旨在找出如何扩展已验证模型的目标。
    *   此模型最适合读取为主的工作负载。 您将大部分时间花在便宜的快照隔离读取上，而不花大量时间在悲观主义事务写入上。
    *   Blogger 示例，它首先担心正确性，然后担心优化。
        *   Blogger 有 280 个 Servlet。 低频高复杂度操作，例如用户通过发送文本消息来创建博客，然后他们希望将该博客合并到现有博客中。
        *   花费大量的时间才能将其创建为由精心设计的工作流程精心安排的一系列幂等操作。
        *   使用 ACID 交易，Blogger 会更快。 在没有性能优势的情况下花费在这些复杂的编程任务上的时间本来可以减少某些高频页面的 50 毫秒，从而产生更大的整体影响。
    *   与在单台计算机上编程相同的过程。 您从互斥锁开始，然后才尝试原子操作。
    *   仅执行弱一致性的 NoSQL 数据库正在对整个系统实施广泛应用的过早优化。 应该选择值得的页面。
*   在 Google 看到的模式中保留提交顺序
    *   他们在设计过程中考虑的经验法则：
        *   如果 T1 在 T2 之前完成，那么他们想保留这一事实。 T2 与 T1 之间存在提交顺序相关性。
        *   假设 T3 写了一些 T4 读取的内容，因此存在传统的数据依赖性，因此 T3 必须始终在 T4 之前发生。
        *   T1 和 T2 在 T3 和 T4 之间没有关系。
        *   **系统性能来自并发运行没有依赖性的事务。**
        *   **目标是保留与原始历史记录相同的依存顺序。**
        *   可序列化是一个带有大量变化的重载术语。
        *   线性化是从并发编程中借鉴的一种思想，并且很好地适用于在分布式数据库之上对分布式系统进行编程。
            *   包含可序列化性，无法上下班提交顺序。
            *   即使没有可检测的依存关系，即使一笔交易发生在另一笔交易之前，也必须保留该订单，即使该交易发生在不同的机器上。
    *   示例架构：
        *   一个分区是购买的广告表。 在美国写仲裁，在欧洲写只读副本。
        *   这些广告的展示在美国的一个分区。 例如，在 2:00 和 2:01，有人观看了小狗广告。
        *   欧洲的一个分区，用于展示广告。
        *   **在美国有一个只读的欧洲数据副本，反之亦然。 这样可以使双方进行过时的读取和快速的写入，而无需越过池塘。 您仍然可以在任何一侧高效地使用 MapReduce。**
    *   交易示例：
        *   交易 1：用户购买了广告。
        *   在后台，广告投放系统正在不断扫描分区以显示广告，并发送检索广告查询。
        *   广告投放系统会随着时间累积其要保存在数据库中的一批展示次数。
        *   事务 2：服务器写入印象分区以记录印象。
        *   这是两个不同的分区，不同的副本，不同的数据中心以及潜在的不同大陆。
    *   现在，您想编写一个 SQL 查询来审核小时级别的展示次数。 选择一个时间戳。
        *   根据时间戳记只有三个合法结果：
            *   既看不到广告也看不见。
            *   看到广告，但没有展示。
            *   查看添加和所有展示。
        *   在所有系统中，他们都在替换 MapReduce 或查询必须容忍结果的无限变化。
            *   它可能会看到展示，但看不到广告。
            *   **针对这种弱语义编写查询意味着很难分辨出腐败，错误或并发异常之间的区别。**
        *   解决此问题的方法是通过单个中央服务器序列化每个更新。 如果更新位于一起，则有效，但不适用于分区遍布全球的分散式模型。
*   在全球分布式数据库中扩展 Spanner 所需语义的选项：
    *   **一个分区模型** 。 大量的 WAN 通信。 将所有分区都包含在每个事务中。
    *   **集中时间戳预言** 。 如果您同时在两个不同的大陆上进行更新，则效果不佳。
    *   **Lamport 时钟** 。 通过每个外部系统和协议传播时间戳。 如果您的系统数量和协议数量都不足，则此方法有效，当您拥有大量不受控制的分布式系统或协议（例如与贸易伙伴一起使用）或它们只是协议时，您将无法正常工作 不碰。 在 Google 进行了几次尝试，但是在复杂的系统中始终无法成功完成时间戳。
    *   **建立一个分布式时间戳预告片** 。 其中的一个 TrueTime 是 Google 常规时间清理产生的。 时间有一个 epsilon 时间，因此您知道进行现在呼叫的实时时间在该时间间隔内。 源自一堆不同数据中心中的 GPS 接收器，这些数据中心由原子钟备份。 GSP 系统有时确实存在错误。 一次代码推送可以消除大量卫星，因此备份非常有用。
*   TrueTime
    *   目标不变式：写 A 和 B，如果 A 发生在 B 之前，则意味着 A 在 B 开始之前完成，那么 A 的时间戳应小于 B。完成意味着任何人都可以看到效果。 不仅是客户，还有 Paxos 奴隶。
        *   使用此不变式，意味着您可以说在特定时间戳记下的快照读取是可序列化的。
    *   TrueTime 与天体导航的工作原理类似，只是那是硬错误界限，而不仅仅是猜测。
    *   每台 Google 服务器中都有一个时间守护程序，每台服务器中都有一个水晶，每个数据中心都有一些来自不同制造商的 GPS 的时间主控器，以解决错误的多样性，其中一些具有原子钟来交叉检查 GPS。
    *   守护程序每 30 秒与时间主对话，并获得时间修复。 在两者之间，死者根据自己的晶体进行侦察。 服务器错误容限随着时间的推移而扩大，他们选择了百万分之 200。
    *   现在几点了？ 在本地计算机上读取时间。 将 GetTime 发送到时间主机。 返回时间为 T。再次读取本地服务器时间。 你得到一些增量。 然后，您可以扩展该增量以获得所需的任何误差范围。 回来了一个ε。 现在您可以说时间在[t t + e）]中。 时间不早于 t，因为时间主因您收到 t 的回应而报告了 t 的因果关系。 但是会有很多偏差，因为邮件可能是在 epsilon 之前发送的。 您不知道往返 t 产生的位置。 开始漂移之前的主要错误是与主设备的往返时间。
    *   您可以建立 GPS 以外的其他系统来分配时间。 例如，LED 闪烁并为所有系统提供时钟脉冲。 使用心跳系统，该系统定期与中央服务器对话，并且在这些心跳之间，所有服务器都认为时间是固定的。 GPS 在那并且可以工作。 原子钟是一种方便的交叉检查。 而且所有硬件都不贵。
*   当 Paxos 领导程序从客户端接收到提交请求时，该分区的 Paxos 领导程序流
    *   接收开始提交。
    *   获取事务锁定。 任何两阶段提交系统中的典型值。 抓住读写锁，以确保冲突的事务不会同时执行。
    *   选择一个时间戳记，以使 true.max 大于当前时间。
    *   并行执行两件事：运行 Paxos 以就写入内容达成共识，等待真正的时间肯定超过该写入时间戳记。
    *   通常，Paxos 将花费比等待更长的时间，因此它不会增加额外的开销。
    *   通知 Paxos 奴隶解锁。
    *   确认返回客户端。
*   为什么起作用？ 通过等到提交时间戳记过去，我们将所有将来的事务推向更大的时间戳记。 保证下一个事务的时间戳大于前一个事务的时间戳。 **每个事务都同意选择一个比其起始点大的时间戳，并且每个事务都同意将其提交推迟到自己的提交时间戳记过去。**
    *   当 Paxos 进行得太快或只有一个副本，而您只是要提交到本地磁盘时，TrueTime epsilon 可能比提交该提交所需要的时间大。
    *   当 TrueTime epsilon 达到峰值时发生，例如 TrueTime 母版下降并且您必须转到更远程的 TrueTime 母版的情况下。 或者当 Paxos 副本异常关闭时。
    *   现实中的 epsilon 在 1 到 7 毫秒之间反弹。
    *   当通过使用具有更高 QoS 的时间数据包来改进网络时，尾部延迟降低了。 < SDN 的一个论点，即通过系统以更高的 QoS 获得诸如时间和 Paxos 之类的控制数据包。
    *   通过更频繁地轮询时间主数据，以高 QoS 轮询，改善内核处理，在 NIC 驱动程序中记录时间戳，购买更好的振荡器，注意内核错误（节能模式（您使用的时钟））来减少 epsilon。

## 读取路径

*   类型：
    *   在读取-修改事务中，看起来与标准的两阶段锁定相同，只是它发生在 Paxos 领导者身上。 获取读锁。
    *   不属于事务的强读取，客户端不会基于该读取进行写入。 Spanner 将选择一个更大的时间戳并在该时间戳上进行读取。
    *   当您只想知道数据已使用 5 或 10 秒时，就会读取陈旧的数据。 Spanner 将选择落入陈旧范围内的最大已提交时间戳。
    *   MapReduce /批量读取-不在乎数据是否新鲜。 例如，让客户选择一个时间戳并说我想知道所有事情。
*   为强读而选择的时间戳记：
    *   问 TrueTime，现在的时间比现在大。 您知道这也大于所有先前提交的事务的提交时间戳。 并非总是最好的时间戳，因为您希望最大化能够在该时间戳上进行读取的副本。
    *   查看提交历史记录。 从最近的文章中选择一个时间戳。
    *   强制声明预读的范围。 例如，这 5 个用户和该系列产品。 范围的概念高于分区的概念。
    *   准备的分布式事务。 由于分布式事务必须在每个分区上同时提交时间戳，因此存在一个不确定性窗口，在此窗口中，您不会为某些对象提交什么数据来提交时间戳。
*   有效使用原则：
    *   数据局部性仍然很重要。 从一台机器上读取一堆东西比从一堆机器上读取要好。 将客户和订单放在同一分区中。
    *   大用户将跨越多个分区，但是跨分区在事务级别上不会产生语义影响。
    *   设计应用的正确性。 处理数百个需要处理但不需要那么快的细腻坚硬的角落案件。 例如，您一天更改几次 Gmail 过滤器？
    *   放宽仔细审核的高流量查询的语义。 也许在某些情况下，阅读会过时。 您过去读得越远，越能提供更多副本。
    *   Spanner 中的默认语义是它们是可线性化的。
    *   使用闪存备份将允许毫秒写入。
*   第一个大用户：F1
    *   已将对收入至关重要的共享 MySQL 实例迁移到 Spanner。 对 Spanner 数据模型的影响很大。 F1 需要一个强大的 MySQL。
    *   Spanner 最初是 BigTable 之子
        *   包含许多分叉的 BigTable 代码，并带有分布式文件系统隐喻。 您有一棵目录树，每个目录都是地理位置的单位。 这与建立数据库的人无关。
        *   他们添加了结构化密钥，但最终他们只是在构建下一个 Megastore。
        *   他们决定 Spanner 需要更丰富的数据模型，从而确定 Spanner 是协议缓冲区的存储库。 Spanner 将是一个巨大的协议缓冲区。 这似乎是合理的，但同样，它不是用户建模数据的方式。
        *   他们认为 F1 是更好的模型，因此最终他们转向了关系数据模型。

## 他们现在在做什么？

*   **完善 SQL 引擎**。 时间戳加迭代器的位置足以重启跨分区查询。 如果查询需要一分钟，并且负载平衡器在那一刻将您依赖的平板电脑移动到另一台服务器，或者该服务器被抢占，因为您正在共享单元中运行，并且抢占迫使这些平板电脑移动，查询会 不必中止，也不必扔掉工作。 像这样移动服务器很难工作，但对用户而言确实有价值。
*   **对内存使用情况的精细控制**，因此您无需创建分布式死锁，因为当一堆服务器都依赖于它们时，它们都需要内存才能取得进展，而它们都需要取得进展才能释放该内存 。
*   **细粒度的 CPU 调度**。 即使出现大得慢的大查询，也要保持快的查询快。应该对无希望的慢查询进行时间划分，以使快的查询保持快。 保持延迟的尾巴。
*   **基于快照隔离的强读取仍然非常绿色**。 这些正在以越来越多的用例进入生产。
*   **缩放到每个 Paxos 组**的大量副本。 您可以将 Spanner 分区用作严格有序的 pub-sub 方案，在该方案中，您在某个分区的所有位置都有只读副本，并且您正尝试使用它来以有序方式将数据分配到许多不同的数据中心。 这带来了不同的挑战。 如果您没有足够的带宽访问那些数据中心的某些子集，该怎么办？ 您不希望数据在领导者中缓冲太久。 如果您将其溢出到磁盘上，那么当带宽可用时，您就不会招致搜索损失。 它变得像水文学。 您需要将所有这些数据在不同的时间发送到不同的位置，并且希望在变化的条件下使所有流平稳地移动。 平稳地意味着更少的服务器重启，意味着更好的延迟尾部，意味着更好的编程模型。

## Q & A

*   您如何证明交易之间没有依赖关系？ 假设某人通过电子邮件向某人发送了一些数据，然后该人单击了基于该数据的链接，从而导致了另一个数据中心的写入。 很难说两个不同中心之间的交易之间没有因果关系。 他们希望您能够在整个系统之间建立因果关系的前提下关联整个系统中事务的顺序。
*   最终的一致性和交易模型的空间。
    *   移动是一种情况，用户在更新本地缓存，并且数据将其返回服务器，因此您不必依赖事务，因此必须具有某种最终的一致性机制来合并更改并处理冲突。
    *   允许并发编辑的 Google 文档是另一种情况。 您有五个打开的窗口，它们每个都在进行本地更新。 这些更新异步地冒泡回到服务器。 应用用于合并更新的可操作转换代数，然后才将这些更新应用于数据库的规范副本。

## 相关文章

*   [Alex Lloyd-建筑扳手](http://vimeo.com/43759726) 视频（ [幻灯片](http://berlinbuzzwords.de/sites/berlinbuzzwords.de/files/slides/alex_lloyd_keynote_bbuzz_2012.pdf) ）
*   [Google Spanner 的最令人惊讶的启示：NoSQL 出现了，NewSQL 出现了](http://highscalability.com/blog/2012/9/24/google-spanners-most-surprising-revelation-nosql-is-out-and.html)
*   [面板：大数据，无需 SQL，大问题，无需担心](http://static.usenix.org/multimedia/hotstorage11seltzer) （ [幻灯片](http://static.usenix.org/event/hotstorage11/tech/slides/lloyd.pdf) ）（ [视频](https://www.usenix.org/conference/hotstorage11/panel-big-data-no-sql-big-problems-no-worries) ）
*   [互联网规模的企业问题](http://static.usenix.org/event/hotstorage11/tech/slides/lloyd.pdf)

伟大的帖子托德。 这些方法正在合并！

您的文章内容丰富，但有时我会觉得有些困惑。 谢谢托德。