我们在产品中有很多这样的指标需要计算:
- 每天登陆的用户数(设备数)
- 次日留存数(昨天的激活设备而且今天该设备还活跃),七日留存数,自定义日子的留存数。
诸如此类根据设备数来算的指标,如何实现?
我们之前是将设备ID转换为offset,再将offset转化为bitmap,并将其根据不同维度存在MySQL中。由于每天的写入和查询量太大,MySQL经受了严峻的考验。分库又面临着数据倾斜等各种问题。
我们后来准备将其迁入Druid中,但Druid天生并不支持这种运算。我们能不能增加一个bitmap的自定义扩展,使Druid也能够进行bitmap相关的聚合和查询运算呢?答案是可以的。
为了实现这个功能扩展,我们有两部分需要实现:
- 实现bitmap作为Druid的metric,存到Druid中。
- 定义规则,可以根据json查询bitmap计算以后的值,并返回客户端。
- 定义BitmapDruidModule implements DruidModule
- 在src/main/resources目录下创建目录和文件:META-INF/services/io.druid.initialization.DruidModule
- 在以上文件内写入Module的定义类:io.druid.query.aggregation.bitmap.BitmapDruidModule
- 实现BitmapCubeComplexMetricSerde extends ComplexMetricSerde。
其中:
1> TypeName: bitmapCube
2> 实现其他相关方法。
ComplexMetricExtractor getExtractor()
ObjectStrategy getObjectStrategy()
deserializeColumn(ByteBuffer byteBuffer, ColumnBuilder columnBuilder)
- 将该Serde注册到ComplexMetrics中:
- 实现BitmapCubeAggregator implements Aggregator
- 实现 BitmapCubeAggregatorFactory extends AggregatorFactory
实现combine等相关方法。
- 在BitmapCubeDruidModule中的getJacksonModules() 方法里注册BitmapCubeAggregatorFactory类
public List<? extends Module> getJacksonModules() {
return ImmutableList.of(
new SimpleModule().registerSubtypes(
new NamedType(io.druid.query.aggregation.bitmapcube.BitmapCubeCubeAggregatorFactory.class, BITMAP_CUBE),
......
}
查询条件变为一个Queries的list,完整查询示例如下:
{
"dataSource": "login_user",
"queries": {
"459068#SRC": {
"filter": {
"fields": [
{
"dimension": "dim1",
"value": "BBDB6E271AFE4E5587892165205A20B1",
"type": "selector"
},
{
"dimension": "dim2",
"value": "459068",
"type": "selector"
}
],
"type": "and"
},
"intervals": [
"2018-03-06T00:00:00/2018-03-07T00:00:00"
],
"granularity": "all",
"aggregations": [
{
"fieldName": "offsetBitmap",
"name": "offsetBitmap",
"type": "bitmapCube"
}
],
"queryType": "groupBy",
"limitSpec": {
"type": "default"
},
"dataSource": "login_user"
}
},
"postAggregations": [
{
"field": "459068#SRC",
"func": null,
"name": "459068",
"fields": null,
"type": "bitmapCubeSize"
}
],
"intervals": [
"2018-03-06T00:00:00/2018-03-07T00:00:00"
],
"queryType": "bitmapCube"
}
查询过程可使用线程池将每个子查询进行并行查询,然后将每个子查询的bitmap结果返回给Broker,然后Broker再根据Aggregator规则分别进行聚合,将聚合结果返回客户端。
然后根据Druid提供的接口实现Jersey的Resource,以及各种BitmapCubeQuery,Chest,Aggregator等各种类。
然后按照标准的Druid Extension进行部署。
此处不在详述。