指南--如何在不写代码的情况下建立数据管线

142 阅读17分钟

让我们假设你从你的数据分析师那里收到以下任务。

一个系统每天上传CSV文件,其中包含以下信息:invoice_no, stock_code, description, quantity, unit_price, customer_id, country, invoice_date。计算每个客户和每天售出的商品总数(计数数量)和总购买价值(每件商品数量之和乘以单位价格)。将结果保存在一个CSV文件中,用逗号分隔在S3桶中。

我知道这听起来更像是数据分析师的工作,你们中的一些人可能认为这是一个简单而无聊的任务,但不要担心,我们将在后面添加一些动作。

我的输入文件位于这个目录中:aws-glue-demo-202109/inputs

1_S3-input-Files

好了,让我们看看如何用AWS Glue Studio来做这件事。

要访问它,从主AWS管理控制台选择AWS Glue,然后从左侧面板(ETL下*)点击AWS Glue Studio*。转到作业,在顶部你应该看到创建作业 面板--它允许你以几种不同的方式创建新作业。有源和目标的视觉有空白画布的视觉Spark脚本编辑器,以及Python Shell脚本编辑器

AWS Glue Studio - Create Job Panel

我选择了*带有空白画布的Visual。*它应该创建一个新的、空的、无标题的作业。

在我们开始建立ETL流程之前,让我们到作业细节标签,讨论一些重要的属性,这些属性决定了AWS Glue将如何运行作业。除了作业的名称,你可以随时改变这些设置。

AWS Glue Studio - create job details

  • Glue版本--它决定了作业可用的Apache Spark和Python的版本。**注意:**有些功能可能对特定的版本不可用。例如,在写这篇文章的时候,数据预览 功能在3.0这个最新的版本中是不工作的。
  • 语言--要么是Python,要么是Scala。我将选择Python。
  • 工作器类型 (G.1X或G.2X)--对于G.1X工作者类型,每个工作者映射到1个DPU。对于G.2X,每个工人是2个DPU。我选择了G.1X,因为它的资源比我实际需要的多得多。
  • 工作书签(启用、禁用、暂停)--现在,**这是一个相当重要的变量,**特别是启用 是一个默认值。简而言之:当你启用作业书签时,一旦你处理了一些数据(例如,一个S3文件),**这些数据就会被作业标记为已处理,在接下来的执行中不会被这个作业处理。**书签是与作业绑定的,所以不同的作业可以处理同一个文件。我现在禁用它,因为在开发作业的过程中,我可能要反复处理相同的文件。关于作业书签的扩展描述,请阅读使用作业书签跟踪处理过的数据
  • 重试次数-这一点不言而喻。我现在把这个值从3切换到0,因为我不希望Glue重试执行一个失败的任务,只是因为我犯了一些假的错误,比如说使用一个空文件。

我不会进入高级属性 部分,但请记住,在这里你可以配置你的S3脚本路径、Spark UI日志路径、设置最大并发量、禁用度量等。

注意: 你必须为每个创建的作业设置这些设置,除非你在克隆一个作业--那么它的副本的设置与原始作业相同。

现在,让我们回到 "视觉 "部分。

1.1.从源, 选择Amazon S3节点。

AWS Glue Studio - Source types

数据源属性-S3中设置。

  • S3源类型。S3位置-所以你将直接访问S3文件
  • S3网址:s3://aws-glue-demo-202109/input/
  • 递归: true
  • 数据格式。CSV
  • 分隔符。逗号(,)。

注意: 你被限制在以下定界符上。逗号、Ctrl+A、管号、分号、Tab

AWS Glue Studio - data source properities

现在,让我们去看看输出模式 标签。

AWS Glue Studio - Data Source output schema

嗯,这些数据类型看起来不对--你将不得不改变这一点。点击编辑 按钮,为每个键设置正确的数据类型(根据数据分析员提供的信息)。

AWS Glue Studio

看起来不错,点击下一步 ,进入下一个步骤。

2.做聚合。这里有第一个 "障碍",因为Glue Studio没有一个内置的转换节点,允许我们做聚合。最好的解决办法是使用转换--Spark SQL节点。你可以把节点命名为aggByCustomerByDate,转换 部分,选择Amazon S3 (父节点的名称)作为输入源,并给它一个销售 别名*,* 你可以在SQL代码中使用它作为表名。在代码块 中*,* 你可以放一个简单的SQL查询,该查询取customer_id 列,从invoice_date 列中获取日期,将数量和数量*单价相加,并按客户sale_date分组*。*

让我们假设它没有违反我的 "不写代码行 "的规则。

注意: 这个编辑器不能验证语法,所以在运行前要仔细检查你的查询。

*AWS Glue Studio - transform node
*

3.现在你已经完成了聚合,把它的结果保存到S3。从目标菜单中,选择Amazon S3,挑选CSV格式。你不希望有任何压缩,所以设置s3://aws-glue-demo-202109/output/1st-directS3目标位置

AWS Glue Studio - target node properties4.工作已经准备好了。在右上角,点击保存,然后运行,然后等待。你可以进入 "运行 "选项卡,看到作业进度,以及日志和运行元数据的链接。

一分钟后,你可以看到工作已经成功了。

AWS Glue Studio - job runs finished

现在,转到目标S3目录。你在这里有4个文件。

AWS Glue Studio - output Files

每个文件都包含customer_id, sale_date, total_quantitytotal_sale

Each file contains customer_id, sale_date, total_quantity and total_sale.

好了,这是很快速和简单的。但我对这个解决方案并不满意,你可能也不满意。的确,我对所有的文件都做了计算,但明天新的文件到来时,会发生什么?和你一样,我不想再一次处理所有的数据。我们只对处理新创建的文件感兴趣。

此外,数据分析师也有一些进一步的要求。

1.他们记得,有时数量可能是一个负值,这表明有一个项目被退回。他们希望我将这些行从聚合中排除,并将返回的项目列表存储在一个单独的文件中。

2.如果你仔细看了最后一张图片,你应该注意到第二行缺少一个customer_id的值。分析师希望能摆脱空客户。

3.分析师想把一些客户的数据(名、姓、地址)用聚合法链接到文件中。

4.在四个独立的文件上工作有点麻烦--他们更希望只拥有一个文件。

让我们先解决我的问题。在我看来,有两种可能的方法。

1.前面提到的 "*工作书签"。*正如文档中所说:"工作书签用于跟踪已经处理过的源数据,防止重新处理旧数据。工作书签可用于JDBC数据源和一些亚马逊简单存储服务(Amazon S3)源。工作书签是与工作相联系的。如果你删除一个作业,那么它的作业书签也会被删除。 你可以将你的Glue Spark ETL作业的作业书签倒退到之前的任何作业运行,这允许你的作业重新处理数据。如果你想用同一个作业重新处理所有的数据,你可以重置作业书签"。

2.手动,或通过使用一个函数或一些参数,决定哪些文件(日或日的范围)应该被处理。

第一个解决方案似乎非常酷,但假设我不是100%确定数据是如何加载的--也许文件每天都被覆盖?或者他们会突然开始这么做?或者你要经常修改我们的作业,并为特定的文件重新运行它们,而你不想记住关于重置作业书签的事情?

无论哪种情况,让我们说,为了这篇文章,你根本不能或不想依赖作业书签。你现在能做什么?

你可以在例如Transform - Spark SQL节点 中过滤掉数据,但这并不能真正解决问题--作业仍然会处理所有文件,你只是过滤掉输出或进入聚合的数据。你必须弄清楚如何 "逻辑地 "划分这些文件。

与其直接处理S3文件,不如尝试将S3文件组织成数据库和表格。也许这样你就能更有效地查询数据。为此,请使用AWS Glue Crawler。

简而言之,AWS Glue可以将S3文件组合成表,可以根据它们的路径进行分区。例如,如果你的文件被组织如下。

bucket1/年/月/日/文件.csv

那么AWS Glue可以从bucket1的所有文件中创建一个表,该表将按年、月、日进行分区。分区的创建级别也是可以定义的,例如,你可以为每一个独立的日、月或年创建一个表。你会在这篇关于在AWS Glue中处理分区数据的文章中找到更多细节。

现在,有两个想法供你测试。

1.为每一天创建一个单独的表。

2.创建一个表,按年、月、日进行分区。

但在你创建数据库和表之前,你必须重新组织你的S3桶的结构,从。

bucket/YYYYMMDD_data.csv

到。

bucket/year/month/day/data

所以,与其说是

aws-glue-demo-2021/inputs/20210901_data.csv

aws-glue-demo-2021/inputs/20210902_data.csv

aws-glue-demo-2021/inputs/20210903_data.csv

aws-glue-demo-2021/inputs/20210904_data.csv

aws-glue-demo-2021/inputs/20210905_data.csv

你的文件将以这种方式组织。

aws-glue-demo-2021/inputs/2021/09/01/data.csv

aws-glue-demo-2021/inputs/2021/09/02/data.csv

aws-glue-demo-2021/inputs/2021/09/03/data.csv

aws-glue-demo-2021/inputs/2021/09/04/data.csv

aws-glue-demo-2021/inputs/2021/09/05/data.csv

一旦完成,你就可以开始使用AWS Glue Crawler(它也可以从AWS Glue Studio面板的Glue Console标签中获得。)

AWS Glue Studio - access Glue Crawlers

首先,配置一个爬虫,它将从所有文件中创建一个单一的表格。

点击添加爬虫,然后。

  1. 将爬虫命名为get-sales-data-partitioned,然后点击Next

  2. 保持爬虫源类型为默认设置(爬虫源类型:数据存储和 抓取所有文件夹),然后再次点击下一步

  3. 选择S3作为数据存储,并指定路径为s3://aws-glue-demo-202109/inputs,然后点击下一步

  4. 不,你不想添加另一个数据存储。

  5. 现在,你可以选择一个现有的角色或创建一个新的角色。来吧,创建一个新的。

  6. 现在,选择频率。按需运行。

  7. 为了存储爬虫的输出,创建一个名为sales_partitioned的数据库,并从下拉菜单中选择创建的数据库。在配置选项中,选择忽略变化,不更新数据目录中的表从数据目录中删除表和分区 (我将在后面解释原因) 然后点击下一步

  8. 审查你的爬虫并确认。

AWS Glue Studio - crawler Creation Steps

对于将创建独立表的爬虫,其过程基本相同;唯一的变化是在以下步骤中。

  • 第1步:将其命名为get-sales-data-partitioned-sep。

  • 第2步:选择一个已经存在的角色(为以前的爬虫创建的。)

  • 第7步:在配置爬虫输出中,创建一个名为sales_partitioned_sep 的新数据库*。* 在S3的组行为--表级别, 输入5。为什么是5?好吧,从头算起:是第一层,输入是第二层,年是第三层,月是第四层,而 是第五层*。*

AWS Studio - crawler Create Database

接下来,审查你的爬虫并保存它。在这一点上,你应该有两个爬虫,它们将创建两个独立的数据库--运行这两个数据库并等待。

过了一会儿,你可以看到get-sales-data-partitioned爬虫创建了一个表,get-sales-data-partitioned-sep创建了九个表。

*AWS Glue Studio - glue crawlers*为什么是九个?很可能是因为有人在这期间创建了新的文件。你可以通过进入数据库找到答案,在那里你应该看到你的数据库。

AWS Glue Studio - all databases catalog

sales_partitioned 中,你应该看到一个表。

AWS Glue Studio - sales_partioned catalog

而在sales_partitioned_sep中*,*你应该看到九个表。

AWS Glue Studio - sales_partitioned_sep catalog

第一件引人注目的事情是,其中一个在两个月内重复的表得到了一个丑陋的后缀。但让我们去看看input_partitioned表。在底部你应该看到列,这是你的分区。

AWS Glue studio - edit schema

注意: 现在有必要解释一下,为什么我之前在创建爬虫时勾选了忽略变化,不更新数据目录中的表 这个选项。如果我没有选择这个选项,明天运行爬虫,我给出的列名*(年、月、日*)就会被覆盖回分区_0、分区_1分区_2。

现在,回到你的工作,有哪些选项?在左边,你可以看到我可以从sales_partitioned_sep数据库中选择单个表。在右边,我只有一个表可以选择,但我可以发送分区谓词。

AWS Glue Studio - partition predicates

这两种方法都减少了初始数据的数量。然而,第二种方法有几处我不喜欢的地方。

  • 表的名称没有明确指出(至少在我的情况下)我们正在处理的数据。

  • 当我需要处理超过一天的数据时,我将不得不添加单独的源节点并创建连接,而在第一个解决方案中,我只需要修改困境推倒中的查询。

好的,所以我对第一个方案很满意,但是,在我们继续之前,这里有几个注意事项。

现在我们正在使用数据目录表 ,而不是直接使用S3文件,我们应该注意到,文件的输出已经改变了。我们不再有选择字段--现在数据类型是由Glue Crawler定义的。

那么,如何才能改变这一点呢?有三种选择。

  • 你可以在Glue数据目录中编辑模式类型(与我们改变分区名称的方式相同)。

  • 你可以在第一步中改变类型(就像之前的工作)。

  • 你可以添加一个单独的转换节点来改变类型。

我选择了第三个选项。为什么呢?首先,因为我想让大家看到这一步正在进行(我们正在期待一些数据类型。)其次,因为我想向大家展示另一个转换。

所以,有了分区谓词和应用数据类型的额外步骤,我们的工作看起来像这样。

AWS Glue Studio - Apply mapping

现在,回到分析员的问题上。

首先,让我们来解决项目返回。你想过滤掉单位价格为负数的行,并把它们保存在一个单独的文件中。在setDatatypes节点 之后添加两个额外的转换--过滤节点。第一个*(getSales*)获取unit_price >= 0的记录;第二个*(getReturns)获取unit_price*< 0的记录。在getSales之后,进行聚合,然后保存到S3;在getReturns之后,只需保存到S3。

AWS Glue Studio

注意: 似乎你不能在一个步骤中结合or 运算符。如果你真的需要这样做,你应该使用Spark SQL节点并编写一个自定义的SQL查询。

下一步是什么?尝试从计算中删除客户。现在,这个有点棘手--你想在使用Transform - Filter节点 设置数据类型后将其过滤掉,但这一步仅限于"=, !=, <, >, <=, >=",而且使用Null或空字符串的!=是不可行的...

AWS Glue Studio - remove empty customers

所以,也许你可以在设置数据类型之前过滤掉?嗯,你不能这么做--在应用数据类型之前,你只能用regex过滤匹配值的列。

AWS Glue Studio - language selection box

那么,剩下的是什么?再一次,你必须使用Spark SQL转换来写一个查询,这将过滤掉空值。关键功能是*isnotnull(column) = 1。*我把这个步骤放在应用数据类型之后,过滤掉折扣之前。

AWS Glue Studio - Spark SQL transformation

接下来,我们来处理输出文件的分区数量。在这里我不得不放弃--不使用自定义转换就无法完成(好吧,至少我没弄明白怎么做。)这很难吗?

嗯,这取决于。文档(AWS doc:transforms custom)不是很广泛,只包含一个例子。最重要的是要记住,自定义转换节点只接受glueContextDynamicFrameCollection作为输入,还必须返回DynamicFrameCollectionsDynamicFrames的集合)作为输出。

这意味着什么呢?这意味着为了执行任何转换,在第一步,你必须选择你想对哪个动态框架进行处理。如果转换只有一个父类,那就没有问题了--你选择第一个,然后将DynamicFrame转换为DataFrame

def reducePartitionNumber(glueContext, dfc) -> DynamicFrameCollection:

df = dfc.select(list(dfc.key())[0]).toDF()

然后,你可以做任何你可能想要的Spark转换。在我的例子中,我想减少创建的分区数量,所以我这样做。

df_w_less_partitions = df.coalesce(1)

然后我把数据框架转换回DynamicFrame并作为DynamicFrames集合返回。

df_one_partition = DynamicFrame.fromDF(df_w_less_partitions, glueContext, "one_part_df")

return DynamicFrameCollection({"CustomTransform0": df_one_partition}, glueContext)

总而言之,这个节点应该是这样的。

AWS Glue Studio - node should look like this

但这还不是结束。根据文档和你在上面看到的,"一个自定义代码转换会返回一个动态框架的集合,即使在结果集中只有一个动态框架。"不幸的是,输出节点接受动态框架集合作为输入,所以你将不得不再添加一个步骤,从集合中选择一个特定的动态框架

对于这个步骤,你可以使用SelectFromCollection转换节点,它允许你指出你要使用的数据集。因为这里只有一个父节点,所以只有一个数据集可以选择。

但是如果这个节点有几个父节点或者一个父节点返回许多动态框架(例如SplitFields转换,它将数据框架分割成两个独立的数据框架),你就有选择了。

AWS Glue Studio - SelectFromCollection

做完这些,我们就可以进入最后一项任务:用聚合器将客户信息添加到数据框中。我的分析师把customers.csv文件加载到主文件夹 aws-glue-demo-202109。文件包含以下数据:ID、名字、姓氏、地址。

在我们的工作中,我们首先添加另一个源,它将直接引用S3桶中包含的CSV文件。我给这个节点命名为Customers。AWS Glue Studio会检测数据格式、分隔符并自行定义数据类型。

AWS Glue Studio

然后我们将使用Transform - Join节点 来结合两个来源的数据。这种类型至少需要两个父节点,所以我添加aggByCustomers节点 作为第二个来源。

为什么我在这里加入?好吧,我担心在减少分区数量(用getReducedDynamicFrame节点)后的连接会再次导致多个分区,而更早的连接(在执行聚合之前)效率较低,因为该连接将在更多的行上执行。

AWS Glue Studio - Transform - join node

现在你需要选择连接类型并声明连接条件。因为我的左边数据集是Customers节点,右边是aggByCustomers,而且我知道我没有所有客户的数据,所以我选择右键连接。我使用列idcustomer_id连接这些数据集。

AWS Glue Studio - join type

最后要做的是将reducePartitionsNumber 的父输出从aggByCustomers 改为joinAggregatesWithCustomers,这样就可以了。现在这个作业应该是这样的。

AWS Glue Studio - finished node

让我们运行它,看看会发生什么。一旦作业完成,到S3去,...

......它开始工作了!你只收到两个文件。

AWS Glue Studio - output two files

在第一个文件中,你会看到客户及其数据(如果找到的话)和聚合。

customers with their data (if found) and aggregates

在第二个文件中,你会有返回的项目。

AWS Glue Studio - returned items

嗯,坦率地说,我不喜欢这些列的顺序,因为当连接没有找到客户的数据时,它看起来像这样。

除了另一个Spark SQL节点,我没有看到任何其他的方法来重新排列它们,我只是以不同的顺序选择列。但我们还是保持原样吧。另外,我还可以使用一个FillMissingValues转换节点,并输入例如 "NOT_FOUND"来填补缺失的值,使文件更易读。