首先声明我的参考博客:http://blog.csdn.net/kent7306/article/details/50200061 http://blog.csdn.net/bdchome/article/details/45843559
一:
说明一下我的整体业务需求:
公司要求将nginx日志数据通过flume存到HDFS,然后通过hive处理将数据插入到一张贴源表里面。
nginx日志数据格式为:16/Jun/2017:16:42:18 +0800^{'data':[{'product':'aAPP','userAgent':'864682030329437|LON-AL00','clickElement':'e_login_login','userId':null,'clickTime':'1497602537'}]}
然后通过hive的load方法指定分割符"^"load进一张临时表,然后通过自定义函数处理数据得到符合自己需要的数据格式。
二:hive自定义函数有三种形式:UDF、UDAF、UDTF
UDF:一进一出
UDAF:多进一出 类似sum()、min()、avg()、max()
UDTF:一进多出,通俗来说就是进来一个字段返回多个字段,符合本需求
三:先通过图片直观的了解一下本需求前后的对比
nginx日志数据:
通过load方法load进临时表的数据(只有两个字段:time_stamp string,name string)name为json格式的数据
经过自定义函数处理后的数据:(只处理name字段)
四:对于本需求有了详细的了解之后,废话不多说直接撸代码
自定义UDTF代码:
package myUDF;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;
/**
* 对APPlog数据的解析处理UDTF
* 处理数据json格式
* {'data':[{'product':'aAPP','userAgent':'860209036790711|vivo X6D','clickElement':'e_main_moreInfo','userId':'3916482','clickTime':'1497502470'}]}
* {'data':[{'product':'iAPP|1.2.0','userAgent':'fc95b2415cc54dd5b3999891acee985e|iPhone6s','clickElement':'e_main_moreInfo','userId':'3960209','clickTime':'1497502470'}]}
* @author
*
*/
public class AppJsonTransformUDTF extends GenericUDTF {
private PrimitiveObjectInspector stringOI = null;
/**
* 定义输入、输出格式的类
*/
@Override
public StructObjectInspector initialize(ObjectInspector[] args) throws UDFArgumentException {
if (args.length != 1) {
throw new UDFArgumentException("NameParserGenericUDTF() takes exactly one argument");
}
if (args[0].getCategory() != ObjectInspector.Category.PRIMITIVE
&& ((PrimitiveObjectInspector) args[0]).getPrimitiveCategory() != PrimitiveObjectInspector.PrimitiveCategory.STRING) {
throw new UDFArgumentException("NameParserGenericUDTF() takes a string as a parameter");
}
// 输入格式(inspectors)
stringOI = (PrimitiveObjectInspector) args[0];
// 输出格式(inspectors) -- 有属性的对象
List<String> fieldNames = new ArrayList<String>(7);
List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(7);
fieldNames.add("product"); //设备
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldNames.add("version"); //APP版本
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldNames.add("device_number"); //设备号
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldNames.add("model"); //型号
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldNames.add("clickElement"); //埋点元素
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldNames.add("userId"); //用户ID
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
fieldNames.add("clickTime"); //点击时间
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);
return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);
}
/**
* 处理数据的主要逻辑
* @param APP_log_data
* @return
*/
public ArrayList<Object[]> processInputRecord(String APP_log_data){
String[] jsonKey = new String[]{"product","userAgent","clickElement","userId","clickTime"};
ArrayList<Object[]> result = new ArrayList<Object[]>();
// 忽略null值与空值
if (APP_log_data == null || APP_log_data.isEmpty()) {
return result;
}
JSONObject jsonObject = JSONObject.fromObject(APP_log_data);
JSONArray jsonArray = jsonObject.getJSONArray("data");
JSONObject log_data = jsonArray.getJSONObject(0);
String product1 ;//设备
String product2 ;//APP版本
String product = log_data.getString(jsonKey[0]);
if(product.indexOf("|")!=-1){
//iOS数据有版本号
product1 = product.split("\\|")[0];
product2 = product.split("\\|")[1];
}else {
//安卓数据没有版本号
product1 = product;
product2 = "null";
}
String userAgents = log_data.getString(jsonKey[1]);
String device_number = userAgents.split("\\|")[0]; //设备号
String model = userAgents.split("\\|")[1]; //设备型号
String clickElement = log_data.getString(jsonKey[2]);
String userId = log_data.getString(jsonKey[3]);
String clickTime_stamp = log_data.getString(jsonKey[4]);
/**
* 将十位java时间戳转换为时间
*/
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
long lt = new Long(clickTime_stamp);
Date date = new Date(lt* 1000L);
String clickTime = simpleDateFormat.format(date);
//将数据返回
result.add(new Object[] { product1, product2, device_number, model, clickElement, userId, clickTime});
return result;
}
/**
* 读到一行数据先进入此方法
*/
@Override
public void process(Object[] record) throws HiveException {
final String name = stringOI.getPrimitiveJavaObject(record[0]).toString();
//调用数据处理逻辑
ArrayList<Object[]> results = processInputRecord(name);
Iterator<Object[]> it = results.iterator();
//将数据传输出去
while (it.hasNext()){
Object[] r = it.next();
forward(r);
}
}
@Override
public void close() throws HiveException {
// do nothing
}
}
第一次写,代码可能有点冗余,但是业务实现了就OK。
具体的每个方法是什么作用参考:http://blog.csdn.net/kent7306/article/details/50200061
五:将自定义的UDTF打包上传
建议在$HIVE_HOME目录下创建auxlib目录,将jar包上传到auxlib目录下,这样就不用每次在hive的命令行里敲 add jar ///path 了。
创建临时函数 process_names
hive> CREATE TEMPORARY FUNCTION process_names as 'myUDF.AppJsonTransformUDTF';
使用临时函数
select process_names(name) from test_wcf.test_udtf;
在测试的过程中出现了多次class not found的错,主要是因为要解析json数据有很多jar包缺少
所有jar:
还出现了多次数组下标越界的情况,修改java代码重新打包上传即可,注意重新上传jar包以后要退出hive的命令行重新进入,然后创建函数,否则可能新jar包不起作用。
六:在测试好临时函数以后,就可以创建永久函数了
create function default.AppJsTsfmUDTF as 'myUDF.AppJsonTransformUDTF'
查看函数:
hive> show functions;
找到自定义函数就OK了,如果不放心,就退出hive命令行重新进入,然后show functions 即可。