Hawk原理:通过IEnumerable实现通用的ETL管道
针对IEnumerable已经有多篇文章,本篇介绍如何使用IEnumerable实现ETL. ETL,是英文 Extract-Transform-Load 的缩写,用来描述将数据从来源端经过萃取(extract)、转置(transform)、加载(load)至目的端的过程。通常来说,从原始端采集的数据有很多问题,同时可能业务需求与采集的数据格式不相匹配,所以就必须实现ETL过程。
ETL可以理解为一条清洗管线,数据从一端流入,从另一端流出。数据量可能很大,所以管线不大可能也没有必要加载全部内容。同时,一般情况下,从管线流出来的数据会进入新的数据池,很少直接修改到原表。
从管线的概念可以看出,ETL需要构造可组合的链条,首先实现一组组件,然后实现可将这些组件组装为一条ETL管线的框架。IEnumerable一大堆的LINQ扩展,正好帮我们实现了这一思想。
1. 数据的表达
我们先讨论清楚如何表达数据,因为数据处理涉及到动态增减属性的问题,因此一般的实体类是做不到的,我们采用字典来实现。为此我包装了一个实现IDictionary<string, object>的类。叫做FreeDocument。它可以简单表示如下:
/// <summary>
/// 自由格式文档
/// </summary>
public interface IFreeDocument : IDictionarySerializable, IDictionary<string, object>, IComparable
{
#region Properties IDictionary<string, object> DataItems { get; set; } IEnumerable<string> PropertyNames { get; } #endregion
}
因此数据的处理,本质就是对每一个字典对象中的键值对进行增删改查。
2 .基本组件
数据清洗组件的基接口是ICollumProcess. 定义如下:
public interface ICollumProcess : IDictionarySerializable
{ string CollumName { get; set; } //针对的列名 bool ShouldCalculated { get; set; } //是否需要重新计算 double Priority { get; set; } //优先级 void Finish(); //处理完成时的回收函数 void Init(IList<IFreeDocument> datas); //对数据进行初始化的探测行为 }
更清晰的说,其实派生出四部分:
(1) 生成器
生成器即提供/产生数据的组件。这可能包括生成一个从0-1000的数,获取某个数据表中的数据,或从网页检索的结果。它的接口可以表示如下:
[Interface("ICollumGenerator", "数据生成器", SearchStrategy.FolderSearch)]
public interface ICollumGenerator : ICollumProcess
{
/// <summary>
/// 当前迭代的位置
/// </summary>
int Position { get; set; }
IEnumerable<FreeDocument> Generate();/// <summary>
/// 生成器能生成的文档数量
/// </summary>
/// <returns></returns>
int? GenerateCount();
}
最主要的方法是Generate,它能够枚举出一组数据出来,同时还有可能(有时做不到)得到能够生成文档的总数量。
(2)过滤器
过滤器即能够分析一个文档是否满足条件,不满足则剔除的组件。接口也很简单:
[Interface("ICollumDataFilter", "数据列过滤器", SearchStrategy.FolderSearch)]
public interface ICollumDataFilter : ICollumProcess
{
bool FilteData(IFreeDocument data);
}
(3)排序器
顾名思义,对数据实现排序的接口,定义如下:
[Interface("ICollumDataSorter", "数据排序器", SearchStrategy.FolderSearch)]
public interface ICollumDataSorter : IDictionarySerializable, ICollumProcess,IComparer<object>
{
SortType SortType { get; set; }
IEnumerable<IFreeDocument> Sort(IEnumerable<IFreeDocument> data);
}
排序一般需要升序和降序,但排序最大的问题是破坏了管线的单向流动性和虚拟性。最少LINQ的标准实现上,排序是内存排序,因此必须把数据全部加载进来才能排序,这严重影响了性能。因此目前的排序最好在小数据的情况下进行。
(4)列转换器
它最重要的组件。整个ETL过程,实质上就是不同的列进行变换,组成另外一些列的过程(列就是键值对)。 定义实现如下:
[Interface("ICollumDataTransformer", "数据转换器", SearchStrategy.FolderSearch)]
public interface ICollumDataTransformer : ICollumProcess
{
string NewCollumName { get; set; }
SimpleDataType TargetDataType { get; set; }
ObservableCollection<ICollumDataFilter> FilterLogics { get; set; }
object TransformData(IFreeDocument datas);
IEnumerable<string> AffectedCollums { get; }
}
看着很复杂,但其实就是将文档中的一些列转换为另外一些列。比如对一个字符串的列进行正则替换,或转换其数据类型(如从string变成int)。举个最简单的HTML编解码的例子:
public override object TransformData(IFreeDocument document)
{
object item = document[CollumName];
if (item == null)
return "";
switch (ConvertType)
{
case ConvertType.Decode:
return HttpUtility.HtmlDecode(item.ToString());
break;
case ConvertType.Encode:
return HttpUtility.HtmlEncode(item.ToString());
break;
}
return "";
}
3. ETL管线的设计
相信你已经想到,ETL管线的核心就是动态组装的LINQ了。
一个最基本的ETL管理类,应当具有以下的属性:
public ObservableCollection<ICollumProcess> CurrentETLTools { get; set; } //当前已经加载的ETL工具
protected List<Type> AllETLTools { get; set; } //所有能够使用的ETL工具。当然Type只是此处为了方便理解而设定的,更合适的应该是记录了组件元数据,名字和介绍的扩展类。
以及一个方法:
public IEnumerable<IFreeDocument> RefreshDatas(IEnumerable<IFreeDocument> docuts) //从原始数据转换为新的数据
那么,这个函数的实现可以如下定义:
public IEnumerable<IFreeDocument> RefreshDatas(IEnumerable<IDictionarySerializable> docuts)
{
if (SampleMount <= )
{
SampleMount = ;
} IEnumerable<IFreeDocument> ienumable = docuts.Where(d=>d!=null).Select(d => d.DictSerialize());
Errorlogs = new List<ErrorLog>(); List<IFreeDocument> samples = docuts.Take((int) SampleMount).Select(d => d as IFreeDocument).ToList();
foreach (ICollumProcess tool in
CurrentETLTools.Where(d => d.ShouldCalculated).OrderByDescending(d => d.Priority))
{
tool.SourceCollection = CurrentCollection; tool.Init(samples); if (tool is ICollumDataTransformer)
{
var ge = tool as ICollumDataTransformer; ienumable = Transform(ge, ienumable);
}
if (tool is ICollumGenerator)
{
var ge = tool as ICollumGenerator;
if (!ge.CanAppend) //直接拼接
ienumable = ienumable.Concat(ge.Generate());
else
{
ienumable = ienumable.MergeAll(ge.Generate());
}
} else if (tool is ICollumDataFilter)
{
var t = tool as ICollumDataFilter;
ienumable = ienumable.Where(t.FilteData);
}
else if (tool is ICollumDataSorter)
{
var s = tool as ICollumDataSorter; switch (s.SortType)
{
case SortType.AscendSort:
ienumable = ienumable.OrderBy(d => d, s);
break;
case SortType.DescendSort:
ienumable = ienumable.OrderByDescending(d => d, s);
break;
}
} tool.Finish();
}
return ienumable;
}
基本实现思路如上。即通过优先级排序所有加载的ETL组件,并提取一部分样例数据,为组件进行一次初始化。然后通过组装不同的转换器,生成器,排序器和过滤器,最后即可组装为一个新的ienumable对象。注意整个过程都是延迟计算的,只有在真正需要ETL结果时才会进行实质性的操作。
4. 优化ETL管线和实现虚拟视图
以上就是ETL的基本思路。但是仅仅做到这些是很不够的。以下才是这篇文章的核心。
ETL管线破坏了原有集合的特性,原有集合可能是能够支持索引查询甚至能够执行高性能查找的。但ETL将其退化为仅能够枚举。枚举意味着只能从头访问到尾,不能回退和索引。要想使用新集合,就只能访问其前n个元素,或者全部访问。这显然对一些操作是很不利的。
先考虑索引器。如果能满足以下条件:
(1) 管线中不包括排序器和过滤器,因为它们使得得集合产生了乱序。
(2) 原始集合能够支持索引器
(3) 使用的生成器能够提供生成的大小,同时生成器也能够实现索引器
(4) 转换器应当只实现1到1转换,没有额外的副作用。
那么原始集合和新集合元素的对应关系是可计算的。此时索引器就能发挥作用。在实际使用中,转换器是用的最多的。条件不可谓不苛刻。
关于高性能查找,我们先不考虑针对复杂的SQL查询,先考虑那种最简单的find(item[key]==value)的查询。但这个条件更加苛刻:
(1) key在原始集合中必须支持高性能查找
(2) 满足上述索引器的四个条件
(3) 针对key这一列的操作,转换器必须是可逆的。而且最好能实现1-1映射。
所谓可逆的意思就是说,转换器能从A转换为B,同时也能通过结果B反推出结果A。 但这种条件何其苛刻!a*5=b,这样的操作是可逆的,然而正则转换,替换以及绝大多数的运算都是不可逆的。
怎么办呢?可能的做法,就是转换器在转换过程中,就动态地将key的转换结果保存下来。于是,对新集合的查找操作,最后就能一步步回退到原始集合的查找操作。还有更好的办法么?
如何让新集合应对复杂的SQL查询?首先需要解析SQL, 这可能涉及到大量的数学推导和转换。以至于在实现当中因为限制太多,基本上不可能实现。以筛选key为一定范围的数据为例,每次都需要逆向推导,这种推导难度非常大。
5. 智能ETL和用户体验优化
整个ETL过程,是人为观察数据的特性,组合和配置不同的ETL组件,这一过程能够实现自动化吗?
人是很智能的,它能够观察不同数据的格式和类型,发现其中的特征,比如以下数据:
高楼层/21层,南垡头翠成馨园,2004年建,塔楼
中楼层/5层,南北豆各庄5号院,2003年建,板楼
人通过观察这么两行的数据,就可以大概的判断出这些信息分别代表的是什么意思,以及如何去分割和转换。可以用正则,提取第一个出现的数字,即楼层,再使用\d{4}提取年份,而用逗号分割,即可得到小区名称。
但是,这个操作依旧需要最少懂得一定程序基础的人来参与,如果用机器来做的话,又该如何做呢?自动化步骤可以分为两个层次:
(1) 自动分割和对齐。
数据尤其是来自web的数据,由于本身是由程序生成的,因此在格式上有高度的统一性,同时分隔符也是类似的,包括逗号,分号,空格,斜杠等。因此,可以统计不同分割符出现的次数,以及对应的位置,通过概率模型,生成最可能的分割方案,使得每一条数据分割出来的长度和子项数量尽可能一致。
(2) 自动识别内容
自动识别内容可以依赖于规则或者识别器。一种比较可靠的方法是通过基于正则的文本规则,构造一组规则组。通常200x这样的数值,很容易被理解为年份,而12:32这样的结构,则很容易被识别为时间。通过基于结构的识别引擎,不仅能够识别”这是什么内容“,更能提出其元数据,比如日期中的日月年等信息,为之后的工作做准备。
Web表格最大的好处,在于它的格式一致性。只要分析很少的具有代表性的样例数据,就能够掌握整个数据集的特征。因此完全可以用比较大的代价获得一个尽可能高的识别模块,而在执行过程中尽量提升性能。
Hawk原理:通过IEnumerable实现通用的ETL管道的更多相关文章
- python连接redis、redis字符串操作、hash操作、列表操作、其他通用操作、管道、django中使用redis
今日内容概要 python连接redis redis字符串操作 redis之hash操作 redis之列表操作 redis其他 通用操作,管道 django中使用redis 内容详细 1.python ...
- 基于ETL技术的数字化校园共享数据中心设计
摘要:数据的抽取.转换与加载(ETL)是数据整合的核心过程.在分析高校信息化建设现状基础上,以建立数字化校园.整合数据资源.实现数据共享为目标,提出以ETL为基础建立共享数据中心实现数据整合的方案.介 ...
- .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理
.NET Core中间件的注册和管道的构建(1)---- 注册和构建原理 0x00 问题的产生 管道是.NET Core中非常关键的一个概念,很多重要的组件都以中间件的形式存在,包括权限管理.会话管理 ...
- Spring Cloud Data Flow 中的 ETL
Spring Cloud Data Flow 中的 ETL 影宸风洛 程序猿DD 今天 来源:SpringForAll社区 1 概述 Spring Cloud Data Flow是一个用于构建实时数据 ...
- 【SFA官方译文】:Spring Cloud Data Flow中的ETL
原创: 影宸风洛 SpringForAll社区 昨天 原文链接:https://www.baeldung.com/spring-cloud-data-flow-etl 作者:Norberto Ritz ...
- ETL开发
要进入开发阶段,了解不同的ETL产品. 整个ETL系统中,时间或更精确的,吞吐量是主要关心的内容.这种转换处理任务设计的主要目的归根结底是使得数据装载到展现表中最快并使得最终用户能快速的从这些表中得到 ...
- .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法
.NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...
- 一次修改闭源 Entity Provider 程序集以兼容新 EntityFramework 的过程
读完本文你会知道,如何在没有源码的情况下,直接修改一个 DLL 以去除 DLL 上的强命名限制,并在该程序集上直接添加你的“友元程序集(一种特殊的 Attribute,将它应用在程序集上,使得程序集内 ...
- JavaScript 框架设计(二)
JavaScript 高级框架设计 (二) 上一篇,JavaScript高级框架设计(一)我们 实现了对tag标签的选择 下来我们实现对id的选择,即id选择器. 我们将上一篇的get命名为getTa ...
随机推荐
- Grafana+Prometheus系统监控之webhook
概述 Webhook是一个API概念,并且变得越来越流行.我们能用事件描述的事物越多,webhook的作用范围也就越大.Webhook作为一个轻量的事件处理应用,正变得越来越有用. 准确的说webho ...
- 解决thymeleaf layout布局不生效
今天使用thymeleaf layout布局时总是不生效,特此把解决问题的步骤和几个关键点记录下来备忘. 一.检查依赖 1.thymeleaf必备maven依赖: <dependency> ...
- 十六、Hadoop学习笔记————Zookeeper实战
所有服务器都会先将自己的服务器信息注册到servers中,然后每台服务器都会尝试注册master,哪台注册成功,则哪台就是master服务器. 所有的服务器都会关注master节点的删除事件,这样通过 ...
- Akka(38): Http:Entityof ByteString-数据传输基础
我们说过Akka-http是一个好的系统集成工具,集成是通过数据交换方式实现的.Http是个在网上传输和接收的规范协议.所以,在使用Akka-http之前,可能我们还是需要把Http模式的网上数据交换 ...
- 简单说下 Winform 的分页快速开发框架必须要实现的几个功能之一
简单说下 Winform 的分页快速开发框架必须要实现的几个功能之一 分页非为前端分页 和 后端分页,前端分页只有适用于B/S,B/S的呈现速度远远不如C/S,而C/S则没有这个问题,所以分页必然是 ...
- thinkphp实现文件的下载
首先需要看一下大家使用的Thinkphp的版本,不同的版本使用的方法不同,(在导入公共函数的时候方式不同) 我用的是thinkphp3.2.2版本的,因此直接使用import()函数,直接把使用thi ...
- PHP通过ZABBIX API获取主机信息 VS 直接从数据库获取主机信息
最近项目需要获取linux主机的一些信息,如CPU使用率,内存使用情况等.由于我们本身就装了zabbix系统,所以我只用知道如何获取信息即可,总结有两种方法可以获取. 一.通过ZABBIX API获取 ...
- vue搭建环境
大早起的,没想自己起来那么早,既然起来了,就写点东西吧~最近在看Vue的东西,发现网上也是好多的资源,包括博客和视频 , 我是看的慕课网上的vue ,名字忘记了,价格148的,看了,也整理了笔记,看了 ...
- P1092 虫食算
题目传送:https://www.luogu.org/problem/show?pid=1092 #include <iostream> #include <cstring> ...
- ThinkPHP 5 中AJAX跨域请求头设置方法
最近用thinkphp做项目,在测试环境时,存在接口的测试问题.在tp官网也没能找到相关的解决方法.自已看了一下源码,有如下的解决方案. 在项目目录下面,创建common/behavior/CronR ...