HBase HLog结构和原理

转载URL:  http://greatwqs.iteye.com/blog/1845897


一. HLog在HDFS上位置和RegionServer对应关系

HLog持久化在HDFS之上, HLog存储位置查看:

Java代码  收藏代码
  1. hadoop fs -ls /hbase/.logs  

通过HBase架构图, HLog与HRegionServer一一对应,

Java代码  收藏代码
  1. Found 5 items                                                                                    
  2. drwxr-xr-x - hadoop cug-admin  0 2013-04-11 14:23 /hbase/.logs/HADOOPCLUS02,61020,1365661380729  
  3. drwxr-xr-x - hadoop cug-admin  0 2013-04-11 14:23 /hbase/.logs/HADOOPCLUS03,61020,1365661378638  
  4. drwxr-xr-x - hadoop cug-admin  0 2013-04-11 14:23 /hbase/.logs/HADOOPCLUS04,61020,1365661379200  
  5. drwxr-xr-x - hadoop cug-admin  0 2013-04-11 14:22 /hbase/.logs/HADOOPCLUS05,61020,1365661378053  
  6. drwxr-xr-x - hadoop cug-admin  0 2013-04-11 14:23 /hbase/.logs/HADOOPCLUS06,61020,1365661378832  

HADOOPCLUS02 ~ HADOOPCLUS06 为RegionServer.

上面显示的文件目录为HLog存储. 如果HLog已经失效(所有之前的写入MemStore已经持久化在HDFS),HLog存在于HDFS之上的文件会从/hbase/.logs转移至/hbase/.oldlogs, oldlogs会删除, HLog的生命周期结束.

二. HBase写流程和写HLog的阶段点.

向HBase Put数据时通过HBaseClient-->连接ZooKeeper--->-ROOT--->.META.-->RegionServer-->Region:

Region写数据之前会先检查MemStore.

1. 如果此Region的MemStore已经有缓存已有写入的数据, 则直接返回;

2. 如果没有缓存, 写入HLog(WAL), 再写入MemStore.成功后再返回.

MemStore内存达到一定的值调用flush成为StoreFile,存到HDFS.

在对HBase插入数据时,插入到内存MemStore所以很快,对于安全性不高的应用可以关闭HLog,可以获得更高的写性能.

三. HLog相关源码.

HBase Hlog

1. 总览.

写入HLog主要靠HLog对象的doWrite(HRegionInfo info, HLogKey logKey, WALEdit logEdit)

或者completeCacheFlush(final byte [] encodedRegionName, final byte [] tableName, final long logSeqId, final boolean isMetaRegion),

在这两方法中调用this.writer.append(new HLog.Entry(logKey, logEdit));方法写入操作.

在方法内构造HLog.Entry:使用当前构造好的writer, 见上图引用对象,

 完整实现类: org.apache.hadoop.hbase.regionserver.wal.SequenceFileLogWriter,

HLog 方法createWriterInstance(fs, newPath, conf) 创建 Writer对象.

2. SequenceFileLogWriter 和SequenceFileLogReader

在SequenceFileLogWriter 类中可以看到, 使用Hadoop SequenceFile.Writer写入到文件系统. SequenceFile是HLog在Hadoop存储的文件格式.

HLog.Entry为HLog存储的最小单位.

Java代码  收藏代码
  1. public class SequenceFileLogWriter implements HLog.Writer {  
  2.   private final Log LOG = LogFactory.getLog(this.getClass());  
  3.   // The hadoop sequence file we delegate to.  
  4.   private SequenceFile.Writer writer;  
  5.   // The dfsclient out stream gotten made accessible or null if not available.  
  6.   private OutputStream dfsClient_out;  
  7.   // The syncFs method from hdfs-200 or null if not available.  
  8.   private Method syncFs;  
  9.   // init writer need the key;  
  10.   private Class<? extends HLogKey> keyClass;  
  11.   @Override  
  12.   public void init(FileSystem fs, Path path, Configuration conf)  
  13.       throws IOException {  
  14.       // 1. create Hadoop file SequenceFile.Writer for writer initation.  
  15.       // 2. Get at the private FSDataOutputStream inside in SequenceFile so we can call sync on it. for dfsClient_out initation.  
  16.   }  
  17.   @Override  
  18.   public void append(HLog.Entry entry) throws IOException {  
  19.     this.writer.append(entry.getKey(), entry.getEdit());  
  20.   }  
  21.   @Override  
  22.   public void sync() throws IOException {  
  23.     if (this.syncFs != null) {  
  24.       try {  
  25.        this.syncFs.invoke(this.writer, HLog.NO_ARGS);  
  26.       } catch (Exception e) {  
  27.         throw new IOException("Reflection", e);  
  28.       }  
  29.     }  
  30.   }  
  31. }  

 SequenceFileLogReader为读取HLog.Entry对象使用.

3. HLog.Entry与属性logSeqNum

每一个Entry包含了 HLogKey和WALEdit 

HLogKey包含了基本信息:  

Java代码  收藏代码
  1. private byte [] encodedRegionName;  
  2. private byte [] tablename;  
  3. private long logSeqNum;  
  4. // Time at which this edit was written.  
  5. private long writeTime;  
  6. private byte clusterId;  

logSeqNum是一个重要的字段值, sequence number是作为StoreFile里的一个元数据字段,可以针对StoreFile直接得到longSeqNum;

Java代码  收藏代码
  1. public class StoreFile {  
  2.   static final String HFILE_BLOCK_CACHE_SIZE_KEY = "hfile.block.cache.size";  
  3.   private static BlockCache hfileBlockCache = null;  
  4.   // Is this from an in-memory store  
  5.   private boolean inMemory;  
  6.   // Keys for metadata stored in backing HFile.  
  7.   // Set when we obtain a Reader.  StoreFile row 140  
  8.   private long sequenceid = -1;  
  9.   /** 
  10.    * @return This files maximum edit sequence id. 
  11.    */  
  12.   public long getMaxSequenceId() {  
  13.     return this.sequenceid;  
  14.   }  
  15.   /** 
  16.    * Return the highest sequence ID found across all storefiles in 
  17.    * the given list. Store files that were created by a mapreduce 
  18.    * bulk load are ignored, as they do not correspond to any edit 
  19.    * log items. 
  20.    * @return 0 if no non-bulk-load files are provided or, this is Store that 
  21.    * does not yet have any store files. 
  22.    */  
  23.   public static long getMaxSequenceIdInList(List<StoreFile> sfs) {  
  24.     long max = 0;  
  25.     for (StoreFile sf : sfs) {  
  26.       if (!sf.isBulkLoadResult()) {  
  27.         max = Math.max(max, sf.getMaxSequenceId());  
  28.       }  
  29.     }  
  30.     return max;  
  31.   }  
  32.     /** 
  33.      * Writes meta data. important for maxSequenceId WRITE!! 
  34.      * Call before {@link #close()} since its written as meta data to this file. 
  35.      * @param maxSequenceId Maximum sequence id. 
  36.      * @param majorCompaction True if this file is product of a major compaction 
  37.      * @throws IOException problem writing to FS 
  38.      */  
  39.     public void appendMetadata(final long maxSequenceId, final boolean majorCompaction)  
  40.     throws IOException {  
  41.       writer.appendFileInfo(MAX_SEQ_ID_KEY, Bytes.toBytes(maxSequenceId));  
  42.       writer.appendFileInfo(MAJOR_COMPACTION_KEY,  
  43.           Bytes.toBytes(majorCompaction));  
  44.       appendTimeRangeMetadata();  
  45.     }  
  46.   }  
  47.   /** 
  48.    * Opens reader on this store file.  Called by Constructor. 
  49.    * @return Reader for the store file. 
  50.    * @throws IOException 
  51.    * @see #closeReader() 
  52.    */  
  53.   private Reader open() throws IOException {  
  54.      // ........  
  55.      this.sequenceid = Bytes.toLong(b);  
  56.       if (isReference()) {  
  57.         if (Reference.isTopFileRegion(this.reference.getFileRegion())) {  
  58.           this.sequenceid += 1;  
  59.         }        
  60.       }  
  61.      this.reader.setSequenceID(this.sequenceid);  
  62.      return this.reader;  
  63.    }  
  64. }  

 Store 类对StoreFile进行了管理, 如compact.在 很多StoreFile进行合并时, 取值最大的longSeqNum;

Java代码  收藏代码
  1. public class Store implements HeapSize {  
  2.   /** 
  3.    * Compact the StoreFiles.  This method may take some time, so the calling 
  4.    * thread must be able to block for long periods.   * 
  5.    * <p>During this time, the Store can work as usual, getting values from 
  6.    * StoreFiles and writing new StoreFiles from the memstore.   * 
  7.    * Existing StoreFiles are not destroyed until the new compacted StoreFile is 
  8.    * completely written-out to disk.   * 
  9.    * <p>The compactLock prevents multiple simultaneous compactions. 
  10.    * The structureLock prevents us from interfering with other write operations.   * 
  11.    * <p>We don't want to hold the structureLock for the whole time, as a compact() 
  12.    * can be lengthy and we want to allow cache-flushes during this period.   * 
  13.    * @param forceMajor True to force a major compaction regardless of thresholds 
  14.    * @return row to split around if a split is needed, null otherwise 
  15.    * @throws IOException 
  16.    */  
  17.   StoreSize compact(final boolean forceMajor) throws IOException {  
  18.     boolean forceSplit = this.region.shouldForceSplit();  
  19.     boolean majorcompaction = forceMajor;  
  20.     synchronized (compactLock) {  
  21.       /* get store file sizes for incremental compacting selection. 
  22.        * normal skew: 
  23.        * 
  24.        *         older ----> newer 
  25.        *     _ 
  26.        *    | |   _ 
  27.        *    | |  | |   _ 
  28.        *  --|-|- |-|- |-|---_-------_-------  minCompactSize 
  29.        *    | |  | |  | |  | |  _  | | 
  30.        *    | |  | |  | |  | | | | | | 
  31.        *    | |  | |  | |  | | | | | | 
  32.        */  
  33.       // .............  
  34.       this.lastCompactSize = totalSize;  
  35.   
  36.       // Max-sequenceID is the last key in the files we're compacting  
  37.       long maxId = StoreFile.getMaxSequenceIdInList(filesToCompact);  
  38.   
  39.       // Ready to go.  Have list of files to compact.  
  40.       LOG.info("Started compaction of " + filesToCompact.size() + " file(s) in cf=" +  
  41.           this.storeNameStr +  
  42.         (references? ", hasReferences=true,"" ") + " into " +  
  43.           region.getTmpDir() + ", seqid=" + maxId +  
  44.           ", totalSize=" + StringUtils.humanReadableInt(totalSize));  
  45.       StoreFile.Writer writer = compact(filesToCompact, majorcompaction, maxId);  
  46.       // Move the compaction into place.  
  47.       StoreFile sf = completeCompaction(filesToCompact, writer);  
  48.     }  
  49.     return checkSplit(forceSplit);  
  50.   }  
  51.   /** 
  52.    * Do a minor/major compaction.  Uses the scan infrastructure to make it easy. 
  53.    * 
  54.    * @param filesToCompact which files to compact 
  55.    * @param majorCompaction true to major compact (prune all deletes, max versions, etc) 
  56.    * @param maxId Readers maximum sequence id. 
  57.    * @return Product of compaction or null if all cells expired or deleted and 
  58.    * nothing made it through the compaction. 
  59.    * @throws IOException 
  60.    */  
  61.   private StoreFile.Writer compact(final List<StoreFile> filesToCompact,  
  62.                                final boolean majorCompaction, final long maxId)  
  63.       throws IOException {  
  64.     // Make the instantiation lazy in case compaction produces no product; i.e.  
  65.     // where all source cells are expired or deleted.  
  66.     StoreFile.Writer writer = null;  
  67.     try {  
  68.       // ......  
  69.     } finally {  
  70.       if (writer != null) {  
  71.         // !!!! StoreFile.Writer  write Metadata for maxid.  
  72.         writer.appendMetadata(maxId, majorCompaction);  
  73.         writer.close();  
  74.       }  
  75.     }  
  76.     return writer;  
  77.   }  
  78. }  

 在compact时, 第一个compact(final boolean forceMajor)调用

compact(final List<StoreFile> filesToCompact, final boolean majorCompaction, final long maxId)
此方法最后写入writer.appendMetadata(maxId, majorCompaction); 也就是StoreFile中的appendMetadata方法.

可见, 是在finally中写入最大的logSeqNum. 这样StoreFile在取得每个logSeqNum, 可以由open读取logSeqNum; 

clusterId 保存在Hadoop集群ID.

4. HLog的生命周期

这里就涉及到HLog的生命周期问题了.如果HLog的logSeqNum对应的HFile已经存储在HDFS了(主要是比较HLog的logSeqNum是否比与其对应的表的HDFS StoreFile的maxLongSeqNum小),那么HLog就没有存在的必要了.移动到.oldlogs目录,最后删除.

反过来如果此时系统down了,可以通过HLog把数据从HDFS中读取,把要原来Put的数据读取出来, 重新刷新到HBase.

补充资料:

HBase 架构101 –预写日志系统 (WAL) 

http://cloudera.iteye.com/blog/911700

HLog的结构和生命周期

http://www.spnguru.com/2011/03/hlog%e7%9a%84%e7%bb%93%e6%9e%84%e5%92%8c%e7%94%9f%e5%91%


展开阅读全文

没有更多推荐了,返回首页