HBase写请求分析

本文深入探讨HBase的写请求流程,包括客户端如何通过HTable.put发送请求,以及服务端如何执行写操作。客户端利用缓存和并发控制策略减少对HRegionServer的压力,服务端通过HRegion的批量处理、MVCC和WAL实现高性能和一致性。在服务端,HRegionServer执行事务,包括行锁、时间戳更新、WAL记录和提交事务,确保行级别的事务一致性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    HBase作为分布式NoSQL数据库系统,不单支持宽列表,并且对于随机读写来说也具有较高的性能。在高性能的随机读写事务的同时,HBase也能保持事务的一致性。目前HBase只支持行级别的事务一致性。本文主要探讨一下HBase的写请求流程,主要基于0.98.8版本的实现。

客户端写请求

   HBase提供的Java client API是以HTable为主要接口,对应其中的HBase表。写请求API主要为HTable.put(write和update)、HTable.delete等。以HTable.put为例子,首先来看看客户端是怎么把请求发送到HRegionServer的。

   每个put请求表示一个KeyValue数据,考虑到客户端有大量的数据需要写入到HBase表,HTable.put默认是会把每个put请求都放到本地缓存中去,当本地缓存大小超过阀值(默认为2MB)的时候,就要请求刷新,即把这些put请求发送到指定的HRegionServer中去,这里是利用线程池并发发送多个put请求到不同的HRegionServer。但如果多个请求都是同一个HRegionServer,甚至是同一个HRegion,则可能造成对服务端造成压力,为了避免发生这种情况,客户端API会对写请求做了并发数限制,主要是针对put请求需要发送到的HRegionServer和HRegion来进行限制,具体实现在AsyncProcess中。主要参数设定为:

  • hbase.client.max.total.tasks              客户端最大并发写请求数,默认为100
  • hbase.client.max.perserver.tasks      客户端每个HRegionServer的最大并发写请求数,默认为2
  • hbase.client.max.perregion.tasks      客户端每个HRegion最大并发写请求数,默认为1

    为了提高I/O效率,AsyncProcess会合并同一个HRegion对应的put请求,然后再一次把这些相同HRegion的put请求发送到指定HRegionServer上去。另外AsyncProcess也提供了各种同步的方法,如waitUntilDone等,方便某些场景下必须对请求进行同步处理。每个put和读请求一样,都是要通过访问hbase:meta表来查找指定的HRegionServer和HRegion,这个流程和读请求一致,可以参考文章的描述。

服务端写请求

    当客户端把写请求发送到服务端时,服务端就要开始执行写请求操作。HRegionServer把写请求转发到指定的HRegion执行,HRegion每次操作都是以批量写请求为单位进行处理的。主要流程实现在HRegion.doMiniBatchMutation,大致如下:

  1. 获取写请求里指定行的行锁。由于这些批量写请求之间是不保证一致性(只保证行一致性),因此每次只会尝试阻塞获取至少一个写请求的行锁,其它已被获取的行锁则跳过这次更新,等待下次迭代的继续尝试获取
  2. 更新已经获得行锁的写请求的时间戳为当前时间
  3. 获取HRegion的updatesLock的读锁。
  4. 获取MVCC(Mu
Tephra 在 Apache HBase 的基础上提供了全局一致性的事务支持。HBase 提供了强一致性的基于行和区域的 ACID 操作支持,但是牺牲了在跨区域操作的支持。这就要求应用开发者花很大力气来确保区域边界上操作的一致性。而 Tephra 提供了全局事务支持,可以夸区域、跨表以及多个 RPC 上简化了应用的开发。示例代码:  /**    * A Transactional SecondaryIndexTable.    */   public class SecondaryIndexTable {     private byte[] secondaryIndex;     private TransactionAwareHTable transactionAwareHTable;     private TransactionAwareHTable secondaryIndexTable;     private TransactionContext transactionContext;     private final TableName secondaryIndexTableName;     private static final byte[] secondaryIndexFamily =       Bytes.toBytes("secondaryIndexFamily");     private static final byte[] secondaryIndexQualifier = Bytes.toBytes('r');     private static final byte[] DELIMITER  = new byte[] {0};     public SecondaryIndexTable(TransactionServiceClient transactionServiceClient,                                HTable hTable, byte[] secondaryIndex) {       secondaryIndexTableName =             TableName.valueOf(hTable.getName().getNameAsString()   ".idx");       HTable secondaryIndexHTable = null;       HBaseAdmin hBaseAdmin = null;       try {         hBaseAdmin = new HBaseAdmin(hTable.getConfiguration());         if (!hBaseAdmin.tableExists(secondaryIndexTableName)) {           hBaseAdmin.createTable(new HTableDescriptor(secondaryIndexTableName));         }         secondaryIndexHTable = new HTable(hTable.getConfiguration(),                                           secondaryIndexTableName);       } catch (Exception e) {         Throwables.propagate(e);       } finally {         try {           hBaseAdmin.close();         } catch (Exception e) {           Throwables.propagate(e);         }       }       this.secondaryIndex = secondaryIndex;       this.transactionAwareHTable = new TransactionAwareHTable(hTable);       this.secondaryIndexTable = new TransactionAwareHTable(secondaryIndexHTable);       this.transactionContext = new TransactionContext(transactionServiceClient,                                                        transactionAwareHTable,                                                        secondaryIndexTable);     }     public Result get(Get get) throws IOException {       return get(Collections.singletonList(get))[0];     }     public Result[] get(List<Get> gets) throws IOException {       try {         transactionContext.start();         Result[] result = transactionAwareHTable.get(gets);         transactionContext.finish();         return result;       } catch (Exception e) {         try {           transactionContext.abort();         } catch (TransactionFailureException e1) {           throw new IOException("Could not rollback transaction", e1);         }       }       return null;     }     public Result[] getByIndex(byte[] value) throws IOException {       try {         transactionContext.start();         Scan scan = new Scan(value, Bytes.add(value, new byte[0]));         scan.addColumn(secondaryIndexFamily, secondaryIndexQualifier);         ResultScanner indexScanner = secondaryIndexTable.getScanner(scan);         ArrayList<Get> gets = new ArrayList<Get>();         for (Result result : indexScanner) {           for (Cell cell : result.listCells()) {             gets.add(new Get(cell.getValue()));           }         }         Result[] results = transactionAwareHTable.get(gets);         transactionContext.finish();         return results;       } catch (Exception e) {         try {           transactionContext.abort();         } catch (TransactionFailureException e1) {           throw new IOException("Could not rollback transaction", e1);         }       }       return null;     }     public void put(Put put) throws IOException {       put(Collections.singletonList(put));     }     public void put(List<Putputs) throws IOException {       try {         transactionContext.start();         ArrayList<Put> secondaryIndexPuts = new ArrayList<Put>();         for (Put put : puts) {           List<Put> indexPuts = new ArrayList<Put>();           Set<Map.Entry<byte[], List<KeyValue>>> familyMap = put.getFamilyMap().entrySet();           for (Map.Entry<byte [], List<KeyValue>> family : familyMap) {             for (KeyValue value : family.getValue()) {               if (value.getQualifier().equals(secondaryIndex)) {                 byte[] secondaryRow = Bytes.add(value.getQualifier(),                                                 DELIMITER,                                                 Bytes.add(value.getValue(),                                                 DELIMITER,                                                 value.getRow()));                 Put indexPut = new Put(secondaryRow);                 indexPut.add(secondaryIndexFamily, secondaryIndexQualifier, put.getRow());                 indexPuts.add(indexPut);               }             }           }           secondaryIndexPuts.addAll(indexPuts);         }         transactionAwareHTable.put(puts);         secondaryIndexTable.put(secondaryIndexPuts);         transactionContext.finish();       } catch (Exception e) {         try {           transactionContext.abort();         } catch (TransactionFailureException e1) {           throw new IOException("Could not rollback transaction", e1);         }       }     }   } 标签:Tephra
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值