首先,让我们从HTable.put方法开始。由于这一节有很多方法只是简单的参数传递,我就简单略过,但是,关键的方法我还是会截图讲解,所以希望大家尽可能对照源码进行流程分析。另外,在这一节,我单单介绍put操作在客户端的流程,毕竟,这个内容已经很多了。至于具体服务端的流程,我会在后面的章节中介绍到,欢迎大家到时候阅读。
由于这一节的方法还是比较复杂的,我特地画了一张思维导图,大家可以先通过思维导图来对本节的内容有一个大概的了解,置于具体的流程,我在下面将对照源码的贴图一一为大家讲解(在这里声明一点,我在这一节只介绍单个put操作的流程,至于put批处理,大家有兴趣可以自己研究一下)。
首先,让我们来到HTable.put方法,如下图所示:
这里我先讲一下这一节的最后调用流程,也同时让大家明确一下在本节我着重要讲解的流程是哪块。在上图中我已经表示出来了,后面方法的调用最后调用到了上面新创建的ClientServiceCallable中覆写的rpcCall方法,也就是调用到了ClientServiceCallable.doMutate。关于这个方法中具体与服务端的交互流程在本节我就略过,但是,在后面的内容中,我会谈到类似的情况,如果大家感兴趣的话,可以继续后面的内容。
接下来让我们回到本节的重点。首先是RpcRetryingCallerFactory.newCaller方法的调用,该方法使用RpcRetryingCallerFactory的成员参数创建了RpcRetryingCaller,用于后面对于RetryingCallable的调用(该方法在后面也会多次调用,在后面我就不贴图了)。
接下来让我们来到RpcRetryingCallerImpl.callWithRetries。这个方法是本节中最为重要的方法,在后面也会多次用到。方法虽然比较长,但大多是异常的情况的解决,在本节中我们就单单介绍callable.prepare与callable.call两个方法。至于interceptor.intercept,由于在构造RpcRetryingCallerFactory时默认的interceptor类型为RetryingCallerInterceptorFactory.NO_OP_INTERCEPTOR,在本节并不会有其它影响,所以我们暂时不需要关注。
上面的方法调用的callable具体类型为覆写了rpcCall方法的ClientServiceCallable,下面让我们来到ClientServiceCallable类的内部。ClientServiceCallable继承自RegionServerCallable,因此,上面方法实际调用的是RegionServerCallable.prepare与RegionServerCallable.call。
首先让我们来到RegionServerCallable.prepare方法。这里比较重要的方法我已经框选出来了。需要大家特别留意的是最后的setStubByServiceName,一是因为他比较重要,二是我在后面的内容才会介绍,大家到时候可能忘记了,所以在这里特别提醒一下大家。
1.getCachedLocation,该方法简介调用到了metaCache.getCachedLocation,但此时,由于我们是第一次调用该表的信息,并没有放到缓存中,因此,这里返回的locations = null。
2.然后我们来到RegionInfo.createRegionName,需要注意的是,其入参row就是我们put操作创建的rowKey,也就是我们常说的行键。另外,在metaStartKey中传入的id为HConstants.NINES(NINES = "99999999999999"),而在metaStopKey中传入的id为空字符串。
3.接着构造了Scan。其中withStartRow与withStopRow中的inclusive入参都为true。将reversed设置为true,并且将catalog family设置为"info"(CATALOG_FAMILY_STR = "info")。大家可能注意到了,这里的info列族在我们的表中并不一定存在。到了这里,大家可能就猜到我在前面埋的伏笔了。没错,这里构建的Scan是为了后面的查询后面的META_TABLE_NAME做准备。
4.紧接着来到fro循环中,这里连着调用了两次getCachedLocation,后面的那次调用加了锁,类似我们在单例设计模式中流程,加锁以确保对象不会重复。
5.然后构建了ReversedClientScanner对象。(鉴于之前经验,贴太多图容易扰乱大家的思维,我在这里尽量用文字来介绍)。ReversedClientScanner是ClientScanner的子类,另外,大家需要注意的是,在构造ReversedClientScanner时传入的tableName为TableName.META_TABLE_NAME。在ReversedClientScanner的构造过程中,虽然有一些需要注意的地方,不过,我还是放在后面来描述,以便大家能够更好的理解整个流程。
6.接下来调用了ReversedClientScanner.next,大家千万不要小看这个方法,这个方法里面的一系列调用时非常复杂的,也是本节的另外一个重点,我将在后面详细介绍。
7.然后调用了MetaTableAccessor.getRegionLocations,其入参为ReversedClientScanner.next的返回值。这个方法的详细流程也比较重要,同样,我放到后面为大家讲解。
8.最后调用了cacheLocation,也就是将当前tableName放到缓存中。
上面,我将ConnectionImplementation.locateRegionInMeta方法中调用的各个流程都简单介绍了一下,下面,我就选择其中比较重要的方法来详细描述。
首先让我们来到ReversedClientScanner.next。这个方法调用了ClientScanner.nextWithSyncCache,如下图所示:
上图框选的两个方法都比较重要,让我们首先介绍比较复杂的loadCache,如下图所示。
看到这个方法大家可能比较慌,没有关系,我会在这里为大家一一介绍。
1.首先调用了moveToNextRegion。该方法首先调用closeScanner(其间首先调用了成员变量callable.setClose方法,然后调用了ClientScanner.call方法,这个方法我在后面也会提到,最后将当前成员变量callable中的值置为null,简而言之,将成员变量callable.setClose置为null)。
然后构造了ScannerCallableWithReplicas并赋给成员变量callable。在构造ScannerCallableWithReplicas时需要注意的是其中创建了ReversedScannerCallable。也就是说ScannerCallableWithReplicas的成员变量currentScannerCallable为ReversedScannerCallable。顺便提一下,ScannerCallableWithReplicas的成员变量scan为我们在上面构造的scan。
2.接着调用了ClientScanner.call方法。这里的调用流程比较繁琐。为了更清楚的解释清楚loadCache方法,我们先跳过这里,假设其中已经有了返回值。
3.然后调用了scanResultCache.addAndGet。简单提示一下我们这里的scanResultCache类型为CompleteScanResultCache。
4.然后将结果集中的内容遍历放到成员变量cache中。这里我们可以回过头来看看上面的图。上面图中我框选了cache.poll方法。也就是说cache.poll将在loadCache方法中放入的结果集取出来。
上面我提到过很多次ClientScanner.call方法,但是都没有详细描述,下面我就特意来讲解该方法。其实这个方法很简单,只是调用了方法RpcRetryingCaller.callWithoutRetries。这里的caller是在ReversedClientScanner方法构造时创建的(上面只是提到说构造ReversedClientScanner有需要注意的地方,也就是这里,其截图我在上面也已经贴出来了)。
接下来让我们来到RpcRetryingCallerImpl.callWithoutRetries。这里的入参callable我在上面的方法loadCache已经介绍过了。其类型为ScannerCallableWithReplicas。由于ScannerCallableWithReplicas.prepare方法为空实现,我在这里就不贴图了,接下来将重点放在ScannerCallableWithReplicas.call。
让我们来到ScannerCallableWithReplicas.call,如下图所示。
1.在ClientScanner.closeScanner方法调用时,会走上面的if判断。由于currentScannerCallable.closed的值为true。
2.由于默认的成员变量regionReplication,因此会调用RpcRetryingCallerWithReadReplicas.getRegionLocations。这个方法的调用与我们今天的主要流程并没有什么太多的联系,因此,在这里简单略过。该方法我可能会放在后面的章节中讲到。
3.构造了ResultBoundedCompletionService。这个方法比较重要,在后面的流程中我会反复讲到。
4.调用了addCallsForCurrentReplica,将成员变量currentScannerCallable封装到ScannerCallableWithReplicas.RetryingRPC中,并交由ResultBoundedCompletionService提交。
5.接着调用cs.poll,获取其提交的任务的返回值。
后面我将详细讲解。
首先来到ScannerCallableWithReplicas.addCallsForCurrentReplica方法。容易看到,将成员变量currentScannerCallable封装到RetryingRPC中。然后调用了ResultBoundedCompletionService.submit。这里着重提醒一下大家,这里的currentScannerCallable类型为ReversedScannerCallable。
接着让我们来到ResultBoundedCompletionService.submit,如下图所示。
这里将传入的RetryingRPC封装到QueueingFuture,然后调用了executor.execute。由于QueueingFuture继承自java.util.concurrent.RunnableFuture,也就是在调用executor.execute时,QueueingFuture.run方法会执行。
接下来让我们来到QueueingFuture。在下图中,我框选出了其中比较重要的方法。
首先这里调用了RpcRetryingCallerImpl.callWithRetries方法(由于这个方法我在上面已经提到过了,因此在这里就不贴图了)。重要的是其中的入参future类型为ScannerCallableWithReplicas.RetryingRPC。另外后面将当前QueueingFuture添加到ResultBoundedCompletionService成员变量completedTasks中。
让我们来到ScannerCallableWithReplicas.RetryingRPC.prepare方法。如下图所示。大家可能对这里的成员变量callable比较模糊了,大家可以往上翻到方法addCallsForCurrentReplica的描述,没错这里的callable就是ScannerCallableWithReplicas的成员变量currentScannerCallable。而ScannerCallableWithReplicas.currentScannerCallable正是在构造ScannerCallableWithReplicas时传入的ReversedScannerCallable。
接下来让我们来到ReversedScannerCallable.prepare。由于这是第一次调用prepare方法,因此其成员变量instantiated为false。这里简单提一下,这里的getRow方法获取的是我们调用put时的行键,也就是我们对于目标表的rowKey。由于这里的tableName为TableName.META_TABLE_NAME,其rowKey在后面并没有用到。
然后调用了ReversedScannerCallable.setStub方法。为成员变量stub的赋值。其值为getConnection().getClient(getLocation().getServerName())调用的返回值。
让我们来到ConnectionImplementation.getClient方法。看过我博文《HBase之HRegionServer启动(含与HMaster交互)》的同学看到这里可能就比较熟悉。 没错,这里正是通过ClientProtos.ClientService.newBlockingStub构造了协议ClientProtos.ClientService的客户端stub。关于与服务端交互的流程,我在《HBase之HRegionServer启动(含与HMaster交互)》中已经具体介绍了,大家感兴趣的可以去看一下,我们这里来描述比较重要一个点。
就是computeIfAbsentEx的最后一个入参IOExceptionSupplier。他类似于java中的Supplier(类似的方法调用我在后面讲解方法MetaTableAccessor.getRegionLocations)。
在第一次调用时,我们的stubs中并没有到该serverName的客户端stub,因此调用了入参supplier的get方法。也就是我们上面看到的lambda表达式方法内容被调用。
到这里,ReversedScannerCallable.prepare方法就调用完成了。这个还有一个需要注意的点就是ReversedScannerCallable.prepare方法的最后将其成员变量instantiated置为true。
接下来让我们来到ScannerCallableWithReplicas.RetryingRPC.call方法(这里的callable类型为ReversedScannerCallable)。
这里再次调用了RpcRetryingCallerImpl.callWithRetries,由于ReversedScannerCallable.prepare方法已经调用,并且其成员变量instantiated被置为true,所以上面描述的内容并不会再次调用(这里框选的内容作为后面的伏笔)。
也就是说,接下来应该调用的是ReversedScannerCallable.call。由于其并没有call方法,因此,会一直调用到其父类RegionServerCallable.call。如下图所示。这里的rpcController类型为HBaseRpcControllerImpl。接下来调用了rpcCall方法。由于ReversedScannerCallable中并没有rpcCall方法的实现,而在其父类ScannerCallable有实现rpcCall。
接下来,让我们来到ScannerCallable.rpcCall。由于默认的成员变量scannerId为-1,因此,会调用openScanner。由于openScanner方法仅仅是通过Client协议发送到服务端。关于rpc流程我在博客《hbase之RPC调用流程简介》中已经介绍过了,感兴趣的同学可以去看一下,那篇博文讲的比较浅显,我会在春节期间将那篇内容更新,大家可以关注我,到时候有更新大家也就收到通知了。
然后调用了ResponseConverter.getResults,将服务端的返回的ScanResponse转换为Result。
让我们来到ResponseConverter.getResults。这个方法的主要作用是将CellScanner中Cell的或ScanResponse中的PB类型的results转换为java类型的Result。至于该方法的详细描述我要放到后面开设的第二章节,也就是HBase中客户端协议各个操作中来讲解,因为这里流程是比较复杂的,要结合上服务端的流程才能讲述清楚。所以这里暂时略过。
到这里,一个完整的RpcRetryingCallerImpl.callWithRetries方法调用流程可以说是完结了。然后在ResultBoundedCompletionService.QueueingFuture.run方法的后面,将当前QueueingFuture添加到ResultBoundedCompletionService成员变量completedTasks中(虽然我在上面提到过,但这里还是重述一下,以便我们后面的理解)。
而在我们本节描述的整体流程中,ScannerCallableWithReplicas.addCallsForCurrentReplica方法调用完结。
接下来让我们来到ResultBoundedCompletionService.poll,由于其间接调用了ResultBoundedCompletionService.pollForSpecificCompletedTask,如下图所由于在QueueingFuture.run方法的最后,将自身添加到了completedTasks。因此,上面的方法获取的正是刚刚添加的QueueingFuture。接着调用了ResultBoundedCompletionService.QueueingFuture.get方法。如下图所示。也就是说,这里将result返回。这里result的类型我们需要注意一下,以便后面在类型上面的理解。由于这里QueueingFuture成员变量future的实际类型为ScannerCallableWithReplicas.RetryingRPC。大家可以往上翻到ScannerCallableWithReplicas.RetryingRPC.call,就可以发现,这里的result是从ResponseConverter.getResults获得的Result数组与成员变量callable封装后的Pair对象。接着,将r.getFirst(),也就是实际获得的结果返回。
到这里,大家可能以为要结束了,很遗憾,这里只是到了ClientScanner.call方法的返回。
由于接下来的是两个单独的流程了。一个是MetaTableAccessor.getRegionLocations,另外一个是ConnectionImplementation.cacheLocation。至于这两个流程之外的后续流程比较简单,我就不一一叙述了,相信大家跟着源码与我在前面的提示很容易就可以弄清楚了。而前面提到的那两个单独的流程我将放在后面的一节《HBase之Table.put客户端流程(续)》中介绍。到时候欢迎大家阅读。
大家可以关注我的博客,或者发送邮件到我的邮箱15935152719@163.com来沟通交流大数据相关的知识。感谢大家的阅读,如果觉得不错,希望您可以点击下面的推荐。
- HBase之Table.put客户端流程(续)
上篇博文中已经谈到,有两个流程没有讲到.一个是MetaTableAccessor.getRegionLocations,另外一个是ConnectionImplementation.cacheLocat ...
- HBase二级索引、读写流程
HBase二级索引.读写流程 一.HBse二级索引方案 1.1 基于Coprocessor方案 1.2 Phoenix二级索引特点 1.3 Phoenix 二级索引方案 二.HBase读写流程 2.1 ...
- paip.提升效率--数据绑定到table原理和流程Angular js jquery实现
paip.提升效率--数据绑定到table原理和流程Angular js jquery实现 html #--keyword 1 #---原理和流程 1 #----jq实现的代码 1 #-----An ...
- hbase删除table时,显示table不存在
hbase删除table时,显示table不存在,但是创建table时,显示table存在. 解决方案: 清空zookeeper数据.(重新安装zookeeper)
- Netty 源码学习——客户端流程分析
Netty 源码学习--客户端流程分析 友情提醒: 需要观看者具备一些 NIO 的知识,否则看起来有的地方可能会不明白. 使用版本依赖 <dependency> <groupId&g ...
- HBase介绍(3)---框架结构及流程
HBASE依托于Hadoop的HDFS作为存储基础,因此结构也很类似于Hadoop的Master-Slave模式,Hbase Master Server 负责管理所有的HRegion Server,但 ...
- 一条数据的HBase之旅,简明HBase入门教程-Write全流程
如果将上篇内容理解为一个冗长的"铺垫",那么,从本文开始,剧情才开始正式展开.本文基于提供的样例数据,介绍了写数据的接口,RowKey定义,数据在客户端的组装,数据路由,打包分发, ...
- Hbase 基础 - shell 与 客户端
版权说明: 本文章版权归本人及博客园共同所有,转载请标明原文出处(http://www.cnblogs.com/mikevictor07/),以下内容为个人理解,仅供参考. 一.简介 Hbase是在 ...
- HBase读写数据的详细流程及ROOT表/META表介绍
一.HBase读数据流程 1.Client访问Zookeeper,从ZK获取-ROOT-表的位置信息,通过访问-ROOT-表获取.META.表的位置,然后确定数据所在的HRegion位置: 2.Cli ...
随机推荐
- Moq 在.net Core 单元测试中的使用
Moq,主要用来伪造接口的实现类,实现方法,属性 moq The most popular and friendly mocking framework for .NET What? Moq (pro ...
- tensorflow用dropout解决over fitting-【老鱼学tensorflow】
在机器学习中可能会存在过拟合的问题,表现为在训练集上表现很好,但在测试集中表现不如训练集中的那么好. 图中黑色曲线是正常模型,绿色曲线就是overfitting模型.尽管绿色曲线很精确的区分了所有的训 ...
- 大数据学习之Linux基础01
大数据学习之Linux基础 01:Linux简介 linux是一种自由和开放源代码的类UNIX操作系统.该操作系统的内核由林纳斯·托瓦兹 在1991年10月5日首次发布.,在加上用户空间的应用程序之后 ...
- UOJ#424. 【集训队作业2018】count 多项式,FFT,矩阵
原文链接https://www.cnblogs.com/zhouzhendong/p/UOJ424.html 题解 主席太神仙了! 首先我们把题意转化成:对所有挺好序列建 笛卡尔树,有多少笛卡尔树互不 ...
- .Karma+Jasmine+karma-coverage
单元测试(模块测试)是开发者编写的一小段代码,用于检验被测代码的一个很小的.很明确的功能是否正确.通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为. Karma是一个基于N ...
- Linux-day2-pdf课件
1.Linux文件属性和权限 2.Linux重定向 3.Linux文件查找 4.Linux压缩打包 5.课堂作业 权限相关作业: 题目创建用户carol,ivy,jenny,kevin,alice创建 ...
- git 的常用命令(未完待补充)
一.初始化 git git init 这样会默认创建 master 分支 二.查看当前状态 git status 查看 git 的默认状态 三.创建一个文件,并把它添加到 git 仓库,使用 git ...
- 用canal同步binlog到kafka,spark streaming消费kafka topic乱码问题
canal 1.1.1版本之后, 默认支持将canal server接收到的binlog数据直接投递到MQ, 目前默认支持的MQ系统有kafka和RocketMQ. 在投递的时候我们使用的是非压平的消 ...
- 转载 CSDN 谈谈我对证券公司一些部门的理解(前、中、后台)
谈谈我对证券公司一些部门的理解(前.中.后台) 2018年02月08日 15:11:07 unirong 阅读数:2165 文中对各大部门的分析都是从作者多年经历总结出来的有感之谈,尤其是前台的6 ...
- BZOJ 4804
辣鸡题目毁我青春 易推 \[\sum_{i=1}^n\sum_{i=1}^m \varphi(gcd(i,j))=\sum_{T}\frac{n}{T}\dfrac{m}{T}\sum_{d|T} \ ...