需求
nginx日志格式:
'$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
(暂且不将$remote_addr与$remote_user之间的–看做一个字段,所以一共有9个字段)
举个栗子:
180.173.250.74 – – [08/Jan/2015:12:38:08 +0800] “GET /avatar/xxx.png HTTP/1.1” 200 968 “https://www.iteblog.com/archives/994” “Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36” “180.173.250.01”
(不同字段之间以空格分隔)
具体需求
将nginx的日志按照nginx.conf中定义的字段解析至hive中,偏向于用udf实现。
环境
Hadoop2.6.3三节点(2个slave)
Hive-1.2.1
MySQL-5.1.73
实现
方案一(非Udf)
思路
反思
上述方法是通过hive自带的RegexSerDe(需要在hive中加入hive-contrib-.jar*,类似下文添加Udf jar包的方式),通常情况下我们会通过
ROW FORMAT DELIMITED FIELDS TERMINATED BY '某个分隔符'
默认情况下,Hive的分隔符是‘\u0001’,同时似乎在默认情况下,如果指定多个分隔符,比如‘###’,hive在实际使用中并不支持。
详情请见:
hive分隔符支持多个字符吗?
而这种方案使用的RegexSerDe,我的理解就是一个高逼格的,不是原先单一的,同时较原先复杂的,在某种意义上也可以理解为多分隔符的正则实现
方案二(Udf)
思路
建三张表:
nginx_origin:只有一个String类型的content字段,用它去关联Hdfs上的nginx原始日志,字段值即为那举个栗子中的内容
建表语句
CREATE EXTERNAL TABLE nginx_origin(content string) location 'Hdfs上的nginx日志所在目录的路径';
nginx_temp:
表结构和nginx_origin一样,但是字段里的值为经过Udf函数处理过后,结构化后的值。
建表语句:
create table nginx_temp STORED AS TEXTFILE as select structed(*) from nginx_origin;
Udf函数:
public class StructedNginxLog extends UDF {
//通过正则将nginx日志转换为String类型数组
public String[] structedNginxLog(String logEntryLine){
Pattern pattern = Pattern.compile(
"(\\d+\\.\\d+\\.\\d+\\.\\d+)\\s\\W\\s(.*)\\s(\\[[^\\[\\]]+\\])\\s(\".*\")\\s(\\d{3})\\s(\\d+)\\s(\".*\")\\s(\".*\")\\s(\".*\")");
Matcher matcher = pattern.matcher(logEntryLine);
if(matcher.groupCount()==9){
String[] logArray = new String[9];
while (matcher.find()) {
for (int i = 1; i <= logArray.length; i++) {
logArray[i-1] = matcher.group(i);
}
}
return logArray;
}else{
return null;
}
}
//将结构化在数组中的各字段值重新拼接成temp表里的字段值
public String evaluate(String logEntryLine){
String[] logArray = structedNginxLog(logEntryLine);
if(logArray==null){
return null;
}
else{
StringBuilder constructLogBuilder = new StringBuilder();
for(int i = 0 ;i<logArray.length;i++){
if(i<logArray.length-1){
constructLogBuilder.append(logArray[i]+"\u0002");//需要注意分隔符
}
else{
constructLogBuilder.append(logArray[i]);
}
}
return constructLogBuilder.toString();
}
}
}
Hive添加jar包:
①临时生效:add jar {udf jar包路径}
②永久生效:
修改hive-env.sh,修改最后一行的
#export HIVE_AUX_JARS_PATH=
为exportHIVE_AUX_JARS_PATH=udf的jar文件目录路径
来实现
创建临时函数
create temporary function structed as {继承自UDF的类名};
nginx_push
主要是在建该表时,指定好Udf结构化的那个分隔符,然后指向nginx_temp的hdfs目录即可
建表语句
create external table nginx_push
(remote_addr STRING,remote_user STRING,
time_local STRING, request STRING,
status STRING, body_bytes_sent STRING,
http_referer STRING, http_user_agent STRING,
http_x_forwarded_for STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\u0002' STORED AS TEXTFILE
location 'nginx temp表的hdfs目录路径';
反思
起初是将字段之间的分隔符,通过‘\u0001’定义,但是后来发现,Hive默认的分隔符就是这个,所以如果指定这个的话,temp表中的那个唯一的字段的值就仅仅是原来一长串中的开头——IP地址,后来通过将分隔符修改为\u0002来解决,也想过用‘|’,但是尽量越不常见越好。