在我之前的文章中,我介绍了以模块化、可维护和可序列化的方式对 Spark 应用程序进行编程的设计模式——这次我演示了如何配置通用的 JSON 和 YAML 解析器以在 Spark 应用程序中使用。
Spark 应用程序通常需要摄取 JSON 数据来转换数据,然后将数据保存在数据源中。另一方面,主要需要 YAML 数据来配置 Spark 作业。在这两种情况下,都需要根据预定义的模板解析数据。在 Java Spark 应用程序中,这些模板是 POJO。如何使用从本地或分布式文件系统获取的数据编写单个解析器方法来处理大量此类 POJO 模板?
这篇文章的组织如下:
- 第 1 节演示了如何配置一个通用的紧凑型 JSON 解析器,
- 第 2 节展示了如何对 YAML 解析器执行相同的操作。
可以在此处找到完全可用的代码。我们走吧。
JSON 解析器。
Spark 应用程序可能需要从本地文件系统或分布式Hadoop 文件系统 (hdfs)读取 JSON 文件。通常在本地情况下,会上传配置数据。另一种场景,当本地 JSON 数据上传时,是在本地模式下运行、测试和调试 Spark 应用程序。在这些情况下,最好有一个 JSON 解析器,我们可以在其中快速在本地文件系统和 hdfs 之间切换。
为了解决这个问题,让我们看看本地文件系统和 hdfs 的数据流有何不同。对于本地文件系统,aDataInputStream
的获取方式如下:
爪哇
1
数据输入流数据输入 流 = 新数据输入 流(
2
新 文件输入流(“src/test/resources/file_name.json”));
另一方面,hdfs 数据流的获取方式要复杂得多:
爪哇
1
最终 SparkSession sparkSession = SparkSession
2
. 建设者()。配置(“//--配置参数--//”)
3
. 应用名称( “ APP_NAME_1 ”)
4
<span style="color:#aa5500">//--启用其他功能--//</span>
5
. 获取或创建();
6
SparkContext sparkContext = sparkSession。火花上下文();
7
最终 配置 hadoopConf = sparkContext。hadoopConfiguration ();
8
SerializableConfiguration scfg = new SerializableConfiguration ( hadoopConf );
9
文件 系统文件系统 = 文件系统。获取(scfg.value ());
10
FSDataInputStream 流 = 文件系统。open ( new Path ( "//--hdfs文件路径--" ));
首先,创建一个 Spark 会话;会话配置为使用 hdfs 和其他分布式数据源 ( Apache Hive )。其次,经过4个中间步骤,得到一个distributed的实例FileSystem
。最后,FSDataInputStream
创建了一个分布式。
本地和分布式文件流是相关的:
爪哇
1
公共 类 FSDataInputStream 扩展 DataInputStream 实现 Seekable等_
因此,本地和分布式数据流可以在具有通用<? extends DataInputStream>
输入流类型的同一解析器中使用。让我们尝试以下解决方案:
爪哇
1
公共 类 ParserService {
2
public static < T extends DataInputStream , B > B parseSingleDeclarationStream ( T inputStream ,
3
Class < B > className )抛出 IOException {
4
Gson gson = 新的 GsonBuilder ()。设置日期格式(“yyyy-MM-dd”)。创建();
5
JsonReader reader = new JsonReader ( new InputStreamReader ( inputStream , "UTF-8" ));
6
B 消息 = gson。fromJson (读者,类名);
7
读者。关闭();
8
返回 消息;
9
}
10
}
我们使用 Gson 库来解析输入流。此外,我们需要为这个解析器提供一个 POJO 类名才能工作。下面是一个如何使用这个解析器的例子(详见代码):
爪哇
1
数据输入流数据输入 流 = 新数据输入 流(
2
新文件 输入流(“src/test/resources/declaration.json”));
3
声明 声明 = ParserService。parseSingleDeclarationStream ( dataInputStream , Declaration.class ) ; _
4
assertEquals(声明。getDeclaration_id (),“ABCDE12345” );
5
assertEquals(声明。getDeclaration_details()。长度, 1 );
6
assertEquals(声明。getDeclaration_details ()[ 0 ] 。getDetail_1(),10);
这里Declaration
andDeclarationDetails
是简单的 POJO:
爪哇
1
公共 类 声明{
2
私有 字符串 声明_id;
3
私有 短 声明版本;
4
私有 短 声明类型;
5
私人 日期 声明日期;
6
私人 声明细节[]声明细节;
7
//--getter 和 setter--//
8
}
9
公共 类 DeclarationDetails {
10
私人 int detail_1 ;
11
私人 int detail_2 ;
12
//--getter 和 setter--//
13
}
所以,这个解析器正确地解析了子数组和对象。请注意,只要字符串符合单个模式字符串(在本例中为“yyyy-MM-dd”),Gson 也会解析日期/时间字符串。
YAML 解析器
在我们的项目中,我们使用本地 YAML 文件来指定 Spark 作业。例如,将数据从 Postgres 数据库传输到 Hive 数据库的基本 Spark 作业配置如下所示:
YAML
1
<span style="color:#0000ff">---</span>
2
工作名称:JOB_1
3
hiveTable : hive_table_1
4
rdbTable : rdb_table_1
5
<span style="color:#0000ff">---</span>
6
工作名称:JOB_2
7
hiveTable : hive_table_2
8
rdbTable : rdb_table_2
在这里,我们需要将数据从关系数据库表(rdbTables)传输到相应的配置单元表(hiveTables)。我们需要上传和解析这个配置数据来得到 a List<JobConfig>
,其中 aJobConfig
是
爪哇
1
公共 类 JobConfig {
2
私人 字符串 作业名;
3
私有 字符串 hiveTable ;
4
私人 字符串 选择;
5
//--setter 和 getter--//
6
}
下面的解析器解决了这个问题:
爪哇
1
公共 类 ParserService {
2
public static < T extends JobConfig > List < T > getLocalFSJobList (字符串 路径,
3
类< T >类 名)抛出FileNotFoundException {
4
列表< T > 列表 = 新的 ArrayList <> ();
5
数据输入流 数据流 = 新数据输入 流(
6
新 文件输入流(路径));
7
YamlDecoder dec = 新 YamlDecoder (数据流);
8
YamlStream < T > 流 = dec。asStreamOfType (类名);
9
而(流。hasNext ()){
10
T 项目 = 流。下一个();
11
列表。添加(项目);
12
}
13
返回 列表;
14
}
15
}
这里我们使用YamlDecoder
来自 jyaml 库。我们使用 local DataInputStream
,虽然子流类,就像前一部分一样,也可以工作。此外,此解析器使用<T extends JobConfig>
输出类型。这种更具体的输出类型允许我们在基本JobConfig
类中添加额外的参数。
请注意,不需要扩展 JobConfig 超类以使该算法起作用。我添加了这个约束,因为通常 YAML 文件是本地文件并指定 Spark 作业,因此不需要更通用的类型。
此解析器以下列方式运行:
爪哇
1
<span style="color:#555555">@测试</span>
2
公共 无效 testYmlParser ()抛出 FileNotFoundException {
3
列出< JobConfig > 作业 = ParserService。getLocalFSJobList ( "src/test/resources/jobs.yml " , JobConfig.class ) ;
4
assertEquals(作业。大小(),2);
5
assertEquals ( jobs.get ( 0 ) .getJobName (), “ JOB_1 ” );
6
assertEquals ( jobs.get ( 0 ) .getSelect ( ) , “ rdbTable_1 ” );
7
assertEquals ( jobs.get ( 1 ) .getJobName (), “ JOB_2 ” );
8
assertEquals ( jobs.get ( 1 ) .getSelect ( ) , “ rdbTable_2 ” );
9
}
解析器正确识别单个文件中的多个作业并将这些作业组合成一个列表。有关详细信息,请参阅代码。
结论
在这篇文章中,我演示了如何编写通用的 JSON 和 YAML 解析器。可以为本地和分布式文件系统轻松配置解析器。此外,解析器可以接受广泛的 POJO 模板来解析数据。希望这些技巧对您的项目有所帮助