2014年7月15日星期二

HBase架构(三) Region-分区

        分区是HBase中的Table的最基础的组成部分;每个列族会被存储到一个存储中;它的主要结构如下:
Table       (HBase table)
    Region       (Regions for the table)
         Store          (Store per ColumnFamily for each Region for the table)
              MemStore           (MemStore for each Store for each Region for the table)
              StoreFile          (StoreFiles for each Store for each Region for the table)
                    Block             (Blocks within a StoreFile within a Store for each Region for the table)
 

多分区的考虑

       实际上,HBase在设计上被设计了以较小的分区数(20-200),较大尺寸(5-20Gb)运行在每个RegionServer上;如此设计有以下原因:

  • MSLAB(A memstore-local allocation buffer)需要为每个memstore事先分配2M内存空间,如果有1000个分区存储两个列族的数据,那么还未存储任何数据时,已经在JVM中分配了3.9GB的空间;
    • 注:MSLAB,是为了避免产生过多的内存碎片,首先在memstore中分配一块内存buffer,写入对象就写入到这个连续的buffer中,当回收时一起回收,避免由于回收数据导致产生过多的内存碎片;
  • 当以一个固定速率写入数据时,过多的region会引起总体内存占比过高,从而导致过于频繁的磁盘回写操作;例如有1000个region,一个列族,假设我们设置了regionServer的整体memstore使用上限为5GB,那么当达到了5GB时会强制将内存的数据回写到磁盘,而5GB对于每个memstore来说,每个只有5MB的数据;
  • 由于master要对大量的region进行处理,要花费非常多的时间在分配、批量移动这些region,这会导致ZK的工作非常繁重,并且并不会那么实时了。
  • 旧版本的HBase中,少量的regionServer上的大量region会导致存储文件的索引的增长,会加速heap的使用量,有可能会导致内存压力货OOM Exceptions。
  • 另外一个是对Map/Reduce的任务数的影响;典型的情况是每个HBase的分区对应一个mapper,所以,如果一个RS上只有少数的region,比如5个,那么对于mapreduce任务来说可能不会有足够的任务来执行,当然,1000个region又显得太多了。

Region-RegionServer分配

       这一部分讨论分区是如何被分配到RegionServer上的。
  • 启动:当HBase启动时,分区是按照下面的规则分配的
    • Master会在启动时,会启动AssignmentManager
    • AssignmentManager会在META中查询已存在的region分配数据
    • 如果分区分配仍然有效,那么分配数据会被保留;
    • 如果分区数据无效,那么LoadBalancerFactory会被启动,他会分配region。默认的分配规则:DefaultLoadBalancer 将随机的分配region给regionServer;
    • META会更新RegionServer的分配数据,RegionServer也会开始启动对分配过来的region的服务;
  • 故障机制:如果一个RegionServer发生故障:
    • 分配到该RegionServer上的region立刻成为不可用状态;
    • Master会发现RegionServer发生故障;
    • region分配会被认为失效,将被按照启动时的顺序重新分配;
    • 正在进行的查询将被重试,不会被丢失;
    • 操作将在下述时间内被切换到新的RegionServer上:
      • zookeeper session过期时间+region分割时间+分配/重试 时间

Region-RegionServer局部性

        局部性(Locality)原理:处理器访问存储器时,无论是读取指令还是存取数据,所访问的存储单元在一段时间内都趋向于一个较小的连续区域中。
  • 时间局部性:刚刚被访问过的数据可能很短时间内还会被访问
  • 空间局部性:最近的将来被使用到的数据可能和现在使用的数据在空间地址上很近;
Region-RegionServer局部性问题是通过HDFS的block复制解决的。HDFS客户端在选择写入地址时,默认执行了下述步骤:
  • 第一份复制文件被写到了本地节点;
  • 第二份被写入到不同机架上的一个随机节点;
  • 第三份被写入到统一机架上的剩余节点中的一个随机节点;
  • 随后的复制文件被写入到集群的随机节点中;
HBase在flush或文件合并后解决了数据局部性的问题;在一个RegionServer的故障情况下,另外一个RegionServer被分配的region中可能会包括非本地存储文件(因为数据复制不在本地),可是随着新数据写入到region中,或者表被压缩,storeFile会被重写,会变成本地文件。

Region分裂

       Region在达到一个配置阈值的时候会进行分裂;
       分裂是完全在RegionServer上完成的,Master不会参与。RegionServer分裂一个分区,下线分裂的分区,然后将子分区数据写入META中,在父亲所在的RegionServer打开子分区,并报告这次分裂给Master。

  • 分裂策略
    • 默认分裂策略可以被一个自定义的RegionSplitPolicy实现(HBase0.94+)自定义的策略必须扩展HBase默认的策略:ConstantSizeRegionSplitPolicy
    • 策略可以在HBase的总配置文件中指定,或者在使用每个Table时指定

HTableDescriptor myHtd = ...;
myHtd.setValue(HTableDescriptor.SPLIT_POLICY, MyCustomSplitPolicy.class.getName());


    • 默认分裂策略(0.94.0+)IncreasingToUpperBoundRegionSplitPolicy
      • 同一个表下的region的数量的^3*MemstoreSize*2
        • Memstore的Flush size = 128M,第一次分裂就是256M,分裂为2个region
        • 第二次分裂是2^3*128*2=2G

Region在线合并

        Master和RegionServer都参与了Region的合并;客户端发送合并的RPC命令到Master,然后master根据这些region所在的RegionServer加载的情况,移动需要合并的region到加载region最多的RegionServer中。最后master发送合并请求到这个regionserver,regionserver运行分区合并程序;和分裂很像,分区合并是在regionserver内部完成的,下线这些分区,然后在文件系统合并两个分区,自动从META删除合并中的分区信息,并增加合并分区信息到META中。打开合并后的分区,并报告合并到master。
        下面是一个merge的shell例子:

$ hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME'
          hbase> merge_region 'ENCODED_REGIONNAME', 'ENCODED_REGIONNAME', true
        这是一个异步的过程,调用会立刻返回无需等待;

Store

store包括一个Memstore和0或者多个StoreFile。一个Store对应表的某个分区下的一个列族。

Memstore

        Memstore保存对这个store的数据的修改记录。修改记录是cell的KeyValue值。当一个flush请求被发起时,当前的memstore会被移到快照版本中并且被清除;HBase会使用新的memstore来处理新的请求,并且保存备份快照版本直到flush操作返回成功。备注:当Flash操作 开始时,属于同一个region的所有memstore都会进行flash操作。

Memstore Flush

满足下列的任一条件都会触发memstore的flush操作,需要注意的是,flush的最小单元是region而不是一个memstore。
  • 当一个memstore的大小达到了配置中 hbase.hregion.memstore.flush.size大小,所有同一个region下的memstore都会被清除写入磁盘;
  • 当一个region下所有的memstore的大小达到了hbase.regionserver.global.memstore.upperLimit 的大小, 一个regionServer上的多个region上的memstore会被清除写入磁盘以降低regionServer上整体memstore的大小。清除的顺序是按照region拥有的memstore的占有率降序进行的。Region上的Memstore会被清除直到regionServer上所有的Memstore大小低于hbase.regionserver.global.memstore.upperLimit这个值为止;
  • 当每个regionServer的HLog的数量大于hbase.regionserver.max.logs这个值时,regionServer上的很多Memstore会进行Flash来降低HLog的数量;Flash的顺序是按时间排序,最久存在的Memstore会首先被Flash,直到HLog的数量低于配置的hbase.regionserver.max.logs值。

scans

  • 当一个客户端发起一个scan请求,HBase会生成多个RegionScanner对象,每个region一个,来完成这个请求。
  • RegionScanner包含多个StoreScanner,每个列族一个。
  • 每个StoreScanner又包含多个StoreFileScanner对象,对应列族上的一个storeFile和HFile,另外还包含多个MemstoreScanner对象,对应于Memstore。
  • 这两个scanner扫描出来的对象列表会被合并成一个,由于是按照时间倒序排列的,因此从Memstore扫描出来的对象一直处于合并列表的最后。
  • 当一个StoreFileScanner被构建时,他被添加了一个值为当前Memstore时间戳memstoreTS的MultiVersionConsistencyControl读取检查点,过滤那些超过这个时间的数据记录。

StoreFile(HFile)

你的数据存储在StoreFile中。通过 hbase.hregion.max.filesize可以设置StoreFile的大小,当一个列族的其中一个StoreFile超过这个值,该StoreFile所在的region就要被分裂为两个。

HFile格式

参考之前的博客《[翻译]Apache HFile》

HDFS中的HFile

HBase的Table在HDFS中的目录结构:
/hbase
     /<Table>             (Tables in the cluster)
          /<Region>           (Regions for the table)
               /<ColumnFamily>      (ColumnFamilies for the Region for the table)
                    /<StoreFile>        (StoreFiles for the ColumnFamily for the Regions for the table)
            
HBase WAL
/hbase
     /.logs
          /<RegionServer>    (RegionServers)
               /<HLog>           (WAL HLog files for the RegionServer)
            

Blocks

StoreFile是有很多的block组成的。block的大小在每个列族的基础配置中。

KeyValue

KeyValue类是HBase的核心存储类,KeyValue封装了一个字节数组,并且将地址偏移和长度也封装到了数组中,以便能够判断那段内容是属于KeyValue。
KeyValue格式如下:
  • keylength
  • valuelength
  • key
  • value
其中,key又可以被进一步解析为:
  • rowlength
  • row
  • columnfamily length
  • columnfamiliy
  • columnqualifier
  • timestamp
  • keytype
KeyValue实例不会跨block。例如如果一个8MB的KeyValue,即便blocksize被设置成64KB,KeyValue仍然会被当做一个block进行读取。
下面是一个例子:
  • Put#1: rowkey=row1, cf:attr1=value1
  • Put#2: rowkey=row1, cf:attr2=value2
即使是对同一行进行put操作,每个操作都会创建一个KeyValue实例,
  • Put#1:
    • rowlenght------------->4
    • row----------------------->row1
    • columnfamilylenth-->2
    • columnfamily-------->cf
    • columnqualifier------>attr1
    • timestamp-------------->server time of put
    • keytype----------------->put
  • Put#2
    • rowlenght------------->4
    • row----------------------->row1
    • columnfamilylenth-->2
    • columnfamily-------->cf
    • columnqualifier------>attr2
    • timestamp-------------->server time of put
    • keytype----------------->put
最值得注意的是,key的大小会受row,columnfamily和columnqualifier的长度影响,这三个值越大,key的长度越大;

压缩Compaction

        压缩是为了降低StoreFile的数量来将他们合并,从而使得读操作的效率提高;压缩可能对平台来说是一个资源密集型的操作,众多因素决定它可能会帮助改善或降低性能;
       压缩有两种类型:minor小型压缩和major大型压缩
       minor:通常是操作小数量连续的小尺寸storeFile,重写他们到一个新的storeFile中。小型压缩不会删除delete记录和无效的cell内容。如果minor压缩操作了一个存储中所有的storeFile,那么就会升级成一次大型压缩;如果有大量的小文件需要被压缩,算法会趋向使用minor压缩来清理这些小型文件;
       major:目的是将一个存储中的的所有数据合并为一个storeFile。它会处理那些删除标记和多版本数据。
        压缩和删除:HBase执行删除时,数据并未被物理删除,而是一个墓碑标记会标记这个记录,墓碑标记阻止了数据在获取时被返回。当大型压缩时,数据会被实际删除,墓碑标记也会从storeFile中移除。如果删除是由于过期发生的,那么不会有墓碑标记,过期数据会被过滤而且不会被回写到压缩后的storeFile中。
        压缩和版本:当创建一个列族时,可以指定一个数据版本最大上限,通过配置HColumnDescriptor.setMaxVersions(int versions) 来实现。默认值是3.如果多余这个配置值的数据存在,超出的那些版本对应的数据会被清除。

没有评论:

发表评论