在ODPS平台进行数据处理和分析,有时候进行复杂的数据开发需要使用UDF,JAVA版本的UDF暂且不提,这里只讲述基于Python进行UDF编写,而如何基于Python开发UDF,以及如何进行第三方依赖包的配置,包括如何编译生成Wheel包。大概有几种方法。
总结大概过程,Python UDF开发及使用主要分为几步:
而至于为什么需要自己上传资源包,首先我们知道ODPS是一个分布式的储存和计算框架,如果需要运行某个python package,就需要在每一个worker上保证都可以需要运行这个包,但是如果平台给全部的worker安装统一的python环境,不仅不灵活且特别耗费资源,毕竟每个用户的计算所需要的python dependency可能是非常不同的。
ODPS解决这个问题的方法是通过上传资源的依赖文件的方式。就是说每次运行一个用户自定义的计算时(UDF),ODPS都会把这个UDF所依赖的所有的资源都传输到分配的worker节点,然后在worker节点运行这些依赖的资源。
当依赖包比较单一或者比较少时,使用上述方法是可行的,但是当依赖包比较多、比较复杂时,就会面临几个比较大的问题:
所以就需要一种更省事的方法。况且手动寻找兼容 ODPS 的依赖也比较耗费时间和精力,也容易出错,况且依赖一般还有自己的依赖。
基于上述应用需求,结合别的几篇打包Python依赖上ODPS跑的经验的文章,加了一些自己的优化和实践,总结了本文。主要步骤分以下几步:
对于mac电脑,可以安装:
有的镜像地址非常慢,需要尝试不同的镜像地址,见作者另一篇文章:《Docker配置国内代理解决办法》
本机terminal输入:
docker pull quay.io/pypa/manylinux2010_x86_64
进入到所下载的manylinux镜像的docker环境中,并查看其支持的python版本。
docker run -it quay.io/pypa/manylinux2010_x86_64 /bin/bash
进入容器后执行:
cd /opt/python/
ll
得到:
total 0
lrwxrwxrwx 1 root root 29 Jan 17 05:18 cp310-cp310 -> /opt/_internal/cpython-3.10.2
lrwxrwxrwx 1 root root 29 Jan 17 05:17 cp36-cp36m -> /opt/_internal/cpython-3.6.15
lrwxrwxrwx 1 root root 29 Jan 17 05:18 cp37-cp37m -> /opt/_internal/cpython-3.7.12
lrwxrwxrwx 1 root root 29 Jan 17 05:18 cp38-cp38 -> /opt/_internal/cpython-3.8.12
lrwxrwxrwx 1 root root 29 Jan 17 05:18 cp39-cp39 -> /opt/_internal/cpython-3.9.10
lrwxrwxrwx 1 root root 28 Jan 17 05:18 pp37-pypy37_pp73 -> /opt/_internal/pypy3.7-7.3.7
lrwxrwxrwx 1 root root 28 Jan 17 05:17 pp38-pypy38_pp73 -> /opt/_internal/pypy3.8-7.3.7
如上所示,可以支持pytho3.6、3.7、3.8、3.9、3.10版本
在本地新建一个工作目录,任意命名;
mkdir MyFolder
在里面新建两个文件:一个为项目依赖包清单requirements.txt,一个为build.sh脚本。
numpy
folium
geopandas
在里面填上依赖的包名及版本。也可以:
statsmodels==0.10.1
requests==2.22.0
odps==3.5.1
pandas==0.25.1
numpy==1.17.2
scikit_learn==0.23.2
#!/bin/bash
set -e -x
PROJ=folium_geopandas
PYBIN=/opt/python/cp37-cp37m/bin
"${PYBIN}/pip" install --target __pypackages__ -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/
tar c __pypackages__ | gzip > $PROJ-depends.tar.gz
rm -rf __pypackages__
注释(按照行):
通过docker挂载的方式,生成依赖包的压缩文件包.tar.gz。在本地的terminal中,切换到MyFolder路径之下,运行如下命令:
docker run -it --rm -v $PWD:/build -w /build quay.io/pypa/manylinux2010_x86_64 /bin/bash build.sh
注释:
● 将当前路径(即MyFolder文件夹)中的文件(即requirements.txt和build.sh)挂载到docker中的build目录下,并指定docker环境内的工作路径为build目录——至于docker中是否已存在build目录则无妨
● --rm、-v、-w后面均有空格。
这里会运行的比较慢,见下图,可以看到,不仅找到了numpy、folium、geopandas,还找到了所有的依赖包。


运行完上述代码后,会在MyFolder下生成一个压缩包,即所需要的最终的包。
在MyFolder执行ls,可以看到:
build.sh folium_geopandas-depends.tar.gz requirements.txt
folium_geopandas-depends.tar.gz即为我们所需要的包。
1、在ODPS的资源中以Archive类型上传folium_geopandas-depends.tar.gz并提交。上传资源最大限制200M。
2、Function Studio里,选择UDF,命名print_success.py并提交资源至DataStudio开发环境。
Function Studio为编写 Python UDF 很方便的Web编辑器,当然也可以在DataStudio里直接新建.py资源。
from odps.udf import annotate
@annotate("->string")
class Print_Success(object):
def __init__(self):
import sys
sys.path.insert(0, "work/" "folium_geopandas-depends.tar.gz" "/__pypackages__")
def evaluate(self):
import numpy
import folium
import geopandas
return "import succeed"
Function Studio里,将print_success.py提交函数至DataStudio开发环境,定义函数名为print_success,另外此时需要在额外资源里填入folium_geopandas-depends.tar.gz,如果有多个资源以逗号分割。

odps里,开发环境里输入sql语句测试:
set odps.isolation.session.enable=true;
set odps.sql.python.version=cp37;
SELECT TRY_NUMPY()
;
注释:
输出:
import succeed
如果选择在生产环境应用此函数,需要将folium_geopandas-depends.tar.gz、print_success.py、以及函数print_success发布。