bigtable论文笔记

Bigtable不支持完整的关系数据模型;与之相反,Bigtable为客户提供了简单的数据模型,利用这个模型,客户可以动态控制数据的layout和format。数据通过行和列的名字访问,名字可以是任意的字符串。Bigtable将存储的数据都视为字符串,但是Bigtable本身不去解析这些字符串,客户程序通常会在把各种结构化或者半结构化的数据串行化为字符串。通过选择数据的schema,客户可以控制数据的位置相关性。通过BigTable的schema参数来控制数据是存放在内存中还是硬盘上。

 

数据模型:

Big table是稀疏、分布式、多维sorted map。The map is indexed by a row key, column key and a timestamp。Each value in the map is anuninterpreted array of bytes.

 

 

行关键字可以是任意的字符串(目前支持最大64KB的字符串,但是对大多数用户,10-100个字节就足够了)。对row key之下的数据读写都是原子的(不管读或者写这一行里多少个列)

 

Bigtable通过row key的字典顺序来组织数据。Row range动态分区。每个row range称为”Tablet”,Tablet是 数据分布和负载均衡调整的单位。Short  row range读效率很高。用户可以通过选择合适的row key,在数据访问时有效利用数据的位置相关性,从而更好的利用这个特性。举例来说,在Webtable里,通过反转URL中主机名的方式, 可以把同一个域名下的网页聚集起来组织成连续的行。具体来说,我们可以把maps.google.com/index.html的数据存放在key com.google.maps/index.html下。把相同的域中的网页存储在连续的区域可以让基于主机和域名的分析更加有效。

Columnfamily

列关键字组成的集合叫做“列族“,列族是访问控制的基本单位。存放在同一列族下的所有数据通常都属于同一个类型(我们可以把同一个列族下的数据压缩在一起)。列族在使用之前必须先创建,然后才能在列族中任何的列关键字下存放数据;列族创建后,其中的任何一个列关键字下都可以存放数据。根据我们的设计意图,一张表中的列族不能太多(最多几百个),并且列族在运行期间很少改变。与之相对应的,一张表可以有无限多个列。

列关键字的命名语法如下:列族:限定词。列族的名字必须是可打印的字符串,而限定词的名字可以是任意的字符串。比如,Webtable有个列族language,language 列族用来存放撰写网页的语言。我们在language列族中只使用一个列关键字,用来存放每个网页的语言标识ID。Webtable中另一个有用的列族是 anchor;这个列族的每一个列关键字代表一个锚链接,如图一所示。Anchor列族的限定词是引用该网页的站点名;Anchor列族每列的数据项存放的是链接文本。

访问控制、磁盘和内存的使用统计都是在列族层面进行的。在我们的Webtable的例子中,上述的控制权限能帮助我们管理不同类型的应用:我们允许 一些应用可以添加新的基本数据、一些应用可以读取基本数据并创建继承的列族、一些应用则只允许浏览数据(甚至可能因为隐私的原因不能浏览所有数据)。

 

时间戳:

在Bigtable中,表的每一个数据项都可以包含同一份数据的不同版本;不同版本的数据通过时间戳来索引。Bigtable时间戳的类型是64位 整型。Bigtable可以给时间戳赋值,用来表示精确到毫秒的“实时”时间;用户程序也可以给时间戳赋值。如果应用程序需要避免数据版本冲突,那么它必须自己生成具有唯一性的时间戳。数据项中,不同版本的数据按照时间戳降序排序,即最新的数据排在最前面。

为了减轻多个版本数据的管理负担,我们对每一个列族配有两个设置参数,Bigtable通过这两个参数可以对废弃版本的数据自动进行垃圾收集。用户可以指定只保存最后n个版本的数据,或者只保存“足够新”的版本的数据(比如,只保存最近7天的内容写入的数据)。

在Webtable的举例里,contents:列存储的时间戳信息是网络爬虫抓取一个页面的时间。上面提及的垃圾收集机制可以让我们只保留最近三个版本的网页数据。

 

 

API

 

 

Bigtable提供了建立和删除表以及列族的API函数。Bigtable还提供了修改集群、表和列族的元数据的API,比如修改访问权限。

 

客户程序可以对Bigtable进行如下的操作:写入或者删除Bigtable中的值、从每个行中查找值、或者遍历表中的一个数据子集。图2中的C++代码使用RowMutation抽象对象进行了一系列的更新操作。(为了保持示例代码的简洁,我们忽略了一些细节相关代码)。调用Apply函数对 Webtable进行了一个原子修改操作:它为www.cnn.com增加了一个锚点,同时删除了另外一个锚点。

 

图3中的C++代码使用Scanner抽象对象遍历一个行内的所有锚点。客户程序可以遍历多个列族,有几种方法可以对扫描输出的行、列和时间戳进行限制。例如,我们可以限制上面的扫描,让它只输出那些匹配正则表达式*.cnn.com的锚点,或者那些时间戳在当前时间前10天的锚点。

Bigtable还支持一些其它的特性,利用这些特性,用户可以对数据进行更复杂的处理。首先,Bigtable支持单行上的事务处理,利用这个功能,用户可以对存储在一个行关键字下的数据进行原子性的读-更新-写操作。虽然Bigtable提供了一个允许用户跨行批量写入数据的接口,但是,Bigtable目前还不支持通用的跨行事务处理。其次,Bigtable允许把数据项用做整数计数器。最后,Bigtable允许用户在服务器的地址空间内执行脚本程序。脚本程序使用Google开发的Sawzall【28】数据处理语言。虽然目前我们基于的Sawzall语言的API函数还不允许客户的脚本程序写入数据到Bigtable,但是它允许多种形式的数据转换、基于任意表达式的数据过滤、以及使用多种操作符的进行数据汇总。

Bigtable可以和MapReduce【12】一起使用,MapReduce是Google开发的大规模并行计算框架。我们已经开发了一些Wrapper类,通过使用这些Wrapper类,Bigtable可以作为MapReduce框架的输入和输出。

 

Buildingblocks

Bigtable是建立在其它的几个Google基础构件上的。BigTable使用Google的分布式文件系统(GFS)【17】存储日志文件和数据文件。BigTable集群通常运行在一个共享的机器池中,池中的机器还会运行其它的各种各样的分布式应用程序,BigTable的进程经常要和其它应用的进程共享机器。BigTable依赖集群管理系统来调度任务、管理共享的机器上的资源、处理机器的故障、以及监视机器的状态。

BigTable内部存储数据的文件是Google SSTable格式的SSTable是一个持久化的、排序的、不可更改的Map结构,而Map是一个key-value 映射的数据结构,key和value的值都是任意的Byte串。可以对SSTable进行如下的操作:查询与一个key值相关的value,或者遍历某个 key值范围内的所有的key-value对。从内部看,SSTable是一系列的数据块(通常每个块的大小是64KB,这个大小是可以配置的)。 SSTable使用块索引(通常存储在SSTable的最后)来定位数据块在打开SSTable的时候,索引被加载到内存。每次查找都可以通过一次磁盘搜索完成:首先使用二分查找法在内存中的索引里找到数据块的位置,然后再从硬盘读取相应的数据块。也可以选择把整个SSTable都放在内存中,这样就不 必访问硬盘了。

BigTable还依赖一个高可用的、序列化的分布式锁服务组件,叫做Chubby【8】。一个Chubby服务包括了5个活动的副本,其中的一个副本被选为Master,并且处理请求。只有在大多数副本都是正常运行的,并且彼此之间能够互相通信的情况下,Chubby服务才是可用的。当有副本失效的时候,Chubby使用Paxos算法【9,23】来保证副本的一致性。Chubby提供了一个名字空间,里面包括了目录和小文件。每个目录或者文件可以当成一个锁,读写文件的操作都是原子的。Chubby客户程序库提供对Chubby文件的一致性缓存。每个Chubby客户程序都维护一个与Chubby服务的会话。如果客户程序不能在租约到期的时间内重新签订会话的租约,这个会话就过期失效了(A client’s session expires if it is unable to renew its sessionlease within the lease ‘s expiration time.)。当一个会话失效时,它拥有的锁和打开的文件句柄都失效了。Chubby客户程序可以在文件和目录上注册回调函数,当文件或目录改变、或者会话过期时,回调函数会通知客户程序。

 

Bigtable使用Chubby完成以下的几个任务:确保在任何给定的时间内最多只有一个活动的Master副本;存储BigTable数据的自引导指令的位置(参考5.1节);查找Tablet服务器,以及在Tablet服务器失效时进行善后(5.2节);存储BigTable的模式信息(每张表的列族信息);以及存储访问控制列表。如果Chubby长时间无法访问,BigTable就会失效。最近我们在跨11个Chubby服务实例的14个BigTable集群上测量了这个影响。由于Chubby不可用而导致BigTable中的部分数据不能访问的平均比率是0.0047%(Chubby不能访问的原因可能是Chubby本身失效或者网络问题)。单个集群里,受Chubby失效影响最大的百分比是0.0326%(The percentage for the singlecluster that was most affected byChubby unavailability was 0.0326%.)。

 

实现

Bigtable包括了三个主要的组件:客户程序连接库、一个Master服务器和多个Tablet服务器。针对系统工作负载的变化情况,BigTable可以动态的向集群中添加(或者删除)Tablet服务器。

 

Master服务器主要负责以下工作:为Tablet服务器分配Tablets、检测新加入的或者过期失效的Table服务器、对Tablet服务器进行负载均衡、以及对保存在GFS上的文件进行垃圾收集。除此之外,它还处理schema更改,例如建立表和列族。

 

每个Tablet服务器都管理一个Tablet的集合(通常每个服务器有大约数十个至上千个Tablet)。每个Tablet服务器负责处理它所加载的Tablet的读写操作,以及在Tablets过大时,对其进行分割。

 

和很多Single-Master类型的分布式存储系统【17.21】类似,客户端读取的数据都不经过Master服务器:客户程序直接和Tablet服务器通信进行读写操作。由于BigTable的客户程序不必通过Master服务器来获取Tablet的位置信息(???),因此,大多数客户程序甚至完全不需要和Master服务器通信。在实际应用中,Master服务器的负载是很轻的。

 

一个BigTable集群存储了很多表,每个表包含了一个Tablet的集合,而每个Tablet包含了某个范围内的行的所有相关数据。初始状态下,一个表只有一个Tablet。随着表中数据的增长,它被自动分割成多个Tablet,缺省情况下,每个Tablet的尺寸大约是100MB到200MB。

 

5.1Tablet的位置

 

我们使用一个三层的、类似B+树[10]的结构存储Tablet的位置信息(如图4)。

第一层是一个存储在Chubby中的文件它包含了Root Tablet的位置信息

The root tablet contains the location of all tablets in aspecial METADATA table。each METADATA tablet contains thelocation of a set of user tables。The Root Tablet实际上是METADATA表的第一个Tablet,只不过对它的处理比较特殊 — Root Tablet永远不会被分割 — 这就保证了Tablet的位置信息存储结构不会超过三层。

 

METADATA表中每个row key存储一个Tablet的位置信息,row key由Tablet所在的表的标识符和该Tablet的结束行编码而成的(The METADATA table storesthe location of a tablet under a row key thatis an encoding of the tablet’s table identifier and it is end row

在内存中,METADATA的一行存储大约1KB数据。对于128MB的METADATA Tablet,采用这种三层结构的存储模式,可以标识2^34个Tablet的地址(2^61字节 in 128M Tablet)。

 

客户程序链接库会缓存Tablet的位置信息。如果客户程序不知道某个Tablet的地址信息,或者发现缓存的地址信息不正确,then it recursively moves up the tablet location hierarchy。(客户程序就在树状的存储结构中递归的查询Tablet位置信息);如果客户端缓存为空,那么寻址算法需要三次网络通信,其中包括了一次Chubby读操作;如果客户端缓存地址过期,那么寻址算法可能需要最多6次网络通信,because stale cache entries are only discovered upon (cache) misses.因为只有在缓存中没有查到数据的时候才能发现数据过期 (假设METADATA的Tablet没有被频繁的移动)。Tablet地址信息存放在内存里,不用访问GFS,我们会通过预取Tablet地址来进一步的减少开销:每次读METADATA表时read metadata for more than 1tablet.

 

在METADATA表中还存储了secondary information,包括每个Tablet的事件日志(例如,什么时候一个服务器开始为该Tablet提供服务)。这些信息有助于排查错误和性能分析。

 

5.2Tablet分配

 

在任何一个时刻,一个Tablet只能分配给一个Tablet服务器。Master服务器记录了当前有哪些活跃的Tablet服务器、哪些Tablet分配给了哪些Tablet服务器、哪些Tablet还没有被分配。当一个Tablet还没有被分配、并且刚好有一个Tablet服务器有足够的空闲空间装载该Tablet时(???),Master服务器会给这个Tablet服务器发送一个装载请求,把Tablet分配给这个服务器。

 

BigTable使用Chubby跟踪记录Tablet服务器的状态。当一个Tablet服务器启动时,它在Chubby的一个指定目录下建立一个有唯一性名字的文件,并且获取该文件的独占锁。Master服务器实时监控着这个目录(服务器目录),因此Master服务器能够知道有新的Tablet服务器加入了。如果Tablet服务器丢失了Chubby上的独占锁 — 比如由于网络断开导致Tablet服务器和Chubby的会话丢失 — 它就停止对Tablet提供服务。(Chubby提供了一种高效的机制,利用这种机制,Tablet服务器能够在不增加网络负担的情况下知道它是否还持有锁)。只要文件还存在,Tablet服务器就会试图重新获得对该文件的独占锁;如果文件不存在了,那么Tablet服务器就不能再提供服务了,它会自行退出(so it kills itself)。当Tablet服务器终止时(比如,集群的管理系统将运行该Tablet服务器的主机从集群中移除),它会尝试释放它持有的文件锁,这样一来,Master服务器就能尽快把Tablet分配到其它的Tablet服务器

 

Master服务器负责检查一个Tablet服务器是否已经不再为它的Tablet提供服务了,并且要尽快重新分配它加载的Tablet。Master服务器通过轮询Tablet服务器文件锁的状态来检测何时Tablet服务器不再为Tablet提供服务。如果一个Tablet服务器报告它丢失了文件锁,或者Master服务器最近几次尝试和它通信都没有得到响应,Master服务器就会尝试获取该Tablet服务器文件的独占锁;如果Master服务器成功获取了独占锁,那么就说明Chubby是正常运行的,而Tablet服务器要么是宕机了、要么是不能和Chubby通信了,因此,Master服务器就删除该Tablet服务器在Chubby上的服务器文件以确保它不再给Tablet提供服务。一旦Tablet服务器在Chubby上的服务器文件被删除了,Master服务器就把之前分配给它的所有的Tablet放入未分配的Tablet集合中。为了确保Bigtable集群在Master服务器和Chubby之间网络出现故障的时候仍然可以使用,Master服务器在它的Chubby会话过期后主动退出。但是不管怎样,如同我们前面所描述的,Master服务器的故障不会改变现有Tablet在Tablet服务器上的分配状态。

 

当集群管理系统启动了一个Master服务器之后,Master服务器首先要了解当前Tablet的分配状态,之后才能够修改分配状态。Master服务器在启动的时候执行以下步骤:

(1)Master服务器从Chubby获取一个唯一的Master锁,用来阻止创建其它的Master服务器实例

(2)Master服务器扫描Chubby的服务器文件锁存储目录,获取当前正在运行的服务器列表;(3)Master服务器和所有的正在运行的Tablet表服务器通信,获取每个Tablet服务器上Tablet的分配信息;

(4)Master服务器扫描METADATA表获取所有的Tablet的集合。在扫描的过程中,当Master服务器发现了一个还没有分配的Tablet,Master服务器就将这个Tablet加入未分配的Tablet集合等待合适的时机分配。

 

一种复杂的情况:在METADATA表的Tablet还没有被assign之前是不能够扫描它的。因此,在开始扫描之前(步骤4),如果在第三步的扫描过程中发现Root Tablet还没有assign,Master服务器就把Root Tablet加入到unassigned Tablet集合。这个附加操作确保了Root Tablet会被assign。由于Root Tablet包括了所有METADATA的Tablet的名字,因此Master服务器扫描完Root Tablet以后,就得到了所有的METADATA表的Tablet的名字了。

 

Tablet集合只有在以下事件发生时才会改变:

创建表、删除表、合并tablet、分割Tablet。

Master跟踪记录这些事件,除了最后一个事件外其他事件都是由它发起的。Tablet分割事件需要特殊处理,因为它是由Tablet服务器发起。分割操作完成后,Tablet服务器在METADATA表中记录新的Tablet的信息、提交这个操作,并通知Master服务器。如果未能通知到Master服务器(由于tablet server或master die),随后Master要求Tablet服务器装载这个已分割tablet时会检测到新的Tablet。Tablet server会通知master分割事件,因为tablet server在METADATA表中找到的tablet信息只指定了master要求load tablet的一部分。

 

5.3Tablet服务

如图5所示,Tablet的持久化状态信息保存在GFS上。更改操作提交到REDO日志中(Updates are committed to acommit log that stores redo records)。最近提交的更改操作存放在一个排序的内存缓存中,称为memtable;较早的更新存放在一系列SSTable中。为了恢复一个Tablet,Tablet服务器首先从METADATA表中读取它的元数据。Tablet的元数据包含了该Tablet对应的SSTable列表,以及a set of Redo Point,这些Redo Point指向可能含有该Tablet相关数据的commit logs。Tablet服务器把SSTable的索引读进内存,之后通过apply Redo Point之后提交的更新来重建memtable。

 

 

当Tablet server收到写请求,首先检查请求格式、请求者权限。权限验证方法:读取特定的Chubby文件,包含有写权限的操作者列表。(几乎都能从Chubby缓存中命中)。Valid修改操作会记录在提交日志里。采用批量提交方式(group commit)来提高大量小修改操作的throughput 13,16】。当写操作提交后,更改内容插入到memtable里面。

 

Tablet服务器对读请求会作类似的格式检查和权限检查。合法的读操作is executed on a merged

view of the sequence of SSTablesand the memtable。由于SSTable和memtable是按字典排序的数据结构,因此可以高效生成合并视图。

Incoming read and write operations whiletablets are split and merged.

 

5.4Compactions

随着写操作的执行,memtable的大小不断增加。当memtable的尺寸到达一个门限值的时候,这个memtable就会被冻结,然后创建一个新的memtable;被冻结住memtable会被转换成SSTable,然后写入GFS(Minor Compactionhbase中称为flush)。Minor Compaction有两个目的:shrink Tablet服务器使用的内存,以及在服务器灾难恢复过程中,减少从提交日志里读取的数据量。在Compaction过程中,incoming读写操作仍能继续。

 

每一次MinorCompaction都会创建一个新的SSTable。如果Minor Compaction过程持续进行下去,读操作可能需要合并来自多个SSTable的更新;因此我们通过定期在后台执行Merging Compaction过程合并文件,限制这类文件的数量。Merging Compaction过程读取一些SSTable和memtable的内容,合并成一个新的SSTable。Merging Compaction完成后,相关SSTable和memtable就可以删除了。

 

合并所有的SSTable并生成一个新的SSTable的Merging Compaction过程叫作Major Compaction。非Major Compaction产生的SSTable可能含有已删除数据,SSTables produced by non-major compactions can contain special deletionentries that suppress deleted data in older SSTables that are stilllive)。而Major Compaction生成的SSTable不包含已删除数据。Bigtable循环扫描它所有的Tablet,并且定期对它们执行Major Compaction。Major Compaction机制允许Bigtable回收已删除数据空间,并且确保BigTable能及时清除已经删除的数据,这对存放敏感数据的服务是非常重要。

 

6 优化

上一章我们描述了Bigtable的实现,我们还需要很多优化工作才能使Bigtable到达用户要求的高性能、高可用性和高可靠性。本章描述了Bigtable实现的其它部分,为了更好的强调这些优化工作,我们将深入细节。

 

localitygroups

客户程序可以将多个列族组合成一个locality groups。对Tablet中的每个localitygroup都会生成一个单独的SSTable。将通常不会一起访问的列族分割成不同的locality group可以提高读操作的效率。例如,在Webtable表中,网页的元数据(比如语言和Checksum)可以在一个locality group,网页的内容可以在另外一个locality group:当一个应用程序要读取网页的元数据的时候,它没有必要去读取所有的页面内容。

 

此外,可以以localitygroup为单位设定一些有用的调试参数。比如,可以把一个locality group设定为全部存储在内存中。Tablet服务器依照延迟加载策略将设定为放入内存的locality group的SSTable装载进内存。加载完成之后,访问属于该locality group的列族的时候就不必读取硬盘了。这个特性对于需要频繁访问的小块数据特别有用:Bigtable内部,我们利用这个特性提高METADATA表中具有位置相关性的列族的访问速度

 

压缩

 

客户程序可以控制一个localitygroup的SSTable是否需要压缩;如果需要压缩,那么以什么格式来压缩。每个SSTable的块(块的大小由locality group的优化参数指定)都使用用户指定的压缩格式来压缩。虽然分块压缩浪费了少量空间,但是,我们在只读取SSTable的一小部分数据的时候就不必解压整个文件了。很多客户程序使用了two-pass custom compression scheme。第一遍采用Bentley and McIlroy’s方式[6],这种方式在一个large window里对常见的长字符串进行压缩;第二遍是采用快速压缩算法,即在一个16KB的small window中寻找重复数据。两个压缩的算法都很快,在现在的机器上,压缩的速率达到100-200MB/s,解压的速率达到400-1000MB/s。

 

虽然我们在选择压缩算法的时候重点考虑的是速度而不是压缩的空间,但是这种两遍的压缩方式在空间压缩率上的表现也是令人惊叹。比如,在Webtable的例子里,我们使用这种压缩方式来存储网页内容。在一次测试中,我们在一个压缩的locality group中存储了大量的网页。针对实验的目的,我们没有存储每个文档所有版本的数据,我们仅仅存储了一个版本的数据。该模式的空间压缩比达到了10:1。这比传统的Gzip在压缩HTML页面时3:1或者4:1的空间压缩比好的多;“两遍”的压缩模式如此高效的原因是由于Webtable的行的存放方式:从同一个主机获取的页面都存在临近的地方。利用这个特性,Bentley-McIlroy算法可以从来自同一个主机的页面里找到大量的重复内容。不仅仅是Webtable,其它的很多应用程序也通过选择合适的行名来将相似的数据聚簇在一起,以获取较高的压缩率。当我们在Bigtable中存储同一份数据的多个版本的时候,压缩效率会更高。

 

通过缓存提高读操作的性能

为了提高读操作的性能,Tablet服务器使用二级缓存的策略。Scan cache一级缓存,主要缓存Tablet服务器通过SSTable接口获取的Key-Value对;Block cache二级缓存,缓存从GFS读取的SSTable的Block。对于经常要重复读取相同数据的应用程序来说,扫描缓存非常有效;对于经常要读取刚刚读过的数据附近的数据的应用程序来说,Block缓存更有用(例如,顺序读,orrandom reads of different columns in the same locality group within a hot row)。

 

Bloom过滤器

(http://www.cnblogs.com/heaad/archive/2011/01/02/1924195.html

http://googlechinablog.com/2007/07/bloom-filter.html

)

 

如5.3节所述,一个读操作必须读取构成Tablet状态的所有SSTable的数据。如果这些SSTable不在内存中,那么就需要多次访问硬盘。我们允许客户程序对特定localitygroup的SSTable指定Bloom过滤器【7】,来减少硬盘访问的次数。我们可以使用Bloom过滤器查询一个SSTable是否包含了特定行和列的数据。对于某些特定应用程序,我们只付出了少量的、用于存储Bloom过滤器的内存的代价,就换来了读操作显著减少的磁盘访问的次数。使用Bloom过滤器也隐式的达到了当应用程序访问不存在的行或列时,大多数时候我们都不需要访问硬盘的目的。

 

Commit日志的实现

 

如果我们把对每个Tablet的操作的Commit日志都存在一个单独的文件的话,那么就会产生大量的文件,并且这些文件会并行的写入GFS。根据GFS服务器底层文件系统实现的方案,要把这些文件写入不同的磁盘日志文件时(differentphysical log files),会有大量的磁盘Seek操作。另外,由于批量提交(groupcommit)中操作的数目一般比较少,因此,对每个Tablet设置单独的日志文件也会给批量提交本应具有的优化效果带来很大的负面影响。为了避免这些问题,我们设置每个Tablet服务器一个Commit日志文件,把修改操作的日志以追加方式写入同一个日志文件,因此一个实际的日志文件中混合了对多个Tablet修改的日志记录。

 

使用单个日志显著提高了常规操作的性能,但使得恢复复杂化。当tablet server宕机时,宕机Tablet服务器所加载的Tablet被移到很多其它的Tablet服务器上:每个Tablet服务器都装载很少的几个原来的服务器的Tablet。当恢复一个Tablet的状态的时候,新的Tablet服务器要从原来的Tablet服务器写的日志中提取修改操作的信息,并apply。然而,这些Tablet修改操作的日志记录都混合在同一个日志文件中的。一种方法是新的Tablet服务器读取完整的Commit日志文件,然后只apply它需要恢复的Tablet的修改操作。使用这种方法,假如有100台Tablet服务器,每台都加载了失效的Tablet服务器上的一个Tablet,那么,这个日志文件就要被读取100次(每个服务器读取一次)。

 

为了避免多次读取日志文件,我们首先把日志记录按照关键字(table,row name,log sequence number)排序。排序之后,对同一个Tablet的修改操作的日志记录就连续存放在了一起,因此,我们只要一次磁盘Seek操作、之后顺序读取就可以了。为了并行排序,我们先将日志分割成64MB的段,之后在不同的Tablet服务器对段进行并行排序。这个排序工作由Master服务器来协同处理,并且在一个Tablet服务器表明自己需要从Commit日志文件恢复Tablet时开始执行。

 

在向GFS中写Commit日志的时候可能会引起系统颠簸,原因是多种多样的(比如,写操作正在进行的时候,一个GFS服务器宕机了;或者连接三个GFS副本所在的服务器的网络拥塞或者过载了)。为了确保在GFS负载高峰时修改操作还能顺利进行,每个Tablet服务器实际上有两个日志写入线程,每个线程都写自己的日志文件,并且在任何时刻,只有一个线程是工作的。如果一个线程的在写入的时候效率很低,Tablet服务器就切换到另外一个线程,修改操作的日志记录就写入到这个线程对应的日志文件中。每个日志记录都有一个序列号,因此,在恢复的时候,Tablet服务器能够检测出并忽略掉那些由于线程切换而导致的重复的记录。

 

Tablet恢复提速

当Master服务器将一个Tablet从一个Tablet服务器移到另外一个Tablet服务器时,源Tablet服务器会对这个Tablet做一次Minor Compaction。这个Compaction操作减少了Tablet服务器的日志文件中没有归并的记录,从而减少了恢复的时间。Compaction完成之后,该服务器就停止为该Tablet提供服务。在卸载Tablet之前,源Tablet服务器还会再做一次(通常会很快)Minor Compaction,以消除前面在一次压缩过程中又产生的未归并的记录。第二次MinorCompaction完成以后,Tablet就可以被装载到新的Tablet服务器上了,并且不需要从日志中进行恢复。

 

Exploitingimmutability

包括SSTable缓存在内,bigtable内产生的所有的SSTable都是不可更改的,这样大大简化了系统其他部分。例如,当从SSTable读取数据的时候,我们不必对文件系统访问操作进行同步。这样一来,就可以非常高效的实现对行的并行操作。memtable是唯一一个能被读和写操作同时访问的可变数据结构为了减少在读操作时的竞争,我们对内存表采用COW(Copy-on-write)机制,这样就允许读写操作并行执行。

 

因为SSTable是不变的,因此永久删除被标记“删除”数据变为对废弃的SSTable进行垃圾收集。每个Tablet的SSTable都在METADATA表中注册。Master服务器采用“标记-删除”的垃圾回收方式删除SSTable集合中废弃的SSTable【25】,METADATA表则保存了Root SSTable的集合。

 

最后,SSTable的不变性使得分割Tablet的操作非常快捷。我们不必为每个分割出来的Tablet建立新的SSTable集合,而是共享原来的Tablet的SSTable集合。

 

9 经验教训

在设计、实现、维护和支持Bigtable的过程中,我们得到了很多有用的经验和一些有趣的教训。

 

一个教训是,我们发现,很多类型的错误都会导致大型分布式系统受损,这些错误不仅仅是通常的网络中断、或者很多分布式协议中设想的fail-stop类型的错误。比如,我们遇到过下面这些类型的错误导致的问题:内存数据损坏、网络中断、时钟偏差、机器挂起、扩展的和非对称的网络分区(extended and asymmetric network partitions)、我们使用的其它系统的Bug(比如Chubby)、GFS配额溢出、计划内和计划外的硬件维护。我们在解决这些问题的过程中学到了很多经验,我们通过修改协议来解决这些问题。比如,我们在我们的RPC机制中加入了Checksum。我们在设计系统的部分功能时,不对其它部分功能做任何的假设,这样的做法解决了其它的一些问题。比如,我们不再假设一个特定的Chubby操作只返回错误码集合中的一个值。

 

另外一个教训是,我们明白了在彻底了解一个新特性会被如何使用之后,再决定是否添加这个新特性是非常重要的。比如,我们开始计划在我们的API中支持通常方式的事务处理。但是由于我们还不会马上用到这个功能,因此,我们并没有去实现它。现在,Bigtable上已经有了很多的实际应用,我们可以检查它们真实的需求;我们发现,大多是应用程序都只是需要单个行上的事务功能。有些应用需要分布式的事务功能,分布式事务大多数情况下用于维护二级索引,因此我们增加了一个特殊的机制去满足这个需求。新的机制在通用性上比分布式事务差很多,但是它更有效(特别是在更新操作的涉及上百行数据的时候),而且非常符合我们的“跨数据中心”复制方案的优化策略。

 

还有一个具有实践意义的经验:我们发现系统级的监控对Bigtable非常重要(比如,监控Bigtable自身以及使用Bigtable的客户程序)。比如,我们扩展了我们的RPC系统,因此对于一个RPC调用的例子,它可以详细记录代表了RPC调用的很多重要操作。这个特性允许我们检测和修正很多的问题,比如Tablet数据结构上的锁的内容、在修改操作提交时对GFS的写入非常慢的问题、以及在METADATA表的Tablet不可用时,对METADATA表的访问挂起的问题。关于监控的用途的另外一个例子是,每个Bigtable集群都在Chubby中注册了。这可以帮助我们跟踪所有的集群状态、监控它们的大小、检查集群运行的我们软件的版本、监控集群流入数据的流量,以及检查是否有引发集群高延时的潜在因素。

 

对我们来说,最宝贵的经验是简单设计的价值。考虑到我们系统的代码量(大约100000行non-test code),以及随着时间的推移,新的代码以各种难以预料的方式加入系统,我们发现简洁的设计和编码给维护和调试带来的巨大好处。这方面的一个例子是我们的Tablet服务器成员协议。我们第一版的协议很简单:Master服务器周期性的和Tablet服务器签订租约,Tablet服务器在租约过期的时候Kill掉自己的进程。不幸的是,这个协议在遇到网络问题时会大大降低系统的可用性,也会大大增加Master服务器恢复的时间。我们多次重新设计这个协议,直到它能够很好的处理上述问题。但是,更不幸的是,最终的协议过于复杂了,并且依赖一些Chubby很少被用到的特性。我们发现我们浪费了大量的时间在调试一些古怪的问题(obscurecorner cases),有些是Bigtable代码的问题,有些事Chubby代码的问题。最后,我们只好废弃了这个协议,重新制订了一个新的、更简单、只使用Chubby最广泛使用的特性的协议。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值