put的过程相对比较简单,因为根据LSM-Tree的理论,写入操作会写入到内存中,然后再batch的写入磁盘。
HBase的实现也是如此。
首先,从客户端的batch操作中提取出所有的put操作并放在一个sortedmap中(localput):
Text row = b.getRow(); long lockid = obtainRowLock(row); long commitTime = (timestamp == LATEST_TIMESTAMP) ? System.currentTimeMillis() : timestamp; try { List<Text> deletes = null; for (BatchOperation op: b) { HStoreKey key = new HStoreKey(row, op.getColumn(), commitTime); byte[] val = null; if (op.isPut()) { val = op.getValue(); if (HLogEdit.isDeleted(val)) { throw new IOException("Cannot insert value: " + val); } } else { if (timestamp == LATEST_TIMESTAMP) { // Save off these deletes if (deletes == null) { deletes = new ArrayList<Text>(); } deletes.add(op.getColumn()); } else { val = HLogEdit.deleteBytes.get(); } } if (val != null) { localput(lockid, key, val); } }localput将batch中的put操作的数据存放在targetColumns中,再用update方法update到每个HStore的memcache中:
TreeMap<HStoreKey, byte[]> edits = this.targetColumns.remove(Long.valueOf(lockid)); if (edits != null && edits.size() > 0) { update(edits); }update的过程分为以下几个步骤:
1.hlog增加此次操作的信息
2.遍历每个edit信息。取出key和value,增加memcache的size
3.添加key和value到memcache里
4.如果size大于memcacheflushsize则强制flush
this.log.append(regionInfo.getRegionName(), regionInfo.getTableDesc().getName(), updatesByColumn); long size = 0; for (Map.Entry<HStoreKey, byte[]> e: updatesByColumn.entrySet()) { HStoreKey key = e.getKey(); byte[] val = e.getValue(); size = this.memcacheSize.addAndGet(key.getSize() + (val == null ? 0 : val.length)); stores.get(HStoreKey.extractFamily(key.getColumn())).add(key, val); } if (this.flushListener != null && size > this.memcacheFlushSize) { // Request a cache flush this.flushListener.flushRequested(this); }
1.put操作因为只put到内存的sortedmap中就返回,因此速度非常快,这也是HBase的LSM-Tree引以为豪的地方之一
2.写入memcache时会先加读锁,但好像没加写锁,这是为啥。。。是因为memcache里的sortedmap是Collections.synchronizedSortedMap的吗?
3.可以看出是put的过程是先写hlog再写内存的,因此只要写到内存的数据就可以认为是安全的了
4.每次写入memcache都会check是否到了flush的size,如果到了就会触发flush。
5.flush会将内存中的数据写入hdfs文件系统。这种定期batch的写文件效率是非常高的,而且在没有flush时不占读的io,无形中留了很多io给读操作,增加了读的性能