前言
大数据开发的日常工作中,开发人员经常需要使用 Spark、Flink 等计算引擎作为工具来实现一些 业务逻辑 的计算。
以 Spark 为例,开发人员会使用 SparkSQL、DataFrame、RDD 等不同形式的API来实现业务需求。
通常情况下,简单的需求都可以通过 SparkSQL、DataFrame 很方便的实现,其简洁的API也是其深受数据分析师青睐的原因之一。
但是正是因为 SparkSQL、DataFrame 的高层次封装,在 复杂度较高的计算需求 实现中,可能会出现 实现复杂或者API的功能性无法满足,或者千方百计实现需求之后 性能表现低下,代码段复杂而庞大 的情况。
尽管Spark允许开发人员通过UDF、UDAF等形式提供自定义的函数功能,但是此时很多人会选择使用较为底层的RDD接口进行开发:可控性好、开发与调试方便、性能强劲。
但是使用RDD接口来开发业务需求时,很多小的项目团队并没有一个统一的项目规范,需求开发完全由开发人员个人自己发挥。
各个业务项目的大致流程基本是相同的:
- 创建SparkSession
- 用 spark.table or spark.textFile 等API读取数据源
- 进行RDD的各种 Transformation 和 Action 操作
- 得到数据结果之后再调用 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 提供的数据操作接口 读出数据源 后 调用业务计算逻辑 处理完毕后 写入数据源 中。
通过对项目模块的标准化规范,我们可以以一个 比较统一和简单易懂的开发方式 来进行需求落地。
虽然刚开始使用规范的时候会有人觉得繁琐与不耐烦,如果是手动开发的话谁都会烦,都是一些重复性的苦力活儿,这就是框架规范的缺点:特别繁琐。
但是配套做一些自动化工具来使用的话,相信大部分开发人员都会觉得很酸爽,某种程度上标准化项目就是这么来提升开发效率的。