Google的分布式关系型数据库F1和Spanner

F1是Google开发的分布式关系型数据库,主要服务于Google的广告系统。Google的广告系统以前使用MySQL,广告系统的用户经常需要使用复杂的query和join操作,这就需要设计shard规则时格外注意,尽量将相关数据shard到同一台MySQL上。扩容时对数据reshard时也需要尽量保证这一点,广告系统扩容比较艰难。在可用性方面老的广告系统做的也不够,尤其是整个数据中心挂掉的情况,部分服务将不可用或者丢数据。对于广告系统来说,短暂的宕机服务不可用将带来重大的损失。为了解决扩容/高可用的问题,Google研发了F1,一个基于Spanner(看这里)的跨数据中心的分布式关系型数据库,支持ACID,支持全局索引。2012年初已上线。

F1的几个特性

高可用

可以说,几乎都是Spanner搞定的,Spanner通过原子钟和GPS接收器实现的TrueTime API搞定了跨数据中心时钟误差问题,进而搞定了分布式事务的时序问题,从而搞定了对外部的一致性。多个副本的一致通过Paxos搞定。

全局索引

基于Spanner提供的分布式读写事务(严格的两阶段锁+两阶段提交),F1实现了全局索引。索引表和数据表实际上是两张表,这两张表一般来说存在不同的Spanner机器上,两张表的一致性通过Spanner的分布式读写事务解决。在这里,同一个事务中涉及的全局索引不宜过多,因为每多一个全局索引,相当于多一个两阶段提交中的participant,对于分布式事务来说,participant越多,性能越差,并且事务成功的概率越小。

级联Schema

思想和MegaStore类似,表和表之间有层次关系。将相关表中的相关数据存储在一台机器上。比如对于广告系统来说,就是将一个广告客户以及他的compaign等存储在一起,广告客户作为一张表,compaign作为另外一张表,广告客户表中每行代表一个广告客户,广告客户表叫做root表,compaign表叫做子表,广告客户表中的每行叫做root记录,compaign表中行叫做子记录,那么同一个广告客户下所有的compaign和这个广告客户都存储在同一台Spanner机器上。这样做的好处就是一个操作就可以取到所有的相关数据,join很快,不用跨机。

三种事务

  1. 快照读。 直接利用Spanner提供的快照读事务
  2. 悲观事务。 直接利用Spanner提供的读写事务,加两阶段锁
  3. 乐观事务。 基于Spanner的悲观事务实现的。这样的事务分为两个阶段,第一个阶段是读阶段,持续时间不限,不加任何锁,第二个阶段是写阶段,即commit事务阶段。基本思想是在读阶段将访问的所有行的最后一次修改时间保存在F1客户端,写阶段将所有的时间发到F1,F1开启一个Spanner的读写事务,这个读写事务会重新读取这些行的最后一次修改时间进行check,如果已经变了,说明检测到了写写冲突,事务abort。

F1默认使用乐观事务,主要考虑了如下几个方面:

  1. 由于读阶段不加锁,能容忍一些客户端的误用导致的错误
  2. 同样,读阶段不加锁,适合F1中一些需要和终端交互的场景。
  3. 对于一些出错场景,可以直接在F1 Server进行重试,不需要F1 Client参与。
  4. 由于所有的状态都在F1 Client端维护的,故某个F1 Server挂掉后,这个请求可以发给其他的F1 Server继续处理。

当然,这会带来两个问题:

  1. 对于不存在的行,没有最后一次修改时间,那么在其他读事务执行期间,同一条语句执行多次返回的行数可能不一样,这种情况在repeatable read这种隔离级别下是不允许的,这个问题典型的解决方案是gap锁,即范围锁,在F1中,这个锁可以是root表中root记录的一列,这个列代表一把gap锁,只有拿到这把锁,才能往child表中某个范围插入行。
  2. 对同一行高并发修改性能低。显然,乐观协议不适合这种场景。

部署

Google将广告系统使用的F1和Spanner集群部署在美国的5个数据中心,东海岸两个,西海岸两个,中间一个。相当于每份数据5个副本,其中东海岸一个数据中心被作为leader数据中心。在spanner的paxos实现中,5个副本中有一个leader副本,所有的对这个副本的读写事务都经过它,这个leader副本一般就存在leader数据中心中。由于paxos协议的运行只需要majority响应即可,那么一次paxos操作的延时基本取决于东海岸的leader数据中心和东海岸另外一个数据中心,和中间那个数据中心之间的延时。从这里也可以看出,对于写比较多的F1 Client来说,F1 Client和F1 Server都部署在leader数据中心性能最好。在这个配置下,F1用户的commit延时大概在50ms到150ms之间。读延时大约5~10ms。

参考资料

F1: A Distributed SQL Database That Scales

Spanner: Google’s Globally-Distributed Database

分布式事务实现-Spanner

上一篇:[QualityCenter]设置工作流脚本-新建缺陷时字段自动生成


下一篇:【慕课网实战】一、以慕课网日志分析为例 进入大数据 Spark SQL 的世界