前记
几年前在读google的bigtable论文的时候,当时并没有理解论文里面表达的思想,因而囫囵吞枣,并没有注意到sstable的概念。再后来开始关注hbase的设计和源码后,开始对bigtable传递的思想慢慢的清晰起来,但是因为事情太多,没有安排出时间重读bigtable的论文。在项目里,我因为自己在学hbase,开始主推hbase,而另一个同事则因为对cassandra比较感冒,因而他主要关注cassandra的设计,不过我们两个人偶尔都会讨论一下技术、设计的各种观点和心得,然后他偶然的说了一句:cassandra和hbase都采用sstable格式存储,然后我本能的问了一句:什么是sstable?他并没有回答,可能也不是那么几句能说清楚的,或者他自己也没有尝试的去问过自己这个问题。然而这个问题本身却一直困扰着我,因而趁着现在有一些时间深入学习hbase和cassandra相关设计的时候先把这个问题弄清楚了。
sstable的定义
要解释这个术语的真正含义,最好的方法就是从它的出处找答案,所以重新翻开bigtable的论文。在这篇论文中,最初对sstable是这么描述的(第三页末和第四页初):
简单的非直译:
sstable是bigtable内部用于数据的文件格式,它的格式为文件本身就是一个排序的、不可变的、持久的key/value对map,其中key和value都可以是任意的byte字符串。使用key来查找value,或通过给定key范围遍历所有的key/value对。每个sstable包含一系列的block(一般block大小为64kb,但是它是可配置的),在sstable的末尾是block索引,用于定位block,这些索引在sstable打开时被加载到内存中,在查找时首先从内存中的索引二分查找找到block,然后一次磁盘寻道即可读取到相应的block。还有一种方案是将这个sstable加载到内存中,从而在查找和扫描中不需要读取磁盘。这个貌似就是hfile第一个版本的格式么,贴张图感受一下:
在hbase使用过程中,对这个版本的hfile遇到以下一些问题(参考):
1. 解析时内存使用量比较高。
2. bloom filter和block索引会变的很大,而影响启动性能。具体的,bloom filter可以增长到100mb每个hfile,而block索引可以增长到300mb,如果一个hregionserver中有20个hregion,则他们分别能增长到2gb和6gb的大小。hregion需要在打开时,需要加载所有的block索引到内存中,因而影响启动性能;而在第一次request时,需要将整个bloom filter加载到内存中,再开始查找,因而bloom filter太大会影响第一次请求的延迟。
而hfile在版本2中对这些问题做了一些优化,具体会在hfile解析时详细说明。
sstable作为存储使用
继续bigtable的论文往下走,在5.3 tablet serving小节中这样写道(第6页):
第一段和第三段简单描述,非翻译:
在新数据写入时,这个操作首先提交到日志中作为redo纪录,最近的数据存储在内存的排序缓存memtable中;旧的数据存储在一系列的sstable
中。在recover中,tablet
server从metadata表中读取metadata,metadata包含了组成tablet的所有sstable(纪录了这些sstable的元
数据信息,如sstable的位置、startkey、endkey等)以及一系列日志中的redo点。tablet
server读取sstable的索引到内存,并replay这些redo点之后的更新来重构memtable。
在读时,完成格式、授权等检查后,读会同时读取sstable、memtable(hbase中还包含了blockcache中的数据)并合并他们的结果,由于sstable和memtable都是字典序排列,因而合并操作可以很高效完成。
sstable在compaction过程中的使用
在bigtable论文5.4 compaction小节中是这样说的:
随着memtable大小增加到一个阀值,这个memtable会被冻住而创建一个新的memtable以供使用,而旧的memtable会转换成一个sstable而写道gfs中,这个过程叫做minor compaction。这个minor compaction可以减少内存使用量,并可以减少日志大小,因为持久化后的数据可以从日志中删除。在minor compaction过程中,可以继续处理读写请求。
每次minor compaction会生成新的sstable文件,如果sstable文件数量增加,则会影响读的性能,因而每次读都需要读取所有sstable文件,然后合并结果,因而对sstable文件个数需要有上限,并且时不时的需要在后台做merging compaction,这个merging compaction读取一些sstable文件和memtable的内容,并将他们合并写入一个新的sstable中。当这个过程完成后,这些源sstable和memtable就可以被删除了。
如果一个merging compaction是合并所有sstable到一个sstable,则这个过程称做major compaction。一次major compaction会将mark成删除的信息、数据删除,而其他两次compaction则会保留这些信息、数据(mark的形式)。bigtable会时不时的扫描所有的tablet,并对它们做major compaction。这个major compaction可以将需要删除的数据真正的删除从而节省空间,并保持系统一致性。sstable的locality和in memory
在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。在bigtable中,还可以支持在locality group级别设置是否将所有这个locality group的数据加载到内存中,在hbase中通过column family定义时设置。这个内存加载采用延时加载,主要应用于一些小的column family,并且经常被用到的,从而提升读的性能,因而这样就不需要再从磁盘中读取了。sstable压缩
bigtable的压缩是基于locality group级别:
bigtable的压缩以sstable中的一个block为单位,虽然每个block为压缩单位损失一些空间,但是采用这种方式,我们可以以block为单位读取、解压、分析,而不是每次以一个“大”的sstable为单位读取、解压、分析。sstable的读缓存
为了提升读的性能,bigtable采用两层缓存机制:
两层缓存分别是:
1. high level,缓存从sstable读取的key/value对。提升那些倾向重复的读取相同的数据的操作(引用局部性原理)。
2. low level,blockcache,缓存sstable中的block。提升那些倾向于读取相近数据的操作。
bloom filter
前文有提到bigtable采用合并读,即需要读取每个sstable中的相关数据,并合并成一个结果返回,然而每次读都需要读取所有sstable,自然会耗费性能,因而引入了bloom filter,它可以很快速的找到一个rowkey不在某个sstable中的事实(注:反过来则不成立)。
sstable设计成immutable的好处
在sstable定义中就有提到sstable是一个immutable的order map,这个immutable的设计可以让系统简单很多:
关于immutable的优点有以下几点:1. 在读sstable是不需要同步。读写同步只需要在memtable中处理,为了减少memtable的读写竞争,bigtable将memtable的row设计成copy-on-write,从而读写可以同时进行。2. 永久的移除数据转变为sstable的garbage collect。每个tablet中的sstable在metadata表中有注册,master使用mark-and-sweep算法将sstable在gc过程中移除。3. 可以让tablet split过程变的高效,我们不需要为每个子tablet创建新的sstable,而是可以共享父tablet的sstable。
posted on 2015-09-25 01:35
dlevin 阅读(15684)
评论(0) 编辑 收藏 所属分类:
hbase