MIT6.824 Spanner论文精读

Introduction

Spanner是Google的全球分布式数据库,它主要具有以下特点:

  • 可细粒度动态控制数据副本的配置,包括数据的分片方式,数据的迁移等。
  • 读和写操作的外部一致性
  • 一个时间戳下跨数据库的全球一致性读

Spanner能提供全球范围内的有序事务提交时间戳,而这个功能基于TrueTime API及其实现。

Implementation

在这里插入图片描述
一个Spanner的部署被称为一个universe,其包含如下部分:

  • universe master:管理和控制zone的状态和信息
  • placement driver:周期性和spanservers交互,发现需要转移、更新的数据。
  • 多个zone,每个zone代表了一个数据存储单元,其包含:
    • zonemaster:管理当前zone的spanservers(如分配数据)
    • spanserver:存储和提供数据
    • local proxy:给客户端定位提供数据的spanservers

Spanserver Software Stack

在这里插入图片描述
Spanserver的软件协议栈各部件功能如下:

  • tablet:spanserver中的数据结构,是一个具有版本的key-value条目,其形式为(key:string, timestamp:int64) -> string。tablet的状态存储在Colossus分布式文件系统当中,并且存储于类似B+树的文件和WAL中。
  • Paxos group:每个tablet运行在Paxos协议下,代表一份数据副本,Paxos协议的状态(如log等)也存储在tablet中。多个replica组成一个Paxos group,并有一个leader进行管理。
    • leader的选举租约为10s。
    • 写操作必须经过leader,并由Paxos保证半数复制,读操作可以直接访问最新的副本。
    • leader维护lock table,用于存储如2PL的锁状态。
    • leader拥有transaction manager,用于维护事务状态和事务并发控制。
  • participant leader:基于transaction manager创建,用于协调跨Paxos group的事务,以完成2PC事务提交。

Directories and Placement

在这里插入图片描述
Spanner中定义了directory的概念,它是数据放置的最小单位

  • 每个Paxos group可拆成多个目录,每个目录由一系列共同前缀的键所对应的数据项组成(实际为将tablet进行划分)。
  • directory中的所有数据都具有相同的replication configuration。
  • 目录的replica在其所在的Paxos group中。
  • 目录可以转移到不同的Paxos group,以调整负载,转移操作可以在执行client operations时进行(50MB转移只需要几秒)。
  • Movedir background task用于在Paxos groups间转移目录。
  • 若一个目录过大,会被分片到不同的Paxos group,Movedir实际上移动的时分片而不是目录。

Data Model

Spanner的对外数据特征为:

  • schematized semi-relational tables:每个表的行都必须有名称,可定义外键和级联的关系。
  • a query language:一个查询语言(类SQL)。
  • general purpose transactions:通用的事务(基于2PC+2PL)

Spanner中的Data Model实例如下所示,其结构如下:

  • database由client分割成多个不同层级的hierarchy table(INTERLEAVE IN关键字)
  • 每个hierarchy table的top row为directory table,并包含key K。

在这里插入图片描述

TrueTime

在这里插入图片描述
TrueTime的实现机制:

  • 底层为GPS + atomic clock。 GPS可能会因为天线等故障出现误差,atomic clock可能会因为长时间使用导致的频率误差产生时钟漂移。
  • 每个datacenter中配置有多个time master
    • 一些使用GPS获取时间,一些使用atomic clock获取时间
    • time master之间互相校对时间,若差距过大则会将自己驱逐出去
  • 每个server中都有一个timeslave daemon
    • 每个server收集time master的时间,使用类Marzullo算法同步本地server时钟。
    • 考虑到延迟等影响,引入了时间误差 ϵ \epsilon ϵ

Concurrency Control

基于TrueTime API实现了一个重要特性:在timestamp t时产生的读操作,会看到所有在t之前提交的事务。

Timestamp Management

Spanner支持以下几种读写事务:

  • Read-Write:使用pessimistic lock进行并发控制。
  • Read-Only
    • 必须声明不含写操作
    • 时间戳由leader决定,无锁,可在任何足够新的副本执行
  • Snapshot Read
    • 可读取历史数据,无锁
    • 时间由客户端决定,可以是时间戳,也可以是时间下限(由Spanner具体决定,满足条件即可)

在这里插入图片描述

Paxos Leader Leases

Paxos的leader租约具有以下特点:

  • leader lease的默认时间为10s
    • 成功执行一次写操作会延展leader lease
    • leader lease快到期前会申请延长
  • leader可主动放弃该身份
  • 不同leader的lease interval不同
  • 假设前一个leader使用过的最大时间戳为 s m a x s_{max} smax,则下一个leader的租约起始时间必须满足 T T . a f t e r ( s m a x ) TT.after(s_{max}) TT.after(smax)

Assigning Timestamps to RW Transactions

读写事务需要依赖2PL。Spanner是用在提交事务时的Paxos写操作的时间戳作为事务时间戳。

Spanner提供了读写事务时间戳的两个保证,Paxos group内单调递增和外部一致性。

Paxos group内读写事务timestamp单调递增由以下机制保证:

  • leader不同租约区间互不相交
  • leader分配的时间戳只能在自己租约区间内

WR事务的外部一致性保证为:如果事务 T 2 T_2 T2发生在事务 T 1 T_1 T1提交之后,则事务 T 2 T_2 T2的提交时间戳一定大于事务 T 1 T_1 T1的提交时间戳,即 t a b s ( e 1 c o m m i t ) < t a b s ( e 2 c o m m i t ) ⇒ s 1 < s 2 t_{abs}(e_1^{commit}) < t_{abs}(e_2^{commit}) \Rightarrow s_1 < s_2 tabs(e1commit)<tabs(e2commit)s1<s2

其中一些记号标识说明如下:

  • e i s t a r t e_i^{start} eistart:事务 T i T_i Ti开始事件。
  • e i c o m m i t e_i^{commit} eicommit:事务 T i T_i Ti提交事件。
  • s i s_i si:事务 T i T_i Ti的commit timestamp。
  • s i s e r v e r s_i^{server} siserver:coordinate leader收到commit request事件。

WR事务的外部一致性通过以下机制保证:

  • Start: s i s e r v e r ≤ s i s_i^{server} \leq s_i siserversi
  • Commit Wait: s i < t a b s ( e i c o m m i t ) s_i < t_{abs}(e_i^{commit}) si<tabs(eicommit)

证明如下:
在这里插入图片描述

Serving Reads at a Timestamp

每个replica都会维护一个 t s a f e t_{safe} tsafe,表示副本最近更新的最大时间戳,当读事务的时间戳为 t t t时,可以在满足 t ≤ t s a f e t \leq t_{safe} ttsafe的副本上读取且 t s a f e = m i n ( t s a f e P a x o s , t s a f e T M ) t_{safe}=min(t_{safe}^{Paxos},t_{safe}^{TM}) tsafe=min(tsafePaxos,tsafeTM)

t s a f e P a x o s t_{safe}^{Paxos} tsafePaxos为Paxos write的最近事件, t s a f e T M t_{safe}^{TM} tsafeTM为副本对应的transaction manager维护的安全时间,其具体取值如下:

  • 若当前Paxos group没有已经prepare但没commit的事务(即没夹在2PC中间阶段的事务),则 t s a f e T M = ∞ t_{safe}^{TM}= \infty tsafeTM=
  • 若Paxos group中存在多个这种事务,则 t s a f e T M = m i n i ( s i , g p r e p a r e ) − 1 t_{safe}^{TM}=min_i(s_{i,g}^{prepare})-1 tsafeTM=mini(si,gprepare)1,其中 s i , g p r e p a r e s_{i,g}^{prepare} si,gprepare为Paxos group leader为事务 T i T_i Ti回复prepare消息的timestamp。

Assigning Timestamps to RO Transactions

只读事务分为两个阶段进行:

  • 由Paxos group leader分配一个timestamp s r e a d s_{read} sread
    • s r e a d s_{read} sread的通常选择为 T T . n o w ( ) . l a t e s t TT.now().latest TT.now().latest
    • t s a f e t_{safe} tsafe不够大,可能需要阻塞,因此 s r e a d s_{read} sread可能为一个满足外部一致性的最小时间戳
    • s r e a d s_{read} sread也会增大 s m a x s_{max} smax,保证不同leader lease不相交。
  • 进行 s r e a d s_{read} sread的快照读,可在任何足够新的副本上读

Details

Read-Write Transactions

Spanner在执行读写事务时:

  • 写入数据先缓存于client,直到提交,因此读操作不会受写操作影响
  • 读取时使用wound-wait机制避免死锁
  • 获取读锁然后读取up-to-date数据
  • 事务活跃时client向participant leader发送心跳包
  • 当client完成所有读操作和缓存所有写操作后,开始变种的2PC

Spanner中变种的2PC协议具体如下:

  • client完成了所有读操作并缓存需要写入内容后,开始进行2PC
  • client会选择一个coordinator group并向所有的participant leader发送写请求(包括coordinator leader),并捎带prepare请求
  • participant leader:
    • 先获取写锁
    • 给回复prepare这个事件赋上时间戳 s i p r e p a r e s_i^{prepare} siprepare(此时更新了 t s a f e T M t_{safe}^{TM} tsafeTM
    • 使用Paxos持久化该事件,写入log(此时更新 t s a f e P a x o s ) t_{safe}^{Paxos}) tsafePaxos)
    • 回复coordinator leader并携带 s i p r e p a r e s_i^{prepare} siprepare
  • coordinator leader:
    • 先获取写锁,等待其它participant leader的prepare所有回复
    • 给提交事件附上时间戳 s s s,其需要满足:
      • s > m a x i ( s i p r e p a r e ) s > max_i(s_i^{prepare}) s>maxi(siprepare),保证 t s a f e T M t_{safe}^{TM} tsafeTM的合理性
      • s > T T . n o w ( ) . l a t e s t s > TT.now().latest s>TT.now().latest,保证start阶段约束
    • 使用Paxos持久化该事件,写入log(此时更新 t s a f e P a x o s ) t_{safe}^{Paxos}) tsafePaxos)
    • 等待 T T . a f t e r ( s ) TT.after(s) TT.after(s)成立,保证commit wait约束
    • 告知客户端 s s s,告知事务提交完成
    • 告知其它participant leader(捎带 s s s),让它们也提交事务(也使用Paxos写入提交事件),并释放锁

Read-Only Transactions

只读事务需要分为时间戳 s r e a d s_{read} sread,具体原则如下:

  • 若read scope仅限于单Paxos group:
    • 给该组的leader发起只读事务
    • leader为只读事务分配时间戳 s r e a d = L a s t T S ( ) s_{read}=LastTS() sread=LastTS() L a s t T S ( ) LastTS() LastTS()为Paxos group最近一次的commit write timestamp
  • 若scope跨多Paxos group,有多种选择:
    • 协商各个组不同的 L a s t T S ( ) LastTS() LastTS()
    • 选择 T T . n o w ( ) . l a t e s t TT.now().latest TT.now().latest,但是可能需要等待 t s a f e t_{safe} tsafe变大

Refinements

t s a f e T M t_{safe}^{TM} tsafeTM存在的问题:当存在一个prepared但没commit的事务, t s a f e t_{safe} tsafe无法变大,会阻塞 s r e a d > t s a f e s_{read}>t_{safe} sread>tsafe的读操作,即使可能并没有冲突。

解决方案:建立一个<key, prepare timestamp>的映射关系,存储在lock table中,在进行读操作时,只检查产生lock confilict的 t s a f e t_{safe} tsafe

L a s t T S ( ) LastTS() LastTS()存在的问题:当一个事务刚被提交,一个读请求时间戳必须跟在刚提交事务的后面,造成阻塞,即使读操作不会造成冲突

解决方案,建立一个<key, commit timestamp>的映射关系,存储在lock table中,在进行读操作时,只选择产生lock confilict的最大 L a s t T S ( ) LastTS() LastTS()

t s a f e P a x o s t_{safe}^{Paxos} tsafePaxos:没有发生Paxos write时, t s a f e P a x o s t_{safe}^{Paxos} tsafePaxos无法变大,会造成读阻塞。
解决方案:每个Paxos group leader维护一个下一个Paxos write可能会发生的最小时间戳 M i n N e x t T S ( n ) MinNextTS(n) MinNextTS(n),并令 t s a f e P a x o s = M i n N e x t T S ( n ) − 1 t_{safe}^{Paxos}=MinNextTS(n) - 1 tsafePaxos=MinNextTS(n)1注意, M i n N e x t T S ( n ) MinNextTS(n) MinNextTS(n)必须在租约内,且如果 M i n N e x t T S ( n ) MinNextTS(n) MinNextTS(n)超过了租约期需要先延长租约。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值