Fili 的数据度量(metrics)包括 Druid 度量值聚合和 Fili 度量值聚合,两者涵盖简单的数学运算,到复杂的聚合和后聚合操作。
度量分两类:
一阶(First order)度量 直接聚合 Druid 的度量值/数据。例如,Fili 的两个度量 page_views
和
additive_page_views
,分别计算和他们对应的两个 Druid 度量的 longSums
:druid_page_views
和
druid_additive_page_views
。
高阶(Higher order)度量 由其它度量值计算得来。例如,Fili 里面的 total_page_views
度量,是由 page_views
和
additive_page_views
之和得来的。
Fili 用 MetricDictionary
将度量名称映射到实际的度量对象(object),所以您需要定义两项内容:
度量名称
实际度量对象(object)
您可以用实现 ApiMetricName
interface 的方式来命名度量。该 interface 有两种功能:
提供一个任何地方都适用的统一名称,方便其他地方(例如BaseTableLoader
)使用。
检查该度量是否适用于某个时间精度(TimeGrain
)
例如,以下是一个 enum:
public enum ExampleApiMetricName implements ApiMetricName {
PAGE_VIEWS,
ADDITIVE_PAGE_VIEWS,
TOTAL_PAGE_VIEWS;
private final TimeGrain minimumGrain;
ExampleApiMetricName() {
this.minimumGrain = DefaultTimeGrain.DAY;
}
@Override
public String getApiName() {
return EnumUtils.enumJsonName(this);
}
@Override
public boolean isValidFor(TimeGrain grain) {
//Check if the passed in grain is coarser than the metric's grain.
return grain.compareTo(minimumGrain) >= 0;
}
...
}
该 enum 里面的所有度量适用于以“天”为单位或者时间范围更大的(例如“周”,“月”,“年”等等)时间精度。您也可以参见
Fili-wikipedia-example里面的WikiApiMetricName
,这是一个更为完整的例子。
另外,您还需要把相对应的 Druid 度量名称告诉 Fili。方法是实现 FieldName
interface,实现方法和
ApiMetricName
类似(区别是 Druid 度量不需要时间精度的最小范围)。
实现 FieldName
之后,您就能将 Druid 度量放入 BaseTableLoader
,这个 loader 会用这些 Druid 度量去配置
实际数据列表(physical tables)。具体的加载数据列表方法请参见 Binding Resources
。
WikiDruidMetricName
自带了一个例子。
下一步,您需要编写代码将命名好的度量在 Fili 启动的时候建造并加载到 MetricDictionary
。要完成这一步,您需要实现
MetricLoader
interface,这个 interface 包含了一个方法(method)叫做 loadMetricDictionary
。
举个例子,如果你想放入概述中提到的三个页面访问度量,那么 loadMetricDictionary
method 的实现方式类似于以下代码:
private MetricMaker longSumMaker;
private MetricMaker sumMaker;
@Override
public void loadMetricDictionary(MetricDictionary metricDictionary) {
buildMetricMakers(metricDictionary);
metricInstances = buildMetricInstances(metricDictionary);
addToMetricDictionary(metricDictionary, metricInstances);
}
MetricMaker
是用来搭建 LogicalMetric
的。LogicalMetric
实际上是一个特定的 Druid 查询
语句,加上一个 Mapper
用来做 Druid 访问后期处理。例如,longSumMaker
负责写一个
longSum
聚合, sumMaker
则是用相加运算写一个
arithmetic post aggregation。
在我们的上一个例子中,我们要用到 longSumMaker
和 sumMaker
:
private void buildMetricMakers(MetricDictionary metricDictionary) {
longSumMaker = new LongSumMaker(metricDictionary);
sumMaker = new ArithmeticMaker(metricDictionary, ArithmeticPostAggregationFunction.PLUS);
}
MetricInstance 使用 MetricMaker
来创建一个 metric。在上一个举例里,我们有三个度量:page_views
,
additive_page_views
,total_page_views
。这样一来,每一个度量都需要一个 MetricInstance
:
private List<MetricInstance> buildMetricInstances(MetricDictionary metricDictionary) {
return Arrays.<MetricInstance>asList(
new MetricInstance(PAGE_VIEWS, longSumMaker, DRUID_PAGE_VIEWS),
new MetricInstance(ADDITIVE_PAGE_VIEWS, longSumMaker, DRUID_ADDITIVE_PAGE_VIEWS),
new MetricInstance(TOTAL_PAGE_VIEWS, sumMaker, ADDITIVE_PAGE_VIEWS, PAGE_VIEWS)
);
}
注意,如果你的度量和其他基本度量存在依赖(相关)关系的话,就要在这里指明。page_views
和 additive_page_views
都是 Druid度量,
他们自然而然地依赖于对应的 druid 度量。同时,total_page_views
是通过 additive_page_views
和 page_views
计算得来的。
最后,度量要被加载到 MetricDictionary
。在我们的例子里,我们使用 addToMetricDictionary
method:
private void addToMetricDictionary(MetricDictionary metricDictionary, List<MetricInstance> metrics) {
metrics.stream().map(MetricInstance::make).forEach(metricDictionary::add);
}
Fili wikipedia example 带有一个基本的度量加载器叫做WikiMetricLoader
。
另一个不可或缺的是您需要部署之前定义好的 MetricLoader
。具体方法请详见 Binding Resources 。
大多数自定义度量都是在已经定义好的度量上进行基本的运算,建造器(makers)也是可以重复使用的。对于这种情况,定义一个新的度量只需要在
buildMetricInstances
方法(method)里(或者您自己对应的当中)中添加以下代码:
new MetricInstance(NEW_METRIC_NAME, metricMaker, DEPENDENT, METRIC, NAMES)
然后将 NEW_METRIC_NAME
加入到你的实现的 ApiMetricName。
Fili 自带了一些已经定义好的 makers,详见 Built-in Metrics 。
某些情况下 Fili 自带的建造器不足以满足您的需求,比如说数据计算不能用其他度量搭配数学运算完成,或者你需要一个 Druid 不预先设定的
数据类型。这个时候,您可以自定义自己的 maker。用 ArithmeticMaker
举个例子,这个 maker 是用来进行后期
聚合数学运算的。
第一步,您需要确定度量类型:是一阶还是高阶。
如果是一阶,那么您需要 extend RawAggregationMetricMaker
。某些情况下,您还需要在 Druid 集群中
添加一个 custom Druid aggregation。
如果是高阶,那么就需要 extend MetricMaker
。
ArithmeticMaker
是一个高阶度量,所以是 extends MetricMaker
。
自定义一个 Maker 主要是 override makeInner
method,这个 method 定义了 LogicalMetric
的建造方法:
@Override
protected LogicalMetric makeInner(String metricName, List<String> dependentMetrics) {
...
}
makeInner
执行如下步骤:
合并依赖查询语句(Merge Dependent Queries): 如果存在至少一个相关度量(dependent metric),那么将每个相关度量的
查询语句合并成一个查询语句。合并方法用 MetricMaker::getMergedQuery
method。
ArithmeticMaker
依赖于至少两个其他度量 ,所以相关度量需要合并:
TemplateDruidQuery mergedQuery = getMergedQuery(dependentMetrics);
TemplateDruidQuery
是一个特别方便的 Druid 查询语句模板,用来合并其它 TemplateDruidQuery
。
创建聚合器和后聚合器(Aggregators and Post-Aggregators): 搭建查询语句需要用到的聚合器和后聚合器。
ArithmeticMaker
中,对应的查询语句包含相关度量聚合:每个相关度量聚合的
field accessor 和一个 arithmetic post-aggregation。
Set<Aggregation> aggregations = mergedQuery.getAggregations();
//Creates a field-accessor post-aggregation for the aggregator in each dependentMetric.
List<PostAggregation> operands = dependentMetrics.stream()
.map(this::getNumericField)
.collect(Collectors.toList());
PostAggregation arithmeticPostAgg = new ArithmeticPostAggregation(metricName, function, operands);
创建内层查询(inner query): 如果度量查询需要内含,则需要建造内层查询。
ArithmeticMaker
使用 getMergedQuery
method 搭建的内层查询。详见
AggregationAverageMaker
,那里有一个搭建内层查询的举例。
TemplateDruidQuery innerQuery = mergedQuery.getInnerQuery();
创建 TemplateDruidQuery: 搭建一个 TemplateDruidQuery
。
ArithmeticMaker
搭建如下的 TemplateDruidQuery
:
TemplateDruidQuery templateDruidQuery = new TemplateDruidQuery(
aggregations,
Collections.singletonSet(arithmeticPostAgg),
innerQuery,
mergedQuery.getTimeGrain()
);
创建映射(Mapper): 搭建一个 映射。 如果度量不需要后期 Druid 聚合,那就用
NoOpResultSetMapper
。
ArithmeticMaker
使用 ColumnMapper
,这个映射在搭建的时候被注入(injected),成为 resultSetMapper
。
所以我们只需要建一个新的 resultSetMapper
,配上需要被建造的度量名称:
ColumnMapper mapper = resultSetMapper.withColumnName(metricName);
创建 LogicalMetric: 创建 LogicalMetric
.
return new LogicalMetric(query, mapper, metricName);
映射(Mappers)是 ResultSetMapper
的子类(subclass),用来对返回结果的每一行进行后期 Druid 处理。Fili
遍历每一个LogicalMetric
,将每个的映射组合成一个 function chain,用这样的方式来组建后 Druid 的处理链。Druid 返回数据结果后,
数据被送入 chain 的每一个单元,单元顺序由查询语句中的度量定义顺序决定。
如果要定义一个 Mapper
,您需要 override 两个 methods: map(Result result, Schema schema)
和 map(Schema schema)
。第一
个用来处理修改返回数据的某一行结果,第二个用来处理修改结果的 schema。
为了保证数据处理正确处理每一种数据类型,Result
class 提供了很多 methods ,用来提取某种数据类型的度量的列数据:
getMetricValue
getMetricValueAsNumber
getMetricValueAsString
getMetricValueAsBoolean
getMetricValueAsJsonNode
第一个将数据返回成一个 Object
。其他的将其转换成对应的合适数据类型(getMetricValueAsNumber
会转换成 BigDecimal
)。
NonNumericMetrics
包含了每一个非数值型度量的基本样例映射。
SketchRoundUpMapper
则是一个数值型度量映射。
RowNumMapper
是一个用于数据列相加的映射。
高级(非数值型)度量的配置方法和自定义数值型度量相同。Fili 支持所有原生 JSON 类型:
Numbers, Strings, 和 Booleans 自然而然地解析成对应的 Java 变量类型。JSON Objects 和 Lists 从 Druid 提取出来以后会被解析成
JsonNode
实例。Fili 默认将 Druid 返回的数据原封不动地发给用户。如果需要后 Druid 数据聚合处理,那么可以用一个
Mapper,将其加到映射队列(mapper workflow stage)。添加方法详见 Custom Metrics。
如果 Druid 返回的 JSON 数据是 null
,Fili 会将其解析成 Java null
。