实践背景:
将一段存在五重子查询嵌套与数据转换计算的Oracle SP(Sql Procedure)用Spark SQL实现。并且采用Java进行开发(不能用最爱的Scala了。。。)
这段SQL的核心逻辑接近千行代码,背后涉及到的关联表接近10个。没有文档,没有表ER图可供参考。我更愿将其定义为传统计算模型在大数据背景下的技术转型,或说是升级。
在此将采用Spark SQL的sql开发模式,一般在Spark SQL开发环境下可以选择使用sql(也就是sparkSession.sql(…))或者Spark API方式进行对应的业务开发,两者各有优劣,详细对比请查阅相关资料。
在此主要采用sql方式。
实现过程
1、业务背景学习
针对这样的转换,仅仅是针对SP去进行转换是不行的,Oracle与Spark SQL在API层面还是有许多差别的,我们就称之为SQL兼容问题吧。在trouble shotting的时候如果能够对业务背景有着一定的把握,可以减少很多不必要的损耗,让调bug之路更加顺畅。
而如果在没有详细文档可供参考的情况下,向小伙伴请教就成了不二之选了。
2、SP代码分析报告
拿到这样一个复杂SP当然得看看是否可以进行模块划分啦,一般情况下都是可以的。这就像是代码设计一样,试着回想你有在企业级项目中看到过用高级语言写的function长达500行代码吗?如果有,那我想这个项目的开发维护进程多半是坎坷的。
在对SP进行代码层面观察的时候发现几个代码模块是有着极高相似度的,这个时候就可以考虑是否可以进行模板抽取进行复用了。
此阶段形成的书面文档于后续的开发具有一定的指导性作用。
3、业务细化回溯
针对以上分析报告当然是要请一起参与开发的小伙伴帮忙看看了,聚集团队的力量探讨改进不足的地方。就比如说在某个业务代码模块running在Oracle上是perfect的,但在Spark上却有可能出现兼容或说是适用性问题,集中式与分布式运算还是有挺大差别的。
4、代码设计
经过了前面几轮工作,对SP业务原貌有了更多的把握后就可以进入代码设计阶段了。在这个时候需要考虑scope的问题了;你的代码设计仅仅针对的是这一个SP的转换还是需要提供更多的扩展支持呢,SP会有变动的可能性吗?设计没有一个特定的标准,针对具体的业务背景和需求做出恰到好处的设计实在重中之重。这个时候设计模式相关成了必备的一个知识。
5、代码实现
注意代码开发风格,参考阿里代码开发手册。
6、测试
如果可以,搞个自动化测试检测所有的cell数据是否和原来的SP能够对上!仅凭人工检查部分数据对上是不够的。
踩过的坑
语法兼容问题
在这样的SP中一般会涉及到各种系统函数的转换操作,此时就得考虑在将SP上的代码运用到Spark上是二者的Sql API或者相关语法是否兼容了。就比如说在Oracle上可以直接使用“+”
运算符进行日期的递增,而在Spark Sql直接用这种方法进行计算得到的就是null,在排查出问题后使用date_add()
函数就解决了。通过这次的实践来看:对应数据表cell上显示为null,而检查代码数据本应该是有值并且基本代码逻辑没问题的情况下,就应该考虑是否API兼容问题了。而在显示为null的情况下Spark不会报错,这种问题出现若不是有着严密的测试是很难用肉眼去发现的。当然这种情况还是相对较少的,多数情况下的不兼容在运行阶段就会报错,如此SqlAnalysis错误就比较常见了。
sql代码放哪里
此处针对原有SP代码有着大量的sql可以直接复用,同时为了避免大段sql出现在java代码块中照成不必要的维护困难与sql侵入,此处将sql放在properties文件中,同时利用propertiesUtil工具类对这些sql进行初步解析处理;其实就是字符串处理了。
相关优化
将近五层的查询嵌套尽然产生了五百多个stage,是catalyst不够给力?
在采用Spark Sql方式进行转换中需要将每一个子查询进行tempView抽取,一个转换过程中可能会产生几十个tempView。且不说这种繁琐的工作没有太多技术含量并且容易出错,光是创建tempView进行迭代计算的时候就已经在代码层面造成了字面量浸染;名副其实的搬砖。当然,此处肯定是有着更好的解决方案,我想自身的代码设计能力还是有很大的提升空间的。即使是搬砖,咋也不能用裸手去搬吧,开着铲车去搬效率也天差地别呀!
当然此处除了代码设计层面,还有其他可优化的地方。除了基本的Spark Core相关优化,Spark Sql本身就对我们的Sql解析运行做了许多优化,比如DataSet的Kyro序列化、广播小表、谓词下推等等。其他相关优化可见:
方法可行性反思
针对这个demo,其实各方面的问题还是挺多的。选择大于努力,我相信所有技术的问题都会有解决方案;而在技术选型的问题上,如果我们能够选用最适合的方案,是否会减少很多不必要的问题呢?换句话说,如果此处的目标是在大数据场景下将原本RDB集中式计算的数据升级迁移到分布式计算架构。那么此处采用Hive会不会更好呢?毕竟Spark作为一个运算引擎,我们完全可以配置Hive on Spark实现基于内存为主的高效数据处理。而另一方面,就目前的版本(Spark 2.2)来看,Spark Sql对于复杂嵌套子查询似乎并没有很好的支持,否则也不会出现一个个的tempView。
当然,对于Hive的转换,也同样有待进一步的实践考证。