2021SC@SDUSC
研究方向
我负责的是将查询块QB转换成逻辑查询计划(OP Tree)
如图所示,这是从Apache官网上下载的3.1.2版本的Hive源码大致结构。
下面我们来大致了解以下各种组件的作用。
hive三个最重要的组件:
1.serde: 这个组件是 hive内置的一些序列化解析类,此组件允许用户自己开发自定义序列化、反序列化文件解析器
2.metaStore: hive的元数据服务器,用来存放数据仓库中所有表和分区的信息,hive元数据建表sql、升级sql脚本都存放在此目录下
3.ql: 此组件用于解析sql生成执行计划(hive核心包,熟读此包,可了解hive执行流程核心)
也就是说,我的研究方向因该着重从ql组件中下手。
其他组件
cli: hive命令的入口,用于处理命令行提交作业
service: 此组件所有对外api接口的服务端(通过thrift实现),可用于其他客户端与hive交互,比如jdbc。
common: hive基础代码库,hive各个组件信息的传递也是有此包HiveConf类来管理。
ant: 此组件包含一些ant任务需要的基础代码
bin: 此组件包涵hive里的所有脚本,包括hivecli的脚本
beeline: HiveServer2提供的一个新的命令行工具Beeline
hcatalog: 是apache开源的对于表和底层数据管理统一服务平台,HCatalog底层依赖于Hive metastore
findbugs: Findbugs是一个在java程序中查找bug的程序,它查找bug模式的实例,也就是可能出错的代码实例,注意Findbugs是检查java字节码,也就是*.class文件。
hwi: hive web页面的接口
shims: shims相关类是用来兼容不同的hadoop和hive版本
llap: 是基于tez的一种近实时查询方案
hive辅助组件
conf: 此目录包涵hive配置文件hive-default.xml、hive-site.xml
data: hive测试所用数据
lib: hive运行期间依赖的jar
更多的组件详细介绍可以阅读Apache Hive官网上的文档。
目标代码
功夫不负有心人,经过大约20分钟的寻找,我最终在代码路径ql/src/java/org/apache/hadoop/hive/ql中找到了我的目标分析代码集合——plan文件夹。顾名思义,该文件夹的作用就是将语句转换为计划,也就是我负责研究的方向。在plan文件夹中,第一个文件夹为mapper文件夹,我们从当中的文件进行一个个的解析。
我们首先附上整个java文件的源码。
package org.apache.hadoop.hive.ql.plan.mapper;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import org.apache.hadoop.hive.ql.exec.Operator;
import org.apache.hadoop.hive.ql.optimizer.signature.OpTreeSignature;
import org.apache.hadoop.hive.ql.stats.OperatorStats;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
public class CachingStatsSource implements StatsSource {
private final Cache cache;
public CachingStatsSource(int cacheSize) {
cache = CacheBuilder.newBuilder().maximumSize(cacheSize).build();
}
public void put(OpTreeSignature sig, OperatorStats opStat) {
cache.put(sig, opStat);
}
@Override
public Optional lookup(OpTreeSignature treeSig) {
return Optional.ofNullable(cache.getIfPresent(treeSig));
}
@Override
public boolean canProvideStatsFor(Class> clazz) {
if (cache.size() > 0 && Operator.class.isAssignableFrom(clazz)) {
return true;
}
return false;
}
@Override
public void putAll(Map map) {
for (Entry entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
}
开始解析。
方法CachingStatsSource我们首先来看一下名为CachingStatsSource的方法。
public CachingStatsSource(int cacheSize) {
cache = CacheBuilder.newBuilder().maximumSize(cacheSize).build();
}
这里面包含了一句语法:CacheBuilder.newBuilder().maximunSize(cacheSize).build();
它的作用是什么?经过查阅,我得知cache是用来比如对于同样一次输入不止一次的取值操作时,所使用的缓存。那么这个参数cacheSize就非常显而易见了,它表示的是缓存的容量,当超出maximumSize时就会被回收。相关的,这个maximumSize()方法就是设置最大缓存容量的方法了,也就是说,这整个方法的作用就是设置一个新的缓存块,由传入的参数cacheSize决定该缓存块的大小,之后的所有操作都是基于该缓存块基础上的。
public void put(OpTreeSignature sig, OperatorStats opStat) {
cache.put(sig, opStat);
}
cache.put();
这是一个什么方法?我们继续查阅相关资料。经过查阅,我们得到如下信息:
那么我们知道了,这个方法就是一个设置键值对的作用,那么整个方法的作用也是如此。
@Override public Optionallookup(OpTreeSignature treeSig) { return Optional.ofNullable(cache.getIfPresent(treeSig)); }
那么,这个Optional.ofNullable()是什么方法呢?
我们查阅资料,发现了该源码:
public static Optional ofNullable(T value) {
return value == null ? empty() : of(value);
}
public static Optional empty() {
@SuppressWarnings("unchecked")
Optional t = (Optional) EMPTY;
return t;
}
private static final Optional> EMPTY = new Optional<>();
private Optional() {
this.value = null;
}
public T orElse(T other) {
return value != null ? value : other;
}
这个方法的意思如下:
1.首先执行ofNullable()方法,如果T对象为空,执行empty()方法;不为空,执行of(value)方法;
2.empty()方法,初始化一个空对象Optional(空对象和null不是一回事哈);
3.of(value)方法,将泛型对象T用于Optional构造方法的参数上,返回一个有值的对象
4.经过上面两步,从而保证了Optional不为null,避免了空指针;
那么,在方法里面的另外一个方法getIfPresent(treeSig)是什么呢?
又经过查阅资料,我知道了他的用法。
getIfPresent(key):从现有的缓存中获取,如果缓存中有key,则返回value,如果没有则返回null。那么整个方法的意思就明了了:先从缓存中看有没有目标"treeSig"对应的值,如果有就返回没有就返回Null,然后再调用Optional.ofNullable()方法返回一个不为空的指针,防止抛出空指针异常的错误导致整个进程奔溃,或许加上try-catch语句可以解决。
@Override
public boolean canProvideStatsFor(Class> clazz) {
if (cache.size() > 0 && Operator.class.isAssignableFrom(clazz)) {
return true;
}
return false;
}
其中,对于cache.size()方法,我们很容易得知该方法时返回cache也就是我们该开始预设的缓冲块的大小。而后面的这个Operator.class.isAssignableFrom()方法就需要查阅资料。查阅资料得知:当前Class对象如果是参数Class对象的父类,父接口,或者是相同,都会返回true。也就是说,如果当传入的Class对象符合要求,才会返回true,也就是相当于提供了一层保险机制,相当于变相的说明了这个方法是部分private的,十分聪明的写法。那么整个方法的意思就清楚了:只有当传入的Class对象为当前Class对象的弗雷,父接口或者相同时,和设置的代码块大小大于0的情况下,才能返回true,否则全部返回false。
putAll方法@Override public void putAll(Mapmap) { for (Entry entry : map.entrySet()) { put(entry.getKey(), entry.getValue()); } }
这里后面的put方法我们在前面已经解释说明过了,这里就不再赘述。我们先来看看整个map.entrySet()方法是一个什么方法。经过查阅资料我们可以得知:该方法返回的是此映射中包含的映射的 Set 视图,我们用一个实例来简单说明它的作用:
import java.util.HashMap;
class Main {
public static void main(String[] args) {
// 创建一个 HashMap
HashMap sites = new HashMap<>();
// 往 HashMap 添加一些元素
sites.put(1, "Google");
sites.put(2, "Runoob");
sites.put(3, "Taobao");
System.out.println("sites HashMap: " + sites);
// 返回映射关系中 set view
System.out.println("Set View: " + sites.entrySet());
}
}
该实例的运行结果为:
sites HashMap: {1=Google, 2=Runoob, 3=Taobao}
Set View: [1=Google, 2=Runoob, 3=Taobao]
简单来说,就是把所有的映射关系都呈现出来。
而看起来十分奇怪的语句”Entry
我们使用实例来说明:
for(Map.Entry me : m.entrySet()) {
t.append(me.getKey() + ": " + me.getValue() + "/n");
}
这里的t是一个textarea,m是一个HashMap。
这种遍历Map的方法可以让我们在从Map中取得关键字之后,我们不用每次重复返回到Map中取得相对的值。
如下面是以前的写法:每次都要再从m中读出s所对应的值。
Setkeys = m.keySet( ); if(keys != null) for(String s : keys) t.append(s + ": " + m.get(s) + "/n");
那么整个方法的作用我们也清楚了:遍历整个map里面的键值对,把键值对放到entry里面。
小结本周初次开始了对Hive源码的研究,学习到了许多新奇的知识,也感叹到代码设计的巧妙之处。整个类的方法都是围绕cache进行的,对cache的操作非常集中,我们后续转换为查询任务时也会使用到cache,这是非常基础的。



