由于Hadoop框架是用Java编写的,大多数Hadoop开发人员自然更喜欢用Java编写UDF。然而,Apache也使非Java开发人员能够轻松地使用Hadoop,这是通过使用Hadoop Streaming接口完成的!
Java 实现 UDF,需要引用包含 Hive API 的外部 jar 包,而 Python 无需引起其他外部包;
Java 实现 UDF 后,需要打包后才可被 HiveQL 调用,而通过 Python 实现 UDF 后,可以在 HiveQL 中直接被调用;
Java 实现 UDF,对读入和输出数据方式没有要求,实现的 UDF 可以输入一条记录的指定列数据,输出结果可以直接在 HiveQL 的 WHERE 中用于判断条件使用;Python 实现的 UDF,对读入和输出数据方式有特殊要求,需要对 HiveQL 中表的指定列数据批量读入,然后一对一地批量输出,因此,通过 Python 实现的 UDF 可以结合子查询使用。
Python-UDF开发流程
employees.py:
# -*- coding: utf-8 -*-
import sys
for line in sys.stdin:
line = line.strip()
(emp_id,emp_name) = line.split('\t')
"{}\t{}".format(emp_id, emp_name + ',亲')
Note: py代码最好都加上# -*- coding: utf-8 -*-
通过 Python 实现 Hive 的 UDF,Python 脚本需要以sys.stdin和print的方式读入和输出。Python 实现的 UDF,需要批量的读入数据,并一对一的批量输出。
如果udf代码中通过print输出的数据中通过\t分割的数据个数 小于 hive的接收字段,则后面的字段都会是null。
py udf中stdout是数据流,不是输出流。
要输出信息,需要将log打印到stderr中。
方式1:
print('cnt:{}'.format(cnt), file=sys.stderr)
方式2:
def log(msg):
t = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
sys.stderr.write('{} {}\n'.format(t, msg))
sys.stderr.flush()
需要将自定义Python数据处理文件添加到路径中,然后在查询中应用transform函数。
示例1:
add file /path/employees.py;
select
transform(emp_id, emp_name) using 'python employees.py' as (emp_id, emp_name)
from
employees;
其中, SELECT 中的 columns 是 FROM 中 table 的列名, 而 AS 中的 columns 是经过 USING 中 Python 脚本 python_script 计算返回的列名。
使用transform的时候不能查询别的列。
示例2.1:
2.1相对2.2可能还好点,因为没有了后面join的资源消耗。
如果主要是cpu密集计算,内存不紧张,2.1更好。
ADD ARCHIVE hdfs://path/python.zip;
ADD FILE HDFS路径/.../dir;
select
TRANSFORM(
uuid,
a,b,c,d
txt
) USING 'python.zip/bin/python dir/process.py' as (
uuid string,
....
label bigint
)
from t1;
示例2.2:
也可以把主键id和要处理的列传进去一起返回,再通过主键id去join原表。
这样内存用的少点,不过join可能会更耗时。
ADD ARCHIVE hdfs://path/python.zip;
ADD FILE HDFS路径/.../dir;
select *
from t1
join(
select
TRANSFORM(
uuid,
txt
) USING 'python.zip/bin/python dir/process.py' as (
uuid string,
label bigint
)
from t1
) t2 on t1.uuid = t2.uuid
[使用Python编写Hive UDF]
Note:
1 使用默认的py可能只需要:
ADD FILE /path-to-my-script/my_python_code.py;
USING '/path-to-my-script/my_python_code.py'
也可能是py代码里面指定了#!/usr/local/bin/python。
[How to create a custom UDF for Hive using Python - Cloudera Community - 248486]
2 如果未配置py可能出错:Log Type: syslog_attempt_***
Caused by: java.io.IOException: Cannot run program "py_env.zip/bin/python": error=2, No such file or directory
解决:ADD ARCHIVE hdfs://.../snapshot/py_env.zip;
model_path = sys.argv[1]
也可以自己构建py环境:
ADD ARCHIVE hdfs://path/python.zip;
然后通过using 'python.zip/bin/python employees.py'来使用指定py环境。
ADD ARCHIVE hdfs://.../pyenv.tar.gz; # 环境压缩包可放到git代码同目录,只要是可访问即可。
USING 'pycpu.tar.gz/bin/python dir/inferrence.py'
构建python包的方式可能有:
1 如果平台支持,直接写requirement文件自动安装并构建包。
2 本地打包成压缩包再上传。Note: conda打包可参考,建议重新创建py环境,只构建必须用的包。[打包运行环境conda-pack]
Note: 不要使用add file,这种添加不会自动将压缩包解压,只能通过add archive。否则出错Caused by: java.io.IOException: Cannot run program "dir/pyenv.tar.gz/pyenv.tar/bin/python": error=20, Not a directory
方式1(推荐):
先上传文件或者git地址到hdfs,都放在HDFS路径/*下。
直接在hive中读入目录,注意目录在命令中的写法形式
ADD FILE HDFS路径/.../models; -- 注意这里的basedir=models
select
TRANSFORM (id, text) using 'py_env.zip/bin/python inference.py models/submodel/onnx' as id, label, score -- 这里的文件需要带上basedir
Note:
1 使用ADD FILEs和ADD FILE貌似没啥区别,ADD FILE也可以加整个目录。
2 当然也可以ADD FILE hdfs://.../onnx;...inference.py onnx'...。不过必须保证add file的最后一级目录要写到using字符串命令里面,否则读取不到数据。
3 transformers模型读取时如果读取不到目录里面的文件,就可以报错:AutoConfig.from_pretrained
There was a problem when trying to write in your cache folder (/home/.cache/huggingface/hub). You should set the environment variable TRANSFORMERS_CACHE to a writable directory.
PermissionError: [Errno 13] Permission denied: '/home/.cache'[Accessible location for cache folder]或者Caused by: org.apache.hadoop.ipc.RemoteException(java.io.FileNotFoundException): Path is not a file
4 pyudf中目录读取
Note: python代码中如果要读取文件,文件名都需要带上basedir,如这里是models/***。
log("str(os.getcwd()):\n" + str(os.getcwd()))
log("model_path:\n" + model_path)
for root, ds, fs in os.walk(os.getcwd()):
for f in fs:
log(os.path.join(root, f))
str(os.getcwd()):
/***/hadoop/yarn/***/appcache/application_***/container_***
model_path:
models/submodel/onnx
walk整个os.getcwd()目录的结果:
/***/hadoop/yarn/***/appcache/application_***/container_***/launch_container.sh
/***/hadoop/yarn/***/appcache/application_***/container_***/container_tokens
/***/hadoop/yarn/***/appcache/application_***/container_***/tez-conf.pb
/***/hadoop/yarn/***/appcache/application_***/container_***/hive-exec-***.jar
/***/hadoop/yarn/***/appcache/application_***/container_***/inference.py
/***/hadoop/yarn/***/appcache/application_***/container_***/user-resource/warehouse-udf-dist.jar
Note: walk整个目录没看到model_path,可能是在jar包或者conf指定的某个目录下吧。
方式2:
将目录压缩成zip文件,在hive中读入zip文件,再在代码里面解压
import zipfile
model_path = sys.argv[1]
zip_obj = zipfile.ZipFile(model_path, 'r')
zip_obj.extractall('model_name')
zip_obj.close()
model_path = 'model_name'
[zipfile --- 使用ZIP存档 — Python 3.11.4 文档]
方式3:也可以打包成zip文件后直接通过目录调用
如将py文件(或所在文件夹dir/subdir/)直接打包成zip,然后应该是自动解压的,直接通过目录调用py文件就可以。
ADD ARCHIVE hdfs://***snapshot.zip;
TRANSFORM(***) USING '***/python snapshot.zip/dir/subdir/***.py'
类似上面的[py udf读取目录-方式1]
ADD FILE HDFS路径/.../basedir;
select
TRANSFORM (txt) using 'py_env.zip/bin/python basedir/process.py' as txt, aug_txt
此时process.py是可以直接import basedir目录下的其它目录的。
这里直接import sub_dir.***即可。不需要像读取目录一样,非要basedir/sub_dir/***。
否则出错:ModuleNotFoundError: No module named '***'
ADD ARCHIVE hdfs://path/python3.zip;
ADD FILE hdfs://path/lr_binary_parameter.json;
ADD FILE hdfs://path/lr_predict.py;
SELECT
TRANSFORM(id,f1,f2,f3)
USING 'python3.zip/python36/bin/python lr_predict.py lr_binary_parameter.json binary'
AS (id BIGINT,lr_score DOUBLE);
[Scikit-learn:分类classification_-柚子皮-的博客-CSDN博客]
Caused by: java.io.IOException: Broken pipe
这个错误其实可能是任何py代码中的错误。主要去看log里面的Log Type: stderr。
from:hive:创建自定义python UDF_python实现hive自定义函数_-柚子皮-的博客-CSDN博客
ref: