针对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管道的更多相关文章

  1. python连接redis、redis字符串操作、hash操作、列表操作、其他通用操作、管道、django中使用redis

    今日内容概要 python连接redis redis字符串操作 redis之hash操作 redis之列表操作 redis其他 通用操作,管道 django中使用redis 内容详细 1.python ...

  2. 基于ETL技术的数字化校园共享数据中心设计

    摘要:数据的抽取.转换与加载(ETL)是数据整合的核心过程.在分析高校信息化建设现状基础上,以建立数字化校园.整合数据资源.实现数据共享为目标,提出以ETL为基础建立共享数据中心实现数据整合的方案.介 ...

  3. .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理

    .NET Core中间件的注册和管道的构建(1)---- 注册和构建原理 0x00 问题的产生 管道是.NET Core中非常关键的一个概念,很多重要的组件都以中间件的形式存在,包括权限管理.会话管理 ...

  4. Spring Cloud Data Flow 中的 ETL

    Spring Cloud Data Flow 中的 ETL 影宸风洛 程序猿DD 今天 来源:SpringForAll社区 1 概述 Spring Cloud Data Flow是一个用于构建实时数据 ...

  5. 【SFA官方译文】:Spring Cloud Data Flow中的ETL

    原创: 影宸风洛 SpringForAll社区 昨天 原文链接:https://www.baeldung.com/spring-cloud-data-flow-etl 作者:Norberto Ritz ...

  6. ETL开发

    要进入开发阶段,了解不同的ETL产品. 整个ETL系统中,时间或更精确的,吞吐量是主要关心的内容.这种转换处理任务设计的主要目的归根结底是使得数据装载到展现表中最快并使得最终用户能快速的从这些表中得到 ...

  7. .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法

    .NET Core中间件的注册和管道的构建(3) ---- 使用Map/MapWhen扩展方法 0x00 为什么需要Map(MapWhen)扩展 如果业务逻辑比较简单的话,一条主管道就够了,确实用不到 ...

  8. 一次修改闭源 Entity Provider 程序集以兼容新 EntityFramework 的过程

    读完本文你会知道,如何在没有源码的情况下,直接修改一个 DLL 以去除 DLL 上的强命名限制,并在该程序集上直接添加你的“友元程序集(一种特殊的 Attribute,将它应用在程序集上,使得程序集内 ...

  9. JavaScript 框架设计(二)

    JavaScript 高级框架设计 (二) 上一篇,JavaScript高级框架设计(一)我们 实现了对tag标签的选择 下来我们实现对id的选择,即id选择器. 我们将上一篇的get命名为getTa ...

随机推荐

  1. 逆向知识第十四讲,(C语言完结)结构体在汇编中的表现形式

    逆向知识第十四讲,(C语言完结)结构体在汇编中的表现形式 一丶了解什么是结构体,以及计算结构体成员的对其值以及总大小(类也是这样算) 结构体的特性 1.结构体(struct)是由一系列具有相同类型或不 ...

  2. 大数据基础篇(一):联机分析处理(OLAP) 与 联机事务处理(OLTP)

    联机事务处理(OLTP) OLTP也称实时系统(Real Time System),支持事务快速响应和大并发,这类系统典型的有ATM机(Automated Teller Machine)系统.自动售票 ...

  3. Python之可变类型与不可变类型

    Python常见的数据类型有:数字 字符串 元组 列表 字典 不可变类型:数字 字符串 元组 可变类型: 列表 字典 a = 100 b = [100] def num1(x): x += x pri ...

  4. 【转】javascript中的LHS与RHS

    原文链接:http://www.cnblogs.com/yangxiaoguai132/p/5064625.html 最近在学习javascript过程中,接触了LHS与RHS的概念,刚开始的时候有点 ...

  5. Nginx负载均衡的优缺点

    Nginx的优点是: 1.工作在网络的7层之上,可以针对http应用做一些分流的策略,比如针对域名.目录结构,它的正则规则比HAProxy更为强大和灵活,这也是它目前广泛流行的主要原因之一,Nginx ...

  6. php IP转换整形(ip2long)

    如何将四个字段以点分开的IP网络址协议地址转换成整数呢?PHP里有这么一个函数ip2long.比如 <?php echo ip2long("10.2.1.3"); ?> ...

  7. .Net Core实现将文件上传到七牛云存储

    功能:将图片上传到七牛云存储 准备工作 注册七牛账号,提交实名认证(基本上1天内内审核通过) 登录七牛后台->对象存储->新建空间 (基本概念:https://developer.qini ...

  8. slurm任务调度系统部署和测试(一)

    1.概述 本博客通过VMware workstation创建了虚拟机console,然后在console内部创建了8台kvm虚拟机,使用这8台虚拟机作为集群,来部署配置和测试slurm任务调度系统. ...

  9. lodash源码分析之compact中的遍历

    小时候, 乡愁是一枚小小的邮票, 我在这头, 母亲在那头. 长大后,乡愁是一张窄窄的船票, 我在这头, 新娘在那头. 后来啊, 乡愁是一方矮矮的坟墓, 我在外头, 母亲在里头. 而现在, 乡愁是一湾浅 ...

  10. 機器學習基石(Machine Learning Foundations) 机器学习基石 课后习题链接汇总

    大家好,我是Mac Jiang,非常高兴您能在百忙之中阅读我的博客!这个专题我主要讲的是Coursera-台湾大学-機器學習基石(Machine Learning Foundations)的课后习题解 ...