好久没有写博客了。
最近开始读Apache Carbondata的源码,从presto connector开始读。因为一方面对scala和Spark源码不熟,另一方面是正在做的研究课题是以presto为基础的。
其实carbondata对spark的支持比较好,在源码中有一万多行的scala代码是专门给spark写的一些元数据缓存和查询优化。carbondata本身是华为在主导,也是中国本土企业贡献的第一个apache顶级项目。carbondata的presto connector主要是美团开发的,美团据说用presto比较多。
我用的环境是ubuntu16.04 + openjdk8 + intellij idea 2017.1
从https://github.com/apache/carbondata fork了一个repo到自己的github账号,把源码clone下来,用intellij直接打开就可以。carbondata是用maven管理的项目,intellij会自动识别、下载依赖、创建工程,只是下载依赖可能需要点时间。
carbondata的核心源码都在core目录下。hadoop目录下有hadoop inputformat和outputformat的实现。integration目录下是carbondata和其他查询引擎之间的集成模块,包括hive、spark、spark2、presto等。其中presto下就是presto connector的代码。
Presto Connector
presto connector是presto和其他系统连接的一套接口,参考https://prestodb.io/docs/current/connector.html
presto通过各种不同的Connector从不同的系统中获取元数据和数据。connector中比较主要的类是Plugin、ConnectorFactory、Connector。
编译好的connector实现是一个jar包,放在presto安装目录的plugin子目录下。presto通过扫描找到Plugin接口的实现,调用Plugin中的getConnectorFactories方法,获得ConnectorFactory的实例。ConnectorFactory中的create方法可以创建一个connector实例。
Carbondata的presto connector中这三个接口的实现分别为:CarbondataPlugin、CarbondataConnectorFactory和CarbondataConnector。
FileFactory
在CarbondataPlugin中的getClassLoader方法中,返回了一个FileFactory的classLoader。这个类会在之后被加载和初始化。FileFactory是core中的类,提供一些文件操作的静待方法,可以用来读写本地的或者是HDFS\Alluxio上的carbondata文件。
CarbondataConnector
CarbondataConnector非常简单。其中有几个主要的成员:
private final LifeCycleManager lifeCycleManager;
private final CarbondataMetadata metadata;
private final ConnectorSplitManager splitManager;
private final ConnectorRecordSetProvider recordSetProvider;
private final ClassLoader classLoader;
private final ConnectorPageSourceProvider pageSourceProvider;
其中CarbondataMetadata是carbondata中对presto connector的metadata接口的实现,和元数据相关的函数都在这里,例如getTableSchema,listTableColumns等。
CarbondataMetadata
CarbondataMetadata中一个很重要的成员是:
private CarbonTableReader carbonTableReader;
CarbondataMetadata中几乎所有的方法都用到了这个成员。而CarbonTableReader是对CarbonMetadata(core中用来读写carbondata元数据的单例,注意不是CarbondataMetadata)和FileFactory的封装。
另外,该类中的:
getTableMetadata方法用来获取一个table的元数据,这是一个private方法,被该类中的其他方法调用。其主要代码如下:
CarbonTable carbonTable = carbonTableReader.getTable(schemaTableName);
List<ColumnMetadata> columnsMetaList = new LinkedList<>();
List<CarbonColumn> carbonColumns = carbonTable.getCreateOrderColumn(schemaTableName.getTableName());
for (CarbonColumn col : carbonColumns) {
//show columns command will return these data
Type columnType = CarbondataType2SpiMapper(col.getColumnSchema());
ColumnMetadata columnMeta = new ColumnMetadata(col.getColumnSchema().getColumnName(), columnType);
columnsMetaList.add(columnMeta);
}
//carbondata connector's table metadata
return new ConnectorTableMetadata(schemaTableName, columnsMetaList);
其中carbonTable是CarbonTable的实例,该类是core.metadata.schema.table下的类,又一个关键的类浮出水面。。。createOrderColumn方法获取的是一个表在建表时的column顺序。
getTableHandle可以获取一个table的CarbondataTableHandle实例,这个实例可以在getColumnHandles中作为参数。
getColumnHandles方法返回的是column name和ColumnHandle的Map。实际上这个返回的Map中放的是CarbondataColumnHandle(ColumnHandle接口的实现)的实例。该类中有与column相关元数据并提供了与column元数据操作相关的方法,其中getColumnMetadata方法可用来获取column的元数据对象,其实就是把CarbondataColumnHandle中的成员封装成ColumnMetadata。
CarbonTableReader
这个类算是carbondata presto connector中最关键的一个类了。这个类中不但如上所述封装了FileFactory和CarbonMetadata的一些操作,还有很多和carbondata core打交道的方法。可以从这个类开始阅读carbondata core的源码。
这个类中涉及到的比较关键的carbondata的类和接口包括:
CacheClient:这个名字起的有点让人懵逼,它主要就是有这么个成员:
CacheAccessClient<TableSegmentUniqueIdentifier, SegmentTaskIndexWrapper>
segmentAccessClient;
这个成员就是维护了Segment到SegmentTaskIndexWrapper的映射。CacheAccessClient中封装了一个Cache成员,这个Cache就是个K-V映射,之所以叫Cache是因为它有淘汰机制,可以把Cache在内存中的东西持久化到磁盘。那么主要问题是SegmentTaskIndexWrapper是啥。这个名字同样起的不知所云,它里面主要就是有这么个成员:
private Map<SegmentTaskIndexStore.TaskBucketHolder, AbstractIndex> taskIdToTableSegmentMap;
AbstractIndex下面会讲,TaskBucketHolder同样让人懵逼,目前还不清楚Bucket和Task是个啥概念,和Spark、Hive中的Task和Bucket有啥关系,它里面就俩成员以及equals和hashCode方法:
public String taskNo;
public String bucketNumber;
大概瞄了瞄注释,感觉可能是一个segment在创建的时候是由多个task完成的,每个task创建了一些数据块,这些数据块是一个bucket。bucket可能是介于segment和block之间的一个东西,文档里没有看到这个。
如果以上猜测正确,那么CacheClient就是一个从segment到bucket,再由bucket到这个bucket的b tree 根节点的一个两层映射。后面在仔细读代码验证猜测。
AbstractIndex: 对DataRefNode进行了一次封装,加了写乱七八糟的属性和方法。
DataRefNode: CarbonData中b-tree索引的节点,一个HDFS数据块对应一个DataRefNode(待确定),CarbonData中一个segment下的数据块构成一棵b-tree(也可能是多棵?每个bucket一课b tree?),同一个segment中的数据是有序的(文档里说的,但是如果有bucket的存在,不知道要怎么保证segment内的数据全局有序),可以根据查询条件进行数据块的过滤。一个b tree的根节点也是一个DataRefNode。
CarbonTable,这个名字很清楚,就是一个carbon table。
CarbonFile,这个名字也很清楚,就是一个carbon file,注意这个file可能是一个目录,它有listFiles方法可以列出子目录和文件。后缀面是mdt的carbon file貌似很神奇,因为源码里有这样一段:
for (CarbonFile cf : carbonFileList.listFiles()) {
if (!cf.getName().endsWith(".mdt")) {
for (CarbonFile table : cf.listFiles()) {
tableList.add(new SchemaTableName(cf.getName(), table.getName()));
}
}
}
改天跑一下carbondata研究下mdt是啥。从源码看,一个carbondata的store path下,有一层目录是存各个 schema的,每个schema下有一层目录是存各个table的。
其他类
carbondata presto connector中的其他类就是这些了:
CarbondataModule:这个没啥,就是加载一些其他的类。
CarbondataSplit:主要的成员是:
private final CarbonLocalInputSplit localInputSplit;
记录了一个split(block)在HDFS(也可能是本地或者Alluxio)上的存储位置、所属的segment id等等的元信息。
CarbondataSplitManager:具有一个CarbonTableReader类型的成员,通过reader、根据查询的过滤条件获得相关的splits信息。
CarbondataRecordCursor:这是一个游标,负责读取split,split需要在构造时传入。其中有两个成员需要注意:
private CarbonIterator<Object[]> rowCursor;
private CarbonReadSupport<Object[]> readSupport;
CarbonIterator就是个普通的迭代器抽象类,加了个只会抛异常的默认remove方法。
CarbonReadSupport负责把读上来的数据组装成行。
CarbondataRecordSet:产生ConbondataRecordCursor的地方。
CarbondataRecordSetProvider:产生CarbondataRecordSet的地方,含有一个CarbonTableReader类型的成员。功能和CarbondataSplitManager类似,但是这里产生的是CarbondataRecordSet.