美文网首页
Spanner: Google's Globally-Distr

Spanner: Google's Globally-Distr

作者: 西部小笼包 | 来源:发表于2020-01-05 16:43 被阅读0次

    论文地址
    译文地址

    为什么要读这篇论文?
    首先它是一个很了不起的进步,一个现代的分布式关系型数据库。同时保证了高性能。
    它精妙的运用了精妙的PAXOS。
    它试图去解决一致性和性能之间的问题。

    这里面有很牛逼的想法?
    所有分片被paxos管理来复制(lab 4 的思想)
    尽管在广域网下使用同步复制策略性能也很好。
    读非常快因为只用去最近的副本上拿
    一致性尽管数据分片了。(分片了之后一致性很难,但是这里解决了)
    巧妙的利用时间作为一致性的手段
    分布式事务。

    这篇论文里面说的内容密度非常高。需要读者有一定分布式理论基础,同时还要有一定数据库理论基础。至少需要知道PAXOS, 2PC, 2PL, MVCC等概念的工作原理。我的博客里这些知识都有所涉及。本篇不具体讲细节,只梳理设计背后的脉络。

    首先聊一下shard

    我们来回忆下LAB4,是如何做到这个的?
    首先会有一个MASTER(PAXOS组),还有很多SHARD存储(PAXOS 组)
    PAXOS组用来复制,MASTER管理每个SHARD 负责哪段数据。和标识每套配置的版本号。
    如果master移动一个shard,所有的SHARD存储组最终都能看到新的CONFIG。因为所有的SHARD在后台会去问MASTER有没有新的配置。随后对应的SHARD存储会往RAFT里放LOG来让所有副本都知道变化。这段转移数据从一个SHARD存储到另一个SHARD存储的时间。我在这里叫他hand-off

    如果在hand-off期间,发生了PUT。客户端可能会看到new config, 然后朝还没有数据的SHARD GROUP要数据。或者看到旧的config,然后去问正在转移数据的SHARD GROUP要数据。

    还有的问题是旧的GROUP认为数据发出去了。新的GROUP失败了。怎么办?

    有没有可能2个GROUP都认为他们服务一个SHARD呢?

    有没有可能旧的GROUP在也服务一个SHARD,因为和MASTER的连接断开了?

    做完LAB4的小伙伴一定知道上面的问题不是那么好解决的

    跨地域同步复制

    跨地域同步复制有很多用处,比如抗单地理灾难性事件。可以使得副本里用户更加的近,因为同步所以不会丢失任何更新。
    但是同步复制的性能一直是个很大的问题?


    image.png

    为什么增加了副本,写延迟没有上升?

    为什么读的吞度随着增加副本上升,但是写没有呢?

    因为大量的并发的CLIENT使得这些请求都是并行的发出基于PAXOS。同时spanner 还是一个可以在任一一个副本读的系统。也就是意味着读不需要通过paxos agreement . 这个会使得速度比经过快100倍。

    那么我们是不是也可以写到任一一个副本呢? 读任一副本是正确的吗?

    我们来假设这么一个场景
    你有一个相册,相册有个权限组。你把母亲移除了权限组,然后上传了一张新照片。
    你写权限去了G1(group 1),加照片去了G2(group 2)。 母亲读照片 去了G2一个新的副本,拿权限可能去了G1拿到旧的副本,拿了旧的权限。这就产生了不一致。表现的不像单台机器。母亲就会拿到旧的权限读到你新的照片

    出现上面的问题的原因是因为shard + read from any replica
    shard 会导致每个SHARD 存储组 独立操作,也就是权限组对你存照片的组做的操作无感知,各行其是。

    如何FIX

    最简单的做法是读也经过PAXOS,但是这很慢,COST很大。
    第二种做法就是让读看到一致的数据。SPANNER就是选择这种做法。
    我来举个例子,数据的版本可能分为3个。before #1, between #1 and #2, or after #2。 那么你在这个时间去拿的 必然或落到其中一个版本窗口。如果这个时间上和落库的版本上上是正确的,那么就是正确的了。

    假设我们现在的世界,所有的时间都是一致的。服务器(paxos leader)会给每一个打一个时间戳。DB存储了这个KEY,每一个时间上的VALUE。
    客户端读的时候把当前的时间T发过来,只要去问任一一个副本,要读这个KEY在时间T的数据。
    那么副本只要去返回他VALUE里面的时间X 《= T中X最大的那个VALUE。
    但是REPLICA可能是落后的,也就是LEADER那边最新的写还没发过来。
    也就是说比如上述算出的X是5, 当前时间是10. 5是这个REPLICA最大的这个KEY的时间。但是我们不知道是不是有6的时间的写还没更新过来。我们不能直接返回5的。
    那么如何知道我们的REPLICA已经看到全部的写《=T呢?
    我们只要去判断这个REPLICA有没有看到一个操作的时间是》T的?
    如果有的话,因为PAXOS GROUP 在APPLY操作的时间顺序上是一致的,所以只要等到一个T > 10的操作来了,我们就可以把5的这个VALUE返回过去。(PAXOS可以支持乱序COMMIT日志,但是要保证APPLY日志要有序)

    那么CLIENT选取现在的时间读可以吗?
    是可以的,但是很容易使得副本要去等,比如发生了上述的情况。 如果把时间往前拨一点,有可能就会在这个时间里发生更新而丢掉。

    重新看那个问题

    G1负责权限,G2负责照片
    我在10的时候改了权限,15的时候加了照片。(因为时间是正确的,我是先改权限后加照片)
    母亲选择时间14,我们假设母亲选了一个G1里的落后REPLICA。用时间14去拿,她一定可以等到那个10的更新。随后她用14 去G2拿,可能这个副本是新的,但是他也会去读上一个版本的照片。
    现在就一致了。

    下面的问题是这个时间一致的假设可能吗?如果每个服务器的时间不一致是不是就出问题了?
    同时还有一个性能问题要解决,假设上面那个10之后没有写了,那么母亲用14的时间去拿就永远被阻塞了,因为不确定10之后还有没有比14小的更新。

    如果时间不一致,一定是错的。就是上面那个例子,我们写权限可能时间戳是15,加照片时间戳是10.这些破坏了假设。母亲可以拿到新照片用旧权限。

    解决这个问题,是这个PARER的一大利器:

    TrueTime API

    这个API 通过原子时钟和GPS,2重机制保证高可靠。尽可能的降低时钟失准的概率。同时控制真实时间(wall clock)在一个可容忍的误差范围内。那么对spanner来说只要给每个写一个标量时间来确保一致即可。现在这个时间是个区间了,可以确保准确的时间一定在区间里。

    好了现在的新问题是怎么确保一致?
    我们假设这个误差是10.
    那么在改权限的真实时间是20。 极端情况下API返回[20,30]是MAKE SENSE。
    加照片的真实时间是21. API返回[11, 21]也是MAKE SENSE的。
    这个时候我们一个读请求,很有可能去取个中间值来代表这2次写的真实时间,就出错了。因为S1 = 25, S2 = 16
    我们希望W2(WRITE 2也就是加照片的写)要在W1(改权限)之后。S2 > S1
    这就是外部一致性的不变量。论文里是4.1.2
    他能够使快照图看到写的真实顺序W1,W2

    那么SPANNER如何处理上述的问题呢?
    我们来看论文里的2个RULE:
    Start Rule : coordinator leader 会为写操作Ti分配一个不小于TT.now().latest的时间戳:Si。TT.now().latest的值是在事件e_i_server之后计算得到的。
    Commit Wait Rule : coordinator leader 必须确保在TT.after(si)的返回值为true之前,Client端是永远不可能看到任何提交的数据的。Commit wait就是要确保si比Ti的绝对提交时间要小。

    当一个写请求到了PAXOS LEADER。S是这个写请求的时间戳。
    LEADER 会SET S 为TrueTime now().latest (根据START 规则)
    随后LEADER 会DELAY 直到(s < now().earliest)(TT.after(si) = true)
    (根据Commit Wait Rule)

    随后LEADER 运行PAXOS APPLY 这个WRITE。 随后返回给CLIENT。

    第三次回到那个例子

    W1 at G1, TrueTime says [20,30]
        s1 = 30
        commit wait until TrueTime says [31,41]
        reply to client
      W2 at G2, TrueTime *must* now say >= [21,31]
        (otherwise TrueTime is broken)
        s2 = 31
        commit wait until TrueTime says [32,43]
        reply to client
    

    现在就可以确保S2 一定> S1了。 即使G2的TRUE TIME CLOCK已经尽可能的慢了。如果母亲看到S2,一定确保能看到W1的改动。

    为什么需要START RULE?

    也就是为什么我们要用当前时间的END。
    前一个写会等待直到他的TS< t_abs(当前的wall clock)。 新的写必须要选择一个更大的时间比任何完成的写操作的时间要大。t_abs 最最高是和 now().latest 一样高。所以s = now().latest

    为什么需要COMMIT WAIT TULE?

    为了确保 s < t_abs。否则未来的写 基于起始规则可能会出现一个更小的S。

    为什么要让COMMIT WAIT。为什么不能直接SET S 到一个未来的时间呢?

    因为需要间接地迫使后续写具有足够高的s。系统没有其他的途径去通讯关于最低可以接受的下一个S是多少对于要跨REPLICA GROUP(一般是跨DATA CENTER)的写。
    等待可以帮助外部的agent是序列化的,拥有一个单调递增的时间戳。
    没有等待,我们的例子就会有s1=30, s2=21 的事情发生。

    其实上述这些就是怎么基于true time api 来省去SERVER间本来需要依靠网络通讯发送逻辑时间的成本,造成读非常快。因为TRUE TIME API的误差时间范围很小,比通讯的代价要小的多。因为跨数据中心的同步复制的信息传播速度受限于光速,即使构建专用高速光缆

    我们想要确保顺序——发生在不同地方但是再真实时间上这些操作的顺序是相同的对一个外部的客户端来说——这就是“外部一致性”
    一个简单粗暴的做法是有一个集中式的代理通过很多的交流同步来保证。Spanner通过隐式的时间去同步,时间可以作为一种交流方式。比如我们提前统一在6:00一起吃晚饭。

    这篇论文里还有很多复杂的内容。比如基于上述API 如何去实现事务, 如何设计QUERY LANAGUAGR, SCHEMA如何CHANGE

    具体的可以参考 几篇不错的博客。
    Spanner十问
    Google Spanner原理:地球上最大的单一数据库
    Google Spanner 事务在存储层的实现

    相关文章

      网友评论

          本文标题:Spanner: Google's Globally-Distr

          本文链接:https://www.haomeiwen.com/subject/ejnqactx.html