Rowkey:是table_id:end_row_key表示,tableid是表在创建时,系统赋予的唯一表标识号,0号码保留给metadatatable用。end_row_key是一个任意字符串,表示该记录指向的tablet的最后一行的rowkey。
Accessgroup(可以认为是局部性群组):metadatatable同时包括三个accessgroup,分别是:default、Location、Logging。
ColumnFamily:metadatatable包含五个columnfamilies,分别是:LogDir、Files、Startrow、Location、Event。
其中Startrow记录该行指向的tablet的第一行的rowkey;
Location记录的是该tablet是由云计算平台中哪一台主机维护,以ip:port方式记录。
Files (列族名)含多个qualifier项,每一项qualifier指示accessgroup所保存的文件名列表
Metadata(Metadata也是一个table) schema的XML文件描述形式为:
行关键字是RowKey,schema对应表的结构。
<Schema>
<AccessGroupname="default">
<ColumnFamily>
<Name>LogDir</Name>
</ColumnFamily>
<ColumnFamily>
<Name>Files</Name>
<qulifier>AccessGroup-1</qulifier>
<value>AccessGroup-1下的文件名列表</value>
</ColumnFamily>
</AccessGroup>
<AccessGroupname="location" inMemory="true">
<ColumnFamily>
<Name>StartRow</Name>
</ColumnFamily>
<ColumnFamily>
<Name>Location</Name>
</ColumnFamily>
</AccessGroup>
<AccessGroupname="logging">
<ColumnFamily>
<Name>Event</Name>
</ColumnFamily>
</AccessGroup>
</Schema>
下面的目录可以认为是分布式文件系统的总目录。
HBase的每个Table在根目录下面用一个文件夹来存储,文件夹的名字就是Table的名字。在Table文件夹下面每个Region(tablet)也用一个文件夹来存储,但是文件夹的名字并不是Region的名字,而是Region的名字通过Jenkins Hash计算所得到的字符串。这样做的原因是Region的名字里面可能包含了不能在HDFS里面作为路径名的字符。
在每个Region文件夹下面每个ColumnFamily也有自己的文件夹,在每个ColumnFamily文件夹下面就是一个个HFile文件了。所以整个文件夹结构看起来应该是这个样子的:
/hbase/<tablename>/<encoded-regionname>/<column-family>/<filename>
在每个Region文件夹下面你会发现一个.regioninfo文件,这个文件用来存储这个Region的Meta Data。通过这些Meta Data我们可以重建被破坏的.META.表。
A Store hosts a MemStore and 0 or more StoreFiles (HFiles). A Store corresponds to a column family for a table for a given region.
以下两种文件主要由HRegionServer来管理,但是在有的情况下HMaster会跳过HRegionServer,直接操作这两种文件。
table按照rowkey进行排序,并进行切分为tablet和进行负载均衡的。
不同的列关键字可以被分组到一个集合,我们把这样的一个集合称为一个列族,它是基本的访问控制单元。存储在同一个列族的数据通常是相同类型的(我们将同一列族的数据压缩在一块)。在数据能够存储到某个列族的列关键字下之前,必须首先要创建该列族。我们假设在一个表中不同列族的数目应该比较小(最多数百个),而且在操作过程中这些列族应该很少变化。与之相比,一个表的列数目可以没有限制。“列名可以认为是key中的value,即可设置qualifier固定,也可以存储时,动态设置,此时相当于把一部分value化为key).列族名称必须是可打印的,但是qualifier可能是任意字符串。访问控制以及磁盘的内存分配都是在列族级别进行的。HBase暂不支持列族上的访问控制,BigTable,HBase中的cell含义就是三维索引的立体空间的一个小格,用于容纳value,只是为了方便理解而定义的,在HFile中,可以认为是value所存在的空间。
Bigtable当前并不支持跨行的事务,尽管它提供了一个多个用户的跨行写的接口。其次,Bigtable允许用户将cell作为一个整数计数器来使用。最后,Bigtable支持在服务器地址空间内执行一个客户端脚本。这些脚本是使用google内部开发的数据处理语言sawzall编写的。当前,我们基于Sawzall的API不允许客户端脚本向Bigtable中写回,但是它允许进行各种形式的数据转换,基于各种表达式的过滤以及大量的统计操作符。Bigtable使用GFS来存储日志和数据文件
Bigtable依赖于一个集群管理系统进行job调度,共享机器上的资源管理,处理机器失败以及监控机器状态。????????????????????????????????/
日志与恢复
每个tablet服务器将更新操作append一个日志文件里,将对于不同的tablet的变更放到同一个物理日志文件里(
如果我们为每个tablet的提交日志建立一个独立的日志文件,就会使得大量的文件需要并发写入GFS。由于每个GFS服务器的底层文件系统实现,这些写操作会引起在不同物理日志文件上的大量的磁盘寻道。
另外,每个tablet一个日志文件会降低分组提交优化的效率。为了解决这些问题,????????????????
当一个tablet服务器挂掉后,它负责的那些tablet需要移动到大量其他的tablet服务器上:每个服务器通常都会加载一些该服务器的tablet。为了恢复一个tablet的状态,新的tablet服务器需要通过原来那个tablet服务器的提交日志重新应用这个tablet的变更操作。
为了恢复一个tablet,tablet服务器从METADATA表中读取该tablet的元数据。元数据中包含组成该tablet的SSTable列表(一个tablet有多个AccessGroup组成,而多个(或1个)SSTable构成一个AccessGroup,以及一系列的redo点(指向那些包含该tablet数据的commit日志条目)的集合。Tablet服务器将所有SSTable的索引读入内存,然后通过应用那些从redo点开始以及提交的更新操作来重新构建memtable。????
压缩
用户可以控制对于一个局部性群组的SSTables是否进行压缩,以及使用哪种压缩格式。用户指定的压缩格式会应用在SSTable的每个(逻辑)块上(块大小可以通过一个局部性群组的参数进行控制)。对于每个块单独进行压缩,尽管这使我们丢失了一些空间,但是这使得我们不需要解压整个文件就可以读取SSTable的部分内容。很多用户使用一个两遍压缩模式,第一遍压缩使用BentleyandMcIlroy模式,该模式在一个很大的窗口大小里压缩普通的长字符串。第二遍压缩了一个快速压缩算法,该算法在一个小的16KB窗口大小内查找重复。这两遍压缩都很快速,压缩速率在100-200MB/s,解压速率在400-1000MB/s。
尽管在选择压缩算法时,我们更重视速率而不是空间的减少,但是这个两遍压缩模式或做的出奇地好。比如,在webtable里我们使用这种压缩模式存储网页内容。实验中,我们在一个压缩的局部性群组里存储了大量文档。为了实验目的,我们将文档的版本数限制为1。该压缩模式达到了10:1的压缩率。这比通常的Gzip对于HTML网页的3:1或4:1的压缩率要好多了。这是由我们的webtable的行分布方式造成的:来自相同主机的网页被存储在相邻的位置。这使BentleyandMcIlroy算法可以识别出来自相同站点的大量固有模式。很多应用程序,不仅仅是webtable,选择的行名称使得类似数据会聚集在一起,因此达到了很好的压缩率。当我们在Bigtable中存储相同值的多个版本时压缩率会更好。
接下来看一下数据的大致流程。假设你需要通过某个特定的RowKey查询一行记录,首先Client端会连接,通过Zookeeper,Client能获知哪个Server管理-ROOT- Region。接着Client访问管理-ROOT-的Server,进而获知哪个Server管理.META.表。这两个信息Client只会获取一次并缓存起来。在后续的操作中Client会直接访问管理.META.表的Server,并获取Region分布的信息。一旦Client获取了这一行的位置信息,比如这一行属于哪个Region,Client将会缓存这个信息并直接访问HRegionServer。久而久之Client缓存的信息渐渐增多,即使不访问.META.表也能知道去访问哪个HRegionServer。我们通过在客户端库里进行预取来降低花费:当读取METADATA表时,它会读取不止一个tablet的信息。
现在看一下数据是怎样被写到实际的存储中去的。
Client发起了一个HTable.put(Put)请求给HRegionServer,
权限检查是通过从Chubby读取一个允许的写操作者列表(通常它直接存在于Chubby的客户端缓存中,即HRegionServer的缓存中)完成的。
HRegionServer会将请求匹配到某个具体的HRegion上面。
紧接着的操作时决定是否写WAL log。是否写WAL log由Client传递的一个标志决定,你可以设置这个标志:Put.writeToWAL(boolean)。WAL log文件是一个标准的Hadoop SequenceFile。在文件中存储了HLogKey,这些Keys包含了和实际数据对应的序列号,用途是当RegionServer崩溃以后能将WAL log中的数据同步到永久存储中去。
更新是提交到一个保存了redo记录的提交日志里。在这些更新里,最近提交的那些被保存到内存中一个叫做memtable的有序缓存里,老的更新则被保存在一系列的SSTable中。为了恢复一个tablet,tablet服务器从METADATA表中读取该tablet的元数据。元数据中包含组成该tablet的SSTable列表,以及一系列的redo点(指向那些包含该tablet数据的commit日志条目)的集合。Tablet服务器将所有SSTable的索引读入内存,然后通过应用那些从redo点开始以及提交的更新操作来重新构建memtable。
一个合法的变更会被写入到已提交日志里。操作的提交可以通过分组执行来提高大量小变更操作出现时的吞吐率,当该写操作提交后,它的内容会被插入到memtable里。???修改LogFile也需要磁盘IO所以要合理设计提交方式???????
Put数据会被保存到MemStore中,同时会检查MemStore是否已经满了,如果已经满了,则会触发一个Flush to Disk的请求。HRegionServer有一个独立的线程来处理Flush to Disk的请求,它负责将数据写成HFile文件,它也会存储最后写入的数据序列号,这样就可以知道哪些数据已经存入了永久存储的HDFS中。这个过程称为Minor(次)Compaction(压缩,因为Hfile文件以压缩形式存储)
降低tabletserver的内存使用。随着数据操作的不断增加,CellStore文件也不断增多,当文件个数达到门限时,rangeserver将执行mergingcompaction操作,该操作将选择部分cellstore文件与cellcache合并为一个较大的cellstore文件。此时的mergingcompaction操作中,所有文件和cache的数据被重新排序到新的cellstore文件中,并不执行数据的计算合并操作(每个插入的数据均有其要求的操作),因此所有的delete数据仍然存在于新的cellstore文件中。一旦这个compaction结束,这些CellStore和cache就可以丢弃了。
Bigtable循环扫描它所有的tablet,周期的对它们执行主compaction(合并所有cellstore,hfile,sstables,cache为一个文件,因为tablet大小有上限,因此生成的文件也有上限)。这些主compaction使得Bigtable可以回收那些被已删除的数据使用的资源。???
当一个读操作到达一个tablet服务器时,类似的首先要进行格式和权限检查。一个合法的读操作将会在一系列SSTable和memtable的一个合并视图上执行{因为SSTable一旦写入就不可变,这样就使得更新操作必须写到新的SSTable中,这样就导致同一个key值可能在多个SSTable中出现,这样读取时就必须读取多个SSTable才能得到它真实的最终状态,基本上要检查所有的SSTable,因为SSTable中key有序,所以过滤时比较快捷}。读操作时也会执行mergingcompaction。cellstore和SSTable都是按照key(什么key)排序的。
有一种特殊的mergingcompaction操作,发生在tablet分裂前,rangeserver会将该tablet的所有cellstore文件和cellcache合并为一个大的cellstore文件,该操作称为MajorCompaction。Majorcompaction在合并文件和cache过程中会对计算数据进行合并计算,所产生的新cellstore文件不再包含delete操作的数据。Majorcompaction可以有效地回收存储空间,其次,由于新产生的cellstore文件已经完成数据操作的合并运算,使得数据的读写操作性能更高。谁来进行Majorcompaction,Bigtable循环扫描它所有的tablet,周期的对它们执行主compaction。
BloomFilter
,一些有用的tuning参数也可以以局部性群组为单位进行设置。比如一个局部性群组可以声明为放入内存的。对于声明为放入内存的局部性群组的SSTable在需要时才会加载到tablet服务器的内存中。一旦加载之后,对于该局部性群组的访问就不需要访问磁盘。这个特点对于那些需要经常访问的小片数据很有用:比如在Bigtable内部我们将它应用在METADATA表的location列族上。
正如5.3节所描述的,一个读操作需要从组成该tablet的所有SSTable里读取。如果这些SSTable不在内存,就需要很多磁盘操作。通过让用户为某个局部性群组的SSTables指定对应的Bloomfilters,可以降低磁盘访问次数。一个BloomFilter允许我们查询对应的SSTable是否包含某个给定的row/column对的数据。对于特定的应用程序来说,只需要很少的tablet服务器内存来保存BloomFilter(位数组空间),但可以大大减少读操作所需要的磁盘操作。同时BloomFilter可以避免那些对于不存在的行列的查找访问磁盘。
BloomFilter:该索引部分采用BloomFilter技术,用于64k块内索引(大概率的正确的索引),目前保留,未实现;
块索引:该索引部分记录每64k块的索引,用于快速定位块,它的格式为rowkey (该块内的最大索引)+块在文件内的偏移;
Trailer(跟踪器):用于定位BloomFilter块,块索引部分的定位。系统读取这部分,获取BloomFilter和块索引的位置。
Chubby,zookeeper,hyperspace
Bigtable依赖于一个高可用的一致性分布式锁服务Chubby。Chubby由5个活动副本组成,其中的一个选为master处理请求。当大部分的副本运行并且可以相互通信时,该服务就是活的。Chubby使用Paxos算法来在出现失败时,保持副本的一致性。Chubby提供了一个由目录和小文件组成的名字空间。每个文件或者目录可以当作一个锁来使用,对于一个文件的读写是原子性的。Chubby的客户端库为Chubby文件提供一致性缓存。每个Chubby客户端维护这一个与Chubby服务的会话。如果在租约有效时间内无法更新会话的租约,客户端的会话就会过期。当一个客户端会话过期后,它会丢失所有的锁和打开的文件句柄。Chubby客户端也会在Chubby文件和目录上注册回调函数来处理这些变更或者会话的过期。
Bigtable使用Chubby来完成各种任务:保证任意时刻最多只有一个活动的master;存储Bigtable数据的bootstraplocation(参见5.2节);发现tablet服务器以及finalizetablet服务器的死亡(参见5.2节);保存Bigtableschema信息(每个表的列族信息);存储访问控制列表。如果chubby在一段时间内不可用,Bigtable也会不可用。我们最近在使用了11个chubby实例的14个Bigtable集群进行了测量。由于Chubby不可用而造成的存储在Bigtable上的数据不可用的平均概率是0.0047%。对于单个集群来说,由于Chubby不可用造成的这个概率是0.0326%。
Hyperspace是类似GoogleBigtable系统中的Chubby功能。Chubby在GoogleBigtable系统中主要实现两个功能,一是为Bigtable提供一个粗粒度锁服务;其次是提供高效,可靠的主机选举服务。由于Bigtable的粗粒度锁服务往往构建在具有统一namespace的文件系统上(早期在Database上),因此它同时具备轻量级的存储功能,可以存储一些元数据信息??。如图3-1所示,Hyperspace作为一个服务器族存在,通常情况下,一个HyperspaceServer族一般由5或11台服务器组成,他们基于Paxos选举算法,在服务器中选举出一个ActiveServer,其它的服务器作为StandbyServers存在,在系统运行过程中,ActiveServer和StandbyServers之间会同步状态和数据(数据经常由文件系统或数据库自己完成)。从实现的角度来看,实现1+1的备份方式,也可以作为HyperspaceHA 的一种实现方法,但显然可靠性要低于Chubby的实现方式。
目前,Hyperspace基于BSDDB构建它的锁服务,通过BSDDBAPI,Hyperspace构建一个namespace,该namespace中包含目录和文件节点,每一个目录或文件可以赋予许多的属性,如锁的属性。HyperspaceClient是访问Hyperspace的客户端模块。Hypertable中的其它系统模块通过该Client与HyperspaceServer建立Session链接,通过该链接完成Hyperspace中目录和文件的操作(如读,写,锁操作等)。Session链接采用租约机制,客户端必须定期更新租约期限,如果到期未更新,Hyperspace认为客户端outof service,随机释放客户端所占用的文件和锁资源。HyperspaceClient为客户端应用提供了callback方式的事件通知机制,客户端应用可以通过Session链接,向Hyperspace注册自己关注的对象(目录或文件)的有关事件,也可以向Session本身注册时间callback。如文件,或目录的删除时间,锁操作事件,Session的终止事件等。
由于Hyperspace的设计满足高可靠性要求,因此它除了提供粗粒度锁服务以外,还承担着部分的小数据量的存储任务。如2.2.1中提到的Metadata0tablet location就存储在Hyperspace,作为整个Hypertabletablets索引的根节点。另外,Hyperspace也会存储Metadataschema,accesscontrollist(目前未实现);同时,由于Hypersapce提供了Hypertable系统中所有主机节点的锁服务,所有节点会在Hyperspace的namespace中创建自己的主机节点,并获取相应的锁,因此Hyperspace同时充当了记录Hypertable系统中主机状态的任务,并且根据客户端注册的callback信息分发相应的状态改变事件。
由此可见,Hyperspace作为一个需要设计成具有高可用性的子系统,如果Hyperspace停止工作,Hypertable也就对外不能提供服务了。
HMaster
HBase启动的时候
HMaster负责分配Region(tablet=row range)给HRegionServer,这其中当然也包括-ROOT-表和.META.表的Region。
接下来HRegionServer打开这个Region并创建一个HRegion对象。当HRegion打开以后,它给每个table的每个HColumnFamily创建一个Store实例。每个Store实例拥有一个或者多个StoreFile实例。StoreFile对HFile做了轻量级的包装。除了Store实例以外,每个HRegion还拥有一个MemStore实例和一个HLog实例。现在我们就可以看看这些实例是如何在一起工作的,遵循什么样的规则以及这些规则的例外。
主服务器知道当前有哪些活跃的子表服务器 ,
还知道哪些子表分配到哪些子表服务器,哪些以及哪些子表没有被分配.当一个子表没有被分配
到服务器,同时又有一个服务器的空闲空间足够装载该子表,主服务器就给这个子表服务器材发
送一个装载请求,把子表分配给这个服务器。
master主要负责
处理元数据操作,如表的创建、和删除;
管理rangeserver池,指派rangeserver所维护的rangeserver,并实现rangeserver的loadbalance调度(目前使用roundrobin算法,还为基于服务器负荷调度);
检测rangeserver加入/离开Hypertable系统;
回收DFS的discarded文件
处理表schema变化,如创建新的columnfamily操作(目前为实现)
Master并不直接处理hypertableclient提交的数据,由于metadata表数据作为一个普通的table由系统中的rangeserver维护,而且metadata0的location存储在hyperspace中;因此在hypertable系统中出现masterserver的短暂OOS(outof service)是可以容忍的,(Master并不维护数据,只是管理控制作用,所以断开或重置影响不大)。在masterserver短暂的OOS期间,并不妨碍hypertableclient对已经存在的表的读写操作。
Bigtable使用Chubby来追踪tablet服务器的状态。当一个tablet服务器启动时,它创建并获取一个在给定的Chubby目录上的唯一命名的文件的独占锁。Master通过监控这个目录(服务器目录)来发现tablet服务器。Tablet服务器如果丢失了它的独占锁(比如由于网络问题)就停止它上面的tablet服务。一个tablet服务器会尝试重新获取在该文件上的独占锁,只要该文件还存在。如果该文件也不存在了,那么tablet服务器就永远无法提供该服务了。当一个tablet服务器停止(比如集群管理系统从集群中删除了该tablet服务器机器),它就会尝试释放这个锁这样master就可以更快地重新分配它上面的tablets了。
Master负责检测一个tablet服务器何时停止提供服务,以尽快重新安排它的tablets。为了进行检测,master周期性的向每个tablet服务器询问它的锁状态。如果一个tablet服务器报告它丢失了它的锁,或者master在它的几次尝试中不能到达一个服务器,master会尝试获取该服务器的锁。如果master可以获取该锁,那么Chubby就是活的,而tablet服务器要么是死的要么因为某些问题而无法到达Chubby,那么master就可以通过删除它的server文件来使得该tablet服务器永远都不能提供服务。一旦一个服务器的文件被删除了,master就可以将之前分配给该服务器的所有tablet移到那些为分配的tablet集合中。为了保证一个Bigtable集群不会因为与master和Chubby间的网络问题而变得脆弱,如果master的Chubby会话过期了,master会自杀。然而,如前面所述,master的失败并不会改变tablet服务器的tablet分配。
当master被集群管理系统启动后,在它可以改变tablets之前需要知道它们当前的分配状态。Master在启动时执行如下步骤:1.master在Chubby获得一个唯一的master锁,该锁可以防止出现同时生成多个master实例。2.master扫描Chubby的服务器目录来找到所有活着的服务器。3.master与活着的tablet服务器通信来发现每个服务器安排了哪些tablet。4.master扫描METADATA表来找到tablet集合。当扫描中碰到一个未被分配的tablet,master会将它添加到未分配的tablet集合,并对这个tablet进行分配。
在METADATA的tablets未被分配之前,对于METADATA的扫描不能进行。因此在开始扫描之前(步骤4),如果在步骤3没有发现对于roottablet的分配,master会将roottablet添加到未分配tablets集合中。这个添加将会使roottablet变得可以被分配。因为roottablet包含所有METADATAtablets的名称,master当扫描完roottablet后就能知道METADATA的所有的tablets。
只有当一个表被创建,现有的两个tablets合并为一个,或者一个tablet被分割为一个时,现有的tablet集合才会发生变化。Master能够追踪所有的这些变化,因为它负责维护它们。Tablet分割需要特殊对待因为它们是由一个tablet服务器启动的。Tablet服务器通过将新的tablet的信息记录到METADATA表中来提交这个分割。当分割提交后,它会通知master。为了防止分割通知丢失(因为tablet服务器或者master死了),当master向tablet服务器请求加载刚刚发生分割的那个tablet时,它会检测到这个新的tablet。Tablet服务器会将这个分割通知master,因为它在METADATA表中的tablet键值仅包含了master让它加载的那个tablet的一部分。{假设master没有收到这个分割通知,那么它所记录的tablet与METADATA表中的就是不一致的,这样在它让tablet服务器加载该tablet时就会发现该不一致}。
有时候你会发现一些oldlogfile.log文件(在大多数情况下你可能看不到这个文件),这个文件在一种异常情况下会被产生。这个异常情况就是HMaster对log文件的访问情况产生了怀疑,它会产生一种称作“log splits”的结果。有时候HMaster会发现某个log文件没人管了,就是说任何一个HRegionServer都不会管理这个log文件(有可能是原来管理这个文件的HRegionServer挂了),HMaster会负责分割这个log文件(按照它们归属的Region),并把那些HLogKey写到一个叫做oldlogfile.log的文件中,并按照它们归属的Region直接将文件放到各自的Region文件夹下面。各个HRegion会从这个文件中读取数据并将它们写入到MemStore中去,并开始将数据Flush to Disk。然后就可以把这个oldlogfile.log文件删除了。
注意:有时候你可能会发现另一个叫做oldlogfile.log.old的文件,这是由于HMaster做了重复分割log文件的操作并发现oldlogfile.log已经存在了。这时候就需要和HRegionServer以及HMaster协商到底发生了什么,以及是否可以把old的文件删掉了。从我目前遇到的情况来看,old文件都是空的并且可以被安全删除的。
有一件事情前面一直没有提到,那就是Region的分割。当一个Region的数据文件不断增长并超过一个最大值的时候(你可以配置这个最大值 hbase.hregion.max.filesize),这个Region会被切分成两个。这个过程完成的非常快,因为原始的数据文件并不会被改变,系统只是简单的创建两个Reference文件指向原始的数据文件。每个Reference文件管理原始文件一半的数据。Reference文件名字是一个ID,它使用被参考的Region的名字的Hash作为前缀。例如:1278437856009925445.3323223323。Reference文件只含有非常少量的信息,这些信息包括被分割的原始Region的Key以及这个文件管理前半段还是后半段。HBase使用HalfHFileReader类来访问Reference文件并从原始数据文件中读取数据。前面的架构图只并没有画出这个类,因为它只是临时使用的。只有当系统做Compaction的时候原始数据文件才会被分割成两个独立的文件并放到相应的Region目录下面,同时原始数据文件和那些Reference文件也会被清除。
前面dump出来的文件结构也证实了这个过程,在每个Table的目录下面你可以看到一个叫做compaction.dir的目录。这个文件夹是一个数据交换区,用于存放split和compact Region过程中生成的临时数据。
HFile(SSTable,cellstore) (BigTable,HyberTable按照局部性群组(包含多个列族)组织一个SSTable,cellstore文件,并进行压缩,而HBase中没有局部性群组的概念,直接按照列族组织HFile文件和压缩。
5. 实现
Bigtable实现有3个主要的组件:每个客户端需要链接的库,一个master服务器,很多tablet服务器。tablet服务器可以从一个集群中动态添加(或者删除)来适应工作负载的动态变化。
Master负责将tablet分配到tablet服务器,检测tablet服务器的添加和过期,平衡tablet服务器负载,GFS文件的垃圾回收。另外,它还会处理schema的变化,比如表和列族的创建。
每个tablet服务器管理一个tablets集合(通常每个tablet服务器有10到1000个tablet)。Tablet服务器负责它已经加载的那些tablet的读写请求,也会将那些过于大的tablet进行分割。{tablet服务器本身实际上也是GFS的用户,它们只是负载它加载的那些tablet的管理,这些tablet的物理存储并不一定存放在管理它的服务器上,底层的存储是由GFS完成的,tablet服务器可以只调用它的接口来完成相应任务。而METADATA表中的位置信息应该是指某个tablet由哪个tablet服务器管理,而不是物理上存储在哪个机器上。}
正如很多单master的分布式存储系统,客户端数据的移动并不会经过master:客户端直接与tablet服务器进行通信来进行读写。因为Bigtable客户端并不依赖于master得到tablet的位置信息,大部分的客户端从来不会于master通信。所以,常都是负载很轻的。通过chubby获得rootable位置,存储META信息的服务器也是region Server.
Bigtable集群存储了大量的表。每个表由一系列的tablet组成,每个tablet包含一个行组的所有相关数据。一开始,每个表由一个tablet组成。随着表格的增长,它会自动分割成多个tablet,它们大小默认是100-200MB。
METADATA表的每一个行关键字(由tablet所属的表标识符和它的结束行组成)下存储了一个tablet的位置。每个METADATA行在内存中大概存储了1KB数据。我们限制METADATA的tablet的大小为128MB,我们的三级层次结构足以用来寻址2^34个tablet(如果tablet按照128MB算,就是2^61字节.)每个tablet又将近1KB数据,这样算起来存储这些元信息就需要4TB的数据(因为机器数可以缩减到2个,所以该METADATA表也不可能全部放入内存,而是采用与普通的表一样的存储方式,放在GFS上。但是会把某些特殊信息放在内存中,比如第6节提到的:METADATA中的location列族会被放入内存 }。master实际中通 我们也会将一些额外信息存放在METADATA表里,包括对于每个tablet有关的事件日志(比如一个服务器何时开始提供服务)。这些信息对于调试和性能分析很有帮助。客户端库会缓存tablet的位置信息。如果客户端不知道某个tablet的信息,或者发现缓存的位置信息是错误的,那么它就会递归地在tablet位置存储结构中查找。如果客户端缓存是空的,定位算法需要三次网络往返,包括从Chubby的一次读操作。如果客户端缓存是陈旧的,定位算法将需要多达6次的往返,因为陈旧的缓存值只有在不命中时才会被发现(假设METADATA tablet并不会经常移动)。尽管Tablet位置信息是存储在内存中(如上所述),不需要访问GFS,但是我们还是通过在客户端库里进行预取来降低花费:当读取METADATA表时,它会读取不止一个tablet的信息。
同一个ROWKEY可以存放在不同的HFILE中,因为三维结构组成一个HFILE,而ROWKEY只是一维。
每个SSTable内部包含一系列的块(通常每个块是64KB大小,但是该大小是可配置的)。一个块索引(保存在SSTable的尾部)是用来定位block的,当SSTable打开时该索引会被加载到内存。一次查找可以通过一次磁盘访问完成:首先通过在内存中的索引进行一次二分查找找到相应的块,然后从磁盘中读取该块。另外,一个SSTable可以被完全映射到内存,这样就不需要我们接触磁盘就可以执行所有的查找和扫描
三/四维逻辑结构的使用方法
HBase主要解决的是数据的组织和存储策略问题(逻辑上只有一个大表BigTable)
表的分裂沿行区间(RowRange)切分,一张表在生长过程中,在一定的行区间被分裂为多个行区间(RowRange),每一个行区间成文一个新的小表(Tablet)(100-200MB)。行组{rowrange,将它翻译为行组,一个rowrange可能由多个行组成}是可以动态划分的。每个行组叫做一个tablet(包含存储keyValue的多个HFile,以及维护HFile的数据结构),是数据存放以及负载平衡的单位。在云计算平台中,分裂出来的Tablet基于LoadBalance原则被分布在不同的主机上维护,从而,对表的操作演化成对各小表(Tablet)并发的操作,处理效率显然高于对整个大表的操作。用户在使用HBase时合理的选择RowKey,可以得到更好的数据处理效率(因为相邻的RowKey可以存放在同一个行组中。 数据在存储前经过了排序(ROWKEY)和压缩(有HBase自动维护,可以指定排序和压缩方法),表中的数据类型都被串行化为字符数据。KEY中: ROWKEY按从文件头按升序排, Column保持schema定义的顺序???, 列族:列族是存储文件的组织结构,并且在scheme中有反映,而Qualifer则没有。
存储在同一个列族的数据通常是相同类型的(我们将同一列族的数据压缩在一块)。
在数据能够存储到某个列族的列关键字下之前,必须首先要创建该列族。我们假设在一个表中不同列族的数目应该比较小(最多数百个),而且在操作过程中这些列族应该很少变化。与之相比,一个表的列数目可以没有限制。
列族中的列一般具有相同的类型属性。系统在存储和访问表时,都是以ColumnFamily为单元组织;
垃圾回收机制:
二级缓存
为了读性能进行缓存
为了提高读性能,tablet服务器使用一个二级缓存。
扫描缓存是一个用来缓存由SSTable返回给tablet服务器的key-value对的高级缓存。扫描缓存主要用于那些倾向于重复读取相同数据的应用,
块缓存是用来缓存从GFS读取的SSTable块的低级缓存。块缓存则用于那些倾向于从最近读取的数据的邻近位置读取数据的应用(比如顺序读,或者读取一个热点行内的相同局部性群组里的不同列值????)。
关于HFile中KeyType的类型和使用方法:
SStable中的一行数据一旦写入便不可更改。
比如我们从SSTable中读取时,不需要对文件系统的访问进行任何同步,因为该行数据永远不会改变。唯一的可以读写的可变数据结构就是memtable。为了减少在memtable读取时的竞争,我们对每个memtable行进行写时复制,这就允许读写并行处理。
已删除数据的清除就转换成了对于过时的SSTable的垃圾回收。每个tablet的SSTables会注册在METADATA表中。Master服务器采用“标记-删除”的垃圾回收方式删除SSTable集合中废弃的SSTable。
最后,SSTable的不可变性使得我们可以快速分割tablet。我们让子tablets共享父tablet的SSTables,而不是为每个子tablet生成新的SSTables。