HBase源码分析之HRegion上compact流程分析(二)

HBase源码分析之HRegion上compact流程分析(三) 在《HBase源码分析之HRegion上compact流程分析(二)》一文中,我们没有讲解真正执行合并的CompactionContext的compact()方法。现在我们来分析下它的具体实现。 阅读详情

 

2016年03月03日 21:38:04 辰辰爸的技术博客 阅读数:2767

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.youkuaiyun.com/lipeng_bigdata/article/details/50791205

        继《HBase源码分析之HRegion上compact流程分析(一)》一文后,我们继续HRegion上compact流程分析,接下来要讲的是针对表中某个列簇下文件的合并,即HStore的compact()方法,代码如下:

 

 
  1. /**

  2. * Compact the StoreFiles. This method may take some time, so the calling

  3. * thread must be able to block for long periods.

  4. *

  5. * 合并存储文件。该方法可能花费一些时间,

  6. *

  7. * <p>During this time, the Store can work as usual, getting values from

  8. * StoreFiles and writing new StoreFiles from the memstore.

  9. * 在此期间,Store仍能像往常一样工作,从StoreFiles获取数据和从memstore写入新的StoreFiles

  10. *

  11. * Existing StoreFiles are not destroyed until the new compacted StoreFile is

  12. * completely written-out to disk.

  13. *

  14. * <p>The compactLock prevents multiple simultaneous compactions.

  15. * The structureLock prevents us from interfering with other write operations.

  16. *

  17. * <p>We don't want to hold the structureLock for the whole time, as a compact()

  18. * can be lengthy and we want to allow cache-flushes during this period.

  19. *

  20. * <p> Compaction event should be idempotent, since there is no IO Fencing for

  21. * the region directory in hdfs. A region server might still try to complete the

  22. * compaction after it lost the region. That is why the following events are carefully

  23. * ordered for a compaction:

  24. * 1. Compaction writes new files under region/.tmp directory (compaction output)

  25. * 2. Compaction atomically moves the temporary file under region directory

  26. * 3. Compaction appends a WAL edit containing the compaction input and output files.

  27. * Forces sync on WAL.

  28. * 4. Compaction deletes the input files from the region directory.

  29. *

  30. * Failure conditions are handled like this:

  31. * - If RS fails before 2, compaction wont complete. Even if RS lives on and finishes

  32. * the compaction later, it will only write the new data file to the region directory.

  33. * Since we already have this data, this will be idempotent but we will have a redundant

  34. * copy of the data.

  35. * - If RS fails between 2 and 3, the region will have a redundant copy of the data. The

  36. * RS that failed won't be able to finish snyc() for WAL because of lease recovery in WAL.

  37. * - If RS fails after 3, the region region server who opens the region will pick up the

  38. * the compaction marker from the WAL and replay it by removing the compaction input files.

  39. * Failed RS can also attempt to delete those files, but the operation will be idempotent

  40. *

  41. * See HBASE-2231 for details.

  42. *

  43. * @param compaction compaction details obtained from requestCompaction()

  44. * @throws IOException

  45. * @return Storefile we compacted into or null if we failed or opted out early.

  46. */

  47. @Override

  48. public List<StoreFile> compact(CompactionContext compaction) throws IOException {

  49. assert compaction != null;

  50. List<StoreFile> sfs = null;

  51.  
  52. // 从合并上下文CompactionContext中获得合并请求CompactionRequest,即cr

  53. CompactionRequest cr = compaction.getRequest();;

  54.  
  55. try {

  56. // Do all sanity checking in here if we have a valid CompactionRequest

  57. // because we need to clean up after it on the way out in a finally

  58. // block below

  59. //

  60.  
  61. // 获取compact开始时间compactionStartTime

  62. long compactionStartTime = EnvironmentEdgeManager.currentTime();

  63.  
  64. // 确保合并请求request不为空,实际上getRequest已经判断并确保request不为空了,这里为什么还要再做判断和保证呢?先留个小小的疑问吧!

  65. assert compaction.hasSelection();

  66.  
  67. // 从合并请求cr中获得需要合并的文件集合filesToCompact,集合中存储的都是存储文件StoreFile的实例

  68. // 这个文件集合是在构造CompactionRequest请求,或者合并其他请求时,根据传入的参数或者其他请求中附带的文件集合来确定的,

  69. // 即请求一旦生成,需要合并的文件集合filesToCompact就会存在

  70. Collection<StoreFile> filesToCompact = cr.getFiles();

  71.  
  72. // 确保需要合并的文件集合filesToCompact不为空

  73. assert !filesToCompact.isEmpty();

  74.  
  75. // 确保filesCompacting中包含所有的待合并文件filesToCompact

  76. synchronized (filesCompacting) {

  77. // sanity check: we're compacting files that this store knows about

  78. // TODO: change this to LOG.error() after more debugging

  79. Preconditions.checkArgument(filesCompacting.containsAll(filesToCompact));

  80. }

  81.  
  82. // Ready to go. Have list of files to compact.

  83. LOG.info("Starting compaction of " + filesToCompact.size() + " file(s) in "

  84. + this + " of " + this.getRegionInfo().getRegionNameAsString()

  85. + " into tmpdir=" + fs.getTempDir() + ", totalSize="

  86. + StringUtils.humanReadableInt(cr.getSize()));

  87.  
  88. // Commence the compaction.

  89. // 开始合并,调用CompactionContext的compact()方法,获得合并后的新文件newFiles

  90. List<Path> newFiles = compaction.compact();

  91.  
  92. // TODO: get rid of this!

  93. // 根据参数hbase.hstore.compaction.complete确实是否要完整的完成compact

  94. // 这里有意思,这么处理意味着,新旧文件同时存在,新文件没有被挪到指定位置且新文件的Reader被关闭,对外提供服务的还是旧文件,啥目的呢?快速应用于读?

  95. if (!this.conf.getBoolean("hbase.hstore.compaction.complete", true)) {

  96. LOG.warn("hbase.hstore.compaction.complete is set to false");

  97.  
  98. // 创建StoreFile列表sfs,大小为newFiles的大小

  99. sfs = new ArrayList<StoreFile>(newFiles.size());

  100.  
  101. // 遍历新产生的合并后的文件newFiles,针对每个文件创建StoreFile和Reader,关闭StoreFile上的Reader,

  102. // 并将创建的StoreFile添加至列表sfs

  103. for (Path newFile : newFiles) {

  104. // Create storefile around what we wrote with a reader on it.

  105. StoreFile sf = createStoreFileAndReader(newFile);

  106.  
  107. // 关闭其上的Reader

  108. sf.closeReader(true);

  109. sfs.add(sf);

  110. }

  111.  
  112. // 返回合并后的文件

  113. return sfs;

  114. }

  115.  
  116. // Do the steps necessary to complete the compaction.

  117. // 执行必要的步骤以完成这个合并

  118.  
  119. // 移动已完成文件至正确的地方,创建StoreFile和Reader,返回StoreFile列表sfs

  120. sfs = moveCompatedFilesIntoPlace(cr, newFiles);

  121.  
  122. // 在WAL中写入Compaction记录

  123. writeCompactionWalRecord(filesToCompact, sfs);

  124.  
  125. // 替换StoreFiles:

  126. // 1、去除掉所有的合并前,即已被合并的文件compactedFiles,将合并后的文件sfs加入到StoreFileManager的storefiles中去,

  127. // storefiles为Store中目前全部提供服务的存储文件列表;

  128. // 2、正在合并的文件列表filesCompacting中去除被合并的文件filesToCompact;

  129. replaceStoreFiles(filesToCompact, sfs);

  130.  
  131.  
  132. // 根据合并的类型,针对不同的计数器做累加,方便系统性能指标监控

  133. if (cr.isMajor()) {// 如果是Major合并

  134.  
  135. // 计数器累加,包括条数和大小

  136. majorCompactedCellsCount += getCompactionProgress().totalCompactingKVs;

  137. majorCompactedCellsSize += getCompactionProgress().totalCompactedSize;

  138. } else {// 如果不是Major合并

  139.  
  140. // 计数器累加,包括条数和大小

  141. compactedCellsCount += getCompactionProgress().totalCompactingKVs;

  142. compactedCellsSize += getCompactionProgress().totalCompactedSize;

  143. }

  144.  
  145. // At this point the store will use new files for all new scanners.

  146. // 至此,store将会为所有新的scanners使用新的文件

  147. // 完成合并:归档旧文件(在文件系统中删除已被合并的文件compactedFiles,实际上是归档操作,将旧的文件从原位置移到归档目录下),关闭其上的Reader,并更新store大小

  148. completeCompaction(filesToCompact, true); // Archive old files & update store size.

  149.  
  150. // 记录日志信息

  151. logCompactionEndMessage(cr, sfs, compactionStartTime);

  152.  
  153. // 返回StoreFile列表sfs

  154. return sfs;

  155. } finally {

  156.  
  157. // 完成Compaction请求:Region汇报合并请求至终端、filesCompacting中删除请求中的所有待合并文件

  158. finishCompactionRequest(cr);

  159. }

  160. }

        下面,我们来概述下整个流程:

 

        1、首先,从合并上下文CompactionContext中获得合并请求CompactionRequest,即cr;

        2、获取compact开始时间compactionStartTime;

        3、确保合并请求request不为空:

              实际上getRequest已经判断并确保request不为空了,这里为什么还要再做判断和保证呢?先留个小小的疑问吧!

        4、从合并请求cr中获得需要合并的文件集合filesToCompact:

              集合中存储的都是存储文件StoreFile的实例,这个文件集合是在构造CompactionRequest请求,或者合并其他请求时,根据传入的参数或者其他请求中附带的文件集合来确定的,即请求一旦生成,需要合并的文件集合filesToCompact就会存在。

        5、确保需要合并的文件集合filesToCompact不为空;

        6、确保filesCompacting中包含所有的待合并文件filesToCompact:

              那么这个filesCompacting中的文件是何时添加的呢?

        7、开始合并,调用CompactionContext的compact()方法,获得合并后的新文件newFiles:

              这一步是核心流程,它会持有通过scanner访问待合并文件,然后将数据全部写入新文件,后续文章会着重分析。

        8、根据参数hbase.hstore.compaction.complete确实是否要完整的完成compact,默认为true:

               8.1、如果配置的是false,则:

                        8.1.1、创建StoreFile列表sfs,大小为newFiles的大小;

                        8.1.2、遍历新产生的合并后的文件newFiles,针对每个文件创建StoreFile和Reader,关闭StoreFile上的Reader,并将创建的StoreFile添加至列表sfs;

                        8.1.3、返回合并后的文件列表sfs;

               8.2、如果配置的是true,则:

                        8.2.1、移动已完成文件至正确的地方,创建StoreFile和Reader,返回StoreFile列表sfs;

                        8.2.2、在WAL中写入Compaction记录;

                        8.2.3、替换StoreFiles:包括去除掉所有的合并前,即已被合并的文件compactedFiles,将合并后的文件sfs加入到StoreFileManager的storefiles中去,storefiles为Store中目前全部提供服务的存储文件列表,还有正在合并的文件列表filesCompacting中去除被合并的文件filesToCompact。

                        8.2.4、根据合并的类型,针对不同的计数器做累加,方便系统性能指标监控;

                        8.2.5、完成合并:归档旧文件(在文件系统中删除已被合并的文件compactedFiles,实际上是归档操作,将旧的文件从原位置移到归档目录下),关闭其上的Reader,并更新store大小;

                        8.2.6、记录日志信息;

                        8.2.7、完成Compaction请求:Region汇报合并请求至终端、filesCompacting中删除请求中的所有待合并文件;

                        8.2.8、返回StoreFile列表sfs。

        至此,整个流程详述完毕。接下来,我们针对其中的部分细节,再做详细描述。

        首先,真正执行合并的CompactionContext的compact()方法我们暂时不讲,只需要知道它会持有通过scanner访问待合并文件,然后将数据全部写入新文件,并得到这些新文件的集合newFiles即可,我们会在后续文章详细介绍。

        接下来,在获得合并后的新文件newFiles之后,我们会根据一个参数来确定后续处理流程,这个参数就是hbase.hstore.compaction.complete,由它来确定是否完整的结束一次合并操作,这完整与非完整的主要区别,或者说实质性区别就是:由谁来继续对外提供数据读取服务。

        先来看下非完整性结束,它会为合并后的每个文件创建StoreFile和Reader实例,同时关闭新文件上的Reader,也就意味着扔继续由旧文件提供数据读取服务,而新文件与旧文件同时存在,旧文件位置不变,涉及到列簇CF下的目前所有可用storefiles列表不变,存储的仍是旧文件的StoreFile对象;

        而对于完整性结束来说,它会移动已完成文件至正确的地方,创建StoreFile和Reader,返回StoreFile列表sfs,然后在WAL中写入Compaction记录,并替换掉storefiles,根据合并的类型,针对不同的计数器做累加,方便系统性能指标监控,归档旧文件(在文件系统中删除已被合并的文件compactedFiles,实际上是归档操作,将旧的文件从原位置移到归档目录下),关闭其上的Reader,并更新store大小,完成Compaction请求:Region汇报合并请求至终端、filesCompacting中删除请求中的所有待合并文件等等,很多复杂的操作。不要着急,我们就其中复杂的地方,一个个的解释:

        1、移动已完成文件至正确的地方,创建StoreFile和Reader,返回StoreFile列表sfs

        这个是通过moveCompatedFilesIntoPlace()方法实现的,代码如下:

 

 
  1. private List<StoreFile> moveCompatedFilesIntoPlace(

  2. CompactionRequest cr, List<Path> newFiles) throws IOException {

  3.  
  4. // 创建StoreFile列表sfs

  5. List<StoreFile> sfs = new ArrayList<StoreFile>(newFiles.size());

  6.  
  7. // 遍历newFiles

  8. for (Path newFile : newFiles) {

  9. assert newFile != null;

  10.  
  11. // 将新文件newFile挪至正确地点,并创建StoreFile和Reader

  12. StoreFile sf = moveFileIntoPlace(newFile);

  13. if (this.getCoprocessorHost() != null) {

  14. this.getCoprocessorHost().postCompact(this, sf, cr);

  15. }

  16. assert sf != null;

  17. sfs.add(sf);

  18. }

  19. return sfs;

  20. }

        首先呢,创建StoreFile列表sfs,遍历合并后的文件newFiles,将新文件newFile挪至正确地点,并创建StoreFile和Reader。而文件位置改变,则是通过moveFileIntoPlace()方法实现的,它的代码如下:

 

 

 
  1. // Package-visible for tests

  2. StoreFile moveFileIntoPlace(final Path newFile) throws IOException {

  3.  
  4. // 检测新文件

  5. validateStoreFile(newFile);

  6.  
  7. // Move the file into the right spot

  8. // 移动文件至正确的地点

  9. Path destPath = fs.commitStoreFile(getColumnFamilyName(), newFile);

  10.  
  11. // 创建StoreFile和Reader

  12. return createStoreFileAndReader(destPath);

  13. }

        我们发现,移动文件实际上是通过HStore的成员变量fs的commitStoreFile()方法来完成的。这个fs是HRegionFileSystem类型的变量,HRegionFileSystem是HRegion上文件系统的一个抽象,它实现了各种文件等的实际物理操作。我们来看下它的commitStoreFile()方法:

 

 

 
  1. /**

  2. * Move the file from a build/temp location to the main family store directory.

  3. * @param familyName Family that will gain the file

  4. * @param buildPath {@link Path} to the file to commit.

  5. * @param seqNum Sequence Number to append to the file name (less then 0 if no sequence number)

  6. * @param generateNewName False if you want to keep the buildPath name

  7. * @return The new {@link Path} of the committed file

  8. * @throws IOException

  9. */

  10. private Path commitStoreFile(final String familyName, final Path buildPath,

  11. final long seqNum, final boolean generateNewName) throws IOException {

  12.  
  13. // 根据列簇名familyName获取存储路径storeDir

  14. Path storeDir = getStoreDir(familyName);

  15.  
  16. // 如果在文件系统fs中不存在路径的情况下创建它时失败则抛出异常

  17. if(!fs.exists(storeDir) && !createDir(storeDir))

  18. throw new IOException("Failed creating " + storeDir);

  19.  
  20. String name = buildPath.getName();

  21. if (generateNewName) {

  22. name = generateUniqueName((seqNum < 0) ? null : "_SeqId_" + seqNum + "_");

  23. }

  24. Path dstPath = new Path(storeDir, name);

  25. if (!fs.exists(buildPath)) {

  26. throw new FileNotFoundException(buildPath.toString());

  27. }

  28. LOG.debug("Committing store file " + buildPath + " as " + dstPath);

  29. // buildPath exists, therefore not doing an exists() check.

  30. if (!rename(buildPath, dstPath)) {

  31. throw new IOException("Failed rename of " + buildPath + " to " + dstPath);

  32. }

  33. return dstPath;

  34. }

        非常简单,根据列簇名familyName获取存储路径storeDir,检测并在必要时创建storeDir,根据buildPath来获取文件名name,然后利用storeDir和name来构造目标路径storeDir,通过rename()方法实现文件从buildPath至dstPath的移动即可。

 

        而创建StoreFile和Reader的方法最终调用的是createStoreFileAndReader()方法,代码如下:

 

 
  1. private StoreFile createStoreFileAndReader(final StoreFileInfo info)

  2. throws IOException {

  3. info.setRegionCoprocessorHost(this.region.getCoprocessorHost());

  4. StoreFile storeFile = new StoreFile(this.getFileSystem(), info, this.conf, this.cacheConf,

  5. this.family.getBloomFilterType());

  6. storeFile.createReader();

  7. return storeFile;

  8. }

        StoreFile是一个存储数据文件。Stores通常含有一个或多个StoreFile,而Reader是其内部类,由Reader来提供文件数据的读取服务。

 

        2、在WAL中写入Compaction记录

        这个过程是通过writeCompactionWalRecord()方法来完成的,代码如下:

 

 
  1. /**

  2. * Writes the compaction WAL record.

  3. * 在WAL中写入合并记录

  4. *

  5. * @param filesCompacted Files compacted (input).

  6. * @param newFiles Files from compaction.

  7. */

  8. private void writeCompactionWalRecord(Collection<StoreFile> filesCompacted,

  9. Collection<StoreFile> newFiles) throws IOException {

  10.  
  11. // 如果region中的WAL为空,则直接返回

  12. if (region.getWAL() == null) return;

  13.  
  14. // 将被合并的文件路径添加至inputPaths列表

  15. List<Path> inputPaths = new ArrayList<Path>(filesCompacted.size());

  16. for (StoreFile f : filesCompacted) {

  17. inputPaths.add(f.getPath());

  18. }

  19.  
  20. // 将合并后的文件路径添加至inputPaths列表

  21. List<Path> outputPaths = new ArrayList<Path>(newFiles.size());

  22. for (StoreFile f : newFiles) {

  23. outputPaths.add(f.getPath());

  24. }

  25.  
  26. // 获取HRegionInfo,即info

  27. HRegionInfo info = this.region.getRegionInfo();

  28.  
  29. // 构造compaction的描述信息CompactionDescriptor

  30. CompactionDescriptor compactionDescriptor = ProtobufUtil.toCompactionDescriptor(info,

  31. family.getName(), inputPaths, outputPaths, fs.getStoreDir(getFamily().getNameAsString()));

  32.  
  33. // 利用WALUtil工具类的writeCompactionMarker()方法,在WAL中写入一个合并标记

  34. WALUtil.writeCompactionMarker(region.getWAL(), this.region.getTableDesc(),

  35. this.region.getRegionInfo(), compactionDescriptor, this.region.getSequenceId());

  36. }

        逻辑比较简单:

 

        1、将被合并的文件路径添加至inputPaths列表;

        2、将合并后的文件路径添加至outputPaths列表;

        3、获取HRegionInfo,即info;

        4、构造compaction的描述信息CompactionDescriptor;

        5、利用WALUtil工具类的writeCompactionMarker()方法,在WAL中写入一个合并标记。

        首先说下这个compaction的描述信息CompactionDescriptor,其中包含了表名TableName、Region名EncodedRegionName、列簇名FamilyName、存储Home路径StoreHomeDir、合并的输入CompactionInput、合并的输出CompactionOutput等关键信息,完整的描述了合并的全部详细信息。其构造代码如下:

 

 
  1. public static CompactionDescriptor toCompactionDescriptor(HRegionInfo info, byte[] family,

  2. List<Path> inputPaths, List<Path> outputPaths, Path storeDir) {

  3. // compaction descriptor contains relative paths.

  4. // input / output paths are relative to the store dir

  5. // store dir is relative to region dir

  6. CompactionDescriptor.Builder builder = CompactionDescriptor.newBuilder()

  7. .setTableName(ByteStringer.wrap(info.getTableName()))

  8. .setEncodedRegionName(ByteStringer.wrap(info.getEncodedNameAsBytes()))

  9. .setFamilyName(ByteStringer.wrap(family))

  10. .setStoreHomeDir(storeDir.getName()); //make relative

  11. for (Path inputPath : inputPaths) {

  12. builder.addCompactionInput(inputPath.getName()); //relative path

  13. }

  14. for (Path outputPath : outputPaths) {

  15. builder.addCompactionOutput(outputPath.getName());

  16. }

  17. builder.setRegionName(ByteStringer.wrap(info.getRegionName()));

  18. return builder.build();

  19. }

       最后,利用WALUtil工具类的writeCompactionMarker()方法,在WAL中写入一个合并标记,我们来看下代码:

 

 

 
  1. /**

  2. * Write the marker that a compaction has succeeded and is about to be committed.

  3. * This provides info to the HMaster to allow it to recover the compaction if

  4. * this regionserver dies in the middle (This part is not yet implemented). It also prevents

  5. * the compaction from finishing if this regionserver has already lost its lease on the log.

  6. * @param sequenceId Used by WAL to get sequence Id for the waledit.

  7. */

  8. public static void writeCompactionMarker(WAL log, HTableDescriptor htd, HRegionInfo info,

  9. final CompactionDescriptor c, AtomicLong sequenceId) throws IOException {

  10.  
  11. // 从合并信息CompactionDescriptor中获取表名tn

  12. TableName tn = TableName.valueOf(c.getTableName().toByteArray());

  13.  
  14. // we use HLogKey here instead of WALKey directly to support legacy coprocessors.

  15.  
  16. // 根据region的名字、表明tn,创建一个WALKey

  17. WALKey key = new HLogKey(info.getEncodedNameAsBytes(), tn);

  18.  
  19. // WAL中添加一条记录,包括表的描述信息HTableDescriptor、WALKey、Compaction信息WALEdit、序列号sequenceId

  20. // Compaction信息WALEdit是根据WALEdit的createCompaction()方法,由HRegionInfo、CompactionDescriptor获取的

  21. //

  22. log.append(htd, info, key, WALEdit.createCompaction(info, c), sequenceId, false, null);

  23.  
  24. // 同步日志

  25. log.sync();

  26. if (LOG.isTraceEnabled()) {

  27. LOG.trace("Appended compaction marker " + TextFormat.shortDebugString(c));

  28. }

  29. }

        它实际上在WAL中append了一条记录,包括表的描述信息HTableDescriptor、WALKey、Compaction信息WALEdit、序列号sequenceId,而Compaction信息WALEdit是根据WALEdit的createCompaction()方法,由HRegionInfo、CompactionDescriptor构造的。代码如下:

 

 

 
  1. /**

  2. * Create a compacion WALEdit

  3. * @param c

  4. * @return A WALEdit that has <code>c</code> serialized as its value

  5. */

  6. public static WALEdit createCompaction(final HRegionInfo hri, final CompactionDescriptor c) {

  7.  
  8. // 将CompactionDescriptor转化成byte []

  9. byte [] pbbytes = c.toByteArray();

  10.  
  11. // 构造KeyValue,包括Region的startKey、“METAFAMILY”字符串、

  12. // "HBASE::COMPACTION"字符串、当前时间和合并描述CompactionDescriptor的二进制形式

  13. KeyValue kv = new KeyValue(getRowForRegion(hri), METAFAMILY, COMPACTION,

  14. EnvironmentEdgeManager.currentTime(), pbbytes);

  15.  
  16. // 将KeyValue添加至WALEdit,并返回WALEdit实例

  17. return new WALEdit().add(kv); //replication scope null so that this won't be replicated

  18. }

        代码注释比较详细,不再赘述。

 

        3、替换StoreFiles,其中包括亮点:

             (1)去除掉所有的合并前,即已被合并的文件compactedFiles,将合并后的文件sfs加入到StoreFileManager的storefiles中去,storefiles为Store中目前全部提供服务的存储文件列表;

             (2)正在合并的文件列表filesCompacting中去除被合并的文件filesToCompact;

        具体代码replaceStoreFiles()方法如下:

 

 
  1. @VisibleForTesting

  2. void replaceStoreFiles(final Collection<StoreFile> compactedFiles,

  3. final Collection<StoreFile> result) throws IOException {

  4.  
  5. // 加锁,上读写锁ReentrantReadWriteLock的写锁,意味着这是一把互斥锁

  6. this.lock.writeLock().lock();

  7. try {

  8. // 通过StoreFileManager的addCompactionResults()方法,将被合并的文件

  9. // 去除掉所有的合并前,即已被合并的文件compactedFiles

  10. // 将合并后的文件加入到StoreFileManager的storefiles中去,storefiles为Store中目前全部提供服务的存储文件列表

  11. this.storeEngine.getStoreFileManager().addCompactionResults(compactedFiles, result);

  12.  
  13. // 正在合并的文件列表filesCompacting中去除被合并的文件

  14. filesCompacting.removeAll(compactedFiles); // safe bc: lock.writeLock();

  15. } finally {

  16. // 解锁

  17. this.lock.writeLock().unlock();

  18. }

  19. }

        4、完成合并:归档旧文件(在文件系统中删除已被合并的文件compactedFiles,实际上是归档操作,将旧的文件从原位置移到归档目录下),关闭其上的Reader,并更新store大小。completeCompaction()代码如下:

 

 

 
  1. /**

  2. * <p>It works by processing a compaction that's been written to disk.

  3. *

  4. * <p>It is usually invoked at the end of a compaction, but might also be

  5. * invoked at HStore startup, if the prior execution died midway through.

  6. *

  7. * <p>Moving the compacted TreeMap into place means:

  8. * <pre>

  9. * 1) Unload all replaced StoreFile, close and collect list to delete.

  10. * 2) Compute new store size

  11. * </pre>

  12. *

  13. * @param compactedFiles list of files that were compacted

  14. */

  15. @VisibleForTesting

  16. protected void completeCompaction(final Collection<StoreFile> compactedFiles, boolean removeFiles)

  17. throws IOException {

  18. try {

  19. // Do not delete old store files until we have sent out notification of

  20. // change in case old files are still being accessed by outstanding scanners.

  21. // Don't do this under writeLock; see HBASE-4485 for a possible deadlock

  22. // scenario that could have happened if continue to hold the lock.

  23. // 通知Reader观察者

  24. notifyChangedReadersObservers();

  25. // At this point the store will use new files for all scanners.

  26.  
  27. // let the archive util decide if we should archive or delete the files

  28. LOG.debug("Removing store files after compaction...");

  29.  
  30. // 遍历已被合并的文件completeCompaction,关闭其上的Reader

  31. for (StoreFile compactedFile : compactedFiles) {

  32. compactedFile.closeReader(true);

  33. }

  34.  
  35. // 在文件系统中删除已被合并的文件compactedFiles,实际上是归档操作,将旧的文件从原位置移到归档目录下

  36. if (removeFiles) {

  37. this.fs.removeStoreFiles(this.getColumnFamilyName(), compactedFiles);

  38. }

  39. } catch (IOException e) {

  40. e = RemoteExceptionHandler.checkIOException(e);

  41. LOG.error("Failed removing compacted files in " + this +

  42. ". Files we were trying to remove are " + compactedFiles.toString() +

  43. "; some of them may have been already removed", e);

  44. }

  45.  
  46. // 4. Compute new store size

  47. // 计算新的store大小

  48. this.storeSize = 0L;

  49. this.totalUncompressedBytes = 0L;

  50.  
  51. // 遍历StoreFiles,计算storeSize、totalUncompressedBytes等大小

  52. for (StoreFile hsf : this.storeEngine.getStoreFileManager().getStorefiles()) {

  53. StoreFile.Reader r = hsf.getReader();

  54. if (r == null) {

  55. LOG.warn("StoreFile " + hsf + " has a null Reader");

  56. continue;

  57. }

  58. this.storeSize += r.length();

  59. this.totalUncompressedBytes += r.getTotalUncompressedBytes();

  60. }

  61. }

 

        其他代码注释中都有,这里,我们要单独说下HRegionFileSystem的removeStoreFiles()方法,如下:

 
  1. /**

  2. * Closes and archives the specified store files from the specified family.

  3. * @param familyName Family that contains the store files

  4. * @param storeFiles set of store files to remove

  5. * @throws IOException if the archiving fails

  6. */

  7. public void removeStoreFiles(final String familyName, final Collection<StoreFile> storeFiles)

  8. throws IOException {

  9. HFileArchiver.archiveStoreFiles(this.conf, this.fs, this.regionInfoForFs,

  10. this.tableDir, Bytes.toBytes(familyName), storeFiles);

  11. }

        它最终是通过HFileArchiver的archiveStoreFiles()方法来完成的,代码如下:

 

 

 
  1. /**

  2. * Remove the store files, either by archiving them or outright deletion

  3. * @param conf {@link Configuration} to examine to determine the archive directory

  4. * @param fs the filesystem where the store files live

  5. * @param regionInfo {@link HRegionInfo} of the region hosting the store files

  6. * @param family the family hosting the store files

  7. * @param compactedFiles files to be disposed of. No further reading of these files should be

  8. * attempted; otherwise likely to cause an {@link IOException}

  9. * @throws IOException if the files could not be correctly disposed.

  10. */

  11. public static void archiveStoreFiles(Configuration conf, FileSystem fs, HRegionInfo regionInfo,

  12. Path tableDir, byte[] family, Collection<StoreFile> compactedFiles) throws IOException {

  13.  
  14. // sometimes in testing, we don't have rss, so we need to check for that

  15. if (fs == null) {

  16. LOG.warn("Passed filesystem is null, so just deleting the files without archiving for region:"

  17. + Bytes.toString(regionInfo.getRegionName()) + ", family:" + Bytes.toString(family));

  18. deleteStoreFilesWithoutArchiving(compactedFiles);

  19. return;

  20. }

  21.  
  22. // short circuit if we don't have any files to delete

  23. // 判断被合并文件列表compactedFiles的大小,如果为0,立即返回

  24. if (compactedFiles.size() == 0) {

  25. LOG.debug("No store files to dispose, done!");

  26. return;

  27. }

  28.  
  29. // build the archive path

  30. if (regionInfo == null || family == null) throw new IOException(

  31. "Need to have a region and a family to archive from.");

  32.  
  33. // 获取归档存储路径

  34. Path storeArchiveDir = HFileArchiveUtil.getStoreArchivePath(conf, regionInfo, tableDir, family);

  35.  
  36. // make sure we don't archive if we can't and that the archive dir exists

  37. // 创建路径

  38. if (!fs.mkdirs(storeArchiveDir)) {

  39. throw new IOException("Could not make archive directory (" + storeArchiveDir + ") for store:"

  40. + Bytes.toString(family) + ", deleting compacted files instead.");

  41. }

  42.  
  43. // otherwise we attempt to archive the store files

  44. if (LOG.isDebugEnabled()) LOG.debug("Archiving compacted store files.");

  45.  
  46. // Wrap the storefile into a File

  47. StoreToFile getStorePath = new StoreToFile(fs);

  48. Collection<File> storeFiles = Collections2.transform(compactedFiles, getStorePath);

  49.  
  50. // do the actual archive

  51. // 通过resolveAndArchive()执行归档

  52. if (!resolveAndArchive(fs, storeArchiveDir, storeFiles)) {

  53. throw new IOException("Failed to archive/delete all the files for region:"

  54. + Bytes.toString(regionInfo.getRegionName()) + ", family:" + Bytes.toString(family)

  55. + " into " + storeArchiveDir + ". Something is probably awry on the filesystem.");

  56. }

  57. }

        层层调用啊,接着来吧,继续看关键代码:

 

 

 
  1. // 如果是文件

  2. if (file.isFile()) {

  3. // attempt to archive the file

  4. if (!resolveAndArchiveFile(baseArchiveDir, file, startTime)) {

  5. LOG.warn("Couldn't archive " + file + " into backup directory: " + baseArchiveDir);

  6. failures.add(file);

  7. }

  8. }

        而这个resolveAndArchiveFile()方法不是简单的删除文件,而是通过rename()方法将旧的存储文件挪至了归档路径下,代码如下:

 

 

 
  1. // move the archive file to the stamped backup

  2. Path backedupArchiveFile = new Path(archiveDir, filename + SEPARATOR + archiveStartTime);

  3. if (!fs.rename(archiveFile, backedupArchiveFile)) {

  4. LOG.error("Could not rename archive file to backup: " + backedupArchiveFile

  5. + ", deleting existing file in favor of newer.");

  6. // try to delete the exisiting file, if we can't rename it

  7. if (!fs.delete(archiveFile, false)) {

  8. throw new IOException("Couldn't delete existing archive file (" + archiveFile

  9. + ") or rename it to the backup file (" + backedupArchiveFile

  10. + ") to make room for similarly named file.");

  11. }

  12. }

        5、完成Compaction请求:Region汇报合并请求至终端、filesCompacting中删除请求中的所有待合并文件

 

        这部分是由方法finishCompactionRequest()完成的,代码如下:

 

 
  1. private void finishCompactionRequest(CompactionRequest cr) {

  2. // Region汇报合并请求至终端

  3. this.region.reportCompactionRequestEnd(cr.isMajor(), cr.getFiles().size(), cr.getSize());

  4.  
  5. //

  6. if (cr.isOffPeak()) {

  7. offPeakCompactionTracker.set(false);

  8. cr.setOffPeak(false);

  9. }

  10.  
  11. // filesCompacting中删除请求中的所有待合并文件

  12. synchronized (filesCompacting) {

  13. filesCompacting.removeAll(cr.getFiles());

  14. }

  15. }

        读者可自行分析,不再赘述。

 

        好了,就先到这里吧,且待下回分解!

在银河麒麟V10 SP1上安装Wine 9.0运行Windows程序 Wine是一个面向Linux等类Unix系统的兼容层,通过将Windows API调用翻译为Linux系统调用,使Windows应用程序能够在非Windows平台上运行。在国产化迁移进程中,银河麒麟V10 SP1等操作系统常因缺少Windows软件兼容性而受到阻碍。Wine 9.0作为2024年发布的重要版本,改进了WoW64模式、Wayland支持等关键技术,为运行办公软件、行业客户端提供了可行方案。从环境检查、依赖安装、编译配置到中文字体与运行库设置,系统梳理在麒麟V10 SP1上构建Wine 9.0环 阅读详情

相关推荐

HBase源码分析之org.apache.hadoop.hbase.regionserver包

1、Splitpolicy是Region在满足什么条件下,需要split操作,0.94版本中默认是IncreasingToUpperBoundRegionSplitPolicy 1)ConstantSizeRegionSplitPolicy, 2)KeyPrefixRegionSplitPolicy, 3)RegionSplitPolicy,可以在配置文件中指定policy的实现hbase.

云计算、分布式架构、K8S、大数据、机器学习、搜索、推荐、广告 4406

Hadoop Archive: File Compaction for HDFS

Thanks to Tsz-Wo Nicholas Sze and Mahadev Konar for this article. (Tue July 27, 2010) The Problem of Many Small Files The Hadoop Distributed File System (HDFS) is designed to store and process

Mac Track 1429

HBase的compact流程分析

关于hbase的compact意义及其应用背景已经有很多文章做了描述,这里不再赘述,本文以1.1.2背景的hbase为例,从源码中分析hbase中compact请求发出到接受处理的流程,限于作者水平有限,分析中可能有疏漏或者错误的地方,希望大家指正,一起学习提高。 从regionserver开始分析compact的流程,regionserver初始化的时候会初始化两个与compact相关的线程分

进一步有一步的欢喜 6574

hbase regionserver挂掉报错has too many store files delaying flush up to 90000ms

今天在使用datax同步数据到hbase的时候,随着同步的数据越来越多,发现同步的速度越来越慢,且慢慢的出现同步数据为0的情况,以及regionserver间歇性挂掉,最后完全挂掉了。 首先说一下,使用的hbase是单节点的,自己用来测试的。没有做过多的配置。 hbase的表只设置了一个列族,列数不到30列。机器内存16G。 当数据写到500万之后,开始变慢。最后写到800万regionserver崩溃。 2020-05-25 10:31:53,453 INFO [RpcServer.defau

ASN_forever的博客 1705

Mediapipe手势识别实战——基于关节角度计算实现动态手势分类

本文详细介绍了如何利用Mediapipe进行手势识别,通过计算手指关节弯曲角度实现动态手势分类。文章从基础原理出发,讲解了关节角度计算的数学方法,并提供了Python实现代码。同时,还分享了数字手势和OK手势的识别实战经验,以及性能优化和工程实践中的关键技巧,帮助开发者快速掌握实时手势识别技术。

weixin_26891599的博客 404

「转载」vaptcha 等手势验证码图像识别与轨迹提取(思路)

作者:文安哲 从博客重开以后就说要更文章,鸽了蛮久了,赶紧补上一篇嫖嫖各位老公的流量。 最近看群里面大家讨论研究手势验证码比较多,然后我也顺带研究做了一下,给各位老公们分享一下做的过程。 首先,一起来康康手势验证码长啥样! 大概就是长这个样子,需要按照图像中的那条轨迹再图片上滑动,不得不说这类验证码确实体验感拉满,让人很想立马关掉这个网站。 那么言归正传,这类验证码该怎么做识...

AI悦创·编程私教1v1 3608

深入理解 HBase Compaction 机制

HBase有很多话题可以聊,包括读写路径上涉及到的一些数据结构,性能优化以及优化读写做的一些设计等。今天要探讨的Compaction机制就是HBase架构上做的一个重要优化,其实关于HBase的架构设计、Flush&Compaction等文章已经多不胜数了,本文也是主要参考HBase官网、社区的一些文章总结,从各个关键点进行总结,旨在为了帮助我们更加牢固的掌握Compaction机制。 为什么要执行 Compaction 熟悉HBase的同学应该知道,HBase是基于一种LSM-Tree(Log-

Data & Analysis 1768

2023-07-04 Fast-Lio论文中公式(6)与BCH公式中近似雅可比公式的一致性推导

将(1)式代入(6)式,就有。

wmorries的博客 480

hdfs文件损坏 现象:安装完 hbase之后,进入16010界面后发现 regionserver只有一个

hbase 会依赖 hdfs ,hbase在启动regionserver的时候会看对应节点 hdfs 的服务 datanode是否是健康的,如果datanode未启动,或者是进入了安全模式,hbase 的regionserver就不会启动。3 使用这条命令完成 坏块的清理,重启hbase ,hbase会对hdfs的状态进行重新检查,发现是健康的,就会启动regionserver了。挂起恢复: 静态的先读进来,内存的信息文件,直接都会内存中,运行起来。关闭服务:越上层的 服务,先关闭,最后在关闭底层服务。

weixin_53589204的博客 550

金融数据自动化:从同花顺API到Excel的智能转换之旅

本文详细介绍了如何利用同花顺API实现金融数据自动化处理,并将其智能转换为Excel格式的全流程。通过Python编程和Excel高级技巧,读者可以掌握从数据获取、清洗到输出的完整解决方案,大幅提升金融数据分析的效率和准确性。文章特别强调了同花顺API的使用技巧和Excel自动化输出的最佳实践。

sql99的博客 462

Aruba CX交换机VSX配置实战:如何避免脑裂导致的网络中断(含详细命令)

本文提供Aruba CX交换机VSX配置的实战指南,重点讲解如何通过构建高可靠的ISL链路聚合与独立隔离的Keepalive链路,有效避免网络脑裂风险。文章包含详细的命令步骤,从基础配置到高级故障排查,帮助网络工程师构建坚如磐石的高可用核心网络,确保业务流量零中断。

weixin_29231725的博客 173

HBase源码分析之HRegion上compact流程分析(一)

首先来想两个问题:1、何谓compact?2、它产生的背景是怎样的? compact是指HBase表中HRegion上某个Column Family下,部分或全部HFiles的合并。它是由于数据在持续写入后,MemStore达到一定阈值,被flush到磁盘上,形成许许多多的文件,这些文件如果不做处理,将会严重影响HBase数据读取的效率。所以,在HBase系统内部,需要定期在满足一定条件的情况下,或者由人为手动触发,将这许多文件合并成一个大文件。 那么,在HRegion上,c

辰辰爸的博客 3293

VS2022打包C#安装包(最新、最全)

VS2022打包C#安装包(最新、最全)

qq_37959253的博客 9912

HBase源码分析之HRegionServer上MemStore的flush处理流程(二)

继上篇文章《HBase源码分析之HRegionServer上MemStore的flush处理流程(一)》遗留的问题之后,本文我们接着研究HRegionServer上MemStore的flush处理流程,重点讲述下如何选择一个HRegion进行flush以缓解MemStore压力,还有HRegion的flush是如何发起的。

辰辰爸的博客 2548

816华为OD机试真题 新系统 - 电池充电时间计算 (Java/Py/C/C++/Js/Go)

816华为OD机试真题 新系统 - 电池充电时间计算 (Java/Py/C/C++/Js/Go)

算法大师! 477

风光储并网对电力系统稳定性影响的仿真研究

本文研究了风光储并网对电力系统稳定性的影响,基于MATLAB/Simulink平台搭建了IEEE 9节点测试系统的风光储并网模型。研究对比了光储、风储和风光储三种并网配置在多种干扰下的稳定性表现。结果表明:光储系统对频率变化最敏感,风储系统具有更强的功率波动适应能力,而风光储联合系统通过互补效应展现出最佳综合稳定性。储能系统在所有配置中均显著改善了频率恢复性能,验证了其在新能源并网中的关键支撑作用。研究为高比例可再生能源电力系统的稳定运行提供了仿真依据和技术参考。

huanghm88的专栏 156

hbase compact源码分析

final boolean majorCompaction, final long maxId) 1.根据filesToCompat,生成hfile所对应的StoreFileScanner  2.创建StoreScanner 类,负责顺序便利所有要compact的file,对生成的storeFileScanner进行封装,主要方法为next                    2.1创建ScanQueryMatcher 类,负责判断是否过滤删除keyvalue

385

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

HBase(0.94.5)的Compact和Split源码分析

经过对比,0.94。5以后版本主要过程基本类似(有些新功能和细节增加) 一、Compact 2.1. Compact主要来源 来自四个方面:1、Memstoreflush时;2、HRegionServer定期做Compaction Checker时;3、HBaseAdmin客户端发起的请求;4、CompactTool发起。 1)...

weixin_30648587的博客 162

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

python版本及其whl(3.7)安装包

python版本安装包 python-3.7.3-amd64.exe 链接:https://pan.baidu.com/s/1Vum8ErgVgvWH_jGPv9qnkw 提取码:6666 python-3.7.7-amd64.exe 链接:https://pan.baidu.com/s/19uEJWVoCIFijOuD6gOkbBQ 提取码:6666 python-3.9.1-amd64.exe 链接:https://pan.baidu.com/s/1Xm7b1m4OXkMnHphwwE_wkw 提取码:

cwy1715325765的博客 960

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

HBase之RegionCompact

为什么要合并region? 由于写缓存中的store(列族存储文件)每次刷写都会产生一个storeFile(hdfs上的列族存储文件),同一条数据的不同版本或不同标记可能分布在不同的storeFile中,每次查询数据时需要遍历多个storeFile。为了减少hdfs上storeFile的个数,以及清理掉过期和删除的数据,会进行storeFileCompact。 Compaction 分为两种,分别是 Minor Compaction 和 Major Compaction。Minor Compaction 会

weixin_44911106的博客 341

HBase选择Store file做compaction的算法

The algorithm is basically as follows: Run over the set of all store files, from oldest to youngest If there are more than 3 (hbase.hstore.compactionThreshold) store files left and the current store file is 20% larger then the sum of all younger store file

萌兔兔MMQ!! 385

cassandra1.1.0中Compaction部分源代码解析——LeveledCompactionStrategy

近两天事情有点小多,更新速度不太给力,当然这都是借口。Ok,开始LeveledCompactionStrategy的分析。 前篇博客中也提到Compaction启动是在cfs中进行的,其实流程就是调用相应Compaction的构造函数初始化对应的对象。具体构造函数的完成的任务如下: 1)调用父类的构造函数,生成一个延时启动的线程,该线程的功能是在启动5分钟之后调用CompactionMan...

数据库天地 232
上一篇: Bookmarks 各种技术 参考
下一篇: Trafodion 安装之HBase配置优化
王树民
博客等级 码龄10年 912粉丝 560原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值