前面我的一篇文章http://hi.baidu.com/knuthocean/blog/item/12bb9f3dea0e400abba1673c.html引用了对Google App Engine工程师关于Bigtable/Megastore replication的文章。当时留下了很多疑问,比如:为什么Google Bigtable 是按照column family级别而不是按行执行replication的?今天重新思考了Bigtable replication问题,有如下体会:
1. Bigtable/GFS的设计属于分层设计,和文件系统/数据库分层设计原理一致,通过系统隔离解决工程上的问题。这种分层设计带来了两个问题,一个是性能问题,另外一个就是Replication问题。由于存储节点和服务节点可能不在一台机器,理论上总是存在性能问题,这就要求我们在加载/迁移Bigtable子表(Bigtable tablet)的时候考虑本地化因素;另外,GFS有自己的replication机制保证存储的可靠性,Bigtable通过分离服务节点和存储节点获得了很大的灵活性,且Bigtable的宕机恢复时间可以做到很短。对于很多对实时性要求不是特别高的应用Bigtable由于服务节点同时只有一个,既节约资源又避免了单点问题。然后,Bigtable tablet服务过于灵活导致replication做起来极其困难。比如,tablet的分裂和合并机制导致多个tablet(一个只写,其它只读)服务同一段范围的数据变得几乎不可能。
2. Google replication分为两种机制,基于客户端和基于Tablet Server。分述如下:
2-1). 基于客户端的replication。这种机制比较简单,实现如下:客户端读/写操作均为异步操作,每个写操作都尝试写两个Bigtable集群,任何一个写成功就返回用户,客户端维护一个retry list,不断重试失败的写操作。读操作发到两个集群,任何一个集群读取成功均可。然后,这样做有两个问题:
a. 客户端不可靠,可能因为各种问题,包括程序问题退出,retry list丢失导致两个集群的数据不一致;
b. 多个客户端并发操作时无法保证顺序性。集群A收到的写操作可能是"DEL item; PUT item";集群B的可能是"PUT item; DEL item"。
2-2). 基于Tablet Server的replication。这种机制实现较为复杂,目的是为了保证读服务,写操作的延时仍然可能比较长。两个集群,一个为主集群,提供读/写服务;一个为slave集群,提供只读服务,两个集群维持最终一致性。对于一般的读操作,尽量读取主集群,如果主集群不可以访问则读取slave集群;对于写操作,首先将写操作提交到主集群的Tablet Server,主集群的Tablet Server维护slave集群的元数据信息,并维护一个后台线程不断地将积攒的用户表格写操作提交到slave集群进行日志回放(group commit)。对于一般的tablet迁移,操作逻辑和Bigtable论文中的完全一致;主集群如果发生了机器宕机,则除了回放commit log外,还需要完成宕机的Tablet Server遗留的后台备份任务。之所以要按照column family级别而不是按行复制,是为了提高压缩率从而提高备份效率。如果主集群写操作日志的压缩率大于备份数据的压缩率,则可能出现备份不及时,待备份数据越来越多的问题。
假设集群A为主集群,集群B是集群A的备份,集群切换时先停止集群A的写服务,将集群A余下的备份任务备份到集群B后切换到集群B;如果集群A不可访问的时间不可预知,可以选择直接切换到集群B,这样会带来一致性问题。且由于Bigtable是按列复制的,最后写入的一些行的事务性无法保证。不过由于写操作数据还是保存在集群A的,所以用户可以知道丢了哪些数据,很多应用可以通过重新执行A集群遗留的写操作进行灾难恢复。Google的App Engine也提供了这种查询及重做丢失的写操作的工具。
想法不成熟,有问题联系:knuthocean@163.com
posted on 2009-12-18 22:05
Programmers 阅读(376)
评论(0) 编辑 收藏