Percolator 对外提供两个主要的功能, 一个是分布式事务, 另外一个是 observers, 这里简单介绍一下 Percolator 中分布式事务的实现方法. 以下内容都出自对 google 论文 Large-scale Incremental Processing Using Distributed Transactions and Notifications 的学习. 这里介绍的内容只是论文中的一个小部分, 有兴趣的同学请阅读原文
在一个分布式系统中, 实现对事务的支持是一件蛮有挑战的事情. 分布式系统的巨大并发量, 高错误容忍性, 物理上的分散, 都为事务的实现埋下重重障碍. Percolator 实现在 bigtable 的基石上, 充分利用了 bigtable 的特性, 巧妙的实现了对事务的支持.
在实现分布式事务的时候, Percolator 使用了两个基本的服务, 一个提供精确时间用来产生timestamp, 另外一个提供一种简单的分布式锁, 用来检查一个进程是否还活着. 这两个服务这里不做介绍了. 我们集中精力来看 Percolator 是如何充分利用 bigtable 的单行原子性以及多版本这两个特性来实现自己的分布式事务的. 简单来说, 就是把多列, 多表的事务的提交和回滚变成一个个单行事务. 我们看论文中的具体例子.
这个例子是从Bob的账户中转7美金到joe的账户. 这是一个涉及到两个 row 的事务.
下面的表示中, 冒号前面的部分是一个版本号, 可以通过时间服务来解决其取值问题, lock 这个列用来放锁的情况. write 这个列放最终的数据写入情况.
下面的叙述中请大家注意, 单行的操作是有原子性的( bigtable 的保证).
key bal:data bal:lock bal:write
Bob 6: 6: 6: data @ 5
5: $10 5: 5:
Joe 6: 6: 6: data @ 5
5: $2 5: 5:
1 这个是初始状态, bob 账户有10美金, joe 有2个美金. write 列中的 6:data @ 5 表示 当前的数据是 version 为 5 的(感谢 bigtable 的多版本支持)
Bob 7:$3 7: I am primary 7:
6: 6: 6: data @ 5
5: $10 5: 5:
Joe 6: 6: 6: data @ 5
5: $2 5: 5:
2 事务的第一个阶段, bob的账户变成3美金了. 注意 lock 列被加锁, 并且标明自己是 primary. 每个事务中, 只有一个primary, 也正是这个primary的存在, 使得我们能够用行原子性来实现分布式事务.
Bob 7: $3 7: I am primary 7:
6: 6: 6: data @ 5
5: $10 5: 5:
Joe 7: $9 7: primary@Bob.bal 7:
6: 6: 6: data @ 5
5: $2 5: 5:
3 现在给joe加上7美金, 所以joe是9美元了, 注意 joe 这一行的 lock 是指向 primary 的一个指针.
Bob 8: 8: 8: data@7
7: $3 7: 7:
6: 6: 6: data @ 5
5: $10 5: 5:
Joe 7: $9 7: primary @ Bob.bal 7:
6: 6: 6:data @ 5
5: $2 5: 5:
4 事务提交的第一阶段, 提交 primary, 移除lock 列的内容 在 write 列写入最新数据的 version
Bob 8: 8: 8: data @ 7
7: $3 7: 7:
6: 6: 6: data @ 5
5: $10 5: 5:
Joe 8: 8: 8: data@7
7: $9 7: 7:
6: 6: 6: data @ 5
5:$2 5: 5:
5 事务提交的第二阶段, 提交除 primary 之外其它部分. 提交的方式也是移除 lock, 同时在 write 列写入新数据的 version
从这个讲述我们看到是怎么把两行的事务用单行原子性搞定的了. 事务是否提交完全取决于 primary. 如果 primary 提交了, 则lock列被清空, write列标识了正确的版本号. 反之就是未提交. 这种方式可以用来处理多列, 多表, 的事务, 而且可以并发执行的事务数量几乎是无限的. 因为没有任何全局锁.
这个分布式协议下, 如果出现了一个线程执行了事务的一部分crash掉, 或者出现冲突时如何解决, 大家可以自己推演一下或者去读原论文, 这里不再详述.