上一篇文章里,我试图论证了一个观点:在日志分析场景下的DSL设计,宜采用数据管道风格。

不过,并不是所有时候,数据分析的流程都是单向的一条线。

下图是阿里云PAI平台文档中的一个示例截图:

这是一个做机器学习时非常常见的流程图。虽然我们一般说法中,也是下面这种单向的:

数据导入 -> 数据预处理 -> 特征工程 -> 模型调整 -> 效果评估

但是在预处理和特征工程的时候,少不了需要通过统计分析手段来决定一些调整方案;在效果评估和模型选择的时候,也是需要同时运行不同模型来相互参照。

最终就变成了一个图而非线性的流程了。

当然,并不是说用线性管道就达不到相同目的了——我们可以通过子查询的形式达到最终一致的结果。但是这个过程意味着一部分流程的计算是重复运行的。在普通的搜索统计时,这个无所谓。第一消耗可能不大,第二诸如Elasticsearch等后台引擎对一模一样的query是有query cache的,所以子查询的搜索聚合结果,在主查询的时候其实是复用的。

但是在机器学习的时候,问题可能就严重一些了。因为这些可能消耗的资源不少,运行时间也不短,每次都从头开始确乎就是一种浪费了。所以有必要在DSL语法上,想到一种更合适的结构。(像阿里云等平台这样搞可视化拖动当然也行,但是对智能运维产品本身设计不一致)

这时候,我想起来年初的时候,在devopsweekly邮件上看到过的一个开源项目,名叫dgsh。地址见:https://www.dmst.aueb.gr/dds/sw/dgsh/

dgsh的写法示例如下:

#!/usr/bin/env dgsh

tee |
\{\{
	printf 'File type:\t'
	file -

	printf 'Original size:\t'
	wc -c

	printf 'xz:\t\t'
	xz -c | wc -c

	printf 'bzip2:\t\t'
	bzip2 -c | wc -c

	printf 'gzip:\t\t'
	gzip -c | wc -c
}} |
cat

看起来就是我们想说的这个意思。不过在语法设计上,靠空行来切分并行任务,还是有点怪怪的。

此外,去年曾经还有一个项目,在做竞品调研的时候闯进过我的眼界:Juttle。这是Jut.io开源的项目,jut.io曾经入选过2015年的Gartner ITOA Cool Vendor名单,不过2016年就倒闭了,关门前把这个系统开源出来……

read elastic -from :2015-01-01: -to :2015-07-01:
  category = cat_in AND type ~ '*${type_in}*'
|(
  reduce count()
  | view tile -title 'GitHub events count (${cat_in}, ${type_in})' -row 0 -col 0;
  reduce count() by repo_name
  | sort count -desc
  | head 10
  | view table -title 'GitHub events for top 10 repos (${cat_in}, ${type_in})' -row 0 -col 1;
  reduce -from :2015-01-01: -over :w: -every :d: count() by repo_name
  | view timechart -keyField 'repo_name' -title 'Rolling count of GitHub events (${cat_in}, ${type_in})' -row 1 -col 0;
)

这里采用了分号;来区分并行任务。显然比单纯的空行好看且明确一些。不过使用圆括号()来作为并行任务的区域表达,又有另一种误解,因为加减乘除运算是使用圆括号来表达优先级的。

所以综合来看,采用花括号\{\{}}配合分号;可能是最好的结构了。那么文首的那个机器学习流程可以表达成这样:

wumai_data_1
  | eval feature_XXX = somecommand(xxx)
  | \{\{
    bucket feature_XXX span=1000 as numberrange
     | chart numberrange over other yyy,zzz;
    fit StandardScaler *
     | sample ratio=0.2
     | \{\{
        fit RandomForestClassifier predict_field from feature_* into rf_model
         | apply rf_model
         | `confusionmatrix("predict_field","predicted(predict_field)")`;
        fit LogisticRegression predict_field from feature_* into lg_model
         | apply lg_model
         | `confusionmatrix("predict_field","predicted(predict_field)")`;
    }}
}}

看起来还不错呢~哼哼,看我这个思路后续会跟其他竞品雷同不~