浅谈Spanner

2022-06-02 00:00:00 数据 事务 时间 副本 保证

在2012年的OSDI上,谷歌发表了《Spanner: Google’s Globally-Distributed Database》,其中介绍了谷歌第二代的数据库,也就是Bigtable的继任者——Spanner。在使用Bigtable的过程中,谷歌的开发人员逐渐意识到Bigtable的一些不足之处,比如不能处理变化的数据格式,不能保证大范围内数据库的一致性以及对跨行事务的处理。谷歌为了解决这些问题,开发出了Spanner。

总体架构

谷歌设计Spanner的一个重要目标是对全球范围内的数据库进行管理,为了更加清晰有效地划分和管理数据,Spanner划分了多个层级。其中,的是Universe,在论文中谷歌表示目前只有三个Universe,包括一个用于测试或后台运行的Universe,一个用于部署或生产的Universe和一个仅用于生产的Universe。每个Universe下面包含多个Zone,每个Universe使用UniverseMaster和PlaceDriver检测和管理Zone,这里的Zone就相当于Bigtable的Server,对应实际情况中的一台或多台物理机器。而每个Zone中有一个ZoneMaster管理多个LocationProxy和数百至数千个SpannerServer,其中,LocationProxy负责将客户端的请求转发到对应的SpannerServer。论文中给出的示意图如下:

论文中仅仅披露了SpannerServer的具体内容,所以下面我们也只讨论SpannerServer。由于Spanner是为了代替Bigtable而设计的,所以SpannerServer的内部架构其实和Bigtable有一点类似,但是Spanner又作出了很多优化。内部架构的示意图如下:

这张示意图以三副本的情况为例介绍了Spanner Server的内部架构。Spanner和Bigtable相似的地方在于,他们都基于分布式的文件系统GFS,这里的Colossus是第二代GFS,具体的实现细节仍未公布,而且他们都使用了Tablet作为单位管理存储数据,但是,这里的Tablet和Bigtable中Tablet是有些不同的。但是再上面一层就有些不同了,因为Bigtable中数据基于爬虫获得而且使用了SSTable存储数据,这保证了数据的性,所以在Bigtable中没有使用具体的算法保证数据的一致性,但也因为这一点,Bigtable没有很好地支持事务。Spanner中使用了多副本的机制备份数据,同时在副本之间使用Single Paxos算法保证了数据一致性,在这里,谷歌可能是为了提高Paxos算法的性能并降低耦合,他们并不是把整个机器作为Paxos算法的基本角色,而是将机器中的数据分割为多个Paxos Group,每个机器中的相同数据被标识为同一Group,每次运行Paxos算法都仅由关联的Paxos Group参与。再往上一层就是各机器之间的关系了,Spanner使用主从结构管理副本,Leader节点要额外维护LockTable和Transaction Manager,其中LockTable的作用类似Bigtable中的Chubby,用于协调各副本之间的并发操作,Transaction Manager则用于管理分布式事务。Leader节点还要负责所有的写操作和和其他节点的沟通。

Paxos Group仍然是很大的操作单位,想要更加灵活地进行数据迁移工作就需要更小的数据单位。于是Spanner将每个Paxos Group分割为多个Directory,而每个Directory包含若干个拥有连续前缀Key的数据。不得不说,这里的连续前缀Key有点像SSTable的设计。Spanner将Directory作为物理位置记录的单元,同时也是均衡负载和数据迁移的基础单元。这很好理解,均衡负载是把请求转发到拥有相同数据的机器,数据迁移是把数据从一台机器复制到另一台机器,这两者都需要目的机器的物理地址,所以小只能把Directory作为单位。

Spanner把在Paxos Group之间迁移Directory设计为后台任务,但是由于数据迁移可能造成读写阻塞,所以它不被设计成事务。操作的时候是先将实际数据移动到指定位置,然后再用一个原子的操作更新元数据,完成整个移动过程。

这里要特别说明的是,Spanner中的Tablet和Bigtable中的Tablet有些不同。Bigtable中的Tablet可以简单地看做是若干连续的有序记录,而Spanner中的Tablet则被设计成一种容器,其不一定是连续的有序记录,而可能包括多个副本的数据。

数据模型

Spanner和Bigtable的数据模型差别也很大,其数据模型如下。很容易地可以发现,其从Bigtable中类似于关系型的数据库变成了类似于K-V的数据库。

这种变化重要的原因是Bigtable的数据模型仅适用于类似PageRank等数据格式长期稳定且不怎么变化的任务,如果任务需要快速版本迭代可能就不再使用。

另一方面,在Google内部有一个Megastore数据库,尽管要忍受性能不够的折磨,但是在Google有300多个应用在用它,包括Gmail, Picasa, Calendar, Android Market和AppEngine。而这仅仅因为Megastore支持一个类似关系数据库的语法和同步复制。所以,Spanner决定支持数据库的语法,论文中说这种语法类似于SQL语句。其结果如下:

可以看到,Spanner底层的存储结构仍然是K-V的形式,但是在创建Albums的表时,其指出了父类表为User,那么在Directory中组织成了上图中关联的User和Albums相邻的形式。那么在进行SQL查询的时候,就可以通过顺序读写得到数据,相比随机读写要快得多。

TrueTime

前面讨论了Spanner总体架构,内部架构以及数据模型,平常的论文可能到此就结束了,但是这篇论文到这里进入重要的部分,因为Spanner引入了一个开创性的想法,使用TrueTime标记时间。而TrueTime指的是真实的时间戳,谷歌使用GPS和原子钟两个物理元件得到这个时间。正常情况下使用GPS获取该时间,如果GPS由于电波影响不能工作,那么原子钟就会接替任务直到GPS恢复工作。Spanner中提供了三个和其相关的API,如下图所示:

其中,TT.now()返回的值称为TTinterval,它不是一个确切的时刻,而是一段时间,包括早时间戳Earlist和晚时间戳Latest。因为全球范围内的时间总是不可能完全同步,各机器的通信也有延迟,所以只要在一个时间段内,他们就认为是同步的。另外两个函数的伪代码定义如下

TT.after(t) = TT.now().earliest > t.latest
TT.before(t) = TT.now().latest < t.earliest

相关文章