緣起
最近看Apache Druid的源代碼(0.5很老的版本),印象最深的就是對Jackson的多態反序列化和注入的使用了,這裏也屬於自己的知識盲點,看着複雜的json直接反序列化爲可用對象,直呼過癮。所以一直想找個機會實踐一下,這不需求就來了。
需求
我們的實時指標統計是通過Flink Steam SQL計算爲維度、指標後後入Hbase,然後通過Phoenix SQL給前臺展示。現在新接入Kylin數據源,需要兼容查詢Kylin數據源。
看到這個需求,變化點在查詢方式上,抽象出查詢服務,提供多態支持,但各個指標查詢需要的信息是不同的。如Kylin數據源需要查詢的表名,聚合函數等,但Hbase數據源壓根不需要這些信息,這時候就該Jackson多態反序列化出廠了。
實踐
數據庫新增querySpec字段,描述指標的個性化需求,如{“type”:“rc”,“tableName”:“TASK_SNAPSHOT”,“fieldName”:“EVENTCODE”}
{“type”:“hbase”}
看如下代碼:
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
/**
* Created by yihaibo on 2019-09-11.
*/
@JsonTypeInfo(use=JsonTypeInfo.Id.NAME, property = "type", defaultImpl = HbaseQuerySpec.class )
@JsonSubTypes(value = {
@JsonSubTypes.Type(value = HbaseQuerySpec.class, name = "hbase"),
@JsonSubTypes.Type(value = RcHttpQuerySpec.class, name = "rc")
})
public interface QuerySpec {
String getMetricQueryServiceName();
}
/**
* hbase數據源個性化信息
*/
public class HbaseQuerySpec implements QuerySpec {
@Override
public String getMetricQueryServiceName() {
return "hbaseQueryService";
}
}
/**
* kylin數據源個性化信息(已通過http服務化)
*/
public class RcHttpQuerySpec implements QuerySpec {
@Getter
@Setter
@JsonProperty
private String tableName;
@Getter
@Setter
@JsonProperty
private String fieldName;
@Override
@JsonIgnore
public String getMetricQueryServiceName() {
return "rcMetricQueryService";
}
}
我們看到新增了getMetricQueryServiceName方法,用於指定處理該數據源的service名稱。
有了個性化參數,可以寫查詢服務了
public interface MetricQueryService {
/**
*
* @param dataQueryDTO 通用查詢參數
* @param querySpec 個性化配置信息
* @return
*/
List<Map<String,Object>> queryData(DataQueryDTO dataQueryDTO, QuerySpec querySpec);
}
/**
* 查詢Hbase原始數據
* */
@Service("hbaseQueryService")
public class HbaseMetricQueryService implements MetricQueryService {
@Override
public List<Map<String,Object>> queryData(DataQueryDTO dataQueryDTO, QuerySpec querySpec){
....
}
/**
* 查詢kylin數據
* */
@Service("rcMetricQueryService")
public class RcMetricQueryService implements MetricQueryService {
@Override
public List<Map<String, Object>> queryData(DataQueryDTO dataQueryDTO, QuerySpec querySpec) {
RcHttpQuerySpec rcHttpQuerySpec = (RcHttpQuerySpec)querySpec;
...
}
反序列化querySpec,獲取服務名稱,然後通過spring獲取到該service bean,傳遞處理參數
QuerySpec querySpec = JsonUtil.decode(querySpec, QuerySpec.class);
String serviceName = querySpec.getMetricQueryServiceName();
MetricQueryService metricQueryService = CtxUtil.getBean(serviceName, MetricQueryService.class);
List<Map<String,Object>> data = metricQueryService.queryData(dataQueryDTO, querySpec);
@Component
@Slf4j
public class CtxUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
applicationContext = ctx;
log.info("init ctx ok");
}
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
public static <T> T getBean(String name, Class<T> clazz) {
return applicationContext.getBean(name, clazz);
}
}
反思
Jackson多態反序列化的確是開發力氣,可以去掉不少if else樣例代碼,精簡配置。但感覺用Jackson注入還是不太合適,Jackson對象還是作爲POJO比較合適,邏輯層還是service層或單獨bo去處理,需要注入可以用spring或guice,職則方面,方便test case。
最近在研究Apache Druid,主要學習一些分佈式處理知識,以及想看下列式存儲到底是如何做的。實時OLAP的確用處多多,Apache Kylin也在擴展實時處理,期待兩着的碰撞與融合。