作者|董哲需求背景数据检测上线前,通过编写SQL进行数据校验。从写SQL到分析运行结果,不仅耗时长,而且反复消耗计算资源,检测上线后,只需要一次检测就可以得到整张表的检测报告,但是我们也发现了一些后面的问题,主要在三点:看不到检测到的数据详情和关联的行详情,无法对数据进行预处理。探测仍然需要资源调度,平均等待时间为分钟。与质量监测无关联,探查数据后续方向不明。针对这些问题,我们进一步开发动态探测需求,解决以下问题:基于大数据预览的探测支持数据的函数级预处理。检测结果秒级更新,实时响应。连接数据监控,探索SQL的生成方式。本文主要介绍动态检测的应用场景及相关技术实现。应用场景检测主要应用于元数据管理、数据研发、数据仓库开发、数据治理等领域,可为对数据质量有要求的场景提供数据质量发现和识别能力。目标用户除了研发学生,还包括算法建模、数据挖掘等不专注于SQL研发的群体。Probe可以有效突破三个闭环:元数据管理->Probe->数据预览Probe(库表质量报告)数据监控<->DataProbe动态Probe->SQL->数据开发->调试->ProbeReport(QualityAnalysis)名词解释全量调查:基于对库表的全量调查,后台引擎执行并显示调查后的列的统计分布结果。动态探查:基于抽样的部分数据探查,展示字段详情,可对数据进行运算预处理,实时动态展示统计分布结果。数据获取后的处理由前端执行。两者对比示意图。除了数据采样部分在后端完成外,其余的都在前端实现。包括大数据展示、探测计算、卡片联动、操作栈交互、未来函数编辑和SQL生成。技术架构1)采样能力:根据质量分布特征提取数据。目前做的是随机抽样,后续的尝试都是基于特征抽样。2)数据显示:大容量数据载体,支持数据处理实时显示。前端目前基于虚拟滚动表,未来计划迁移到canvas表。3)前端巡检:实时巡检,数据分布可视化展示,质量指标高亮显示。4)数据处理能力:函数处理能力(GroupBy..)5)操作栈:需要对数据操作进行管理和回溯,实现基于不可变和操作流的操作栈。6)编辑器:提供完整功能的功能,需要:词法分析、智能提醒、语法高亮。antlr4在编辑器实现功能的基础上实现了词法分析,配合monaco编辑器实现了一些智能提醒和语法高亮。7)生成SQL:将可视化的交互操作转化为可执行的SQL。目前sql生成器有以下几种方式:基于链式调用生成,基于标签模板生成,基于AST(抽象语法树)做关键技术,实现大数据渲染。由于动态检测场景,前端需要支持最多5000条数据的展示和交互,所以在渲染方面压力很大,主要集中在探针卡和数据预览两部分。检测卡包含了特定列的一些关键信息汇总,比如0值,Null值,枚举值等,如下图红框所示:检测卡部分有很多自定义的内容,所以虚拟列表方案用于渲染。支持折叠状态和展开状态:数据预览部分展示了探针的所有数据集合,可以快速查看原始数据的详细内容。由于内容同质化比较高,数据预览采用基于团队内部维护的canvas版本Table方案渲染,如下图红框所示:卡片联动由于宽度差异较大卡片和数据预览栏,上下部分的滑动是独立的,当你选择查看特定栏目时,上下对齐位置会不一样麻烦,为了解决这个问题,本块增加一个自动定位功能,演示效果如下:这部分需要解决两个问题:卡片中点的坐标计算和自动定位逻辑。中点坐标计算逻辑如下://计算卡片中点坐标index为卡片序号,adsorSider表示是否吸边getCardCenter(index:number,adsorSider?:boolean){...//获取卡片信息constcardBox:IBaseBox=this.cardList[index];//获取列信息constcolBox:IBaseBox=this.colList[index];constclientWidth=getClientWidth();if(adsorbSider){//吸边处理if(cardBox.offsetclientWidth){returncardBox.offset+cardBox.width-clientWidth;}returnthis.cardScroll;}returngetTargetPosition(colBox,this.tableScroll,cardBox);}//获取滚动目标位置//originBox:滚动开始对象//originScroll:滚动开始向左滚动//targetBox:滚动结束对象constgetTargetPosition=(originBox:IBaseBox,originScroll:number,targetBox:IBaseBox)=>{constclientWidth=getClientWidth();如果(!originBox||!targetBox)返回0;让offsetLeftSider=Math.max(originBox?.offset-originScrol升,0);if(offsetLeftSider+targetBox.width>=clientWidth){if(targetBox.offset+targetBox.width>clientWidth){//这里容易出现吸边returntargetBox.offset+targetBox.width-clientWidth;}否则{返回0;}}constscroll=targetBox?.offset-offsetLeftSider+(targetBox.width-originBox.width)/2;returnMath.max(Math.min(targetBox.offset,scroll),0);}get到达中点坐标后,自动定位需要满足以下规则:选中卡片后,自动定位表格滚动并定位到底部的中心。如果不能满足对齐标准,则应尽可能靠近所选卡片的位置。选择表格列后,卡片应自动滚动定位到顶部居中对齐,如果不能满足对齐标准,应尽可能靠近所选表格的位置。搜索到选中的列后,卡片和表格必须自动满足以上两个规则,滚动到可见区域。规则中有几种边框情况,参考下图:中心对齐是滚动距离允许时卡片和列宽的理想对齐方式,边缘对齐是卡片在开始和结束位置滚动不足以滚动的情况满足居中对齐要求另外还有一种对齐方式,当卡片的宽度远大于列宽,且不是起始位置或结束位置时。比如卡片B无法滚动,卡片A的宽度占据了底部第二列的一部分,所以此时卡片B只能高亮对齐底部列。操作栈的动态检测支持对检测结果进行列删除、过滤、排序等基础分析能力,如下图红框所示:用户对检测结果的每一次操作都会被记录为一个操作,多个操作串联起来形成操作栈,您可以自由修改或删除操作栈中的操作,实时查看最新结果,对操作进行过滤。在串行计算中,所有的操作都被抽象成一个Input+Logic=Ouput的结构。input是输入参数,可以是指某一列的数据,也可以是上次运算的结果,也可以是其他的计算值。逻辑是运算的具体逻辑,负责根据Input转换生成Output,Output可以作为最终结果进行渲染,也可以进入下一步再次参与计算,以删除列操作为例,下面是大致的代码实现:ColDelOpt{run=(params:IOptEngineMetaInfo)=>{//操作输入部分const{columns=[],dataSourceMap={}}=params;const{fields=[]}=this.params;//操作逻辑部分constnextColumns=columns.filter((item)=>!fields.includes(item.name));//操作输出return{columns:nextColumns,dataSourceMap}}}可以看到ColDelOpt内部有一个run方法,支持传入一个列信息columns和数据集dataSourceMap的params对象,其中params为抽象了外部输入参数Input,run方法内部的逻辑部分是抽象的Logic部分,final方法返回值包含最新的columns和dataSourceMap,是Output部分。基于这种结构,可以将所有的用户操作初始化到不同的Opt实例中,由操作引擎统一调用实例的run方法,传入需要的参数,最终得到计算结果。2)操作修改后如何进行二次计算操作栈的计算由计算引擎完成,计算引擎负责根据外部事件自动执行已有操作的数据处理工作。引擎执行流程和大致代码如下://操作引擎类OptEngine{//操作列表privateoptList:IOptEngineItem[]=[];//原始数据privatemetaData:IOptEngineMetaInfo={columns:[],dataSourceMap:{},};//执行运算符optRun=()=>{let{columns=[],dataSourceMap={}}=this.metaData;如果(!this.optList.length)返回{列,dataSourceMap};for(letindex=0;indexvoid)=>{//加载数据this.setupMetaData(metaInfo);//加载操作栈this.setupOptList(optList.map((item)=>{//行过滤if(item.type===OPT_TYPE.FILTER){returnnewFilterOpt({key:item.key,params:item.params})}//其他类型的操作...//返回默认的原始值returnnewIdentityOpt({key:item.key,})}));//执行运算计算constresult=this.optRun();//返回数据return{//计算列columns:result.columns,//执行结果dataSource:Object.entries(result.dataSourceMap).map(([key,value])=>({field:key,value})),//操作栈执行异常信息errorInfo:result.errorInfo};}}应用实践一个小例子来演示动态检测的使用在前端开发的过程中,有一个真实的场景。为了排查竖屏显示(1080*1920)的一个bug,我们想找到关联的用户,看看他们的分布情况,这样我们就可以很方便地使用动态检测来找到他们。后续计划重点关注动态探测操作的丰富性和后续数据趋势,如离线数据导出、SQL生成等。技术方向主要集中在以下几个方面:更多的探测类型和图表支持动态探测目前支持空值、枚举值、零值、数据统计等基本检测功能。未来计划支持对map、json、time、sql语句等类型的识别检测。同时提供更丰富的图表支持。操作栈的编辑器体验动态检测目前主要基于类Excel操作。未来主要提供编辑器级别的操作体验,可以提供HSQL支持的大部分功能,包括对多表join功能的支持。运行过程的SQL生成动态检测目前还没有完成SQL能力建设。未来会结合编辑器级别的操作,支持多表,配合词法分析功能,提供更精准的SQL生成能力。