[HBase]Write Path

转载自:http://iwinit.iteye.com/blog/1824881

HBase的批量put操作主要步骤

1.同个region的put视为同一批操作

2.对批量操作按rowkey进行字节排序

Java代码   收藏代码
  1. Collections.sort(actionsForRegion);  

 3.检查region server的全局内存是否超过阀值,如超过则唤醒flush线程进行flush操作

 

Java代码   收藏代码
  1. public void reclaimMemStoreMemory() {  
  2.     //如果超过高水位,默认为堆内存的0.4,阻塞rpc线程直到内存减少到预期  
  3.     if (isAboveHighWaterMark()) {  
  4.       lock.lock();  
  5.       try {  
  6.         boolean blocked = false;  
  7.         long startTime = 0;  
  8.         while (isAboveHighWaterMark() && !server.isStopped()) {  
  9.           .....  
  10.       //给flush线程提交一个task  
  11.           wakeupFlushThread();  
  12.           try {  
  13.             // we should be able to wait forever, but we've seen a bug where  
  14.             // we miss a notify, so put a 5 second bound on it at least.  
  15.             flushOccurred.await(5, TimeUnit.SECONDS);  
  16.           } catch (InterruptedException ie) {  
  17.             Thread.currentThread().interrupt();  
  18.           }  
  19.         }  
  20.         ....  
  21.       } finally {  
  22.         lock.unlock();  
  23.       }  
  24.     }   
  25.     //如果超过低水位,默认为堆内存的0.35,给flush线程提交一个task,不阻塞线程  
  26.     else if (isAboveLowWaterMark()) {  
  27.       wakeupFlushThread();  
  28.     }  
  29.   }  
 

 

4.检查这个region的memstore内存大小是否超过限制,超过则唤醒flush线程对该region进行flush,异步操作

 

Java代码   收藏代码
  1.  private void checkResources()  
  2.      throws RegionTooBusyException, InterruptedIOException {  
  3.   
  4.   .....  
  5.    boolean blocked = false;  
  6.    long startTime = 0;  
  7.    //当前region内存大小超过blockingMemStoreSize,默认为memstoreFlushSize的2被,memstoreFlushSize默认128M  
  8.    while (this.memstoreSize.get() > this.blockingMemStoreSize) {  
  9. //给flush线程发个请求  
  10.      requestFlush();  
  11.     。。。。。  
  12.      blocked = true;  
  13. //等待一段时间,10s  
  14.      synchronized(this) {  
  15.        try {  
  16.          wait(Math.min(timeToWait, threadWakeFrequency));  
  17.        } catch (InterruptedException ie) {  
  18.          final long totalTime = EnvironmentEdgeManager.currentTimeMillis() - startTime;  
  19.          if (totalTime > 0) {  
  20.            this.updatesBlockedMs.add(totalTime);  
  21.          }  
  22.          LOG.info("Interrupted while waiting to unblock updates for region "  
  23.            + this + " '" + Thread.currentThread().getName() + "'");  
  24.          InterruptedIOException iie = new InterruptedIOException();  
  25.          iie.initCause(ie);  
  26.          throw iie;  
  27.        }  
  28.      }  
  29.    }  
  30. ......  
  31.  }  

 

5.拿行锁,如果拿不到锁,则不处理

 

Java代码   收藏代码
  1.  private Integer internalObtainRowLock(final byte[] row, boolean waitForLock)  
  2.      throws IOException {  
  3. //检查row的范围是否在这个region里  
  4.    checkRow(row, "row lock");  
  5.    startRegionOperation();  
  6.    try {  
  7.      HashedBytes rowKey = new HashedBytes(row);  
  8.      //行锁是一个Latch,释放的时候Latch减1,等待线程就会被唤醒  
  9.      CountDownLatch rowLatch = new CountDownLatch(1);  
  10.   
  11.      // loop until we acquire the row lock (unless !waitForLock)  
  12.      while (true) {  
  13. //put一把  
  14.        CountDownLatch existingLatch = lockedRows.putIfAbsent(rowKey, rowLatch);  
  15. //如果锁不存在,则认为拿到锁  
  16.        if (existingLatch == null) {  
  17.          break;  
  18.        }   
  19. //已经有锁了,则等待锁释放或超时  
  20. else {  
  21.          // row already locked  
  22.          if (!waitForLock) {  
  23.            return null;  
  24.          }  
  25.          try {  
  26.            if (!existingLatch.await(this.rowLockWaitDuration,  
  27.                            TimeUnit.MILLISECONDS)) {  
  28.              throw new IOException("Timed out on getting lock for row="  
  29.                  + Bytes.toStringBinary(row));  
  30.            }  
  31.          } catch (InterruptedException ie) {  
  32.            // Empty  
  33.          }  
  34.        }  
  35.      }  
  36.   
  37.      // loop until we generate an unused lock id  
  38. //锁id是一个原子递增的整数  
  39.      while (true) {  
  40.        Integer lockId = lockIdGenerator.incrementAndGet();  
  41.        HashedBytes existingRowKey = lockIds.putIfAbsent(lockId, rowKey);  
  42.        if (existingRowKey == null) {  
  43.          return lockId;  
  44.        } else {  
  45.          // lockId already in use, jump generator to a new spot  
  46.          lockIdGenerator.set(rand.nextInt());  
  47.        }  
  48.      }  
  49.    } finally {  
  50.      closeRegionOperation();  
  51.    }  
  52.  }  
 6.修改KeyValue的timestamp为当前时间

 

7.拿mvcc的写事务id

Java代码   收藏代码
  1.    public WriteEntry beginMemstoreInsert() {  
  2.    synchronized (writeQueue) {  
  3. //事务id是一个原子递增的long  
  4.      long nextWriteNumber = ++memstoreWrite;  
  5.        //entry用来存这个事务的状态,是否已完成  
  6.      WriteEntry e = new WriteEntry(nextWriteNumber);  
  7.      writeQueue.add(e);  
  8.      return e;  
  9.    }  
  10.  }  
 8.写入memstore的内存kv列表

 

 

Java代码   收藏代码
  1.   private long internalAdd(final KeyValue toAdd) {  
  2.   //堆内存加了多少  
  3.   long s = heapSizeChange(toAdd, this.kvset.add(toAdd));  
  4.   timeRangeTracker.includeTimestamp(toAdd);  
  5.   this.size.addAndGet(s);  
  6.   return s;  
  7. }  
 9.写Hlog,但不flush,仍在内存
Java代码   收藏代码
  1.    private long append(HRegionInfo info, byte [] tableName, WALEdit edits, UUID clusterId,  
  2.      final long now, HTableDescriptor htd, boolean doSync)  
  3.    throws IOException {  
  4.      ......  
  5.      long txid = 0;  
  6.      synchronized (this.updateLock) {  
  7. //log的序列号  
  8.        long seqNum = obtainSeqNum();  
  9.        // The 'lastSeqWritten' map holds the sequence number of the oldest  
  10.        // write for each region (i.e. the first edit added to the particular  
  11.        // memstore). . When the cache is flushed, the entry for the  
  12.        // region being flushed is removed if the sequence number of the flush  
  13.        // is greater than or equal to the value in lastSeqWritten.  
  14.        // Use encoded name.  Its shorter, guaranteed unique and a subset of  
  15.        // actual  name.  
  16.        byte [] encodedRegionName = info.getEncodedNameAsBytes();  
  17. //region第一个修改的事务id,flush时所有大于等于该值的entry都会被写入文件  
  18.        this.lastSeqWritten.putIfAbsent(encodedRegionName, seqNum);  
  19.        HLogKey logKey = makeKey(encodedRegionName, tableName, seqNum, now, clusterId);  
  20.        doWrite(info, logKey, edits, htd);  
  21.        this.numEntries.incrementAndGet();  
  22. //事务id,代表第几条log  
  23.        txid = this.unflushedEntries.incrementAndGet();  
  24.        if (htd.isDeferredLogFlush()) {  
  25.          lastDeferredTxid = txid;  
  26.        }  
  27.      }  
  28.      // Sync if catalog region, and if not then check if that table supports  
  29.      // deferred log flushing  
  30.      if (doSync &&   
  31.          (info.isMetaRegion() ||  
  32.          !htd.isDeferredLogFlush())) {  
  33.        // sync txn to file system  
  34.        this.sync(txid);  
  35.      }  
  36.      return txid;  
  37.    }  
 
Java代码   收藏代码
  1. 写log的cache  
  2. // appends new writes to the pendingWrites. It is better to keep it in  
  3. // our own queue rather than writing it to the HDFS output stream because  
  4. // HDFSOutputStream.writeChunk is not lightweight at all.  
  5. synchronized void append(Entry e) throws IOException {  
  6.   pendingWrites.add(e);  
  7. }  
 10.释放行锁
Java代码   收藏代码
  1.  public void releaseRowLock(final Integer lockId) {  
  2. if (lockId == nullreturn// null lock id, do nothing  
  3. //先删除lock id  
  4. HashedBytes rowKey = lockIds.remove(lockId);  
  5. if (rowKey == null) {  
  6.   LOG.warn("Release unknown lockId: " + lockId);  
  7.   return;  
  8. }  
  9. //再删除lock  
  10. CountDownLatch rowLatch = lockedRows.remove(rowKey);  
  11. if (rowLatch == null) {  
  12.   LOG.error("Releases row not locked, lockId: " + lockId + " row: "  
  13.       + rowKey);  
  14.   return;  
  15. }  
  16. //lock释放  
  17. rowLatch.countDown();  
 11.flush Hlog到HDFS

 

 

Java代码   收藏代码
  1. // sync all transactions upto the specified txid  
  2. private void syncer(long txid) throws IOException {  
  3.   Writer tempWriter;  
  4.   synchronized (this.updateLock) {  
  5.     if (this.closed) return;  
  6.     tempWriter = this.writer; // guaranteed non-null  
  7.   }  
  8.   // if the transaction that we are interested in is already   
  9.   // synced, then return immediately.  
  10.   //当前flush到第一个日志了,有可能已经被其他rpc线程flush掉了  
  11.   if (txid <= this.syncedTillHere) {  
  12.     return;  
  13.   }  
  14.   try {  
  15.     long doneUpto;  
  16.     long now = System.currentTimeMillis();  
  17.     // First flush all the pending writes to HDFS. Then   
  18.     // issue the sync to HDFS. If sync is successful, then update  
  19.     // syncedTillHere to indicate that transactions till this  
  20.     // number has been successfully synced.  
  21.     synchronized (flushLock) {  
  22.       if (txid <= this.syncedTillHere) {  
  23.         return;  
  24.       }  
  25.       doneUpto = this.unflushedEntries.get();  
  26. /当前所有cache的log  
  27.       List<Entry> pending = logSyncerThread.getPendingWrites();  
  28.       try {  
  29. //写,但没sync到HDFS  
  30.         logSyncerThread.hlogFlush(tempWriter, pending);  
  31.       } catch(IOException io) {  
  32.         synchronized (this.updateLock) {  
  33.           // HBASE-4387, HBASE-5623, retry with updateLock held  
  34.           tempWriter = this.writer;  
  35.           logSyncerThread.hlogFlush(tempWriter, pending);  
  36.         }  
  37.       }  
  38.     }  
  39.     // another thread might have sync'ed avoid double-sync'ing  
  40.     if (txid <= this.syncedTillHere) {  
  41.       return;  
  42.     }  
  43.     try {  
  44. /sync到HDFS,写失败重试一次  
  45.       tempWriter.sync();  
  46.     } catch(IOException io) {  
  47.       synchronized (this.updateLock) {  
  48.         // HBASE-4387, HBASE-5623, retry with updateLock held  
  49.         tempWriter = this.writer;  
  50.         tempWriter.sync();  
  51.       }  
  52.     }  
  53.     //当前已sync的日志  
  54.     this.syncedTillHere = Math.max(this.syncedTillHere, doneUpto);  
  55.   
  56.    ......  
  57.   } catch (IOException e) {  
  58.     LOG.fatal("Could not sync. Requesting close of hlog", e);  
  59.     //回滚。  
  60.     requestLogRoll();  
  61.     throw e;  
  62.   }  
  63. }  

 

Java代码   收藏代码
  1. @Override  
  2.  public void append(HLog.Entry entry) throws IOException {  
  3.    entry.setCompressionContext(compressionContext);  
  4.    try {  
  5.      //SequenceFile写入  
  6.      this.writer.append(entry.getKey(), entry.getEdit());  
  7.    } catch (NullPointerException npe) {  
  8.      // Concurrent close...  
  9.      throw new IOException(npe);  
  10.    }  
  11.  }  

 12.修改mvcc的读事务id

Java代码   收藏代码
  1. public void completeMemstoreInsert(WriteEntry e) {  
  2.   //递增读事务id  
  3.   advanceMemstore(e);  
  4.   //等待之前的请求全部完成  
  5.   waitForRead(e);  
  6. }  

 

Java代码   收藏代码
  1.   boolean advanceMemstore(WriteEntry e) {  
  2.    synchronized (writeQueue) {  
  3. //事务结束  
  4.      e.markCompleted();  
  5.   
  6.      long nextReadValue = -1;  
  7.      boolean ranOnce=false;  
  8.      //遍历队列,拿到最近已完成的事务id,如果中间有一个请求还未完成,则可能拿到的事务id比当前事务小  
  9.      while (!writeQueue.isEmpty()) {  
  10.        ranOnce=true;  
  11.        WriteEntry queueFirst = writeQueue.getFirst();  
  12.   
  13.        if (nextReadValue > 0) {  
  14.          if (nextReadValue+1 != queueFirst.getWriteNumber()) {  
  15.            throw new RuntimeException("invariant in completeMemstoreInsert violated, prev: "  
  16.                + nextReadValue + " next: " + queueFirst.getWriteNumber());  
  17.          }  
  18.        }  
  19.   
  20.        if (queueFirst.isCompleted()) {  
  21.          nextReadValue = queueFirst.getWriteNumber();  
  22.          writeQueue.removeFirst();  
  23.        } else {  
  24.          break;  
  25.        }  
  26.      }  
  27.   
  28.      if (!ranOnce) {  
  29.        throw new RuntimeException("never was a first");  
  30.      }  
  31.   
  32. //修改读事务的id,所有小于该id的事务都已完成,对read可见  
  33.      if (nextReadValue > 0) {  
  34.        synchronized (readWaiters) {  
  35.          memstoreRead = nextReadValue;  
  36.          readWaiters.notifyAll();  
  37.        }  
  38.      }  
  39.      if (memstoreRead >= e.getWriteNumber()) {  
  40.        return true;  
  41.      }  
  42.      return false;  
  43.    }  
  44.  }  

 

Java代码   收藏代码
  1. /** 
  2.  * Wait for the global readPoint to advance upto 
  3.  * the specified transaction number. 
  4.  */  
  5. public void waitForRead(WriteEntry e) {  
  6.   boolean interrupted = false;  
  7.   synchronized (readWaiters) {  
  8.     //如果前面请求还未处理完,则等待它们结束  
  9.     while (memstoreRead < e.getWriteNumber()) {  
  10.       try {  
  11.         readWaiters.wait(0);  
  12.       } catch (InterruptedException ie) {  
  13.         // We were interrupted... finish the loop -- i.e. cleanup --and then  
  14.         // on our way out, reset the interrupt flag.  
  15.         interrupted = true;  
  16.       }  
  17.     }  
  18.   }  
  19.   if (interrupted) Thread.currentThread().interrupt();  
  20. }  

 13.检查memstore的内存大小是否超过memstoreFlushSize,是则请求flush,异步

14.返回结果,如果put操作没拿到行锁,则结果是null


http://blog.cloudera.com/blog/2012/06/hbase-write-path/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值