Spark 从零到开发(八)nginx日志清洗并持久化实战

本文将介绍如何清洗nginx日志并存储到mysql中,附带azkaban定时任务协作完成对access.log的清洗任务。

1. 查看nginx日志格式

cd /var/log/nginx

[root@FantJ nginx]# cat access.log
140.205.205.25 - - [19/Aug/2018:03:41:59 +0800] "GET / HTTP/1.1" 404 312 "-" "Scrapy/1.5.0 (+https://scrapy.org)" "-"
185.55.46.110 - - [19/Aug/2018:03:56:16 +0800] "GET / HTTP/1.0" 404 180 "-" "-" "-"
80.107.89.207 - - [19/Aug/2018:03:56:25 +0800] "GET / HTTP/1.1" 404 191 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.7 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7" "-"
140.205.205.25 - - [19/Aug/2018:04:13:52 +0800] "HEAD / HTTP/1.1" 404 0 "-" "Go-http-client/1.1" "-"
139.162.88.63 - - [19/Aug/2018:04:31:56 +0800] "GET http://clientapi.ipip.net/echo.php?info=1234567890 HTTP/1.1" 404 207 "-" "Go-http-client/1.1" "-"
......

我们需要根据这个格式来写正则表达式,对数据进行过滤。上面是我的日志格式。

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

这是我nginx的日志配置。(centos版本默认配置)。

2. 正则表达式测试

    public static void main(String[] args) {
        Pattern p = Pattern.compile("([^ ]*) ([^ ]*) ([^ ]*) (\\[.*\\]) (\\\".*?\\\") (-|[0-9]*) (-|[0-9]*) (\\\".*?\\\") (\\\".*?\\\")([^ ]*)");
        Matcher m = p.matcher("202.173.10.31 - - [18/Aug/2018:21:16:28 +0800] \"GET / HTTP/1.1\" 404 312 \"http://www.sdf.sdf\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36\" \"-\"\n");
        while (m.find()) {
            System.out.println(m.group(1));
            System.out.println(m.group(2));
            System.out.println(m.group(3));
            System.out.println(m.group(4));
            System.out.println(m.group(5));
            System.out.println(m.group(6));
            System.out.println(m.group(7));
            System.out.println(m.group(8));
            System.out.println(m.group(9));
            System.out.println(m.group(10));
            System.out.println(m.toString());
        }
    }

控制台输出:

202.173.10.31
-
-
[18/Aug/2018:21:16:28 +0800]
"GET / HTTP/1.1"
404
312
"http://www.xxx.top"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36"

证明我们的正则可以使用。

3. Spark程序实现

上一章我介绍了RDD和DF之间的转换和临时表Sql的执行,这章节增加了对RDD数据的持久化操作,我将把RDD数据集存储到mysql中。

3.1 创建mysql表
CREATE TABLE `access` (
  `remote_addr` varchar(255) DEFAULT NULL,
  `remote_user` varchar(255) DEFAULT NULL,
  `time_local` varchar(255) DEFAULT NULL,
  `request` varchar(255) DEFAULT NULL,
  `status` varchar(255) DEFAULT NULL,
  `byte_sent` varchar(255) DEFAULT NULL,
  `refere` varchar(255) DEFAULT NULL,
  `http_agent` varchar(255) DEFAULT NULL,
  `http_forward_for` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `acc_addr_count` (
  `remote_addr` varchar(255) DEFAULT NULL,
  `count` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

第一个表是log的全部数据内容,第二个表是对ip数目做一统计。这两个表都在我的数据库nginx中。

3.2 编写DBHelper.java
public class DBHelper {

    private String url = "jdbc:mysql://192.168.27.166:3306/nginx";
    private String name = "com.mysql.jdbc.Driver";
    private String user = "root";
    private String password = "xxx";

    //获取数据库连接
    public Connection connection = null;

    public DBHelper(){
        try {
            Class.forName(name);
            connection = DriverManager.getConnection(url,user,password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public void close() throws SQLException {
        this.connection.close();
    }
}
3.3 编写实体类(javaBean)

我将用反射的方法完成对整条log的清洗,用动态元素创建来完成对acc_addr_count表的收集。(不清楚这两种方法的可先看下上一章)

NginxParams.java

public class NginxParams implements Serializable {
    private String remoteAddr;

    private String remoteUser;

    private String timeLocal;

    private String request;

    private String status;

    private String byteSent;

    private String referer;

    private String httpUserAgent;

    private String httpForwardedFor;

setter and  getter ...methods...

    @Override
    public String toString() {
        return "NginxParams{" +
                "remoteAddr='" + remoteAddr + '\'' +
                ", remoteUser='" + remoteUser + '\'' +
                ", timeLocal='" + timeLocal + '\'' +
                ", request='" + request + '\'' +
                ", status='" + status + '\'' +
                ", byteSent='" + byteSent + '\'' +
                ", referer='" + referer + '\'' +
                ", httpUserAgent='" + httpUserAgent + '\'' +
                ", httpForwardedFor='" + httpForwardedFor + '\'' +
                '}';
    }
}
3.4 编写清洗代码

NginxLogCollect.java

public class NginxLogCollect implements Serializable {

    static DBHelper dbHelper = null;
    public static void main(String[] args) {
        SparkConf conf = new SparkConf().setAppName("NginxLogCollect").setMaster("local");
        JavaSparkContext sc = new JavaSparkContext(conf);
        sc.setLogLevel("ERROR");

        SQLContext sqlContext = new SQLContext(sc);

        JavaRDD<String> lines = sc.textFile("C:\\Users\\84407\\Desktop\\nginx.log");
        JavaRDD<NginxParams> nginxs = lines.map((Function<String, NginxParams>) line -> {
            Pattern p = Pattern.compile("([^ ]*) ([^ ]*) ([^ ]*) (\\[.*\\]) (\\\".*?\\\") (-|[0-9]*) (-|[0-9]*) (\\\".*?\\\") (\\\".*?\\\")([^ ]*)");
            Matcher m = p.matcher(line);
            NginxParams nginxParams = new NginxParams();
            while (m.find()){
                nginxParams.setRemoteAddr(m.group(1));
                nginxParams.setRemoteUser(m.group(2));
                nginxParams.setTimeLocal(m.group(4));
                nginxParams.setRequest(m.group(5));
                nginxParams.setStatus(m.group(6));
                nginxParams.setByteSent(m.group(7));
                nginxParams.setReferer(m.group(8));
                nginxParams.setHttpUserAgent(m.group(9));
                nginxParams.setHttpForwardedFor(m.group(10));
            }
            return nginxParams;
        });
        /**
         * 使用反射方式,将RDD转换为DataFrame
         */
        DataFrame nginxDF = sqlContext.createDataFrame(nginxs,NginxParams.class);
        /**
         * 拿到一个DataFrame之后,就可以将其注册为一个临时表,然后针对其中的数据执行sql语句
         */
        nginxDF.registerTempTable("nginxs");

        DataFrame allDF = sqlContext.sql("select * from nginxs");
        //统计ip访问数
        DataFrame addrCount = sqlContext.sql("select remoteAddr,COUNT(remoteAddr)as count from nginxs GROUP BY remoteAddr  ORDER BY count DESC");
        /**
         * 将查询出来的DataFrame ,再次转换为RDD
         */
        JavaRDD<Row> allRDD = allDF.javaRDD();
        JavaRDD<Row> addrCountRDD = addrCount.javaRDD();
        /**
         * 将RDD中的数据进行映射,映射为NginxParams
         */
        JavaRDD<NginxParams> map = allRDD.map((Function<Row, NginxParams>) row -> {
            NginxParams nginxParams = new NginxParams();
            nginxParams.setRemoteAddr(row.getString(4));
            nginxParams.setRemoteUser(row.getString(5));
            nginxParams.setTimeLocal(row.getString(8));
            nginxParams.setRequest(row.getString(6));
            nginxParams.setStatus(row.getString(7));
            nginxParams.setByteSent(row.getString(0));
            nginxParams.setReferer(row.getString(2));
            nginxParams.setHttpUserAgent(row.getString(3));
            nginxParams.setHttpForwardedFor(row.getString(1));
            return nginxParams;
        });

        /**
         * 将数据collect回来,然后打印
         */

//        List<NginxParams> nginxParamsList = map.collect();
//        for (NginxParams np:nginxParamsList){
//            System.out.println(np);
//        }

        dbHelper = new DBHelper();
        String sql = "INSERT INTO `access` VALUES (?,?,?,?,?,?,?,?,?)";
        map.foreach((VoidFunction<NginxParams>) nginxParams -> {
            PreparedStatement pt = dbHelper.connection.prepareStatement(sql);
            pt.setString(1,nginxParams.getRemoteAddr());
            pt.setString(2,nginxParams.getRemoteUser());
            pt.setString(3,nginxParams.getTimeLocal());
            pt.setString(4,nginxParams.getRequest());
            pt.setString(5,nginxParams.getStatus());
            pt.setString(6,nginxParams.getByteSent());
            pt.setString(7,nginxParams.getReferer());
            pt.setString(8,nginxParams.getHttpUserAgent());
            pt.setString(9,nginxParams.getHttpForwardedFor());
            pt.executeUpdate();
        });

        String addrCountSql = "insert into `acc_addr_count` values(?,?)";
        addrCountRDD.foreach((VoidFunction<Row>) row -> {
            System.out.println("row.getString(0)"+row.getString(0));
            System.out.println("row.getString(1)"+row.getLong(1));
            PreparedStatement pt = dbHelper.connection.prepareStatement(addrCountSql);
            pt.setString(1,row.getString(0));
            pt.setString(2, String.valueOf(row.getLong(1)));
            pt.executeUpdate();
        });
    }
}

4. 执行完后查看数据库:

5. 总结

5.1 集群中执行

上面例子执行在本地,如果打包运行在服务器,需要执行脚本。

/home/fantj/spark/bin/spark-submit \
--class com.fantj.nginxlog.NginxLogCollect\
--num-executors 1 \
--driver-memory 100m \
--executor-memory 100m \
--executor-cores 3 \
--files /home/fantj/hive/conf/hive-site.xml \
--driver-class-path /home/fantj/hive/lib/mysql-connector-java-5.1.17.jar \
/home/fantj/nginxlog.jar \

并修改setMaster()sc.textFile()的参数。

5.2 定时任务实现

我们可以将执行脚本打包写一个azkaban的定时job,然后做每天的数据统计。当然,这里面还有很多细节,比如nginx日志按天分割等。但是都是一些小问题。(不熟悉azkaban的:Azkaban 简单入门

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 关于Mongodb的全面总结 MongoDB的内部构造《MongoDB The Definitive Guide》...
    中v中阅读 31,789评论 2 89
  • 1.1、 分配更多资源 1.1.1、分配哪些资源? Executor的数量 每个Executor所能分配的CPU数...
    miss幸运阅读 3,139评论 3 15
  • 1、 性能调优 1.1、 分配更多资源 1.1.1、分配哪些资源? Executor的数量 每个Executor所...
    Frank_8942阅读 4,465评论 2 36
  • 一个十字架,八九条错综的棉线卡在关节。有人把十字架向左倾,我的左手就往下耷拉着,右肩挺立。我一向这么听话。 我知道...
    林和山风阅读 866评论 0 3
  • 你思愁意难 他思情更切 问君何处诉情丝 春雨深处滴更愁 思愁思情深切切 换心换德浓厚宜 愿君含灯思相伴 化作一缕消...
    慕斯百合阅读 319评论 0 0