hbase

Hbase架构师3-Hbase源码分析

2020-09-22  本文已影响0人  fat32jin

HBase的源码:

1、集群启动
master启动
regionsever启动
2、put 插入数据源码分析
delete
3、get 查询数据源码分析
scan
4、三个重要的行为:
flush
split
compact
5、寻路
根据rowkey定位region的位置

注意要点:

1、版本问题:
hbase-0.96 hbase-0.98
hbase-1.2.x hbase-1.4.x
hbase-2.x 这是我们选择的版本

2、版本越低,其他非核心流程的代码相对更少,对你的干扰更少

3、切记最好画图!

4、关于hbase的源码是否要编译呢。
选择性的。如果要编译:推荐安装 cygwin
zookeeper
hive
hdfs
kafka
hbase

HBase2.x源码分析-HMaster启动流程分析

正常的启动历程:

启动一个 master
再启动 N 个 regionserver
启动一个 master
在这个过程中,会有选举!大概率:先启动的master会成为 active

HBase集群启动的命令:start-hbase.sh
这个命令的底层是:
hbase-daemon.sh start master
这个命令的底层:
java org.apache.hadoop.hbase.master.HMaster arg1 arg2
底层转到
HMaster.main()

class HMaster extends HRegionServer implements MasterServices
1、HMaster 是 HRegionServer 的子类
2、HMaster 绝大部分的服务,都是通过 MasterServices 来实现的

比如:listTable()  craeteTable()

↓ ***▲ ▼ ——》

HMaster正常的启动历程: 0;25:00 ~ 2:00:00
HMaster.main() 
               ▼ 
            new HMasterCommandLine(HMaster.class).doMain(args);
         ——》 父类 ServerCommandLine#doMain
                ▼
            int ret = ToolRunner.run(HBaseConfiguration.create() 
             ↓  
          HMasterCommandLine#run()
               ▼
         return startMaster();
          ......
         return stopMaster();
                 ▲
          ——0  》上面   startMaster()  方法 启动服务器
                        ▼
                  /********                 
                 *   注释: 通过反射构造 HMaster 实例, HMaster 是 HRegionServer 的子类,
                 *   1、先看 HMaster 中的构造方法做了什么
                 *   2、再进入 Hmaster 的 run() 方法看看做了什么
                 *   3、masterClass = HMaster.class
                 */
                  if(LocalHBaseCluster.isLocal(conf)) {
                    // local模式
                   ......    
                 } else {
                       // 集群模式
                   ......    
                       HMaster master = HMaster.constructMaster(masterClass, conf);
                    ......  
                   /******** 
                 *   1、hmaster 通过 start() 启动
                 *   2、hmaster 肯定也是一个线程
                 *   3、当执行 start 方法之后,应该转到 hmaster 的 run() 继续阅读
                 */ 
                        master.start();
                        master.join();                       
                  }
                      ▲ 
             ——0. 1  》上面  HMaster#constructMaster 方法是调用  HMaster(final Configuration conf) 构造函数                               
                     ——0. 1.1 》super(conf); // HMaster父类 HRegionServer#构造器里面的方法
                               ▼
                             /********        
                              *   1、创建 RPCServer
                              *   2、最重要的操作:初始化一个 ZKWatcher 类型的 zookeeper 实例,
                              *        用来初始化创建一些 znode 节点
                              *   3、启动 RPCServer
                              *   4、创建 ActiveMasterManager  用来选举和管理hmaster的状态
                              *   hmaster除了调用自己的构造方法,也会调用 父类hregionserver的构造方法
                              */
                           rpcServices = createRpcServices();
                          RpcRetryingCallerFactory.instantiate(this.conf);
                         regionServerAccounting = new RegionServerAccounting(conf);
                          initializeFileSystem();
                         zooKeeper = new ZKWatcher()   
                            this.rpcServices.start(zooKeeper);
                               //ChoreService 里面包装了一个线程池,用来处理 hmaster 或者 hre
                            this.choreService = new ChoreService(getName(), true);
                             // ExecutorService 同样也是包装了一个线程池,用来处理其他的 RpcClient 发送过来的请求
                              this.executorService = new ExecutorService(getName());
                             //  启动 jetty,Region Server WebUI
                            putUpWebUI();
                                 ▲ 
                          ——》上面的   new ZKWatcher()   #构造函数
                                          ▼
                                     /**** 
                                      *   注释: 初始化一堆关于 hbase 在 zookeeper 创建的 znode 节点          
                                       *   hbae装好了之后u,默认在zookeeper有一个/hbase 的root znode
                                       *   zookeeper.znode.rs = /hbase/rs
                                      *   zookeeper.znode.draining.rs = /hbase/draining
                                      *   zookeeper.znode.backup.masters = /hbase/backup-masters
                                      *  /
                                     this.znodePaths = new ZNodePaths(conf);
                                    .......
                                       /* 注释: 获取 zk 链接
                                          *   recoverableZooKeeper 这个实例里面有一个 ZooKeeper zk 的成员变量
                                         *   HMaster HRegionserver 都是通过 recoverableZooKeeper 的 
                                        *  zk 实例来完成操作 zookeeper
                                        * /
                                 this.recoverableZooKeeper = ZKUtil.connect(conf, quorum, pendingWatcher, identifier);
                                  ......
                                /****
                                  *   注释: 创建一堆必要的父节点
                                  *   1、baseZNode = /hbase
                                  *   2、rsZNode = /hbase/rs
                                  *   .............
                                  */
                                   createBaseZNodes();
              
                      —— 0.1.2 》 HMaster 自己构造器里面的代码
                               /*** 
                                 *   注释: 创建 activeMasterManager
                                 *   帮助 HMaster 去选举  Active 角色
                                *   监控 HMaster的状态,也要去监控 zookeper 集群中的对应的  active master 的znode
                                 */ 
                               if(!conf.getBoolean("hbase.testing.nocluster", false)) {
                                  this.activeMasterManager = 
                                     new ActiveMasterManager(zooKeeper, this.serverName, this);
                                } else {
                                        this.activeMasterManager = null;
                               }
        —— 0. 2 》 回到 上面 0处  master.start(); 方法,即运行HMaster 的run()方法
                               ▼                          
                        /*
                         *   注释: 启动 Web 服务
                         */
                        int infoPort = putUpJettyServer();

                        /*
                         *   注释:重要的方法
                         */
                        startActiveMasterManager(infoPort);
                             ▼
              /* 
                *   注释: 一上线,就通过 MasterAddressTracker.setMasterAddress() 方法创建代表自己存在与否的 backup znode emphamaral 节点
              *   一上线,都不是 active master,所以先成为 backup master,然后创建代表自己的临时 znode 节点
               *   1、如果时机条件正确,则成为 active master,则刚才创建的 临时znode 节点被 ActiveMasterManager 显示删除
               *   2、如果不能成为 active master 不需要做什么
                *   3、如果这个 backup master宕机,则 zookeeper 会自动删除这个临时节点
                 */
                 String backupZNode = 
                 ZNodePaths.joinZNode(zooKeeper.getZNodePaths().backupMasterAddressesZNode, 
                serverName.toString());
               //  :ZNodePaths.joinZNode 唯一的目的只有一个:拼接一个znode路径:
              //   当前master一启动就立即在 /hbsae/backup-masters/ 下面创建一个临时节点
             //   代表自己上线了。但是并没有立即成为  active  
             
               //  这儿是去创建 Backup Master ZNode
              if(!MasterAddressTracker.setMasterAddress(zooKeeper, backupZNode, serverName, infoPort)) {
               。。。。。。
               /* 重点!!: 阻塞到直到有 ActiveMaster 为止, 但是默认是 False,不会执行,
                  *   除非有人显示调整这个参数为 True
                   *   因为可能同时有多个 amster 启动。
                    *   等待是否有 activeMaster
                   */      
             if(conf.getBoolean(HConstants.MASTER_TYPE_BACKUP, 
                  HConstants.DEFAULT_MASTER_TYPE_BACKUP)) { 
                               while(!activeMasterManager.hasActiveMaster()) {                
                                 Threads.sleep(timeout);  
                  }
                }
              /* 注释: if 中的方法,完成注册和争抢成为 Active Master 的工作
             */
               @@@   if (  activeMasterManager.blockUntilBecomingActiveMaster(timeout, status)) {
                         ——》 ActiveMasterManager#blockUntilBecomingActiveMaster
                                         ▼
                 /** 去注册 Active Master, 记住:也是临时节点
                  *   分布式锁的方式来实现的!
                 *   MasterAddressTracker.setMasterAddress() 创建一个 临时 znode 节点,
                 *   多个 hmsater 如果同时启动,则可能同时都在创建这个 代表 active 身份的 znode 节点
                 *   谁创建成功了,谁才能成为 active
                 *  所以:如下的代码,如果能进入到 if ,就证明当前 hmaster 争抢这把分布式锁成功了。
                 *  当前 hmaster 才能成为 active                 *
                 *  接着再做一件事:就是删除之前一上线 就创建的 backup znode 节点
                 */
                if(MasterAddressTracker.setMasterAddress(this.watcher,  // 抢锁

                    /* 如果注册成功,则删除自己原先创建的 backup master znode 临时节点
                     */                    
                    if(ZKUtil.checkExists(this.watcher, backupZNode) != -1) {
                        LOG.info("Deleting ZNode for " + backupZNode + " from backup master directory");
                        ZKUtil.deleteNodeFailSilent(this.watcher, backupZNode);
                    }

                    /*  将znode保存在文件中,这将允许检查启动脚本是否崩溃
                     */                   
                    ZNodeClearer.writeMyEphemeralNodeOnDisk(this.sn.toString());
                    /*  设置 Master 为 Active 状态
                     */                  
                    startupStatus.setStatus("Successfully registered as active master.");
                    /* : 到上面为止,也只是知道有 Active Master了,但是到底是谁不知道
                 *   所以通过获取 Active Master ZNode节点的数据得知
                 */
                String msg;
                byte[] bytes = ZKUtil.getDataAndWatch(this.watcher, 
                                         this.watcher.getZNodePaths().masterAddressZNode);
                if(bytes == null) {
                    msg = ("A master was detected, but went down before its address " 
                                  + "could be read.  Attempting to become the next active master");
                } 
                                       ▲ 
                       ——》 回到@@@   if (  activeMasterManager.blockUntilBecomingActiveMaster处
                              // 下面这个方法非常复杂重要
                              finishActiveMasterInitialization(status);         
                                             ▼
                              /*  初始化 ProcedureExecutor
                                 *   将来你的各种请求,比如 createTable, listTable, put, get 等等这些用户请求,
                                 *    都是被封装成一个个的
                                 *   Procedure 来执行的。 放在线程池里面执行。
                               *   ProcedureExecutor 命令执行器!!!
                               */
                               createProcedureExecutor();
                             /*  初始化 AssignmentManager 用来帮助 HMaster 去分配那些 region
                                  给那些 regionsever 去管理
                                *   默认的实现就是:AssignmentManager
                                */
                                   this.assignmentManager = createAssignmentManager(this);


![image.png](https://img.haomeiwen.com/i11332520/735619ee163723d5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
2 RegionServer正常的启动历程: 2;00:00 ~ 2;12:00

HBase集群启动的命令:start-hbase.sh
这个命令的底层是:
hbase-daemon.sh start regionserver
这个命令的底层:
java org.apache.hadoop.hbase.regionserver.HRegionServer arg1 arg2
底层转到
HMaster.main()

class HRegionServer extends HasThread implements RegionServerServices
1、HMaster 是 HRegionServer 的子类, HRegionServer 是 HasThread 的子类
HasThread 是 RUnnable 的子类,所以 HMaster 和 HRegionServer 都是线程
入口:main() run() 方法

HRegionServer.main()
    HRegionServer 的构造方法
    HRegionServer 的 run()
        preRegistrationInitialization();
            initializeZooKeeper();
            setupClusterConnection();
            this.rpcClient = RpcClientFactory.createClient()
        reportForDuty();
            masterServerName = createRegionServerStatusStub(true);
            this.rssStub.regionServerStartup(null, request.build());
        handleReportForDutyResponse(w);
            createMyEphemeralNode();
            initializeFileSystem();
            ZNodeClearer.writeMyEphemeralNodeOnDisk(getMyEphemeralNodePath());
            setupWALAndReplication();
            startServices();
            startReplicationService();
        tryRegionServerReport(lastMsg, now);
            rss.regionServerReport(null, request.build());
3 Put流程源码分析: 2;21:00 ~

核心流程:

代码入口:
table.put(new Put())

大致流程:
1、首先客户端会请请求zookeepre拿到meta表的位置信息,这个meta表的region到底在那个regionserver1里面
2、发请求请求这个regionserver1扫描这个meta表的数据,确定我要插入的数据rowkey到底在那个用户表的region里面。并且还拿到这个region在那个regioinserver2的信息
3、发送请求,请求regionserver2扫描 当前用户表的regioin, 执行插入
1、先记录日志
2、写入数据到 memstore
写到 ConcurrentSkipListMap delegatee
3、判断是否需要进行flush
1、再次判断是否需要进行 compact
2、判断是否需要进行 split

——》开始 HTable#put()

/* callable = ClientServiceCallable, 是 RegionServerCallable 的子类。
* 最终执行:ClientServiceCallable.doMutate(request); 提交
* 将来各种兜兜转转,一定会回到调用: callable.rpcCall()
/
ClientServiceCallable<Void> callable = new ClientServiceCallable<Void>(this.connection, getName(), put.getRow(),
this.rpcControllerFactory.newController(), put.getPriority()) {
@Override
protected Void \color{#FF3030}{rpcCall}() throws Exception {
/
* 构建一个 Put 请求
* 仅仅只是把 put 变成 request
/
MutateRequest request = RequestConverter.buildMutateRequest(getLocation().getRegionInfo().getRegionName(), put);
// 具体提交执行(使用probubuf协议序列化,并提交RPC请求)
doMutate(request);
▲ ↓
——》 RSRpcServices#Mutate()
/
*

3.1 -Region定位分析 开始 3:00:00
3.1 -Region定位分析 结束 3:23:00
                                                ——》HRegion.put()
3.2 -写入内存memstore 及Flush分析 Memstore形成HFile 开始 3:23:00
                                                                  ▼ 
                                                  HRegion#checkResources() //检查资源
                                                             ......
                                                   /** 
                                                    *   1、checkResources(); 会涉及到flush, 
                                                   *      进行 put 操作之前检查资源是否足够
                                                    *   2、doBatchMutate(put); 是用来真正完成put操作的。
                                                   *      完成 put 操作之后,也需要判断 memstore 是不是要进行 flush
                                                   */ 
                                              doBatchMutate(put);    
                                            ——  》HRegion#doBatchMutate
                                                 —— ... 》HRegion#batchMutate(BatchOperation<?> batchOp)
                                                            ▼
              // 完成数据插入到 memstore 的动作 
                        3    doMiniBatchMutate(batchOp);
                                                       ......
                            // 写完数据之后,判断是否有需要进行 flush                  
                         4    requestFlushIfNeeded();
                                                     ▲
                   ——接3 》 doMiniBatchMutate(batchOp);
                                                         ▼  
                                                 //六个步骤
                                               /*第一步
                                                  *   对BatchOperation对象上锁,返回的是一个表示
                                                 * 正在处理中的对象 MiniBatchOperationInProgress                                                       
                                                  *   HBase 是有行级的事务的!  
                                                 *     现在执行的各种  Put 的 rowkey 所对应的 这一行数据的 锁我们都得拿到
                                                */ 
                                         miniBatchOp = batchOp.lockRowsAndBuildMiniBatch(acquiredRowLocks);
                                            /*第2步   
                                               *   更新所有操作对象的时间戳,确保是最新的。
                                            *        就只是更新 keyvalue 的时间戳为 本机服务器的时间戳!
                                             */
                                   long now = EnvironmentEdgeManager.currentTime();
                                 batchOp.prepareMiniBatchOperations(miniBatchOp, now, acquiredRowLocks);
                                             /*第3步   
                                               *   初始化或构造 WAL edit对象 
                                             */
                                      List<Pair<NonceKey, WALEdit>> walEdits = 
                                        batchOp.buildWALEdits(miniBatchOp);
                                              /*第4步   
                                               *  将WALEdits对象提交并持久化(即写WAL)
                                             */
                                       mvcc.complete(writeEntry);
                                             /*第5步   
                                               *  写 memStore
                                             */
                                    writeEntry = 
                              5    batchOp.writeMiniBatchOperationsToMemStore(miniBatchOp, writeEntry);
                                             /*第6步   
                                               *  完成写入操作
                                             */
                              6     batchOp.completeMiniBatchOperations(miniBatchOp, writeEntry);
                                                     ▲
                              ——上接  5   》 batchOp.writeMiniBatchOperationsToMemStore
                                                                              ↓                                                          
                                                                HRegion#
                                                                MutationBatchOperation#
                                                                writeMiniBatchOperationsToMemStore()
                                                                           ▼    
                                                               super.writeMiniBatchOperationsToMemStore(
                                                                            ▼  
                                      /* 
                                       *   1、一个region其实按照列簇会分成多个不同的 store
                                        *   2、现在插入数据的时候,可以这一个miniBatch 会包含多个不同的列簇的数据
                                     *   3、不同的列簇,拥有不同的memstore
                                 *   4、到了这个步骤必须把所有的数据,区分开,不同的列簇的,数据,分别进行插入
                              */
                                    applyFamilyMapToMemStore(familyCellMaps[index], memStoreAccounting);                                
                                                                            ▼  
                                //按照列簇循环写入
                              for(Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) {
                                     region.applyToMemStore(region.getStore(family), cells, 
                                                                         ▼  
                         if(upsert) {
                                       store.upsert(cells, getSmallestReadPoint(), memstoreAccounting);
                                    } else {   //  store 包含  Memstore 和 StoreFile 
                                              store.add(cells, memstoreAccounting);
                                                        ▼  添加到内存
                                                                 memstore.add(cells, memstoreSizing);
                                                              ↓
                                           AbstractMemStore.add
                                                        ▼  循环cell添加
                                           for(Cell cell : cells) { 
                                                add(cell, memstoreSizing);    }
                                                  ——》AbstractMemStore.add
                                                      ——》doAddOrUpsert
                                                        ——》AbstractMemStore#doAdd()
                                                             ——》AbstractMemStore#internalAdd()
                                                                ——》 MutableSegment#add()
                                                                    ——》Segment#internalAdd()
                                                                                   ▼  
                                                                           //终于放进内存
                                                        5.1       boolean succ = getCellSet().add(cell);
                                                                                //更新元数据信息 
                                                                            updateMetaInfo(cell, succ,  
                                                                                   ▲
                                                                  ——上接5.1 》CellSet.add()
                                                                                    ▼  
                                                                     / *   delegatee 是 ConcurrentSkipListMap
                                                                  *   即 memstore的内存结构: 跳表  
                                                            *   基于链表来实现的,
                                                 *  兼顾插入和排序,和查询效率,这是最终的步骤
                                                         */
                                                                          return this.delegatee.put(e, e) == null;
                                                                                                           ▲
                               ——上接  6   完成写入》 batchOp.completeMiniBatchOperations
                                       ——》HRegion.BatchOperation.completeMiniBatchOperations
3.2 -写入内存memstore 及Flush分析 Memstore形成HFile 结束 3:45:00
                         ——上接4 》requestFlushIfNeeded()
                             ——》reqeustFlush()
                             ——》requestFlush0()
                                              ↓
                                      MemStoreFlusher#requestFlush()
                                               ▼
                                  // 加入队列 一定有一个地方是取poll  fqe 来执行 flush                 
                                   this.flushQueue.add(fqe);
                                                  ↓     搜索flushQueue.poll 
                                            MemStoreFlusher#FlushHandler#run()
                                                  ▼ 
                                    //*** 注释: 取出队列执行处理                  
                             fqe = flushQueue.poll(threadWakeFrequency, TimeUnit.MILLISECONDS);
                                           ......
                               //*** 溢写! 
                              if(!flushRegion(fre)) { 
                                ——》flushRegion(HRegion region, boolean emergencyFlush,
                                                                 boolean forceFlushAllStores, FlushLifeCycleTracker tracker) 
                                                                              ▼3件事
                                            1  FlushResult flushResult = region.flushcache(
                                             // 注释:溢写完毕之后,判断是否需要进行 compact 
                                           2  boolean shouldCompact = flushResult.isCompactionNeeded();
                                          // 注释:溢写完毕之后,判断是否需要进行 split
                                           3   boolean shouldSplit = region.checkSplit() != null;
                                         ——1 》 HRegion#flushcache
                                                                         ▼
                                     //   注释: 执行真正的 Flush 方法                
            FlushResultImpl fs = internalFlushcache(specificStoresToFlush, status, 
                                                                                 writeFlushRequestWalMarker, tracker);
                                                    ——》HRegion#internalFlushcache
                                                        ——》HRegion#internalFlushCacheAndCommit
                                                                                            ▼
                                                                       flush.flushCache(status);
                                                                                             ↓ 
                                                                          Hstore#flushCache
                                                                         ▼HStore.this.flushCache
                                                                                ▼ flusher.flushSnapshot
                                                                                                        ↓
                                                                               DefaultStoreFlusher#flushSnapshot   
                                                                                            ▼
                                                     / *   注释: writer = StoreFileWriter
                                                     *   flush 把  跳表中的数据 写到磁盘文件。  需要输出流
                                                     */                                                         
                                                           writer = store.createWriterInTmp(cellsCount,.
                                                                       /* 
                                                                          *   1、scanner 扫描器
                                                                          *   2、writer 输出流
                                                                          */
                                                          performFlush(scanner, writer, smallestReadPoint, throughputController);
                                                    ——》 StoreFlusher#performFlush()
                                                                                         ▼
                                                 /* 注释:sink = StoreFileWriter
                                                               *   sink = Writer                          *   c = Cell
                                                                */ 
                                                            sink.append(c);
                                                                           ↓
                                                           StoreFileWriter#append()
                                                                            ▼
                     // TODO_MA 注释: 每次溢写的一个HFile,我们都要为其构建一个 bloomfilter
                              appendGeneralBloomfilter(cell);
                               appendDeleteFamilyBloomFilter(cell);

                               // TODO_MA 注释: 写出cell到文件
                             writer.append(cell);
                            trackTimestamps(cell);
                                                               ↓
                                      HFileWriterImpl.append(cell)
                                                              ▼
                    / *   注释:
                        *   1、hbase的底层都是存储的 二进制字节数据
                        *   2、encode  编码, 事实上充当写出的动作 
                        */
                      this.unencodedDataSizeWritten += this.dataBlockEncoder.encode(cell, 
                       this.encodedDataSizeWritten += this.userDataStream.size() - posBeforeEncode;
                                                               ↓
                                         NoOpDataBlockEncoder#encode
                                       ——》 NoneEncoder#write 
                                                                       ▼                                                          
                                                          /* : 真正写出数据
                                                             *   数据终于写到 HFile 中了,完全符合期望
                                                               *   把一个 Cell 对象,分成10个细节组成,写到 HFile 文件
                                                                *   这个 HFile 文件的 输出流对象就是: out
                                                               */
                                                      int size = KeyValueUtil.oswrite(cell, out, false);
                                                                       ▼   
                                                                  /*   把一个 Cell 对象,分成10个细节组成,写到 HFile 文件
                                                                   *   这个 HFile 文件的 输出流对象就是: out
                                                                  */
                                      // 注释: 写出 rowkey 数据
                                       out.write(cell.getRowArray(), cell.getRowOffset(), rlen);
                                       //  注释: 写出 family length
                                       // Write cf - 1 byte of cf length followed by the family bytes
                                       out.write(flen);
                                       out.write(cell.getFamilyArray(), cell.getFamilyOffset(), flen);
                                       // write qualifier
                                       out.write(cell.getQualifierArray(), cell.getQualifierOffset(), qlen);
                                       // write timestamp
                                       StreamUtils.writeLong(out, cell.getTimestamp());
                                       // write the type
                                       out.write(cell.getTypeByte());
                                         // write value
                                         out.write(cell.getValueArray(), cell.getValueOffset(), vlen);
image.png
3.3 -split分析

reqeustSplit()
HBase2.x源码分析-Compact分析
reqeustCompact()

3.4 -Compact分析

reqeustCompact()

4 Get流程源码分析:

代码入口:
table.get(new Get())

大致流程:
1、首先客户端会请请求zookeepre拿到meta表的位置信息,这个meta表的region到底在那个regionserver1里面
2、发请求请求这个regionserver1扫描这个meta表的数据,确定我要插入的数据rowkey到底在那个用户表的region里面。并且还拿到这个region在那个regioinserver2的信息
3、发送请求,请求regionserver2
1、首先去blockcache ,进行查询 读缓存
2、先去布隆过滤器中进行判断
1、如果判断这个rowkey不存在,则不需要扫描 HFile
2、如果判断这个rowkey存在,则需要扫描HFile

上一篇下一篇

猜你喜欢

热点阅读