HBase的Compact和Split源码分析与应用--基于0.94.5

经过对比,0.94。5以后版本主要过程基本类似(有些新功能和细节增加)

 

 

一、       Compact

2.1.   Compact主要来源

来自四个方面:1、Memstoreflush时;2、HRegionServer定期做Compaction Checker时;3、HBaseAdmin客户端发起的请求;4、CompactTool发起。

1)      MemstoreFlush在flushRegion方法中有相关处理,整个方法主要逻辑:

A 对一个flush请求,判断不是META表且文件很多,则

1)  如果该请求等待了最长时间,则打印日志(Waited -- ms on a compaction to clean up 'too many store files';waited long enough... proceeding with flush of),然后进行flush。

2)  如果是第一次从队列出列(getRequeueCount<=0),则打印日志(RegionXXX has too many store files; delaying flush up to XXX ms)。然后先看split请求是否成功,如果不能,就发送compact请求(compactSplitThread.requestCompaction)。并把flush的请求放入队列(等待时间为原1/100)

B 最终通过flushCache方法flush,然后checkSplit,若要split则requestSplit,否则如果flushCache的返回值是true,则requestCompact。

2)      HRegionServer做CompactionChecker时,逻辑比较简单,首先检查文件数,看是否需要compact,否则检查store的isMajorCompaction,然后根据配置的优先级,进行判断来决定按最高的优先级进行major。(只要大于0,值越低,优先级越高,1是user-最高。如果小于等于0,则是storefile太多,大于region的文件数阻塞阀值,所以会阻塞region的写)

细节:store的isMajorCompaction。该方法首先检查major的周期是否到了,未到,就返回false;若到了,一种情况是不只一个文件,则打印日志(Major compaction triggered on store SSS; time since last majorcompaction MMM ms)返回true。另外一种情况只有一个文件,如果文件是major生成且ttl设置为有限,而上次紧缩的时间还没有到ttl这么长,就打印日志(Skipping major compaction of SSSS because one (major) compacted fileonly and oldestTime xx ms is < ttl=),返回false;否则只要ttl设置且超时了,打印日志(Majorcompaction triggered on store SSS, because keyvalues outdated; time since lastmajor compaction MMM ms)返回true。

3)      HBaseAdmin通过接口compact发送请求,到具体的RegionServer,RegionServer直接向compactSplitThread发送requestCompaction。

4)      CompactTool直接通过hdfs路径生成Region实例,创建Mock的Store,然后通过Store的compact方法进行。该方法类应在Region不提供服务的情况进行。

5)      Major Compact的时机:

(1)    需紧缩的文件数等于Store的所有文件数,自动升级为主紧缩

filesToCompact.getFilesToCompact().size() ==this.storefiles.size()

(2)    TTL超时(ttl设置情况下)

需紧缩的StoreFile中,有StoreFile的内容已经过期(检查修改时间最早的StoreFile即可)

Ø  系统的主紧缩时间

Ø  Family设定的主紧缩时间

Ø  ttl不是永久

Ø  对于单个StoreFile,有标记:isMajorCompaction表明是否由主紧缩生成。有最小和最大时间戳记录

(3)    手动,强制:forcemajor && priority == PRIORITY_USER

 

hbase源码分析_大数据

2.2.   Compact过程:

hbase源码分析_java_02

 

1)      首先,调用Store.requestCompaction,该方法返回CompactRequest。具体处理如下:

2)      第一步,将所有StoreFile作为compact候选者,去除正在compact的文件(策略:所有比正在compact队列里面最新文件要老的都排除)。

3)      第二步,候选者由coprocessor处理,如果coprocessor覆盖了策略,以coprocessor的结果为准,返回结果。

否则第三步,采用系统算法选取(compactSelection方法,其中两个关键点,就是会直接删除过期的storefile,还有根据配置的最多最少文件进行选取,另开专题详述)。

第四步,将候选者放入正在compact的列表。最后判断,如果此次要compact该region的所有文件,则升级为major。设置priority后返回。

该步全程对fileCompacting对象加锁。

1.  接着,设置compactRequest的server为当前的regionserver,如果传入了priority参数,则设置为传入的值,然后调用Store的throttleCompact找到是large还是small的线程池。

2.  通过线程池,异步执行compact。Compact的过程如下:

执行CompactRequest的run方法:

第一步,调用Region的compact(不加锁,因为只和split冲突,而split在regionserver上是串行的)。首先检查个参数是否有效,检查和设置writestate(writesEnabled才可以继续),打印开始日志(Starting compaction on XX_Store in region YYYY [as an off-peakcompaction]),在MonitoredTask注册状态。

第二步,调用Store的compact,检查参数,检查fileCompacting是否包含这些文件(加锁检查),获取MaxSequenceId和Log相关对象,打印文件个数和大小(Starting compaction of NN file(s) in SSSS of RRR into tmpdir=…)

第三步,Store使用自己的compactor对象compact,先对每个file,取得reader后获取信息,包括bloom filter的keycount(kv对象);如果是major,计算所有文件最早的putTS,(如果debug,打印Compacting 文件名,keycount=,bloomtype=,size=,encoding=,earliestPutTs=<major才有>),获取compactKVMax和压缩算法。对每个文件,得到一个StoreFileScanner,找到所有scanner的getSmallestReadPoint,用coprocessor的preCompactScannerOpen进行处理,得到或者新建一个StoreScanner(首先通过ttl、bloom filter、timerange过滤一些scanner,其次定位每个scanner到startKey,通过KeyValueHeap将scanners连在一起,通过key比较和heap机制内部实现了对外scan时key的顺序访问),对临时文件建一个writer,通过StoreScanner获取数据writer写入。

第四步,Store的completeCompact方法将新文件放入family的目录,正常打开,通知监听storefile更改的对象,打印debug信息(Removing store files after compaction...),通过HFileArchiver.archiveStoreFiles对原hfile归档,如果fs为null,直接删除这些文件。

第五步,计算新store的大小,打印完成信息(Completed [major] compaction of NN file(s) in SSS of RRR intoSF_PATH,…total size is )。

CompactRequest会打印Completed/compaction: CRCRCR ; duration=,然后如果成功要判断是不是要split。

二、             SPLIT

2.1.   split来源:

1、Memstore flush时直接CompactSplitThread.requestSplit;2、HBaseAdmin客户端发起的请求,HRegionServer收到后,转CompactSplitThread.requestSplit处理;

hbase源码分析_ui_03

2.2.   split过程

hbase源码分析_线程池_04

1)      如果是HBaseAdmin发起,在HRegionServer的splitRegion方法中,首先检查region.flushcache()和region.forceSplit(设置force为true,并设置splitPoint),然后region.checkSplit(),在checkSplit()中主要处理包括:

(1) 检查是否元数据表

(2) 根据split策略判断是否应当split,默认IncreasingToUpperBoundRegionSplitPolicy下的机制(检查region的shouldForceSplit,如果是hbaseadmin发起就直接返回可以split的true。否则继续:获得这个RegionServer上这个表的region个数,如果为0,则“Store阀值”设为table的maxFileSize,否则根据公式min(r^2*flushSize,maxFileSize)计算,接着检查每个store的canSplit,如果存在文件引用,返回false,否则有store的大小大于“Store阀值”的,debug<ShouldSplitbecause familyName  size=SSS,sizeToCheck=CCC, regionsWithCommonTable=RRRR>),

(3) 或者在ConstantSizeRegionSplitPolicy下(检查region的shouldForceSplit,如果是hbaseadmin发起就会得到true;然后通过每个store的canSplit,检查是否存在文件引用,如果存在就不能split,否则如果shouldForceSplit为true,或者store的size大于maxFileSize,都返回true)

(4) 获得splitPoint,通过RegionSplitPolicy的getSplitPoint方法获取,主要实现都在RegionSplitPolicy中:首先如果this.region.getExplicitSplitPoint(一般是hbaseAdmin发起设置的才有)有设置,直接返回结果。否则检查每个store的getSplitPoint(进行一些完整性验证,找到最大的storeFile,通过storeFile的Reader的midKey()<会通过HFileReaderV2的midKey()调用HFileBlockIndex的midKey获取>,得到hfile的中间块的第一个KeyValue,如果这个KV不是storeFile.Reader的第一个和最后一个KV<debug: cannot split because midkey is the same as first or lastrow>,就返回其rowKey为结果),将最大的store的splitPoint返回。另外还有IncreasingToUpperBoundRegionSplitPolicy的两个子类,实现了这个方法,DelimitedKeyPrefixRegionSplitPolicy保证以分隔符前面的前缀为splitPoint,保证相同RowKey前缀的数据在一个Region中;KeyPrefixRegionSplitPolicy保证具有相同前缀的row在一个region中(要求设计中前缀具有同样长度)

(5) 检查得到的splitKey是否在region中,如果不在,会抛异常:Requestedrow out of range for calculated split on HRegion …startKey=,endKey=,row=….

2)      CompactSplitThread在requestSplit会使用线程池运行split,并最终由SplitRequest的run方法执行:如果最终传入的midKey为null,debug:Region RRRRnot splittable because midkey=null

(1) 首先新建一个SplitTransaction,其中splitdir=父region的path/.splits

(2) SplitTransaction.prepare,第一步检查是否可split(reference、closing、closed),第二步检查splitrow(原midKey)是否null,检查splitrow是否与startKey相同或者不在region中:info:Split row isnot inside region key range or is equal to startkey:。。,然后返回false。第三步计算子region的ts,如果ts比父region的还小,warn:Clock skew; parent regions id is。。。,but current timehere is,但将父region的ts+1,最后一步建立regionInfo对象:hri_a和hri_b

(3) SplitTransaction.execute,真正进行split的地方,第一步:createDaughters,第二步openDaughters,第三步transitionZKNode。

a)        createDaughters,info:Starting splitof region RRR。这里面test部分的分析略去。在zk上createNodeSplitting以标识父region在splitting,journal中加入开始split的日志,将zknode的状态从splitting改到splitting,创建split的目录splitdir。然后,关闭父region(等待flush和compact完成、如果需要进行preflush,获得writelock后,flush,使用一个名为StoreCloserThread-R_NAME的线程池并发地关闭store,设置closed标志,设置status),其中Store的关闭:(先将storefiles作为result,并将原storefiles置为空<避免Metrics中出现>,同样用线程池关闭storefile)。

疑问:// Disable compacting and flushing by background threads for this region.谁是back thread

                       a.1         如果关闭父region没有发生异常,且hstoreFilesToSplit为null,则说明有其他线程在关闭region,抛出closedByOtherException,否则打印:No store files to split for the region…。否则:从service中remove:services.removeFromOnlineRegions。

                       a.2         在父region的splits目录下splitStoreFiles:有多少file就创建多大的线程池,对每一个文件,用StoreFileSplitter进行处理。如果超过fileSplitTimeout的时限,则抛出异常:Took too long to split the files and create the references, abortingsplit。StoreFileSplitter中根据familyName等信息,生成Reference,并保持在splitdir/[daughter]_EncodedName/familyName/storefileName.RegionEncodedName,内容包括是文件的高半部分内容(bottom),还是底半部分,另外还有splitRow的最小可能rowkey。

                       a.3         创建Region A,创建HRegion对象,将请求数/2分配给A,将hdfs上splitdir上的ref文件move到A的正常目录下。

                       a.4         同样方式创建B。

                       a.5         MetaEditor.offlineParentInMeta。建立一个Put:将offline/split设为hregionInfo,将A和B的信息放入该行,put到META表中。

 

b)   openDaughters,(Performtime consuming opening of the daughter regions)

如果RS正在停止,INFO:Not opening daughtersXXX and YYY because stopping=。。。stop=。。。开启两个DaughterOpener hasThread对象(thread名字为:[SERVERNAME]-daughterOpener=[encodedName]),并启动线程。线程执行内容为:openDaughterRegionà daughter.openHRegionà initialize àinitializeRegionInternals,大多流程都在其中,见下面细述。

                       b.1        首先checkRegioninfoOnFilesystem检查hdfs上是否有HRegionInfo记录文件;cleanupTmpDir从hdfs上删除父region的.tmp目录;getStoreOpenAndCloseThreadPool(hbase.hstore.open.and.close.threads.max);

                       b.2        然后并行打开storefileàinstantiateHStore(new Store),并对每个store的maxSeqId和MaxMemstoreTS进行处理,找到最小的maxSeqId和最大的MaxMemstoreTS(seqId会作为edits的名字),接着mvcc.initialize。

                       b.3        接着replayRecoveredEditsIfAny:{HLog.getSplitEditFilesSorted;isZeroLengthThenDelete,WARN:< File [LOGFILE] is zero-length, deleting. >;replayRecoveredEdits——如果出错,检查“hbase.skip.errors”,确定是否HLog.moveAsideBadEditsFile;this.rsAccounting.clearRegionReplayEditsSize ;所有的edits处理完后,如果replay了数据(seqId判断),则internalFlushcache;最后删除edits文件,返回其中最大seqId};

                       b.4        之后cleanupAnySplitDetritus:cleanupDaughterRegion;cleanupSplitDir;对于daughter A已经创建完成,正在创建B的过程中rs死掉,没有处理;

                       b.5        再删除.merges这个目录(前文没有这个)

                       b.6        最后设置一些Region的读写权限、split策略

最后openDaughters调用 services .postOpenDeployTasks(s.hasReferences()或s.needsCompaction()会请求一次compact,INFO: Donewith post open deploy task for region=)à services.addToOnlineRegions

c)   transitionZKNode,首先在zk上将该region的SPLITTING状态的node转换为SPLIT状态(转换可能失败有三个原因:splitting node不存在、不是splitting状态、版本错误),然后检查该node是否存在(master会处理该情况,并删除该节点,当tickleNodeSplit返回-1),每100ms检查一次,并且每10次打印(debug:Still waiting on the master to process the split for…)

(4) 如果异常,rollback

(5) 如果其他异常或者rollback过程异常,checkFileSystem

(6) Master的AssignmentManager.handleRegion会监控zk的split相关node 的状态变化:caseRS_ZK_REGION_SPLIT:会做一些状态检查的处理,细节和整体状态变化有关系;如果收到split消息,但regionstate为null,则可能打印(强制master这边信息offline都不可行,则warn:Received SPLIT for region… from server …, but it doesn't existanymore…。如果强制offlineok,INFO:Received SPLIT for region… from server …, but region was not firstin SPLITTING state; continuing。然后使用SplitRegionHandler监控split过程(循环检查split node状态,来确定是否成功,成功会删除parent的split node)。如果出NoNodeEx异常会DEBUG:The znode XXX does not exist. May be deleted already.如果是其他异常mater会退出:master.abort(Error deleting SPLIT node in ZK for transition ZK node(   …”

 

三、       影响运行的相关参数

2.1.            紧缩参数

•       hbase.hstore.compaction.ratio(1.2)

–         通常发生当file size <= sum(smaller_files) * ratio 时将会minor

–         默认1.2F

•       hbase.hstore.compaction.ratio.offpeak(5.0)

–         默认5.0F

–         非高峰时期的ratio,需要定非高峰

•    hbase.offpeak.start.hour(-1)

•    hbase.offpeak.end.hour(-1)

•       hbase.hstore.compaction.min.size(memstoresize)

–         无条件紧缩比这个size小的文件

•       hbase.hstore.compaction.max.size(Long.Max)

–         从不紧缩比这个size大的文件(除非正在spliting?)

•       hbase.hstore.compaction.min(3)

–         minor compact时,最少需要的文件个数,>2

–         hbase.hstore.compactionThreshold(老版本的配置)

•       hbase.hstore.compaction.max(10)

–         紧缩时,最多的文件数

•       hbase.store.delete.expired.storefile(true)

–         默认true,删除ttl到期的storefile

–         当不是强制主紧缩的时候生效

•       hbase.hstore.compaction.kv.max(10)

–         Compact每次scan最大kv个数

•       hbase.hstore.compaction.complete(true)

–         完成后是否move文件到适当位置等并执行coprocessor

•       hbase.hregion.majorcompaction(24h)

–         主紧缩检查的周期,到期时会检查文件的ttl是否超时

•       hbase.hregion.majorcompaction.jitter(20%-4.8h)

–         检查主紧缩时,避免主紧缩风暴的随机百分比

•       hbase.regionserver.thread.compaction.throttle

–         判断是进行large和small compaction的regijon大小阀值

–         默认2 * this.minFilesToCompact * this.region.memstoreFlushSize

–         hbase.server.thread.wakefrequency(10,000ms)

–         紧缩检查周期*hbase.server.thread.wakefrequency.multiplier

•       hbase.regionserver.compactionChecker.majorCompactPriority(Integer.MAX)

–         紧缩检查时,主紧缩的优先级

•       hbase.regionserver.thread.compaction.large(1)

–         大紧缩的线程数,>0

•       hbase.regionserver.thread.compaction.small(1)

–         小紧缩的线程数,>0

2.2.            破裂参数:

•       hbase.server.thread.wakefrequency(10*1000ms)

–         这里是从flushQueue中获取可以flush的entry超时时间

•       hbase.regionserver.regionSplitLimit(Integer.MAX)

–         一个server上最大的region个数,大于此,可以不破裂

•       hbase.regionserver.thread.split(1)

–         split的最大线程数

•       hbase.regionserver.fileSplitTimeout(30000)

–         split超时时间

–         IOE:Took toolong to split the files and create the references, aborting split

•       hbase.hstore.report.interval.edits(2000)

–         replay edit时检查是否报告的间隔

•       hbase.master.assignment.timeoutmonitor.timeout(18000)

–         Master的timeout,实际上,master会循环等待这么长时间。

•       hbase.hstore.report.period(9000)

–         报告的时间间隔Master的timeout的一半

2.3.            破裂策略:

可以在创建表的时候进行定义

•       IncreasingToUpperBoundRegionSplitPolicy

–         根据公式min(r^2*flushSize,maxFileSize)确定split的maxFileSize

•       ConstantSizeRegionSplitPolicy

–         maxFileSize决定split大小

•       DelimitedKeyPrefixRegionSplitPolicy

–         保证以分隔符前面的前缀为splitPoint,保证相同RowKey前缀的数据在一个Region中

•       KeyPrefixRegionSplitPolicy

–         KeyPrefixRegionSplitPolicy.prefix_length

–         以长度作为标准,保证相同RowKey前缀的数据在一个Region中



–         要求设计中前缀具有同样长度

 


 

 

 -----------------

http://hadoop.apache.org/docs/r0.19.1/cn/cluster_setup.html

 

配置



接下来的几节描述了如何配置Hadoop集群。



配置文件

对Hadoop的配置通过conf/目录下的两个重要配置文件完成:

  1. hadoop-default.xml - 只读的默认配置。
  2. hadoop-site.xml - 集群特有的配置。

要了解更多关于这些配置文件如何影响Hadoop框架的细节,请看这里

此外,通过设置conf/hadoop-env.sh中的变量为集群特有的值,你可以对bin/目录下的Hadoop脚本进行控制。



集群配置

要配置Hadoop集群,你需要设置Hadoop守护进程的运行环境和Hadoop守护进程的运行参数

Hadoop守护进程指NameNode/DataNode 和JobTracker/TaskTracker。



配置Hadoop守护进程的运行环境

管理员可在conf/hadoop-env.sh脚本内对Hadoop守护进程的运行环境做特别指定。

至少,你得设定JAVA_HOME使之在每一远端节点上都被正确设置。

管理员可以通过配置选项HADOOP_*_OPTS来分别配置各个守护进程。 下表是可以配置的选项。

守护进程

配置选项

NameNode

HADOOP_NAMENODE_OPTS

DataNode

HADOOP_DATANODE_OPTS

SecondaryNamenode

HADOOP_SECONDARYNAMENODE_OPTS

JobTracker

HADOOP_JOBTRACKER_OPTS

TaskTracker

HADOOP_TASKTRACKER_OPTS

例如,配置Namenode时,为了使其能够并行回收垃圾(parallelGC), 要把下面的代码加入到hadoop-env.sh : 
export HADOOP_NAMENODE_OPTS="-XX:+UseParallelGC ${HADOOP_NAMENODE_OPTS}" 

其它可定制的常用参数还包括:

  • HADOOP_LOG_DIR
  • HADOOP_HEAPSIZE - 最大可用的堆大小,单位为MB。比如,1000MB。 这个参数用于设置hadoop守护进程的堆大小。缺省大小是1000MB。


配置Hadoop守护进程的运行参数

这部分涉及Hadoop集群的重要参数,这些参数在conf/hadoop-site.xml中指定。

参数

取值

备注

fs.default.name

NameNode的URI。

hdfs://主机名/

mapred.job.tracker

JobTracker的主机(或者IP)和端口。

主机:端口

dfs.name.dir

NameNode持久存储名字空间及事务日志的本地文件系统路径。

当这个值是一个逗号分割的目录列表时,nametable数据将会被复制到所有目录中做冗余备份。

dfs.data.dir

DataNode存放块数据的本地文件系统路径,逗号分割的列表。

当这个值是逗号分割的目录列表时,数据将被存储在所有目录下,通常分布在不同设备上。

mapred.system.dir

Map/Reduce框架存储系统文件的HDFS路径。比如/hadoop/mapred/system/。

这个路径是默认文件系统(HDFS)下的路径, 须从服务器和客户端上均可访问。

mapred.local.dir

本地文件系统下逗号分割的路径列表,Map/Reduce临时数据存放的地方。

多路径有助于利用磁盘i/o。

mapred.tasktracker.{map|reduce}.tasks.maximum

某一TaskTracker上可运行的最大Map/Reduce任务数,这些任务将同时各自运行。

默认为2(2个map和2个reduce),可依据硬件情况更改。

dfs.hosts/dfs.hosts.exclude

许可/拒绝DataNode列表。

如有必要,用这个文件控制许可的datanode列表。

mapred.hosts/mapred.hosts.exclude

许可/拒绝TaskTracker列表。

如有必要,用这个文件控制许可的TaskTracker列表。

通常,上述参数被标记为 final 以确保它们不被用户应用更改。



现实世界的集群配置

这节罗列在大规模集群上运行sort基准测试(benchmark)时使用到的一些非缺省配置。

  • 运行sort900的一些非缺省配置值,sort900即在900个节点的集群上对9TB的数据进行排序:

参数

取值

备注

dfs.block.size

134217728

针对大文件系统,HDFS的块大小取128MB。

dfs.namenode.handler.count

40

启动更多的NameNode服务线程去处理来自大量DataNode的RPC请求。

mapred.reduce.parallel.copies

20

reduce启动更多的并行拷贝器以获取大量map的输出。

mapred.child.java.opts

-Xmx512M

为map/reduce子虚拟机使用更大的堆。

fs.inmemory.size.mb

200

为reduce阶段合并map输出所需的内存文件系统分配更多的内存。

io.sort.factor

100

文件排序时更多的流将同时被归并。

io.sort.mb

200

提高排序时的内存上限。

io.file.buffer.size

131072

SequenceFile中用到的读/写缓存大小。

  • 运行sort1400和sort2000时需要更新的配置,即在1400个节点上对14TB的数据进行排序和在2000个节点上对20TB的数据进行排序:

参数

取值

备注

mapred.job.tracker.handler.count

60

启用更多的JobTracker服务线程去处理来自大量TaskTracker的RPC请求。

mapred.reduce.parallel.copies

50

 

tasktracker.http.threads

50

为TaskTracker的Http服务启用更多的工作线程。reduce通过Http服务获取map的中间输出。

mapred.child.java.opts

-Xmx1024M

使用更大的堆用于maps/reduces的子虚拟机


Slaves

通常,你选择集群中的一台机器作为NameNode,另外一台不同的机器作为JobTracker。余下的机器即作为DataNode又作为TaskTracker,这些被称之为slaves

在conf/slaves文件中列出所有slave的主机名或者IP地址,一行一个。



日志

Hadoop使用Apache log4j来记录日志,它由Apache Commons Logging框架来实现。编辑conf/log4j.properties文件可以改变Hadoop守护进程的日志配置(日志格式等)。



历史日志

作业的历史文件集中存放在hadoop.job.history.location,这个也可以是在分布式文件系统下的路径,其默认值为${HADOOP_LOG_DIR}/history。jobtracker的web UI上有历史日志的web UI链接。

历史文件在用户指定的目录hadoop.job.history.user.location也会记录一份,这个配置的缺省值为作业的输出目录。这些文件被存放在指定路径下的“_logs/history/”目录中。因此,默认情况下日志文件会在“mapred.output.dir/_logs/history/”下。如果将hadoop.job.history.user.location指定为值none,系统将不再记录此日志。

用户可使用以下命令在指定路径下查看历史日志汇总
$ bin/hadoop job -history output-dir 
这条命令会显示作业的细节信息,失败和终止的任务细节。 
关于作业的更多细节,比如成功的任务,以及对每个任务的所做的尝试次数等可以用下面的命令查看
$ bin/hadoop job -history all output-dir 

一但全部必要的配置完成,将这些文件分发到所有机器的HADOOP_CONF_DIR路径下,通常是${HADOOP_HOME}/conf。



Hadoop的机架感知



HDFS和Map/Reduce的组件是能够感知机架的。

NameNode和JobTracker通过调用管理员配置模块中的APIresolve来获取集群里每个slave的机架id。该API将slave的DNS名称(或者IP地址)转换成机架id。使用哪个模块是通过配置项topology.node.switch.mapping.impl来指定的。模块的默认实现会调用topology.script.file.name配置项指定的一个的脚本/命令。 如果topology.script.file.name未被设置,对于所有传入的IP地址,模块会返回/default-rack作为机架id。在Map/Reduce部分还有一个额外的配置项mapred.cache.task.levels,该参数决定cache的级数(在网络拓扑中)。例如,如果默认值是2,会建立两级的cache- 一级针对主机(主机 -> 任务的映射)另一级针对机架(机架 -> 任务的映射)。



启动Hadoop



启动Hadoop集群需要启动HDFS集群和Map/Reduce集群。

格式化一个新的分布式文件系统:
$ bin/hadoop namenode -format

在分配的NameNode上,运行下面的命令启动HDFS:
$ bin/start-dfs.sh

bin/start-dfs.sh脚本会参照NameNode上${HADOOP_CONF_DIR}/slaves文件的内容,在所有列出的slave上启动DataNode守护进程。

在分配的JobTracker上,运行下面的命令启动Map/Reduce:
$ bin/start-mapred.sh

bin/start-mapred.sh脚本会参照JobTracker上${HADOOP_CONF_DIR}/slaves文件的内容,在所有列出的slave上启动TaskTracker守护进程。



停止Hadoop



在分配的NameNode上,执行下面的命令停止HDFS:
$ bin/stop-dfs.sh

bin/stop-dfs.sh脚本会参照NameNode上${HADOOP_CONF_DIR}/slaves文件的内容,在所有列出的slave上停止DataNode守护进程。

在分配的JobTracker上,运行下面的命令停止Map/Reduce:
$ bin/stop-mapred.sh 

bin/stop-mapred.sh脚本会参照JobTracker上${HADOOP_CONF_DIR}/slaves文件的内容,在所有列出的slave上停止TaskTracker守护进程。



一、

转:http://koven2049.iteye.com/blog/974714

master启动过程:

-->首先初始化HMaster

-->创建一个rpcServer,其中并启动

-->启动一个Listener线程,功能是监听client的请求,将请求放入nio请求队列,逻辑如下:

-->创建n个selector,和一个n个线程的readpool,n由"ipc.server.read.threadpool.size"决定,默认为10

-->读取每个请求的头和内容,将内容放入priorityQueue中

-->启动一个Responder线程,功能是将响应队列里的数据写给各个client的connection通道,逻辑如下:

-->创建nio selector

-->默认超时时间为15 mins

-->依次将responseQueue中的内容写回各通道,并关闭连接

-->buffer=8k(代码写死)

-->如果该请求的返回没有写完,则放回队列头,推迟再发送

-->对于超时未完成的响应,丢弃并关闭相应连接

-->启动N(n默认为10)个Handler线程,功能是处理请求队列,并将结果写到响应队列

-->读取priorityQueue中的call,调用对应的call方法获得value,写回out并调用doRespond方法,处理该请求,并唤醒writable selector

-->启动M(m默认为0)个Handler线程以处理priority

-->注册zookeeper watcher

 

-->block直至成为active master 

    -->先检查配置项"hbase.master.backup",自己是否backup机器,如果是则直接block直至检查到系统中的active master挂掉(默认每3分钟检查一次) 

    -->否则,注册zookeeper watcher,如果注册成功则成为active master,否则阻塞并反复通过watcher检查在运行的master是否挂掉或stop状态 

 

-->进入finishInitialization()函数并初始化master: 

    -->先检查hbase版本,如果是第一次启动hbase,则将版本信息写入/hbase/hbase.version,如果下次启动时这个文件不存在,则无法启动!如果不小心删掉了必须自己写一个版本 信息上去 

    --> 检查和保证root region的完好,如果root region还不存在,则创建root region以及第一个meta region 

    -->创建HConnectionImplementation实现的连接 

    -->创建server manager,后续绝大部分master工作都是通过server manager完成的 

    -->创建并启动CatalogTracker线程,用以跟踪root和meta两个特殊table的状态,它内部又启动RootRegionTracker和MetaNodeTracker两个线程 

    -->创建并启动RegionServerTracker线程,用以跟踪所有online region的状态,一旦有node delete状态则会通知server manager将其注销 

    -->创建并启动ClusterStatusTracker线程,用以跟踪整个cluster的up和down状态 

    -->向zookeeper注册AssignmentManager,用以管理region的分配 

    -->创建线程池ExecutorService,在其中运行以下线程 

        MASTER_META_SERVER_OPERATIONS 

        MASTER_SERVER_OPERATIONS 

        MASTER_CLOSE_REGION 

        MASTER_OPEN_REGION 

        MASTER_TABLE_OPERATIONS

--> 以守护方式运行LogCleaner线程,作用是每1分钟清理(.oldlogs)目录

--> 启动一个http server:InfoServer,作用是在60010端口上提供界面展示

--> 允许在初始化HMaster类时启动的Handler线程开始提供响应(通过开关标志)

    -->然后master需要等待regionserver的报告,满足以下这些条件后返回当前所有region server上的region数后继续: 

       a 至少等待4.5s("hbase.master.wait.on.regionservers.timeout") 

       b 成功启动regionserver节点数>=1("hbase.master.wait.on.regionservers.mintostart") 

       c 1.5s内没有regionsever死掉或新启动("hbase.master.wait.on.regionservers.interval") 

 

 

    -->然后splitLogAfterStartup,从hlog中恢复数据,这是一个很长的逻辑: 

        -->依次检查每一个hlog目录,查看它所属的region server是否online,如果是则不需要做任何动作,region server自己会恢复数据,如果不是,则需要将它分配给其它 的region server 

        -->split是加锁操作: 

        --> 创建一个新的hlogsplitter,遍历每一个server目录下的所有hlog文件,依次做如下操作。(如果遇到文件损坏等无法跳过的错误,配 置"hbase.hlog.split.skip.errors=true"以忽略之) 

        -->启动"hbase.regionserver.hlog.splitlog.writer.threads"(默认为3)个线程,共使用128MB内存,启动这些写线程 

        -->先通过lease机制检查文件是否能够append,如果不能则死循环等待 

        -->把hlog中的内容全部加载到内存中(内存同时被几个写线程消费) 

             -->把有损坏并且跳过的文件移到/hbase/.corrupt/目录中 

             --> 把其余己经处理过的文件移到/hbase/.oldlogs中,然后删除原有的server目录 

             --> 等待写线程结束,返回新写的所有路径 

       -->解锁

    写线程逻辑: 

        -->从内存中读出每一行数据的key和value,然后查询相应的region路径。如果该region路径不存在,说明该region很可能己经被split了,则不处理这部分数 据,因为此时忽略它们是安全的。 

        -->如果上一步能查到相应的路径,则到对应路径下创建"recovered.edits"文件夹(如果该文件夹存在则删除后覆盖之),然后将数据写入该文件夹 

 

    -->完成split Hlog的操作后,开始分配root和meta表: 

      -->分配root表: 

          -->block住直到root的region server从transaction状态中恢复正常 

          -->检查root的region server在zookeeper中是否己经有值并且正常可访问,如果不正常或没有则删除原有节点再重新分配(随机分配) 

      --> 分配meta表: 

         -->过程同root表的分配 

 

    -->如果启动后online的region servers上的regions总数为0,则表示这是个fresh start,清除掉zookeeper上的所有节点,重新开始watching 

         --> 开始分配user表 

               -->扫描meta中的所有表,依次分配,分配方案如下: 

                    -->如果"hbase.master.startup.retainassign"为true(默认为true),则分配时按meta表中原有的信息来分配,即原来在哪里就还分到哪里,如果哪个region在原有的 server info中找不到所属的region server则从online region server中随机挑选 

                    -->否则随机循环添加region,会保证balance 

              -->分配方案设计好后,开始执行分配的线程,默认超时时间10分钟 

   -->如果启动后online的region servers不为0,则表示很可能master挂掉过,可能是重新启动的。此时系统中己有region servers了,需要先处理region server上的regions: 

        -->转入processFailover处理流程

-->先调用rebuildUserRegions函数,它扫描.META.表,更新所有的server和对应的regionInfo信息,找出offline的server,加入deadServers

-->处理deadServers

-->遍历所有的region,在zk上创建它们的node,把它们加入transition和unassign列表,并置状态为offline

-->调用ServerShutdownHandler的processDeadRegion方法,处理所有dead regions

-->跳过所有disabled的table对应的region

-->查看该region是否己经split,如果是,需要fix它的子region

-->查看子region的info是否找不到了,如果是的话修补之(即重新将meta表中的region信息添加进来并assign它)

-->然后扫描zk中的transition列表,对不同的事件做不同处理

-->RS_ZK_REGION_CLOSING:将它简单添加到regionsInTransition队列中

-->RS_ZK_REGION_CLOSED:将它添加到regionsInTransition队列中并且创建一个ClosedRegionHandler线程去处理它

-->ClosedRegionHandler流程:

-->按root/meta/user region分优先级

-->如果这个region对应的table己经disabled或disabling,那么下线它并返回

-->下线这个region,然后再将它分配给一个server(不太明白为什么此时需要assign一次)

-->M_ZK_REGION_OFFLINE:同上

-->RS_ZK_REGION_OPENING:将它简单添加到regionsInTransition队列中

-->RS_ZK_REGION_OPENED:将它添加到regionsInTransition队列中,如果它对应的原来的RS还存在,则创建一个OpenedRegionHandler线程来处理,否则不处理,等待meta扫描时去分配它

-->OpenedRegionHandler流程:

-->按root/meta/user region分优先级

-->先删除zk中己经打开的对应的node

-->上线这个region

-->如果这个region对应的table己经disabled或disabling,那么unassign它

 

 

    --> 启动balancer线程并放入守候线程,默认循环时间为300秒 

    -->balancer线程的作用是定期均衡所有region server上的region数量,工作过程如下: 

        -->三种情况不进行balance: 

             -->balance开关没有打开(硬编码打开了) 

             -->有至少一个region正处于regionsInTransition状态(split结束但还没有清除) 

             -->有正在下线处理的region server 

        --> 制定出balance计划: 

             -->计算总的region数目,以及online server数量,每个server的region数目都会均衡到平均数。 

        -->执行balance计划: 

             --> 将执行计划放入assignment的执行计划列表 

             -->检查该region是否又进入了transaction状态,如果是则跳过 

             -->将该region置为pending_close状态 

             --> 向region server发送close region的信号 

        --> 真实执行计划在其它时候(下一篇文章介绍)

 

    -->启动meta扫描线程并放入守候线程,默认循环时间为300秒 

         --> meta扫描线程的作用是定期清理己经split的region并将它删除,工作过程如下: 

              --> 连接meta server,scan表的info信息,扫描所有region,从region info中读出是否split的信息 

              -->如果己经split,则先获取splitA和splitB的region info 

              -->如果splitA和splitB的reference都不再指向父region了(从它们的regionPath可以得出),则将父region删除掉,删掉流程如下: 

              --> 将region的状态置为offline 

              -->将该region从regionsInTransition中删除掉 

              --> 将该region从regionPlan中删除掉,避免再进行balance之类的操作 

              -->将该region的region info从AssignmentManager的regions树中以及AssignmentManager中存放的每个servers中删掉 

              -->删除meta表里的该region的目录 

              -->通知meta region server删除该region 

 

启动完成


 

 

 

二、

 

数据字典用来存储了系统的元数据。HBase的元数据包括:用户表的定义、表的切分方案、分片的分布情况(即分片分布在哪个regionserver上)、分片对应的数据文件和日志文件。其中,分片和数据文件的映射关系是通过目录映射实现的,即不同的分片的数据文件存储在不同的目录中,其中目录的名称是分片的名称。【对比:在Hypertable中分片与数据文件的映射关系记录在表中】

如图3-13所示。HBase使用表-ROOT-和表.META存储元数据【Oracle也用表存储元数据】。和普通的表一样,这两张表对应的分片分布在RegionServer上的。.META.存储了所有用户表的元数据库,可以被分裂,其元数据存储在表-ROOT-中。-ROOT-有且只有一个分片,不能被分裂。ZooKeeper的节点ZK/hbase/root-region-server存储了加载-ROOT-分片的RegionServer的地址信息,是整个系统的入口。

注:在本文描述的HBase0.90.0中,.META.还不支持分裂,不过最新版本已经支持分裂.META.了。

图3-13 定位分片的层次

1 表.META.

表.META.的定义脚本如下:

create '.META.', {NAME => 'info', VERSIONS => 10, COMPRESSION => 'none', IN_MEMORY => true, BLOCKCACHE => true, BLOCKSIZE =>8096,TTL => 2147483647, BLOOMFILTER => 'NONE', REPLICATION_SCOPE => 0 }



family 

qualifer 

描述

 

 

 

info

regioninfo

分片的属性信息,包括startkey,endkey,对应表的定义等

server

加载本分片的RS地址信息

serverstartcode

加载本分片的RS的启动时间,用于MasterServer区分RS是否重启

splitA

子分片A的regioninfo

splitB 

子分片B的regioninfo



表3-1 .META.的定义

.META.的rowKey是分片的名称。regionname的格式如表3-2.



分片类型

分片名称的格式

-ROOT- 

tablename+','+startkey+','+regionid

规定-ROOT-分片的regionid为0,所以名称为: "-ROOT-,,0"

.META. 

tablename+','+startkey+','+regionid

0.90.3版本不支持.META.分裂,规定.META.分片的regionid为1所以名称为:".META.,,1"

普通分片

tablename+','+startkey+','+regionid+'.'+md5+'.'其中md5是tablename+','+startkey+','+regionid的md5码



表3-2 分片名称的格式

分片名称中存在regionid的原因?

父分片分裂后,由于垃圾回收机制的需要,父分片的元数据不能立即在.META.得到删除,所以父分片和子分片的元数据会同时出现在.META.中。但是第一个子分片与父分片的tablename和startkey都一样, tablename+startkey无法唯一标识一个分片,所以在分片名称的尾部增加了一个regionid,从而使得分片名称可以唯一标记一个分片。regionid实际是分片的生成时间,子分片的regionid一定要比父分片的regionid要大。

 

regioninfo的定义参见下图。

图3-14 序列化的regioninfo

 



字段

描述

version 

版本号。为0

endkey 

 

offLine 

用于分裂。true表示已经closed,但是还不能删除,因为其数据文件还在被子分片使用

regionId 

分片创建当前时间

regionName 

见表3-2

split 

true:本分片已经被split。

startKey

 

tableDesc 

见表3-4

hashCode 

前面所有字段的hash值



表3-3 regioninfo的各个字段的定义

 



字段

描述

version 

name 

表名

isRoot 

是否是表-ROOT- ,冗余信息?

isMeta 

是否是表.META. ,冗余信息?

tableAttrNum 

字段tableAttrList中表属性的个数

tableAttrList 

见表3-5

familyListNum 

列族的数量

familyList 

见表3-6



表3-4 tableDesc的定义

 



名称

描述

默认值

FAMILIES

未使用?

 

IS_ROOT

是否是表"-ROOT-"

 

IS_META

是否是表".META."

 

READONLY

表的状态

false

DEFERRED_LOG_FLUSH

若为true。写数据时,无需确认WAL是否写入磁盘,即可返回。以数据的持久性交换写性能。

false

MAX_FILESIZE

分片所有数据文件的总大小达到这个阀值后,需要分裂分片。单位是字节。

256M 

MEMSTORE_FLUSHSIZE

当分片的所有MemStore的总大小到达这个阀值后,需要把MemStore中的数据写入磁盘数据文件。单位是字节。

64M 



表3-5 tableAttrList

 



名称

描述

默认值

NAME 

列族名称

VERSIONS

最大版本数

TTL

time to live 

Integer.MAX_VALUE,表示永久有效

IN_MEMORY

是否常驻内存

false 

COMPRESSION

列族的压缩算法

包括 "lzo" "gz" "none"

"none"

COMPRESSION_COMPACT

major时的压缩算法

同COMPRESSION

BLOOMFILTER

"NONE" "ROW" "ROWCOL"

"NONE"

REPLICATION_SCOPE

多个数据中心备份数据的参数。【待研究】

BLOCKSIZE

StoreFile的 data block大小,

单位字节

64*1024 

BLOCKCACHE

是否缓存其block

true 



表3-6 familyAttrList

2 表-ROOT-

表-ROOT-存储了表的.META.的元数据,并且不可以分裂。创建-ROOT-表的脚本如下:

create '-ROOT-', {NAME => 'info', VERSIONS => 10, COMPRESSION => 'none', IN_MEMORY => true, BLOCKCACHE => true, BLOCKSIZE =>8096,TTL => 2147483647, BLOOMFILTER => 'NONE', REPLICATION_SCOPE => 0 }

3 系统入口

从某种程度上来说,zookeeper是一个小型文件系统,可以用来存储及少量的数据。ZK/hbase/root-region-server是zookeeper中的一个文件,用来存储了加载-ROOT-分片的RegionServer的地址信息(主机名+port),是HBase集群的总入口。