Spark最佳实践-项目规范

前言

大数据开发的日常工作中,开发人员经常需要使用 Spark、Flink 等计算引擎作为工具来实现一些 业务逻辑 的计算。

以 Spark 为例,开发人员会使用 SparkSQL、DataFrame、RDD 等不同形式的API来实现业务需求。

通常情况下,简单的需求都可以通过 SparkSQL、DataFrame 很方便的实现,其简洁的API也是其深受数据分析师青睐的原因之一。

但是正是因为 SparkSQL、DataFrame 的高层次封装,在 复杂度较高的计算需求 实现中,可能会出现 实现复杂或者API的功能性无法满足,或者千方百计实现需求之后 性能表现低下,代码段复杂而庞大 的情况。

尽管Spark允许开发人员通过UDF、UDAF等形式提供自定义的函数功能,但是此时很多人会选择使用较为底层的RDD接口进行开发:可控性好、开发与调试方便、性能强劲

但是使用RDD接口来开发业务需求时,很多小的项目团队并没有一个统一的项目规范,需求开发完全由开发人员个人自己发挥。

各个业务项目的大致流程基本是相同的:

  1. 创建SparkSession
  2. 用 spark.table or spark.textFile 等API读取数据源
  3. 进行RDD的各种 Transformation 和 Action 操作
  4. 得到数据结果之后再调用 saveAsTable or saveAsTextFile 写入外部数据源中

虽然看起来流程挺一致的,但是其中仍然存在以下问题:

  • 业务代码混乱
  • 团队成员代码风格不一,有的喜欢一长串一长串的写,有的喜欢将过程封装
  • 即使将过程封装了,但是封装的边界没有明确定义,仍然会有人不小心“越界”
  • 读写数据源的API使用不统一
  • 各个计算引擎对各个数据源都有不同的读写API接口提供使用,一些比较繁杂的API可能会被人“错误”使用
  • 同时也会有人时常忘记对应接口如何使用,反复查阅资料
  • 重复的编码工作
  • 理论上所有业务项目,除了业务逻辑是变化的之外,其余应该都是一个不变的模板
  • 开发人员应该专注于变化的业务逻辑,而不是每次都要分一些精力出来处理其他“边边角角”的事情

没有规范任由团队成员发挥的话,尽管有些成员能写一手漂亮的代码,但是你并不能保证所有人都这么优秀。

时间一久项目中代码的 坏味道 会越来越多,最后混乱的程度可能会超出你的想象。

为了解决以上问题,我们建议:定义一个项目规范,所有业务项目都需要遵守这个规范。

俗话说,有规矩成方圆

有了项目规范,所有人都遵守这个标准来开发。

有了这个标准,我们就可以在标准化的基础上做很多事情,比如 定义自动化工具来帮助开发人员解放双手

本文讨论的项目规范可以作为一种参考,以供读者与相关开发人员翻阅。

一、项目规范

和Java项目规范类似,以 模块化项目 的结构来定义项目规范可以为业务项目提供 结构化标准,其可以规整所有 混乱的业务项目结构

项目结构标准化的重要性:

  • 项目统一管理与生成
  • 方便快速搭框架
  • 所有开发人员遵守相同的编码规范
  • 易于交接与维护

以下模块划分和Java项目类似,略微有些细节差异。

1.1 api模块

业务计算逻辑模块,不应该出现任何 Spark等执行框架的API保持模块独立性与可移植

理论上该模块可以独立构成一个单机程序执行,这样可以将最重要的业务逻辑根据需要迁移到任意计算引擎中,如 Spark 到 Spark Streaming、Flink 甚至 Hive UDF 等。

  • 对外只提供接口调用,不可直接在外部实例化具体类(工厂模式)
  • 所有service业务逻辑需要有对应的测试用例
  • 事务控制、所有异常捕获和处理
  • 依赖common

1.2 common模块

项目内通用的常量、枚举、POJO实体类、工具函数等,视情况分离,可集成到 context 中

  • 不包含任何业务逻辑
  • 不依赖其他模块
  • 相关工具保持单例

1.3 context模块

Spark或者其他程序 执行入口,负责初始化各种计算引擎的环境变量。

  • 系统 全局配置(conf)与脚本(bin) 集中管理
  • 依赖server、api、common
  • 程序关键点需要打印日志以便后续debug使用

1.4 server模块

整个项目中整合了业务逻辑调用、数据源读写等操作的模块,需求简单的情况下可以直接集成到 context 中。

该模块中根据不同的接口操作类,还划分了 dal、service与manager三个包。

1.4.1 dal包

主要是对数据进行操作,如读写常用的库:Hive、MySQL、HBase;以及读写文件系统:HDFS。

dal中的所有使用都由接口来定义,不同的接口实现使用不同的应用框架API,如Spark、Flink应该为两个独立的dal实现,在后续service使用过程中可以自由切换。

需要遵循以下原则:

  • 所有bean对象,定义在dal
  • 不得在dal写各种业务逻辑、数据清洗逻辑
  • 一张表对应一个dal接口、一个bean,对应多个独立的dao实现
  • 不允许在1个dao中同时操作多个表

包结构如下:

  • basic: basic包下主要放一些基础对象,如BaseDao,所有dao都需要完善 TABLE_NAME
  • bean: 定义数据源表结构,不同的数据源可以定义在不同的包中,如hive、hbase、mysql等
  • dao: 接口具体实现,用来操作数据表。如:增删改查

1.4.2 service包

和dao对接,一个service对应一个dao,service的使用都由接口来定义。

一个service下有两个实现包:

  • 正常实现包:直接对接dao,简单处理一些判断:如参数不合法校验等。
  • 测试实现包:模拟数据,可以不通过dao获取,从本地文件生成或代码中生成。

不同的计算框架有不同的service实现,如spark、flink等(需要传入其环境变量)。

1.4.3 manager包

  • 调用service包实现数据增删改查
  • 调用api模块进行业务逻辑组合
  • 提供函数接口给context模块调用执行

二、代码框架

基于以上项目模块的划分,我们可以看到,api、common是 每次都会变化的业务逻辑和通用属性的抽取,而 context 是根据业务需要的计算引擎和运行环境设置的 执行入口

以上三个模块都是 根据业务需求变化比较大的,而server模块则是负责对 其他各个模块的调用与整合,最后通过 manager 提供统一的函数接口给 context 入口调用执行。

所以 server 模块是这个项目规范中可以 自动化 起来的重点目标。

基于这个目标,我们开发了一个 大数据业务开发 基准项目的雏形,开发人员能够做到开箱即用,不必再花太多精力在研究计算引擎与各个数据源的接口和API如何调用,专注于业务逻辑的实现,提升开发效率。

项目地址:https://github.com/chubbyjiang/aisql-bigdata-base

使用介绍

org.aisql.bigdata.base.framework 包中提供了几种常见大数据项目需要用到的数据源。

framework 以 模块化项目 的结构提供了 各个数据源基础的Dao、Service接口与默认实现

配合自动化的代码生成工具,可以一键生成 server 模块的代码文件直接使用。

现在我们来看一下规范+自动化的威力,例如现有 default.t_users 表需要读取。

开发人员仅需要生成代码文件并复制到项目中,写代码如下:

val service = new UsersService
//读取整个表
val allRdd:RDD[Users] = service.selectAll()
//字段筛选
val allRdd:RDD[Users] = service.selectAllWithCols(Seq(“name”,”age”))
//条件过滤
val allRdd:RDD[Users] = service.selectAllByWhere(“age>10”)
//读取1000条数据
val demoRdd:RDD[Users] = service.selectDemo()
//写入表
service.insertInto(demoRDD)
service.createTable(demoRDD)

是不是 so easy?

其实所做的内容也就是在 server 模块中封装了 常用的不同计算引擎对不同数据源的读写操作API,并 自动化了 bean、dal、service 三个部分的代码生成

使得开发人员可以直接使用 service 提供的数据操作接口 读出数据源调用业务计算逻辑 处理完毕后 写入数据源 中。

通过对项目模块的标准化规范,我们可以以一个 比较统一和简单易懂的开发方式 来进行需求落地。

虽然刚开始使用规范的时候会有人觉得繁琐与不耐烦,如果是手动开发的话谁都会烦,都是一些重复性的苦力活儿,这就是框架规范的缺点:特别繁琐

但是配套做一些自动化工具来使用的话,相信大部分开发人员都会觉得很酸爽,某种程度上标准化项目就是这么来提升开发效率的。

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注