当前位置: 首页 > 工具软件 > BigTable > 使用案例 >

Bigtable:结构化数据的分布式存储系统

阎单鹗
2023-12-01

相关说明

Bigtable是一个用于管理结构化数据的分布式存储系统,其设计目的是为了通过数千个服务器管理大规模数据。谷歌许多的项目例如,web索引、谷歌地球和谷歌金融都使用了Bigtable来存储大规模数据。这些应用对Bigtable提出了非常不同的要求,包括数据大小(从URL到网页到卫星图像)和延迟要求(从后端批量处理到实时数据服务)。尽管有这些不同的需求,但Bigtable还是为这些谷歌产品提供了灵活、高性能的解决方案。在论文中,描述了Bigtable提供的简单数据模型,他为客户提供了对数据布局和格式的动态控制,并描述了Bigtable的设计和实现。

Bigtable实现了四个目标:广适用性,可扩展性,高性能和高可用性。在很多方面,Bigtable类似于数据库,它与数据库共享许多实现策略,并行数据库和主存储数据库实现了可扩展性和高性能,但Bigtable提供了与此类系统不同的接口。Bigtable不支持完整的关系数据模型;相反,他为客户提供了一个简单的数据模型,支持对数据布局和格式的动态控制,并允许客户端推断底层存储中表示的数据的位置属性。使用可以是任意字符串的行和列名称来索引数据。Bigtable还将数据视为未解释的字符串,尽管客户经常讲各种形式的结构化和半结构化数据序列化为这些字符串。客户可以通过在模式中仔细选择来控制数据的位置。最后,Bigtable架构参数允许客户端动态控制从内存还是从磁盘提供数据。

数据结构

Bigtable是稀疏的,分布式的,持久的多维有序映射。 地图由行键,列键和时间戳索引; 映射中的每个值都是未解释的字节数组。

(row:string, column:string, time:int64) → string

在研究了类Bigtable系统的各种潜在用途后,我们确定了这个数据模型。 作为推动我们的一些设计决策的一个具体例子,假设我们想要保留许多不同项目可以使用的大量网页和相关信息的副本; 让我们将这个特定的表称为Webtable。 在Webtable中,我们将URL用作行键,将网页的各个方面用作列名,并将网页的内容存储在内容中:在获取它们的时间戳下的列,如图所示。

[外链图片转存失败(img-xMUmHjYy-1567404854819)(/Users/wuzi/Desktop/bigtable_figure1.png)]

Rows

表中的行键是任意字符串(当前最大为64KB,但对于大多数用户来说,10-100字节是典型的大小)。 单个行键下的每次数据读取或写入都是原子的(无论行中读取或写入的不同列的数量如何),这一设计决策使客户端更容易在出现并发时推断系统的行为 更新到同一行。

Bigtable按行键维护按字典顺序排列的数据。 表的行范围是动态分区的。 每个行范围称为tablet,它是分配和负载平衡的单位。 因此,短行范围的读取是有效的,并且通常需要仅与少量机器通信。 客户端可以通过选择行键来利用此属性,以便它们获得数据访问的良好位置。 例如,在Webtable中,通过反转URL的主机名组件,将同一域中的页面组合在一起成为连续的行。 例如,我们会在关键com.google.maps / index.html下存储maps.google.com/index.html的数据。 将来自同一域的页面存储在彼此附近使得一些主机和域分析更有效。

Column Families

列键被分组为称为列族的集合,这些集合构成了访问控制的基本单元。存储在列族中的所有数据通常具有相同的类型(我们将同一列族中的数据压缩在一起)。 必须先创建列族,然后才能将数据存储在该族中的任何列键下; 在创建族之后,可以使用族中的任何列键。 我们的意图是表中不同列系列的数量很小(最多数百个),并且系列在运行期间很少发生变化。 相反,表格可能具有无限数量的列。

使用以下语法命名列键:family:qualifier。 列族名称必须是可打印的,但限定符可以是任意字符串。 Webtable的示例列族是语言,它存储编写网页的语言。 我们在语言系列中只使用一个列键,它存储每个网页的语言ID。 该表的另一个有用的列系列是锚; 此系列中的每个列键代表一个锚点,如图1所示。限定符是引用站点的名称; 单元格内容是链接文本。

访问控制以及磁盘和内存帐户都在列族级别执行。 在我们的Webtable示例中,这些控件允许我们管理几种不同类型的应用程序:一些用于添加新的基础数据,一些用于读取基础数据并创建派生列族,还有一些仅允许查看现有数据(可能不会 甚至出于隐私原因查看所有现有数据)。

Timestamps

Bigtable中的每个单元格都可以包含相同数据的多个版本; 这些版本由时间戳索引。 Bigtable时间戳是64位整数。 它们可以由Bigtable分配,在这种情况下,它们以微秒表示“实时”,或由客户端应用程序明确分配。需要避免冲突的应用程序必须自己生成唯一的时间戳。 不同版本的单元以递减的时间戳存储,以便可以首先读取最新版本。

为了减少对版本化数据的管理,我们支持两个每列系列设置,告诉Bigtable自动垃圾收集单元版本。 客户端可以指定仅保留单元的最后n个版本,或者仅保留足够新版本(例如,仅保留在过去七天中写入的值)。

在我们的Webtable示例中,我们将存储在contents:列中的已爬网页面的时间戳设置为实际爬网这些页面版本的时间。 上面描述的垃圾收集机制让我们只保留每个页面的最新三个版本。

Building Blocks

Bigtable建立在其他几个谷歌基础设施上。 Bigtable使用分布式Google文件系统(GFS)来存储日志和数据文件。Bigtable集群通常在运行各种其他分布式应用程序的共享计算机池中运行,而Bigtable进程通常与来自其他应用程序的进程共享相同的计算机。Bigtable依赖于集群管理系统,用于调度作业,管理共享计算机上的资源,处理计算机故障以及监视计算机状态。

Google SSTable文件格式在内部用于存储Bigtable数据。 SSTable提供从键到值的持久的,有序的不可变映射,其中键和值都是任意字节串。提供操作以查找与指定键相关联的值,并迭代指定键范围内的所有键/值对。在内部,每个SSTable都包含一系列块(通常每个块的大小为64KB,但这是可配置的)。 块索引(存储在SSTable的末尾)用于定位块; 打开SSTable时,索引将加载到内存中。可以使用单个磁盘搜索执行查找:我们首先通过在内存索引中执行二进制搜索,然后从磁盘读取相应的块来找到适当的块。 可选地,SSTable可以完全映射到内存中,这使我们可以在不触摸磁盘的情况下执行查找和扫描。

Bigtable依赖于一种名为Chubby的高可用性和持久性分布式锁定服务。Chubby服务由五个活动副本组成,其中一个被选为主服务器并主动服务请求。当大多数副本正在运行并且可以相互通信时,该服务是活动的。Chubby使用Paxos算法来保证其副本在失败时保持一致。 Chubby提供了一个由目录和小文件组成的命名空间。每个目录或文件都可以用作锁,对文件的读写是原子的。 Chubby客户端库提供了对Chubby文件的一致缓存。每个Chubby客户端都使用Chubby服务维护会话。 如果客户端会话无法在租约到期时间内续订其会话租约,则会话期满。当客户端的会话到期时,它会丢失任何锁定并打开句柄。 Chubby客户端还可以在Chubby文件和目录上注册回调,以通知更改或会话到期。

Bigtable使用Chubby执行各种任务:确保任何时候最多只有一个活跃的主人;存储Bigtable数据的引导位置; 发现tablet服务器并最终确定tablet服务器down的数量;存储Bigtable架构信息(每个表的列族信息); 并存储访问控制列表。如果Chubby长时间不可用,Bigtable将无法使用。

实现

Bigtable实现有三个主要组件:链接到每个客户端的库,一个主服务器和许多tablet服务器。Tablet服务器可以从群集中动态添加(或删除)以适应工作负载的变化。

主服务器负责将Tablet分配给Tablet服务器,检测Tablet服务器的添加和到期,平衡Tablet服务器负载以及GFS中文件的垃圾收集。 此外,它还处理架构更改,例如表和列族创建。

每个Tablet服务器管理一组Tablet(通常我们每台Tablet服务器有10到1000个Tablet)。 Tablet服务器处理对已加载的Tablet的读写请求,并且还会分割已经过大的Tablet。

与许多单主分布式存储系统一样,客户端数据不会通过主服务器:客户端直接与Tablet服务器通信以进行读写操作。 由于Bigtable客户端不依赖主服务器来获取Tablet位置信息,因此大多数客户端从不与主服务器通信。 结果,主体在实践中轻载。

Bigtable集群存储了许多表。 每个表格由一组Tablet组成,每个Tablet包含与行范围相关的所有数据。 最初,每个表只包含一个Tablet。 随着表格的增长,它会自动拆分为多个Tablet,默认情况下每个大小约为100-200 MB。

Tablet Location

我们使用类似于B + - 树[10]的三级层次结构来存储Tablet位置信息(如图2)。

[外链图片转存失败(img-2fJjJvjd-1567404854820)(/Users/wuzi/Desktop/bigtable_tablet_location.png)]

第一级是存储在Chubby中的文件,其中包含根Tablet的位置。根Tablet包含特殊METADATA表中所有Tablet的位置。每个METADATA Tablet都包含一组用户Tablet的位置。根Tablet只是METADATA表中的第一个Tablet,但是经过专门处理 - 它从不拆分 - 以确保Tablet位置层次结构不超过三个级别。

METADATA表将Tablet的位置存储在行键下,该行键是Tablet的表标识符及其结束行的编码。每个METADATA行在内存中存储大约1KB的数据。对于128 MB METADATA Tablet的适度限制,我们的三级位置方案足以处理234个Tablet(或128 MBTablet中的261个字节)。

客户端库缓存Tablet位置。 如果客户端不知道Tablet的位置,或者缓存位置信息的发送者是否不正确,则它会递归地向上移动Tablet位置层次结构。如果客户端的缓存为空,则位置算法需要三次网络往返,包括从Chubby读取一次。如果客户端的缓存是陈旧的,则位置算法最多可能需要六次往返,因为只有在未命中时才会发现过时的缓存条目(假设METADATA Tablet不会频繁移动)。

虽然Tablet位置存储在内存中,因此不需要GFS访问,但我们通过使用客户端库预取Tablet位置来进一步降低此成本:只要读取METADATA表,它就会读取多个Tablet的元数据。我们还将辅助信息存储在METADATA表中,包括每个Tablet所有事件的日志(例如服务器开始提供时)。 此信息有助于调试和性能分析。

Tablet Assignment

每个Tablet一次分配到一台Tablet服务器。 主设备会跟踪Tablet服务器的集合,以及Tablet到Tablet服务器的当前分配,包括哪些Tablet未分配。如果Tablet未分配,并且Tablet服务器具有足够的Tablet空间,则主计算机会通过向Tablet服务器发送Tablet加载请求来分配Tablet。

Bigtable使用Chubby来跟踪Tablet服务器。 当Tablet服务器启动时,它会创建并获取对特定Chubby目录中唯一命名文件的独占锁定。主服务器监视此目录(服务器目录)以发现Tablet服务器。 如果Tablet服务器失去其独占锁定,它将停止为其Tablet提供服务:例如,由于网络分区导致服务器丢失其Chubby的会话。(Chubby提供了一种有效的机制,允许Tablet服务器检查它是否仍然保持其锁定而不会产生网络流量。)只要文件仍然存在,Tablet服务器就会尝试在其文件上重新获取一个特定的锁定。 如果该文件不再存在,则Tablet服务器将永远无法再次服务,因此它会自行终止。每当Tablet服务器终止时(例如,因为集群管理系统正在从集群中移除Tablet服务器的机器),它就会尝试释放其锁定,以便主服务器更快地重新分配其Tablet。

主负责检测Tablet服务器何时不再服务其Tablet,以及尽快重新分配这些Tablet。要检测Tablet服务器何时不再为其Tablet提供服务,主服务器会定期向每个Tablet服务器询问其锁定状态。如果Tablet服务器报告它已失去锁定,或者主服务器在最近几次尝试期间无法访问服务器,则主服务器会尝试获取服务器文件的独占锁定。如果主服务器能够获得锁定,则Chubby处于活动状态且Tablet服务器已经死机或无法访问Chubby,因此主服务器确保Tablet服务器永远不会通过删除其服务器文件再次服务。删除服务器文件后,主服务器可以将之前分配给该服务器的所有Tablet移动到一组未分配的Tablet中。为了确保Bigtable集群不会对主服务器和Chubby之间的网络问题造成漏洞,如果其Chubby会话到期,主服务器将自行终止。 但是,如上所述,主故障不会改变Tablet到Tablet服务器的分配。

当集群管理系统启动主服务器时,它需要先发现当前的Tablet分配,然后才能更改它们。 主站在启动时执行以下步骤。 (1)主机在Chubby中获取一个唯一的主锁,这会阻止当前的主实例化。 (2)主服务器扫描Chubby中的服务器目录以查找实时服务器。 (3)主设备与每个实时Tablet服务器通信,以发现已为每个服务器分配了哪些Tablet。 (4)主机扫描METADATA表以了解Tablet组。 每当此扫描遇到尚未分配的Tablet时,主控制器会将Tablet添加到一组未分配的Tablet,这使Tablet有资格进行Tablet分配。

一个复杂的问题是,在分配METADATA Tablet之前,不能对METADATA表进行扫描。 因此,在开始此扫描(步骤4)之前,如果在步骤3中未发现根Tablet的分配,则主服务器会将根Tablet添加到一组未分配的Tablet。此添加确保将分配根Tablet。 由于根Tablet包含所有METADATA Tablet的名称,因此主机在扫描根Tablet后会知道所有这些Tablet。

只有在创建或删除表格时才会更改现有Tablet的集合,将两个现有Tablet合并为一个较大的Tablet,或者将现有Tablet拆分为两个较小的Tablet。主能够跟踪这些变化,因为它会启动除最后一个之外的所有变化。 Tablet拆分是专门处理的,因为它们是由Tablet服务器启动的。 Tablet服务器通过在METADATA表中记录新Tablet的信息来提交拆分。当拆分已经提交时,它通知主。 如果拆分通知丢失(由于Tablet服务器或主服务器已经死亡),主服务器会在请求Tablet服务器加载现在已拆分的Tablet时检测新Tablet。Tablet服务器将通知主这次拆分,因为它在METADATA表中找到的Tablet条目将仅指定主机要求加载的Tablet的一部分。

Tablet Serving

平板电脑的持久状态存储在GFS中,如图3所示。

[外链图片转存失败(img-sJ2wzSCS-1567404854821)(/Users/wuzi/Desktop/bigtable_tablet_serving.png)]

更新将提交到存储重做记录的提交日志。 在这些更新中,最近提交的更新存储在称为memtable的排序缓冲区的内存中; 较旧的更新存储在一系列SSTable中。要恢复Tablet,Tablet服务器会从METADATA表中读取其元数据。 此元数据包含构成Tablet和一组重做点的SSTable列表,这些重做点指向可能包含Tablet数据的任何提交日志。服务器将SSTable的索引读入内存,并通过应用自重做点以来已提交的所有更新来重构memtable。

当写操作到达Tablet服务器时,服务器检查它是否格式正确,并且发件人有权执行认证。通过从Chubby文件中读取允许的写入器列表来执行授权(这几乎总是在Chubby客户端缓存中命中)。 将有效的突变写入提交日志。组提交用于提高许多小突变的吞吐量。 写入提交后,其内容将插入到memtable中。

当读取操作到达Tablet服务器时,类似地检查其是否良好和正确的授权。在SSTables序列和memtable的合并视图上执行有效的读操作。 由于SSTable和memtable是按字典顺序排序的数据结构,因此可以有效地形成合并视图。在分割和合并Tablet时,可以继续进行读取和写入操作。

Compactions

当写操作执行时,memtable的大小会增加。 当memtable大小达到阈值时,memtable被冻结,创建一个新的memtable,冻结的memtable转换为SSTable并写入GFS。这个压缩过程有两个目标:它缩小了Tablet服务器的内存使用量,并减少了恢复期间必须从提交日志中读取的数据量(如果此服务器死机)。 在发生压缩时,可以继续进行读写操作。

每次轻微压缩都会创建一个新的SSTable。 如果此行为继续未检查,则读取操作可能需要合并来自任意数量的SSTable的更新。相反,我们通过在后台定期执行合并压缩来限制此类文件的数量。合并压缩读取一些SSTable和memtable的内容,并写出一个新的SSTable。 压缩完成后,可以丢弃输入SSTable和memtable。

将所有SSTable重写为一个SSTable的合并压缩称为主要压缩。 非主要压缩产生的SSTables可以包含特殊的删除条目,这些条目可以抑制仍然存在的旧SSTable中的已删除数据。另一方面,主要的压缩产生不包含删除信息或删除数据的SSTable。Bigtable通过其所有Tablet进行循环,并定期对其进行重大压缩。 这些主要的压缩允许Bigtable回收已删除数据使用的资源,并允许它确保已删除的数据及时从系统中消失,这对于存储敏感数据的服务非常重要。

总结

Bigtable的论文的部分内容,如上所述,基本是英文论文的翻译过程,大致熟悉了Bigtable所做的事情的主要的流程,有Tablet的分配流程,有Tablet如何保存的流程,有Tablet服务器的工作内容,还有Bigtable的文件单位SSTable的保存过程。基本的属于一个分布式,多维的映射表的面向大数据存储的分布式结构化数据表。后续内容会分析单机版的leveldb的实现过程,来进一步阐述该思路的实践过程。由于本人才疏学浅,如有错误请批评指正。

 类似资料: