本文会穿插各种技术问题
先对bigflow 做个简单介绍:
Baidu Bigflow Python是一个Python module,利用module中的数据抽象和API,你可以书写自己的计算任务。它提供了一套函数式编程风格的API,同时将这些API串联/级联成为
数据流管道(bigflow pipeline)
。Bigflow Python能够将Pipeline映射成为不同分布式计算引擎上的计算任务,例如(Spark,目前开源版本只支持local模式和spark模式),极大地简化分布式计算程序的编写和维护。
使用Bigflow Python主要的收益有:
- 简单易用的API
Bigflow Python提供了对分布式数据和计算的高层抽象API,易学易懂;同时作为一个Python module,你可以在Python交互Shell中使用它。- 任务自动优化
所有的计算均为惰性求值(lazy evaluation),Bigflow Python能够看到尽可能大的计算过程并进行关键参数的自动优化,使用Bigflow Python写出的任务能够与那些对特定引擎非常有经验的程序员相当。
在百度公司内部使用情况来看,Bigflow比用户手工实现的作业性能提升50%~200%。Bigflow开源版本Benchmark正在准备中,敬请期待。
介绍完bigflow,让时间回到一个月前~
当时团队在一起把排期一碰,开始了一场新的征程。
对于项目来说,最重要的两件事是:
一是编译通过&&跑通集成测试,二是优化用户体验。
编译
protobuf问题
遇到的第一个问题是protobuf version的问题。spark 自己的protobuf version 是2.5.0,其他的有用到的版本有2.4.1,有3.x.1。出现了一个问题是3.4.1编译出的 xx.pb.cc, xx.pb.h,被用到了2.4.1中。
最后统一成了2.5.0。解决问题。
顺便还解决了一个问题 java/scala包(A)的protobuf依赖问题。 我们提供了一个c++的so, 然后用A包,去调用so里的函数,但A包里没有protobuf的依赖,需要用spark的protobuf依赖。
之前用的是2.4.1的egg包。后改成protobuf-2.5.0-py2.7.egg
因为我们的protoc 用的是2.5.0的。*.py, .c,.java 等都是其生成的,要对应使用这些文件的程序,必须依赖protobuf 2.5.0.
fPIC
我们编译so,但有一些依赖没有加fPIC。然后添加fPIC。 最难搞的是hdfs的lib,并没有添加fPIC编译参数。然后只能改CMakeLists.txt——`add_compile_options(-fPIC -fpermissive)。
代码漏合
有些代码由于合并方式选择不太对,导致代码漏合。
后面会说一下整体的解决方案。目前问题还不是太大,只是个别文件,所以先手动合并了一下。
thrift
thrift 下载很慢,以及版本不统一。
更换为清华的镜像,并统一为0.9.1版本。
依赖python统一
之前依赖python,分不清楚是依赖的系统的还是自己thirdparty。担心后面同样会带来不可预期的问题。然后把统一改为依赖thirdparty中python2.7,并改为动态依赖。
去 Profiler util 相关代码
目前开源版本并没有添加相关优化,讨论了下,先去掉,后面在考虑添加。
一次编译一个新包
保障更新的代码会被更新到使用tar里面。因为一旦发现包没有完全更新,整个流程就得重新走一边。
这样做可以大大降低成本。
集成测试
spark 集群问题
运行时发现java报错为高版本编译出的结果在低版本jre上运行。
我们编译用的java 是1.8.x的,是那个程序用的是1.7.x一开始没有找到。
这里面有一个大坑是spark web页面上显示的是1.8.x,但实际上是1.7.x。
登录机器发现,yarn 运行是1.7.x。这个是写死在yarn-env.sh中的。这个真的是个大坑哈~~
改了之后,解决问题~
厂内外 hadoop put 的差异
提交作业时,发现报目录不存在。 然后定位发现是:
场内的hadoop put 当目录不存在时,会先创建目录。而社区的需要提前创建目录,再put。
解决方案:在put前,检查目录是不是存在,不存在则用mkdir先创建。
libflpyrt 名字变更
由于libflpyrt.so 和厂内保持不一致导致,作业无法运行。
spark.executor.extraLibraryPath
运行时发现找不到对应的lib库,定位后,发现spark.executor.extraLibraryPath 设置有问题,发现和厂内版本不一致。 也是代码合并时,漏合了。
static 静态变量初始化问题
- 背景:
我们的libbflpyrt.so 需要被加载到spark中运行,so中需要初始化python interpreter。 这个初始化。本需要在spark executor main函数中执行,但是main函数在spark中,无法修改。 以前的做法,是在static 静态变量初始化时,完成python interpreter的初始化。
- 问题:
python interpreter 初始化时,需要用到gflags、glog的库,而这些库,并没有别初始化。 - 方案:
对这样的问题,最好是spark executor 提供相关的接口,初步了解了一下,并没有发现。 目前使用相同的方法实现的:
在task build时,进行初始化,在task release时,进行释放。这样变成了task级别。
问题暂时得到了解决,但在后面跑case的过程中,出现了不可预期的问题——id()函数不可被调用。
这个问题发生,是那么的神奇,感觉简直就是不可思议,完全没有可能。在没有探索之中,想起了之前解决问题时,看到了这么一段话:
Some memory allocated by extension modules may not be freed. Some extensions may not work properly if their initialization routine is called more than once; this can happen if an application calls Py_Initialize()
and Py_Finalize()
more than once.
前面task中,python interper被多次初始化和释放。现在,不就是被多次调用吗,也许就是它,然后改策略为,在task build 中初始化一次, 相当于在main函数中调用初始化。
抽象出一个可以相当于在main函数中调用的全局方式。后面再有这样的调用可以直接添加。至于析构,进程都没有了,随它去吧~
后面继续寻求更优雅的方式。
还有个问题,虽然现在没有了,也记录一下:
中间还遇到interpreter 在析构时,遇到protobuf c 已经析构的问题。本质上也是一个静态变量析构顺序的问题,protobuf 在 interpreter 之前释放了。
解决方法:
protobuf-2.5.0-py2.7.egg中有两种实现方式,c和python的实现。
spark提供了一个可以指定protobuf 使用实现语言的接口,我们指定为python.
"spark.executorEnv.PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION":"python"
添加 LINK_ALL_SYMBOLS
在链接时,只会添加用到的符号,在代码中并没有用到的类,并不会添加相应符号。
具体场景是这样的。libA.a 依赖 libB.a 。在libB.a中有Base类 和其子类Sub1、Sub2,还有一个map<str_name,Base*>
,子类对应的Base*
是在自己的文件中添加到map函数中的。但在libA.a 只有Base *
指针, 通过str_name
在map找对应子类(Sub1)的Base*
。
这样在libA.a并没有用到Sub1, 所以在编译的时候,并没有在把Sub1的符号打到libB.a中,在map中也就没有对应的值,最终导致找不到符号。
一个解决方法,是把符号先都打进来。目前用的是这个。
但感觉应该会有更优雅的方案,后面在了解一下。
注:由于LINK_ALL_SYMBOLS,最终是要打到so里的,就引发了前面介绍的fPIC问题
version 自动生成
为了让用户提交作业更快,bigflow的相关包,并不会每次提交,只有version改变了才会提交。
但每次生成包时,version又没有变化,每次都要手动去生成。这个一旦忘记,成本会很大,很容易出现测试半天,白测的情况。
从手动改成自动生成。
urllib2.urlopen 超时时间。
urllib2.urlopen 需要设置超时时间,但作业跑的时间又不确定,目前先设置为365天。
python 报错not found Default()
因为没有上传,google.protobuf.json_format相关包。
改成了只在使用json_format代码处进行import。这样,json_format相关的函数只会在本地使用,远端并不会调用。
import google.protobuf.json_format as json_format
res = json_format.Parse(response, service_pb2.VoidResponse())
顺便解决了,google.protobuf.json_format (3.x.x)和protobuf-2.5.0-py2.7.egg的冲突问题。
这里要感谢xianjin
的帮助。
hadoop 配置路径
从 conf 变更到etc/hadoop 和场外保持一致。
代码合并
关于厂内外代码合并,这就是一个大坑,之前手动merge,而且还是部分merge,厂外代码也做了修改。导致后续merge无法进行。 而且测试中遇到很多的问题,也是由于merge代码不全导致。
后面还是主席
,把厂内的代码做一个patch,然后提交到厂外。这样合并代码的问题才被解决。
开发模式
大家在一个主干上开发,导致有覆盖代码的情况出现。
然后,采用拉本地分支的方式来做:
提交代码:
git push <local branch>:master
更新代码:
git checkout master && git pull origin master && git checkout <local branch> && git rebase master
后面发现pull request 也很不错:
自己拉一个远程分支,提交后,手动创建pull request,可以让他人评审。
优化用户体验
bigflow 站点,中文网站自动生成脚本构建。
sphinx 编译时,报找不到'sphinxcontrib.napoleon'
换成'sphinx.ext.napoleon'
即可。
主题没有了,换成了html_theme = 'bizstyle'
友好提示
提醒用户设置 JAVA_HOME、HADOOP_HOME。
其他开源准备工作
- doc 目录变更到根目录下。
- 修改example,保障正常运行。
- 删除 dw 等无关代码
- 添加 license文件 和在源文件中添加 copyright
- 功能太复杂,用sed 就是一个大坑。 可以考虑sublime,神器。
bigflow 开源版本运行问题基本完成。优化用户体验工作已经做了部分,后面还有需要继续完善。
感谢
感谢jianwei
支持
感谢yuncong
给予了许多帮助。
感谢 lili
、shanhui
并肩协作以及前期准备。
感谢xianjin
帮忙定位google.protobuf.json_format
等问题。
感谢gonglin
帮忙值周。
感谢团队中每个一起并肩奋斗的伙伴~
小结
bigflow 开源,已经踏出了第一步。
星星之火,开始燎原~
附录
bigflow 官网: https://baidu.github.io/bigflow/zh/index.html
github 地址:https://github.com/baidu/bigflow/