通过Hive及其Udf函数进行Nginx日志分析

需求

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分析nginx日志

反思

上述方法是通过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来解决,也想过用‘|’,但是尽量越不常见越好。

    原文作者:司小幽
    原文地址: https://www.jianshu.com/p/cd094c4b2297
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞