在2010年4月,Google的网页索引更新实现了实时更新,在今年的OSDI大会上,Google首次公布了有关这一技术的论文。 html
在此以前,Google的索引更新,采用的的批处理的方式(map/reduce),也就是当增量数据达到必定规模以后,把增量数据和全量索引库Join,获得最新的索引数据。采用新的索引更新系统以后,数据的生命周期缩短了50%,所谓的数据生命周期是指,数据从网页上爬下来,到展示在搜索结果中这段时间间隔,可是正如Google所强调的,这一系统仅仅是为增量更新所创建的,并无取代map/reduce的批量做业处理模式。 sql
Google的新一代增量索引更新 – Percolator,是创建在Bigtable之上,提供的API也尽可能接近Bigtable的方式,因此整个架构大体是以下的样子: 数据库
事务(Transaction)和锁(Lock)有区别吗? 架构
在关系数据库领域,两者仍是有很大区别的,可是对Percolator而言,Transaction = Lock,因此咱们这里讨论的分布式锁,也能够说是分布式事务,因此下面提到的锁或者事务,指的都是同一件事。 oracle
Percolator利用Bigtable原有的行锁,再加上本身的一些巧妙的作法,实现了分布式锁服务,这就意味着,Google能够实时的更新PB级别的索引库。最近咱们发现Google的搜索结果时效性很好,刚写好的文章,几分钟以后,Google就能够检索到,缘由就在Google的Crawler在抓到新的网页以后,不用再等待必定的时间批量更新索引,而是实时的更新,数据生命周期大大缩短。 nosql
Percolator支持跨行,跨表的事务,充分利用了Bigtable自己已经有的行事务、备份机制。 分布式
在分析Percolator的细节以前,先看一个简单的例子,对Percolator有一个大概的认识,有利于后面的理解。 google
下面的这个例子是把UserA的人气分减掉10,加到UserB的人气分上,key表示每一行的key,data,lock,write是列名字,data存储数据,lock存储锁状态,write表示事务提交后的数据位置引用. spa
初始状态:UserA有100我的气分,UserB有50我的气分 .net
最终状态:UserA有90我的气分,UserB有60我的气分
Step0(初始状态)
Key | Data | Lock | Write |
UserA | 100:t1 | ||
UserB | 50:t2 |
Step1(从UserA中拿出10我的气分)
Key | Data | Lock | Write |
UserA | 90:t2100:t1 | Primary Lock:t2 | t2 |
UserB | 50:t2 |
Step2(把UserB的人气分加10)
Key | Data | Lock | Write |
UserA | 90:t2100:t1 | primary_lock:t2 | t2 |
UserB | 60:t350:t2 | Primary_lock:UserA@data | t3 |
Step3(事务提交)
A:先提交primary(移除锁,写入新的timestamp,在write列写入新数据的位置引用)
Key | Data | Lock | Write |
UserA | t390:t2 100:t1 |
t3:data:t2t2 | |
UserB | 60:t350:t2 | Primary_lock@UserA.data | t3 |
B:再提交非primary(步骤同上)
Key | Data | Lock | Write |
UserA | t390:t2 100:t1 |
t3:data:t2t2 | |
UserB | t460:t3 50:t2 |
t4:data:t3t3 |
事务结束了,UserA有90我的气分,timestamp是t3,Userb有60我的气分,timestamp是t4。(至于锁的写法和write列为何那样写,后面再详细解释)
Percolator锁分为两种,primary和non-primary,在事务提交的过程当中,先提交primary锁,不管是跨行仍是跨表,primary锁都是没有区别的。
事务的提交
事务的提交的过程分两步,以UserA为例:
首先,在write列写入新数据的位置引用,注意不是数据,是引用(理解成指针会更形象),上面step3A 中t3:data:t2表示在t3时刻提交的数据,最新的数据在data列的t2 timestamp
而后,移除lock列的内容。
由于Bigtable支持行锁定,因此上述两步都是在一个Bigtable事务内完成的。
读操做
当一个client在发起读操做以后,首先会向oracle server申请time stamp,接下来Percolator会检查lock列,若是lock列不空,那么读操做试图移除(修复)这个lock或者等待,在后续锁冲突处理详细介绍如何修复。
补充:oracle发放time stamp是严格递增的,并且不是一次发放一个,而是采起批量的方式。
写操做
当一个client发起写操做以后,首先会向oracle server申请time stamp,Percolator会检查write列,若是write列的timestamp大于当前client的timestamp,那么写失败(不能覆盖新的数据 write-write conflict);若是lock列有锁存在,说明当前行正在被另外的client锁定,client要么写失败,要么试图修复(lock conflict)!
Notify机制
Percolator定义了一系列的Observer(相似于数据库的trigger),位于Bigtable的tablet server上,Observer会监视某一列或者某几列,当数据发生变化就会触发Observer,Observer执行完以后,又会建立或者通知后续的Observer,从而造成一个通知的传递。
当一个client在事务提交阶段,crash掉了,那么锁还保留,这样后续的client访问就会被阻止,这种状况叫作锁冲突,Percolator提供了一种简单的机制来解决这个问题。
每一个client按期向Chubby Server写入token,代表本身还活着,当某个client发现锁冲突,那么会检查持有锁的client是否还活着,若是client是working状态,那么client等待锁释放。不然client要清除掉当前锁。
Roll forward & roll back:
Client先检查primary lock是否存在,由于事务提交先从primary开始,若是primary不存在,那么说明前面的client已经提交了数据,因此client执行roll forward操做:把non-primary对应的数据提交,而且清除non-primary lock;若是primary存在,说明前面的client尚未提交数据就crash了,此时client执行roll back操做:把primary和non-primary的数据清除掉,而且清除lock。
Google的分布式锁服务很好了支持了增量索引的实时更新,缩短了数据的生命周期。本文对notify机制介绍的比较简单,感兴趣的请参考论文原文。