﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-上善若水-随笔分类-HBase</title><link>http://www.blogjava.net/DLevin/category/54892.html</link><description>In general the OO style is to use a lot of little objects with a lot of little methods that give us a lot of plug points for overriding and variation.
To do is to be -Nietzsche, To bei is to do -Kant, Do be do be do -Sinatra</description><language>zh-cn</language><lastBuildDate>Sun, 15 Nov 2015 05:16:24 GMT</lastBuildDate><pubDate>Sun, 15 Nov 2015 05:16:24 GMT</pubDate><ttl>60</ttl><item><title>SSTable详解</title><link>http://www.blogjava.net/DLevin/archive/2015/09/25/427481.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Thu, 24 Sep 2015 17:35:00 GMT</pubDate><guid>http://www.blogjava.net/DLevin/archive/2015/09/25/427481.html</guid><wfw:comment>http://www.blogjava.net/DLevin/comments/427481.html</wfw:comment><comments>http://www.blogjava.net/DLevin/archive/2015/09/25/427481.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/DLevin/comments/commentRss/427481.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/DLevin/services/trackbacks/427481.html</trackback:ping><description><![CDATA[<h2> 前记</h2>几年前在读Google的BigTable论文的时候，当时并没有理解论文里面表达的思想，因而囫囵吞枣，并没有注意到SSTable的概念。再后来开始关注HBase的设计和源码后，开始对BigTable传递的思想慢慢的清晰起来，但是因为事情太多，没有安排出时间重读BigTable的论文。在项目里，我因为自己在学HBase，开始主推HBase，而另一个同事则因为对Cassandra比较感冒，因而他主要关注Cassandra的设计，不过我们两个人偶尔都会讨论一下技术、设计的各种观点和心得，然后他偶然的说了一句：Cassandra和HBase都采用SSTable格式存储，然后我本能的问了一句：什么是SSTable？他并没有回答，可能也不是那么几句能说清楚的，或者他自己也没有尝试的去问过自己这个问题。然而这个问题本身却一直困扰着我，因而趁着现在有一些时间深入学习HBase和Cassandra相关设计的时候先把这个问题弄清楚了。<br /><h2>SSTable的定义</h2>要解释这个术语的真正含义，最好的方法就是从它的出处找答案，所以重新翻开BigTable的论文。在这篇论文中，最初对SSTable是这么描述的（第三页末和第四页初）：<br /><fieldset><legend>SSTable</legend>

	
		
		
	
	
		<div class="page" title="Page 3">
			<div class="layoutArea">
				<div class="column">
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">The Google </span><span style="font-size: 10.000000pt; font-family: 'Times'; font-style: italic"><strong>SSTable</strong> </span><span style="font-size: 10.000000pt; font-family: 'Times'">file format is used internally to
store Bigtable data. An <strong>SSTable</strong> provides a <strong>persistent,
ordered immutable map from keys to values, where both
keys and values are arbitrary byte strings.</strong> Operations are
provided to look up the value associated with a specified</span><span style="font-size: 10.000000pt; font-family: 'Times'"> key, and to iterate over all key/value pairs in a specified
key range. Internally, each SSTable contains a sequence
of blocks (typically each block is 64KB in size, but this
is configurable). A block index (stored at the end of the
SSTable) is used to locate blocks; the index is loaded
into memory when the SSTable is opened. A lookup
can be performed with a single disk seek: we first find
the appropriate block by performing a binary search in
the in-memory index, and then reading the appropriate
block from disk. Optionally, an SSTable can be completely mapped into memory, which allows us to perform
lookups and scans without touching disk.
</span>
				
			
		</p>
	

				</div></div></div></fieldset><span style="font-family: Comic Sans MS; font-size: 10pt;">简单的非直译：</span><span style="font-family: Comic Sans MS; font-size: 10pt;"><br /></span><span style="font-family: Comic Sans MS; font-size: 10pt;">SSTable是Bigtable内部用于数据的文件格式，它的格式为文件本身就是一个排序的、不可变的、持久的Key/Value对Map，其中Key和value都可以是任意的byte字符串。使用Key来查找Value，或通过给定Key范围遍历所有的Key/Value对。每个SSTable包含一系列的Block（一般Block大小为64KB，但是它是可配置的），在SSTable的末尾是Block索引，用于定位Block，这些索引在SSTable打开时被加载到内存中，在查找时首先从内存中的索引二分查找找到Block，然后一次磁盘寻道即可读取到相应的Block。还有一种方案是将这个SSTable加载到内存中，从而在查找和扫描中不需要读取磁盘。</span><br /><br />这个貌似就是HFile第一个版本的格式么，贴张图感受一下：<br /><img src="http://www.blogjava.net/images/blogjava_net/dlevin/image0080.jpg" alt="" height="160" border="0" width="554" /><br />在HBase使用过程中，对这个版本的HFile遇到以下一些问题（参考<a href="http://hbase.apache.org/book.html#_hfile_format_2">这里</a>）：<br />1. 解析时内存使用量比较高。<br />2. Bloom Filter和Block索引会变的很大，而影响启动性能。具体的，Bloom Filter可以增长到100MB每个HFile，而Block索引可以增长到300MB，如果一个HRegionServer中有20个HRegion，则他们分别能增长到2GB和6GB的大小。HRegion需要在打开时，需要加载所有的Block索引到内存中，因而影响启动性能；而在第一次Request时，需要将整个Bloom Filter加载到内存中，再开始查找，因而Bloom Filter太大会影响第一次请求的延迟。<br />而HFile在版本2中对这些问题做了一些优化，具体会在HFile解析时详细说明。<br /><h2>SSTable作为存储使用</h2>继续BigTable的论文往下走，在5.3 Tablet Serving小节中这样写道（第6页）：<br /><fieldset><legend>Tablet Serving</legend>

	
		
		
	
	
		<div class="page" title="Page 6">
			<div class="layoutArea">
				<div class="column">
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">Updates are committed to a commit
log that stores redo records. Of these updates, the recently committed ones are stored in memory in a sorted
buffer called a </span><span style="font-size: 10.000000pt; font-family: 'Times'; font-style: italic">memtable</span><span style="font-size: 10.000000pt; font-family: 'Times'">; the older updates are stored in a
sequence of SSTables. To recover a tablet, a tablet server</span><span style="font-size: 10.000000pt; font-family: 'Times'"> reads its metadata from the </span><span style="font-size: 10.000000pt; font-family: 'Courier~45'">METADATA </span><span style="font-size: 10.000000pt; font-family: 'Times'">table. This metadata contains the list of SSTables that comprise a tablet
and a set of a redo points, which are pointers into any
commit logs that may contain data for the tablet. The
server reads the indices of the SSTables into memory and
reconstructs the memtable by applying all of the updates
that have committed since the redo points. </span><br /></p><div class="page" title="Page 6"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'Times'">When a write operation arrives at a tablet server, the
server checks that it is well-formed, and that the sender
is authorized to perform the mutation. Authorization is
performed by reading the list of permitted writers from a
Chubby file (which is almost always a hit in the Chubby
client cache). A valid mutation is written to the commit
log. Group commit is used to improve the throughput of
lots of small mutations [13, 16]. After the write has been
committed, its contents are inserted into the memtable. <br /></span></p>
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">When a read operation arrives at a tablet server, it is
similarly checked for well-formedness and proper authorization. A valid read operation is executed on a merged
view of the sequence of SSTables and the memtable.
Since the SSTables and the memtable are lexicographically sorted data structures, the merged view can be
formed efficiently.
</span></p>
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">Incoming read and write operations can continue
while tablets are split and merged.
</span></p>
				</div>
			</div>
		</div>
	

				</div>
			</div>
		</div>
	
</fieldset><span style="font-family: Comic Sans MS; font-size: 10pt;">第一段和第三段简单描述，非翻译：<br />在新数据写入时，这个操作首先提交到日志中作为redo纪录，最近的数据存储在内存的排序缓存memtable中；旧的数据存储在一系列的SSTable
中。在recover中，tablet 
server从METADATA表中读取metadata，metadata包含了组成Tablet的所有SSTable（纪录了这些SSTable的元
数据信息，如SSTable的位置、StartKey、EndKey等）以及一系列日志中的redo点。Tablet 
Server读取SSTable的索引到内存，并replay这些redo点之后的更新来重构memtable。<br />在读时，完成格式、授权等检查后，读会同时读取SSTable、memtable（HBase中还包含了BlockCache中的数据）并合并他们的结果，由于SSTable和memtable都是字典序排列，因而合并操作可以很高效完成。<br /></span><h2>SSTable在Compaction过程中的使用</h2>在BigTable论文5.4 Compaction小节中是这样说的：<br /><fieldset><legend>Compaction</legend>

	
		
		
	
	
		<div class="page" title="Page 6">
			<div class="layoutArea">
				<div class="column">
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">As write operations execute, the size of the memtable increases. When the memtable size reaches a threshold, the
memtable is frozen, a new memtable is created, and the
frozen memtable is converted to an SSTable and written
to GFS. This </span><span style="font-size: 10.000000pt; font-family: 'Times'; font-style: italic">minor compaction </span><span style="font-size: 10.000000pt; font-family: 'Times'">process has two goals:
it shrinks the memory usage of the tablet server, and it
reduces the amount of data that has to be read from the
commit log during recovery if this server dies. Incoming read and write operations can continue while compactions occur.
</span></p>
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">Every minor compaction creates a new SSTable. If this
behavior continued unchecked, read operations might
need to merge updates from an arbitrary number of
SSTables. Instead, we bound the number of such files
by periodically executing a </span><span style="font-size: 10.000000pt; font-family: 'Times'; font-style: italic">merging compaction </span><span style="font-size: 10.000000pt; font-family: 'Times'">in the
background. A merging compaction reads the contents
of a few SSTables and the memtable, and writes out a
new SSTable. The input SSTables and memtable can be
discarded as soon as the compaction has finished.
</span></p>
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">A merging compaction that rewrites all SSTables
into exactly one SSTable is called a </span><span style="font-size: 10.000000pt; font-family: 'Times'; font-style: italic">major compaction</span><span style="font-size: 10.000000pt; font-family: 'Times'">.
SSTables produced by non-major compactions can contain special deletion entries that suppress deleted data in
older SSTables that are still live. A major compaction,
on the other hand, produces an SSTable that contains
no deletion information or deleted data. Bigtable cycles through all of its tablets and regularly applies major
compactions to them. These major compactions allow
Bigtable to reclaim resources used by deleted data, and
also allow it to ensure that deleted data disappears from
the system in a timely fashion, which is important for
services that store sensitive data.
</span></p>
				</div>
			</div>
		</div>
	
</fieldset><span style="font-family: Comic Sans MS; font-size: 10pt;">随着memtable大小增加到一个阀值，这个memtable会被冻住而创建一个新的memtable以供使用，而旧的memtable会转换成一个SSTable而写道GFS中，这个过程叫做minor compaction。这个minor compaction可以减少内存使用量，并可以减少日志大小，因为持久化后的数据可以从日志中删除。</span>在minor compaction过程中，可以继续处理读写请求。<br /><span style="font-size: 10pt; font-family: Comic Sans MS;">每次minor compaction会生成新的SSTable文件，如果SSTable文件数量增加，则会影响读的性能，因而每次读都需要读取所有SSTable文件，然后合并结果，因而对SSTable文件个数需要有上限，并且时不时的需要在后台做merging compaction，这个merging compaction读取一些SSTable文件和memtable的内容，并将他们合并写入一个新的SSTable中。当这个过程完成后，这些源SSTable和memtable就可以被删除了。<br />如果一个merging compaction是合并所有SSTable到一个SSTable，则这个过程称做major compaction。一次major compaction会将mark成删除的信息、数据删除，而其他两次compaction则会保留这些信息、数据（mark的形式）。Bigtable会时不时的扫描所有的Tablet，并对它们做major compaction。这个major compaction可以将需要删除的数据真正的删除从而节省空间，并保持系统一致性。</span><br /><h2>SSTable的locality和In Memory</h2><span style="font-family: Comic Sans MS; font-size: 10pt;">在Bigtable中，它的本地性是由Locality group来定义的，即多个column family可以组合到一个locality group中，在同一个Tablet中，使用单独的SSTable存储这些在同一个locality group的column family。HBase把这个模型简化了，即每个column family在每个HRegion都使用单独的HFile存储，HFile没有locality group的概念，或者一个column family就是一个locality group。</span><br /><br /><span style="font-family: Comic Sans MS; font-size: 10pt;">在Bigtable中，还可以支持在locality group级别设置是否将所有这个locality group的数据加载到内存中，在HBase中通过column family定义时设置。这个内存加载采用延时加载，主要应用于一些小的column family，并且经常被用到的，从而提升读的性能，因而这样就不需要再从磁盘中读取了。</span><br /><h2>SSTable压缩</h2>Bigtable的压缩是基于locality group级别：<br /><fieldset><legend>Compression</legend>

	
		
		
	
	
		<div class="page" title="Page 7">
			<div class="layoutArea">
				<div class="column">
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">Clients can control whether or not the SSTables for a
locality group are compressed, and if so, which compression format is used. The user-specified compression format is applied to each SSTable block (whose size
is controllable via a locality group specific tuning parameter). Although we lose some space by compressing each block separately, we benefit in that small portions of an SSTable can be read without decompressing the entire file. Many clients use a two-pass custom
compression scheme. The first pass uses Bentley and
McIlroy&#8217;s scheme [6], which compresses long common
strings across a large window. The second pass uses a
fast compression algorithm that looks for repetitions in
a small 16 KB window of the data. Both compression
passes are very fast&#8212;they encode at 100&#8211;200 MB/s, and
decode at 400&#8211;1000 MB/s on modern machines.</span></p></div></div></div></fieldset><span style="font-family: Comic Sans MS; font-size: 10pt;">Bigtable的压缩以SSTable中的一个Block为单位，虽然每个Block为压缩单位损失一些空间，但是采用这种方式，我们可以以Block为单位读取、解压、分析，而不是每次以一个&#8220;大&#8221;的SSTable为单位读取、解压、分析。</span><br /><h2>SSTable的读缓存</h2>为了提升读的性能，Bigtable采用两层缓存机制：<br /><fieldset><legend>Caching for read performance</legend>

	
		
		
	
	
		<div class="page" title="Page 7">
			<div class="layoutArea">
				<div class="column">
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">To improve read performance, tablet servers use two levels of caching. The Scan Cache is a higher-level cache
that caches the key-value pairs returned by the SSTable
interface to the tablet server code. The Block Cache is a
lower-level cache that caches SSTables blocks that were
read from GFS. The Scan Cache is most useful for applications that tend to read the same data repeatedly. The
Block Cache is useful for applications that tend to read
data that is close to the data they recently read (e.g., sequential reads, or random reads of different columns in
the same locality group within a hot row).</span></p></div></div></div></fieldset>两层缓存分别是：<br />1. High Level，缓存从SSTable读取的Key/Value对。提升那些倾向重复的读取相同的数据的操作（引用局部性原理）。<br />2. Low Level，BlockCache，缓存SSTable中的Block。提升那些倾向于读取相近数据的操作。<br /><h2>Bloom Filter</h2>前文有提到Bigtable采用合并读，即需要读取每个SSTable中的相关数据，并合并成一个结果返回，然而每次读都需要读取所有SSTable，自然会耗费性能，因而引入了Bloom Filter，它可以很快速的找到一个RowKey不在某个SSTable中的事实（注：反过来则不成立）。<br /><fieldset><legend>Bloom Filter</legend>

	
		
		
	
	
		<div class="page" title="Page 7">
			<div class="layoutArea">
				<div class="column">
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">As described in Section 5.3, a read operation has to read
from all SSTables that make up the state of a tablet.
If these SSTables are not in memory, we may end up
doing many disk accesses. We reduce the number of
accesses by allowing clients to specify that Bloom fil-
ters [7] should be created for SSTables in a particu-
lar locality group. A Bloom filter allows us to ask
whether an SSTable might contain any data for a spec-
ified row/column pair. For certain applications, a small
amount of tablet server memory used for storing Bloom
filters drastically reduces the number of disk seeks re-
quired for read operations. Our use of Bloom filters
also implies that most lookups for non-existent rows or
columns do not need to touch disk.
</span></p>
				</div>
			</div>
		</div>
	
</fieldset><h2>SSTable设计成Immutable的好处</h2>在SSTable定义中就有提到SSTable是一个Immutable的order map，这个Immutable的设计可以让系统简单很多：<br /><fieldset><legend>Exploiting Immutability</legend>

	
		
		
	
	
		<div class="page" title="Page 8">
			<div class="layoutArea">
				<div class="column">
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">Besides the SSTable caches, various other parts of the
Bigtable system have been simplified by the fact that all </span><span style="font-size: 10.000000pt; font-family: 'Times'">of the SSTables that we generate are immutable. For example, we do not need any synchronization of accesses
to the file system when reading from SSTables. As a result, concurrency control over rows can be implemented
very efficiently. The only mutable data structure that is
accessed by both reads and writes is the memtable. To reduce contention during reads of the memtable, we make
each memtable row copy-on-write and allow reads and
writes to proceed in parallel.
</span>
					</p><div class="page" title="Page 8"><div class="layoutArea"><div class="column"><p><span style="font-size: 10.000000pt; font-family: 'Times'">Since SSTables are immutable, the problem of permanently removing deleted data is transformed to garbage
collecting obsolete SSTables. Each tablet&#8217;s SSTables are
registered in the </span><span style="font-size: 10.000000pt; font-family: 'Courier~45'">METADATA </span><span style="font-size: 10.000000pt; font-family: 'Times'">table. The master removes
obsolete SSTables as a mark-and-sweep garbage collection [25] over the set of SSTables, where the </span><span style="font-size: 10.000000pt; font-family: 'Courier~45'">METADATA
</span><span style="font-size: 10.000000pt; font-family: 'Times'">table contains the set of roots.
</span></p>
					<p><span style="font-size: 10.000000pt; font-family: 'Times'">Finally, the immutability of SSTables enables us to
split tablets quickly. Instead of generating a new set of
SSTables for each child tablet, we let the child tablets
share the SSTables of the parent tablet.
</span></p>
				</div>
			</div>
		</div>
	
</div></div></div></fieldset><span style="font-family: Comic Sans MS; font-size: 10pt;">关于Immutable的优点有以下几点：</span><br /><span style="font-family: Comic Sans MS; font-size: 10pt;">1. 在读SSTable是不需要同步。读写同步只需要在memtable中处理，为了减少memtable的读写竞争，Bigtable将memtable的row设计成copy-on-write，从而读写可以同时进行。</span><br /><span style="font-family: Comic Sans MS; font-size: 10pt;">2. 永久的移除数据转变为SSTable的Garbage Collect。每个Tablet中的SSTable在METADATA表中有注册，master使用mark-and-sweep算法将SSTable在GC过程中移除。</span><br /><span style="font-family: Comic Sans MS; font-size: 10pt;">3. 可以让Tablet Split过程变的高效，我们不需要为每个子Tablet创建新的SSTable，而是可以共享父</span><span style="font-family: Comic Sans MS; font-size: 10pt;">Tablet的SSTable。</span><img src ="http://www.blogjava.net/DLevin/aggbug/427481.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/DLevin/" target="_blank">DLevin</a> 2015-09-25 01:35 <a href="http://www.blogjava.net/DLevin/archive/2015/09/25/427481.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入HBase架构解析（二）</title><link>http://www.blogjava.net/DLevin/archive/2015/08/22/426950.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Sat, 22 Aug 2015 11:40:00 GMT</pubDate><guid>http://www.blogjava.net/DLevin/archive/2015/08/22/426950.html</guid><wfw:comment>http://www.blogjava.net/DLevin/comments/426950.html</wfw:comment><comments>http://www.blogjava.net/DLevin/archive/2015/08/22/426950.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/DLevin/comments/commentRss/426950.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/DLevin/services/trackbacks/426950.html</trackback:ping><description><![CDATA[<h2> 前言</h2>这是<a href="http://www.blogjava.net/DLevin/archive/2015/08/22/426877.html">《深入HBase架构解析（一）》</a>的续，不多废话，继续。。。。<br /><h2>HBase读的实现</h2>通过前文的描述，我们知道在HBase写时，相同Cell(RowKey/ColumnFamily/Column相同)并不保证在一起，甚至删除一个Cell也只是写入一个新的Cell，它含有Delete标记，而不一定将一个Cell真正删除了，因而这就引起了一个问题，如何实现读的问题？要解决这个问题，我们先来分析一下相同的Cell可能存在的位置：首先对新写入的Cell，它会存在于MemStore中；然后对之前已经Flush到HDFS中的Cell，它会存在于某个或某些StoreFile(HFile)中；最后，对刚读取过的Cell，它可能存在于BlockCache中。既然相同的Cell可能存储在三个地方，在读取的时候只需要扫瞄这三个地方，然后将结果合并即可(Merge Read)，在HBase中扫瞄的顺序依次是：BlockCache、MemStore、StoreFile(HFile)。其中StoreFile的扫瞄先会使用Bloom Filter过滤那些不可能符合条件的HFile，然后使用Block Index快速定位Cell，并将其加载到BlockCache中，然后从BlockCache中读取。我们知道一个HStore可能存在多个StoreFile(HFile)，此时需要扫瞄多个HFile，如果HFile过多又是会引起性能问题。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig16.png" height="278" width="769" /><br /><h2>Compaction</h2>MemStore每次Flush会创建新的HFile，而过多的HFile会引起读的性能问题，那么如何解决这个问题呢？HBase采用Compaction机制来解决这个问题，有点类似Java中的GC机制，起初Java不停的申请内存而不释放，增加性能，然而天下没有免费的午餐，最终我们还是要在某个条件下去收集垃圾，很多时候需要Stop-The-World，这种Stop-The-World有些时候也会引起很大的问题，比如参考本人写的<a href="http://www.blogjava.net/DLevin/archive/2015/08/01/426418.html">这篇文章</a>，因而设计是一种权衡，没有完美的。还是类似Java中的GC，在HBase中Compaction分为两种：Minor Compaction和Major Compaction。<br /><ol><li>Minor Compaction是指选取一些小的、相邻的StoreFile将他们合并成一个更大的StoreFile，在这个过程中不会处理已经Deleted或Expired的Cell。一次Minor Compaction的结果是更少并且更大的StoreFile。（这个是对的吗？BigTable中是这样描述Minor Compaction的<span style="font-size: 10.000000pt; font-family: 'Times'">：As write operations execute, the size of the memtable in-
creases. When the memtable size reaches a threshold, the
memtable is frozen, a new memtable is created, and the
frozen memtable is converted to an SSTable and written
to GFS. This </span><span style="font-size: 10.000000pt; font-family: 'Times'; font-style: italic">minor compaction </span><span style="font-size: 10.000000pt; font-family: 'Times'">process has two goals:
it shrinks the memory usage of the tablet server, and it
reduces the amount of data that has to be read from the
commit log during recovery if this server dies. Incom-
ing read and write operations can continue while com-
pactions occur.&nbsp;</span>也就是说它将memtable的数据flush的一个HFile/SSTable称为一次Minor Compaction）</li><li>Major Compaction是指将所有的StoreFile合并成一个StoreFile，在这个过程中，标记为Deleted的Cell会被删除，而那些已经Expired的Cell会被丢弃，那些已经超过最多版本数的Cell会被丢弃。一次Major Compaction的结果是一个HStore只有一个StoreFile存在。Major Compaction可以手动或自动触发，然而由于它会引起很多的IO操作而引起性能问题，因而它一般会被安排在周末、凌晨等集群比较闲的时间。<br /></li></ol>更形象一点，如下面两张图分别表示Minor Compaction和Major Compaction。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig18.png" height="329" width="723" /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig19.png" height="339" width="653" /><br /><h2>HRegion Split</h2>最初，一个Table只有一个HRegion，随着数据写入增加，如果一个HRegion到达一定的大小，就需要Split成两个HRegion，这个大小由hbase.hregion.max.filesize指定，默认为10GB。当split时，两个新的HRegion会在同一个HRegionServer中创建，它们各自包含父HRegion一半的数据，当Split完成后，父HRegion会下线，而新的两个子HRegion会向HMaster注册上线，处于负载均衡的考虑，这两个新的HRegion可能会被HMaster分配到其他的HRegionServer中。关于Split的详细信息，可以参考这篇文章：<a href="http://hortonworks.com/blog/apache-hbase-region-splitting-and-merging/">《Apache HBase Region Splitting and Merging》</a>。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig21.png" height="361" width="675" /><br /><h2>HRegion负载均衡</h2>在HRegion Split后，两个新的HRegion最初会和之前的父HRegion在相同的HRegionServer上，出于负载均衡的考虑，HMaster可能会将其中的一个甚至两个重新分配的其他的HRegionServer中，此时会引起有些HRegionServer处理的数据在其他节点上，直到下一次Major Compaction将数据从远端的节点移动到本地节点。<br /><br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig22.png" height="358" width="714" /><br /><h2>HRegionServer Recovery</h2>当一台HRegionServer宕机时，由于它不再发送Heartbeat给ZooKeeper而被监测到，此时ZooKeeper会通知HMaster，HMaster会检测到哪台HRegionServer宕机，它将宕机的HRegionServer中的HRegion重新分配给其他的HRegionServer，同时HMaster会把宕机的HRegionServer相关的WAL拆分分配给相应的HRegionServer(将拆分出的WAL文件写入对应的目的HRegionServer的WAL目录中，并并写入对应的DataNode中），从而这些HRegionServer可以Replay分到的WAL来重建MemStore。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig25.png" height="368" width="708" /><br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig26.png" height="378" width="724" /><br /><h2>HBase架构简单总结</h2>在NoSQL中，存在著名的CAP理论，即Consistency、Availability、Partition Tolerance不可全得，目前市场上基本上的NoSQL都采用Partition Tolerance以实现数据得水平扩展，来处理Relational DataBase遇到的无法处理数据量太大的问题，或引起的性能问题。因而只有剩下C和A可以选择。HBase在两者之间选择了Consistency，然后使用多个HMaster以及支持HRegionServer的failure监控、ZooKeeper引入作为协调者等各种手段来解决Availability问题，然而当网络的Split-Brain(Network Partition)发生时，它还是无法完全解决Availability的问题。从这个角度上，Cassandra选择了A，即它在网络Split-Brain时还是能正常写，而使用其他技术来解决Consistency的问题，如读的时候触发Consistency判断和处理。这是设计上的限制。<br /><br />从实现上的优点：<br /><ol><li>HBase采用强一致性模型，在一个写返回后，保证所有的读都读到相同的数据。</li><li>通过HRegion动态Split和Merge实现自动扩展，并使用HDFS提供的多个数据备份功能，实现高可用性。</li><li>采用HRegionServer和DataNode运行在相同的服务器上实现数据的本地化，提升读写性能，并减少网络压力。</li><li>内建HRegionServer的宕机自动恢复。采用WAL来Replay还未持久化到HDFS的数据。</li><li>可以无缝的和Hadoop/MapReduce集成。<br /></li></ol>实现上的缺点：<br /><ol><li>WAL的Replay过程可能会很慢。</li><li>灾难恢复比较复杂，也会比较慢。</li><li>Major Compaction会引起IO Storm。</li><li>。。。。<br /></li></ol><h2>参考：</h2>
https://www.mapr.com/blog/in-depth-look-hbase-architecture#.VdNSN6Yp3qx<br />
http://jimbojw.com/wiki/index.php?title=Understanding_Hbase_and_BigTable<br />
http://hbase.apache.org/book.html <br />
http://www.searchtb.com/2011/01/understanding-hbase.html
<br />
http://research.google.com/archive/bigtable-osdi06.pdf<img src ="http://www.blogjava.net/DLevin/aggbug/426950.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/DLevin/" target="_blank">DLevin</a> 2015-08-22 19:40 <a href="http://www.blogjava.net/DLevin/archive/2015/08/22/426950.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入HBase架构解析（一）</title><link>http://www.blogjava.net/DLevin/archive/2015/08/22/426877.html</link><dc:creator>DLevin</dc:creator><author>DLevin</author><pubDate>Sat, 22 Aug 2015 09:44:00 GMT</pubDate><guid>http://www.blogjava.net/DLevin/archive/2015/08/22/426877.html</guid><wfw:comment>http://www.blogjava.net/DLevin/comments/426877.html</wfw:comment><comments>http://www.blogjava.net/DLevin/archive/2015/08/22/426877.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/DLevin/comments/commentRss/426877.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/DLevin/services/trackbacks/426877.html</trackback:ping><description><![CDATA[<h2>前记</h2>
公司内部使用的是MapR版本的Hadoop生态系统，因而从MapR的官网看到了这篇文文章：<a href="https://www.mapr.com/blog/in-depth-look-hbase-architecture#.VdMxvWSqqko">An In-Depth Look at the HBase Architecture</a>，原本想翻译全文，然而如果翻译就需要各种咬文嚼字，太麻烦，因而本文大部分使用了自己的语言，并且加入了其他资源的参考理解以及本人自己读源码时对其的理解，属于半翻译、半原创吧。<br />
<h2>HBase架构组成</h2>
HBase采用Master/Slave架构搭建集群，它隶属于Hadoop生态系统，由一下类型节点组成：HMaster节点、HRegionServer节点、ZooKeeper集群，而在底层，它将数据存储于HDFS中，因而涉及到HDFS的NameNode、DataNode等，总体结构如下：<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArch1.jpg" height="389" width="603" /><br />
其中<strong>HMaster节点</strong>用于：<br />
<ol>
     <li>管理HRegionServer，实现其负载均衡。</li>
     <li>管理和分配HRegion，比如在HRegion split时分配新的HRegion；在HRegionServer退出时迁移其内的HRegion到其他HRegionServer上。</li>
     <li>实现DDL操作（Data Definition
     Language，namespace和table的增删改，column
     familiy的增删改等）。</li>
     <li>管理namespace和table的元数据（实际存储在HDFS上）。</li>
     <li>权限控制（ACL）。</li>
</ol>
<strong>HRegionServer节点</strong>用于：<br />
<ol>
     <li>存放和管理本地HRegion。</li>
     <li>读写HDFS，管理Table中的数据。</li>
     <li>Client直接通过HRegionServer读写数据（从HMaster中获取元数据，找到RowKey所在的HRegion/HRegionServer后）。</li>
</ol>
<strong>ZooKeeper集群是协调系统</strong>，用于：<br />
<ol>
     <li>存放整个
     HBase集群的元数据以及集群的状态信息。</li>
     <li>实现HMaster主从节点的failover。</li>
</ol>
HBase Client通过RPC方式和HMaster、HRegionServer通信；一个HRegionServer可以存放1000个HRegion；底层Table数据存储于HDFS中，而HRegion所处理的数据尽量和数据所在的DataNode在一起，实现数据的本地化；数据本地化并不是总能实现，比如在HRegion移动(如因Split)时，需要等下一次Compact才能继续回到本地化。<br />
<br />
本着半翻译的原则，再贴一个《An In-Depth Look At The HBase Architecture》的架构图：<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig1.png" height="343" width="632" /><br />
这个架构图比较清晰的表达了HMaster和NameNode都支持多个热备份，使用ZooKeeper来做协调；ZooKeeper并不是云般神秘，它一般由三台机器组成一个集群，内部使用PAXOS算法支持三台Server中的一台宕机，也有使用五台机器的，此时则可以支持同时两台宕机，既少于半数的宕机，然而随着机器的增加，它的性能也会下降；RegionServer和DataNode一般会放在相同的Server上实现数据的本地化。<br />
<h2>HRegion</h2>
HBase使用RowKey将表水平切割成多个HRegion，从HMaster的角度，每个HRegion都纪录了它的StartKey和EndKey（第一个HRegion的StartKey为空，最后一个HRegion的EndKey为空），由于RowKey是排序的，因而Client可以通过HMaster快速的定位每个RowKey在哪个HRegion中。HRegion由HMaster分配到相应的HRegionServer中，然后由HRegionServer负责HRegion的启动和管理，和Client的通信，负责数据的读(使用HDFS)。每个HRegionServer可以同时管理1000个左右的HRegion（这个数字怎么来的？没有从代码中看到限制，难道是出于经验？超过1000个会引起性能问题？<strong>来回答这个问题</strong>：感觉这个1000的数字是从BigTable的论文中来的（5 Implementation节）：Each tablet server manages a set of tablets(typically we have somewhere between ten to a thousand tablets per tablet server)）。<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig2.png" height="337" width="724" /><br />
<h2>HMaster</h2>
HMaster没有单点故障问题，可以启动多个HMaster，通过ZooKeeper的Master Election机制保证同时只有一个HMaster出于Active状态，其他的HMaster则处于热备份状态。一般情况下会启动两个HMaster，非Active的HMaster会定期的和Active HMaster通信以获取其最新状态，从而保证它是实时更新的，因而如果启动了多个HMaster反而增加了Active HMaster的负担。前文已经介绍过了HMaster的主要用于HRegion的分配和管理，DDL(Data Definition Language，既Table的新建、删除、修改等)的实现等，既它主要有两方面的职责：<br />
<ol>
     <li>协调HRegionServer
     <ol>
         <li>启动时HRegion的分配，以及负载均衡和修复时HRegion的重新分配。</li>
         <li>监控集群中所有HRegionServer的状态(通过Heartbeat和监听ZooKeeper中的状态)。<br />
         </li>
     </ol>
     </li>
     <li>Admin职能
     <ol>
         <li>创建、删除、修改Table的定义。<br />
         </li>
     </ol>
     </li>
</ol>
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig3.png" /><br />
<h2>
ZooKeeper：协调者</h2>
ZooKeeper为HBase集群提供协调服务，它管理着HMaster和HRegionServer的状态(available/alive等)，并且会在它们宕机时通知给HMaster，从而HMaster可以实现HMaster之间的failover，或对宕机的HRegionServer中的HRegion集合的修复(将它们分配给其他的HRegionServer)。ZooKeeper集群本身使用一致性协议(PAXOS协议)保证每个节点状态的一致性。<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig4.png" height="318" width="703" /><br />
<h2>How The Components Work Together</h2>
ZooKeeper协调集群所有节点的共享信息，在HMaster和HRegionServer连接到ZooKeeper后创建Ephemeral节点，并使用Heartbeat机制维持这个节点的存活状态，如果某个Ephemeral节点实效，则HMaster会收到通知，并做相应的处理。<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig5.png" height="329" width="722" /><br />
另外，HMaster通过监听ZooKeeper中的Ephemeral节点(默认：/hbase/rs/*)来监控HRegionServer的加入和宕机。在第一个HMaster连接到ZooKeeper时会创建Ephemeral节点(默认：/hbasae/master)来表示Active的HMaster，其后加进来的HMaster则监听该Ephemeral节点，如果当前Active的HMaster宕机，则该节点消失，因而其他HMaster得到通知，而将自身转换成Active的HMaster，在变为Active的HMaster之前，它会创建在/hbase/back-masters/下创建自己的Ephemeral节点。<br />
<h3>
HBase的第一次读写</h3>
在HBase 0.96以前，HBase有两个特殊的Table：-ROOT-和.META.（如<a href="http://research.google.com/archive/bigtable-osdi06.pdf">BigTable</a>中的设计），其中-ROOT- Table的位置存储在ZooKeeper，它存储了.META. Table的RegionInfo信息，并且它只能存在一个HRegion，而.META. Table则存储了用户Table的RegionInfo信息，它可以被切分成多个HRegion，因而对第一次访问用户Table时，首先从ZooKeeper中读取-ROOT- Table所在HRegionServer；然后从该HRegionServer中根据请求的TableName，RowKey读取.META. Table所在HRegionServer；最后从该HRegionServer中读取.META. Table的内容而获取此次请求需要访问的HRegion所在的位置，然后访问该HRegionSever获取请求的数据，这需要三次请求才能找到用户Table所在的位置，然后第四次请求开始获取真正的数据。当然为了提升性能，客户端会缓存-ROOT- Table位置以及-ROOT-/.META. Table的内容。如下图所示：<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/image0030.jpg" height="228" width="399" /><br />
可是即使客户端有缓存，在初始阶段需要三次请求才能直到用户Table真正所在的位置也是性能低下的，而且真的有必要支持那么多的HRegion吗？或许对Google这样的公司来说是需要的，但是对一般的集群来说好像并没有这个必要。在BigTable的论文中说，每行METADATA存储1KB左右数据，中等大小的Tablet(HRegion)在128MB左右，3层位置的Schema设计可以支持2^34个Tablet(HRegion)。即使去掉-ROOT- Table，也还可以支持2^17(131072)个HRegion， 如果每个HRegion还是128MB，那就是16TB，这个貌似不够大，但是现在的HRegion的最大大小都会设置的比较大，比如我们设置了2GB，此时支持的大小则变成了4PB，对一般的集群来说已经够了，因而在HBase 0.96以后去掉了-ROOT- Table，只剩下这个特殊的目录表叫做Meta Table(hbase:meta)，它存储了集群中所有用户HRegion的位置信息，而ZooKeeper的节点中(/hbase/meta-region-server)存储的则直接是这个Meta Table的位置，并且这个Meta Table如以前的-ROOT- Table一样是不可split的。这样，客户端在第一次访问用户Table的流程就变成了：<br />
<ol>
     <li>从ZooKeeper(/hbase/meta-region-server)中获取hbase:meta的位置（HRegionServer的位置），缓存该位置信息。</li>
     <li>从HRegionServer中查询用户Table对应请求的RowKey所在的HRegionServer，缓存该位置信息。</li>
     <li>从查询到HRegionServer中读取Row。</li>
</ol>
从这个过程中，我们发现客户会缓存这些位置信息，然而第二步它只是缓存当前RowKey对应的HRegion的位置，因而如果下一个要查的RowKey不在同一个HRegion中，则需要继续查询hbase:meta所在的HRegion，然而随着时间的推移，客户端缓存的位置信息越来越多，以至于不需要再次查找hbase:meta Table的信息，除非某个HRegion因为宕机或Split被移动，此时需要重新查询并且更新缓存。<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig6.png" height="356" width="590" /><br />
<h3>
hbase:meta表</h3>
hbase:meta表存储了所有用户HRegion的位置信息，它的RowKey是：tableName,regionStartKey,regionId,replicaId等，它只有info列族，这个列族包含三个列，他们分别是：info:regioninfo列是RegionInfo的proto格式：regionId,tableName,startKey,endKey,offline,split,replicaId；info:server格式：HRegionServer对应的server:port；info:serverstartcode格式是HRegionServer的启动时间戳。<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig7.png" height="362" width="736" /><br />
<h2>HRegionServer详解</h2>
HRegionServer一般和DataNode在同一台机器上运行，实现数据的本地性。HRegionServer包含多个HRegion，由WAL(HLog)、BlockCache、MemStore、HFile组成。<br />
<ol>
     <li><strong>WAL即Write Ahead Log</strong>，在早期版本中称为HLog，它是HDFS上的一个文件，如其名字所表示的，所有写操作都会先保证将数据写入这个Log文件后，才会真正更新MemStore，最后写入HFile中。采用这种模式，可以保证HRegionServer宕机后，我们依然可以从该Log文件中读取数据，Replay所有的操作，而不至于数据丢失。这个Log文件会定期Roll出新的文件而删除旧的文件(那些已持久化到HFile中的Log可以删除)。WAL文件存储在/hbase/WALs/${HRegionServer_Name}的目录中(在0.94之前，存储在/hbase/.logs/目录中)，一般一个HRegionServer只有一个WAL实例，也就是说一个HRegionServer的所有WAL写都是串行的(就像log4j的日志写也是串行的)，这当然会引起性能问题，因而在HBase 1.0之后，通过<a href="https://issues.apache.org/jira/browse/HBASE-5699">HBASE-5699</a>实现了多个WAL并行写(MultiWAL)，该实现采用HDFS的多个管道写，以单个HRegion为单位。关于WAL可以参考Wikipedia的<a href="https://en.wikipedia.org/wiki/Write-ahead_logging">Write-Ahead Logging</a>。顺便吐槽一句，英文版的维基百科竟然能毫无压力的正常访问了，这是某个GFW的疏忽还是以后的常态？</li>
     <li><strong>BlockCache是一个读缓存</strong>，即&#8220;引用局部性&#8221;原理（也应用于CPU，<a href="http://baike.baidu.com/link?url=Dh2u9KvowXcl2PloHJFTB9vEOoVS3WxPhDCVvbQGL_piyKFQ2iTHYAYf5mLAsFrcBUig6NC7A4-Aki61qnGCTK#3_1">分空间局部性和时间局部性</a>，空间局部性是指CPU在某一时刻需要某个数据，那么有很大的概率在一下时刻它需要的数据在其附近；时间局部性是指某个数据在被访问过一次后，它有很大的概率在不久的将来会被再次的访问），将数据预读取到内存中，以提升读的性能。HBase中提供两种BlockCache的实现：默认on-heap LruBlockCache和BucketCache(通常是off-heap)。通常BucketCache的性能要差于LruBlockCache，然而由于GC的影响，LruBlockCache的延迟会变的不稳定，而BucketCache由于是自己管理BlockCache，而不需要GC，因而它的延迟通常比较稳定，这也是有些时候需要选用BucketCache的原因。这篇文章<a href="http://www.n10k.com/blog/blockcache-101/">BlockCache101</a>对on-heap和off-heap的BlockCache做了详细的比较。</li><strong>
     </strong><li><strong>HRegion是一个Table中的一个Region在一个HRegionServer中的表达</strong>。一个Table可以有一个或多个Region，他们可以在一个相同的HRegionServer上，也可以分布在不同的HRegionServer上，一个HRegionServer可以有多个HRegion，他们分别属于不同的Table。HRegion由多个Store(HStore)构成，每个HStore对应了一个Table在这个HRegion中的一个Column Family，即每个Column Family就是一个集中的存储单元，因而最好将具有相近IO特性的Column存储在一个Column Family，以实现高效读取(数据局部性原理，可以提高缓存的命中率)。HStore是HBase中存储的核心，它实现了读写HDFS功能，一个HStore由一个MemStore
和0个或多个StoreFile组成。<br />
<ol>
<li><strong>MemStore是一个写缓存</strong>(In Memory Sorted Buffer)，所有数据的写在完成WAL日志写后，会
写入MemStore中，由MemStore根据一定的算法将数据Flush到地层HDFS文件中(HFile)，通常每个HRegion中的每个
Column Family有一个自己的MemStore。</li>
     <li><strong>HFile(StoreFile)
用于存储HBase的数据(Cell/KeyValue)</strong>。在HFile中的数据是按RowKey、Column Family、Column排序，对相同的Cell(即这三个值都一样)，则按timestamp倒序排列。</li>
</ol>
     </li>
</ol>
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig8.png" /><br />
虽然上面这张图展现的是最新的HRegionServer的架构(但是并不是那么的精确)，但是我一直比较喜欢看以下这张图，即使它展现的应该是0.94以前的架构。<br />
<img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/image0060.jpg" height="347" width="553" /><br />
<h3>
HRegionServer中数据写流程图解</h3>
当客户端发起一个Put请求时，首先它从hbase:meta表中查出该Put数据最终需要去的HRegionServer。然后客户端将Put请求发送给相应的HRegionServer，在HRegionServer中它首先会将该Put操作写入WAL日志文件中(Flush到磁盘中)。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig9.png" height="363" width="716" /><br />
写完WAL日志文件后，HRegionServer根据Put中的TableName和RowKey找到对应的HRegion，并根据Column Family找到对应的HStore，并将Put写入到该HStore的MemStore中。此时写成功，并返回通知客户端。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig10.png" height="298" width="664" /><br /><h3>MemStore Flush<br /></h3>MemStore是一个In Memory Sorted Buffer，在每个HStore中都有一个MemStore，即它是一个HRegion的一个Column Family对应一个实例。它的排列顺序以RowKey、Column Family、Column的顺序以及Timestamp的倒序，如下所示：<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig11.png" height="351" width="719" /><br />每一次Put/Delete请求都是先写入到MemStore中，当MemStore满后会Flush成一个新的StoreFile(底层实现是HFile)，即一个HStore(Column Family)可以有0个或多个StoreFile(HFile)。有以下三种情况可以触发MemStore的Flush动作，<strong>需要注意的是MemStore的最小Flush单元是HRegion而不是单个MemStore</strong>。据说这是Column Family有个数限制的其中一个原因，估计是因为太多的Column Family一起Flush会引起性能问题？具体原因有待考证。<br /><ol><li>当一个HRegion中的所有MemStore的大小总和超过了hbase.hregion.memstore.flush.size的大小，默认128MB。此时当前的HRegion中所有的MemStore会Flush到HDFS中。</li><li>当全局MemStore的大小超过了hbase.regionserver.global.memstore.upperLimit的大小，默认40％的内存使用量。此时当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中，Flush顺序是MemStore大小的倒序（一个HRegion中所有MemStore总和作为该HRegion的MemStore的大小还是选取最大的MemStore作为参考？有待考证），直到总体的MemStore使用量低于hbase.regionserver.global.memstore.lowerLimit，默认38%的内存使用量。</li><li>当前HRegionServer中WAL的大小超过了hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs的数量，当前HRegionServer中所有HRegion中的MemStore都会Flush到HDFS中，Flush使用时间顺序，最早的MemStore先Flush直到WAL的数量少于hbase.regionserver.hlog.blocksize * hbase.regionserver.max.logs。<a href="http://blog.sematext.com/2012/07/16/hbase-memstore-what-you-should-know/">这里</a>说这两个相乘的默认大小是2GB，查代码，hbase.regionserver.max.logs默认值是32，而hbase.regionserver.hlog.blocksize是HDFS的默认blocksize，32MB。但不管怎么样，因为这个大小超过限制引起的Flush不是一件好事，可能引起长时间的延迟，因而这篇文章给的建议：&#8220;<strong style="color: #339966; font-family: STHeiti; font-size: medium; font-style: normal; font-variant: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 1; word-spacing: 0px; -webkit-text-stroke-width: 0px;">Hint</strong><span style="color: #339966; font-family: STHeiti; font-size: medium; font-style: normal; font-variant: normal; font-weight: normal; letter-spacing: normal; line-height: normal; orphans: auto; text-align: start; text-indent: 0px; text-transform: none; white-space: normal; widows: 1; word-spacing: 0px; -webkit-text-stroke-width: 0px; display: inline !important; float: none;">: keep&nbsp;hbase.regionserver.hlog.blocksize * hbase.regionserver.maxlogs just a bit above hbase.regionserver.global.memstore.lowerLimit * HBASE_HEAPSIZE.</span>&#8221;。并且需要注意，<a href="http://hbase.apache.org/book.html#_memstore_flush">这里</a>给的描述是有错的(虽然它是官方的文档)。<br /></li></ol>在MemStore Flush过程中，还会在尾部追加一些meta数据，其中就包括Flush时最大的WAL sequence值，以告诉HBase这个StoreFile写入的最新数据的序列，那么在Recover时就直到从哪里开始。在HRegion启动时，这个sequence会被读取，并取最大的作为下一次更新时的起始sequence。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig12.png" height="248" width="622" /><br /><h2>
HFile格式</h2>HBase的数据以KeyValue(Cell)的形式顺序的存储在HFile中，在MemStore的Flush过程中生成HFile，由于MemStore中存储的Cell遵循相同的排列顺序，因而Flush过程是顺序写，我们直到磁盘的顺序写性能很高，因为不需要不停的移动磁盘指针。<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig13.png" height="351" width="698" /><br />HFile参考BigTable的SSTable和Hadoop的<a href="http://hadoop.apache.org/docs/current/api/org/apache/hadoop/io/file/tfile/TFile.html">TFile</a>实现，从HBase开始到现在，HFile经历了三个版本，其中V2在0.92引入，V3在0.98引入。首先我们来看一下V1的格式：<br /><img src="http://www.blogjava.net/images/blogjava_net/dlevin/image0080.jpg" alt="" height="160" border="0" width="554" /><br />V1的HFile由多个Data Block、Meta Block、FileInfo、Data Index、Meta Index、Trailer组成，其中Data Block是HBase的最小存储单元，在前文中提到的BlockCache就是基于Data Block的缓存的。一个Data Block由一个魔数和一系列的KeyValue(Cell)组成，魔数是一个随机的数字，用于表示这是一个Data Block类型，以快速监测这个Data Block的格式，防止数据的破坏。Data Block的大小可以在创建Column Family时设置(HColumnDescriptor.setBlockSize())，默认值是64KB，大号的Block有利于顺序Scan，小号Block利于随机查询，因而需要权衡。Meta块是可选的，FileInfo是固定长度的块，它纪录了文件的一些Meta信息，例如：AVG_KEY_LEN, AVG_VALUE_LEN, LAST_KEY, COMPARATOR,  MAX_SEQ_ID_KEY等。Data Index和Meta Index纪录了每个Data块和Meta块的其实点、未压缩时大小、Key(起始RowKey？)等。Trailer纪录了FileInfo、Data Index、Meta Index块的起始位置，Data Index和Meta Index索引的数量等。其中FileInfo和Trailer是固定长度的。<br /><br />HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项，并且有固定的结构。我们来看看里面的具体结构：<br /><img src="http://www.blogjava.net/images/blogjava_net/dlevin/image0090.jpg" alt="" height="93" border="0" width="553" /><br />开始是两个固定长度的数值，分别表示Key的长度和Value的长度。紧接着是Key，开始是固定长度的数值，表示RowKey的长度，紧接着是
RowKey，然后是固定长度的数值，表示Family的长度，然后是Family，接着是Qualifier，然后是两个固定长度的数值，表示Time
  Stamp和Key  Type（Put/Delete）。Value部分没有这么复杂的结构，就是纯粹的二进制数据了。<strong>随着HFile版本迁移，KeyValue(Cell)的格式并未发生太多变化，只是在V3版本，尾部添加了一个可选的Tag数组</strong>。<br />
<br />HFileV1版本的在实际使用过程中发现它占用内存多，并且Bloom File和Block Index会变的很大，而引起启动时间变长。其中每个HFile的Bloom Filter可以增长到100MB，这在查询时会引起性能问题，因为每次查询时需要加载并查询Bloom Filter，100MB的Bloom Filer会引起很大的延迟；另一个，Block Index在一个HRegionServer可能会增长到总共6GB，HRegionServer在启动时需要先加载所有这些Block Index，因而增加了启动时间。为了解决这些问题，在0.92版本中引入HFileV2版本：<br /><img src="http://www.blogjava.net/images/blogjava_net/dlevin/hfilev2.png" alt="" height="418" border="0" width="566" /><br />在这个版本中，Block Index和Bloom Filter添加到了Data Block中间，而这种设计同时也减少了写的内存使用量；另外，为了提升启动速度，在这个版本中还引入了延迟读的功能，即在HFile真正被使用时才对其进行解析。<br /><br />FileV3版本基本和V2版本相比，并没有太大的改变，它在KeyValue(Cell)层面上添加了Tag数组的支持；并在FileInfo结构中添加了和Tag相关的两个字段。关于具体HFile格式演化介绍，可以参考<a href="http://hbase.apache.org/book.html#_hfile_format_2">这里</a>。<br /><br />对HFileV2格式具体分析，它是一个多层的类B+树索引，采用这种设计，可以实现查找不需要读取整个文件：<br /><img alt="" src="http://www.blogjava.net/images/blogjava_net/dlevin/HBaseArchitecture-Blog-Fig14.png" height="349" width="688" /><br />Data Block中的Cell都是升序排列，每个block都有它自己的Leaf-Index，每个Block的最后一个Key被放入Intermediate-Index中，Root-Index指向Intermediate-Index。在HFile的末尾还有Bloom Filter用于快速定位那么没有在某个Data Block中的Row；TimeRange信息用于给那些使用时间查询的参考。在HFile打开时，这些索引信息都被加载并保存在内存中，以增加以后的读取性能。<br /><br />这篇就先写到这里，未完待续。。。。<br /><br />
<h2>参考：</h2>
https://www.mapr.com/blog/in-depth-look-hbase-architecture#.VdNSN6Yp3qx<br />
http://jimbojw.com/wiki/index.php?title=Understanding_Hbase_and_BigTable<br />
http://hbase.apache.org/book.html <br />
http://www.searchtb.com/2011/01/understanding-hbase.html
<br />
http://research.google.com/archive/bigtable-osdi06.pdf<img src ="http://www.blogjava.net/DLevin/aggbug/426877.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/DLevin/" target="_blank">DLevin</a> 2015-08-22 17:44 <a href="http://www.blogjava.net/DLevin/archive/2015/08/22/426877.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>