• 数据仓库(一)——用户行为数据采集


    第一章 数据仓库概念

    数据仓库( Data Warehouse ),是为企业制定决策,提供数据支持的。可以帮助企业,改进业务流程、提高产品质量等。

    数据仓库的输入数据通常包括:业务数据用户行为数据爬虫数据

    业务数据:就是各行业在处理事务过程中产生的数据。比如用户在电商网站中登录、下单、支付等过程中,需要和网站后台数据库进行增删改查交互,产生的数据就是业务数据。业务数据通常存储在MySQL、Oracle等数据库中。

    用户行为数据:用户在使用产品过程中,通过埋点收集与客户端产品交互过程中产生的数据,并发往日志服务器进行保存。比如页面浏览、点击、停留、评论、点赞、收藏等。用户行为数据通常存储在日志文件中。

    爬虫数据:通常事通过技术手段获取其他公司网站的数据。不建议同学们这样去做。

    第二章 项目需求及架构设计

    2.1 项目需求分析

    2.2 项目框架

    2.2.1 技术选型

    2.2.2 系统数据流程设计

    2.2.3 框架版本选型

    2.2.4服务器选型

    2.2.5 集群规模

    2.2.6 集群资源规划设计

    在企业中通常会搭建一套生产集群和一套测试集群。生产集群运行生产任务,测试集群用于上线前代码编写和测试。

    1)生产集群
        (1)消耗内存的分开
        (2)数据传输数据比较紧密的放在一起(Kafka 、Zookeeper)
        (3)客户端尽量放在一到两台服务器上,方便外部访问
        (4)有依赖关系的尽量放到同一台服务器(例如:Hive和Azkaban Executor)

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    nn

    nn

    dn

    dn

    dn

    dn

    dn

    dn

    dn

    dn

     

     

    rm

    rm

    nm

    nm

    nm

    nm

    nm

    nm

     

     

    nm

    nm

     

     

     

     

     

     

     

     

     

     

     

     

     

    zk

    zk

    zk

     

     

     

     

     

     

     

    kafka

    kafka

    kafka

     

     

     

     

     

     

     

    Flume

    Flume

    flume

     

     

    Hbase

    Hbase

    Hbase

     

     

     

     

     

    hive

    hive

     

     

     

     

     

     

     

     

    mysql

    mysql

     

     

     

     

     

     

     

     

    spark

    spark

     

     

     

     

     

     

     

     

    Azkaban

    Azkaban

     

     

     

    ES

    ES

     

     

     


    2)测试集群服务器规划

    服务名称

    子服务

    服务器

    hadoop102

    服务器

    hadoop103

    服务器

    hadoop104

    HDFS

    NameNode

     

     

    DataNode

    SecondaryNameNode

     

     

    Yarn

    NodeManager

    Resourcemanager

     

     

    Zookeeper

    Zookeeper Server

    Flume(采集日志)

    Flume

     

    Kafka

    Kafka

    Flume(消费Kafka)

    Flume

     

     

    Hive

    Hive

     

     

    MySQL

    MySQL

     

     

    Sqoop

    Sqoop

     

     

    Presto

    Coordinator

     

     

    Worker

     

    Azkaban

    AzkabanWebServer

     

     

    AzkabanExecutorServer

     

     

    Spark

     

     

     

    Kylin

     

     

     

    HBase

    HMaster

     

     

    HRegionServer

    Superset

     

     

     

    Atlas

     

     

     

    Solr

    Jar

     

     

    服务数总计

     

    19

    8

    8

    第三章 数据生成模块

    3.1 目标数据

    我们要收集和分析的数据主要包括页面数据事件数据曝光数据启动数据错误数据

    3.1.1 页面

    页面数据主要记录一个页面的用户访问情况,包括访问时间、停留时间、页面路径等信息。

    字段名称

    字段描述

    page_id

    页面id

    home("首页"),

    category("分类页"),

    discovery("发现页"),

    top_n("热门排行"),

    favor("收藏页"),

    search("搜索页"),

    good_list("商品列表页"),

    good_detail("商品详情"),

    good_spec("商品规格"),

    comment("评价"),

    comment_done("评价完成"),

    comment_list("评价列表"),

    cart("购物车"),

    trade("下单结算"),

    payment("支付页面"),

    payment_done("支付完成"),

    orders_all("全部订单"),

    orders_unpaid("订单待支付"),

    orders_undelivered("订单待发货"),

    orders_unreceipted("订单待收货"),

    orders_wait_comment("订单待评价"),

    mine("我的"),

    activity("活动"),

    login("登录"),

    register("注册");

    last_page_id

    上页id

    page_item_type

    页面对象类型

    sku_id("商品skuId"),

    keyword("搜索关键词"),

    sku_ids("多个商品skuId"),

    activity_id("活动id"),

    coupon_id("购物券id");

    page_item

    页面对象id

    sourceType

    页面来源类型

    promotion("商品推广"),

    recommend("算法推荐商品"),

    query("查询结果商品"),

    activity("促销活动");

    during_time

    停留时间(毫秒)

    ts

    跳入时间


    3.1.2 事件

    事件数据主要记录应用内一个具体操作行为,包括操作类型、操作对象、操作对象描述等信息。

    字段名称

    字段描述

    action_id

    动作id

    favor_add("添加收藏"),

    favor_canel("取消收藏"),

    cart_add("添加购物车"),

    cart_remove("删除购物车"),

    cart_add_num("增加购物车商品数量"),

    cart_minus_num("减少购物车商品数量"),

    trade_add_address("增加收货地址"),

    get_coupon("领取优惠券");

    注:对于下单、支付等业务数据,可从业务数据库获取。

    item_type

    动作目标类型

    sku_id("商品"),

    coupon_id("购物券");

    item

    动作目标id

    ts

    动作时间


    3.1.3 曝光

    曝光数据主要记录页面所曝光的内容,包括曝光对象,曝光类型等信息。

    字段名称

    字段描述

    displayType

    曝光类型

    promotion("商品推广"),

    recommend("算法推荐商品"),

    query("查询结果商品"),

    activity("促销活动");

    item_type

    曝光对象类型

    sku_id("商品skuId"),

    activity_id("活动id");

    item

    曝光对象id

    order

    曝光顺序


    3.1.4 启动

    启动数据记录应用的启动信息。

    字段名称

    字段描述

    entry

    启动入口

    icon("图标"),

    notification("通知"),

    install("安装后启动");

    loading_time

    启动加载时间

    open_ad_id

    开屏广告id

    open_ad_ms

    广告播放时间

    open_ad_skip_ms

    用户跳过广告时间

    ts

    启动时间

    3.1.5 错误

    错误数据记录应用使用
    过程中的错误信息,包括错误编号错误信息

    字段名称

    字段描述

    error_code

    错误码

    msg

    错误信息

    3.2 数据埋点

    3.2.1 主流埋点方式(了解)

    目前主流的埋点方式,有代码埋点(前端/后端)可视化埋点全埋点三种。

    代码埋点是通过调用埋点SDK函数,在需要埋点的业务逻辑功能位置调用接口,上报埋点数据。例如,我们对页面中的某个按钮埋点后,当这个按钮被点击时,可以在这个按钮对应的 OnClick 函数里面调用SDK提供的数据发送接口,来发送数据。

    可视化埋点只需要研发人员集成采集 SDK,不需要写埋点代码,业务人员就可以通过访问分析平台的“圈选”功能,来“圈”出需要对用户行为进行捕捉的控件,并对该事件进行命名。圈选完毕后,这些配置会同步到各个用户的终端上,由采集 SDK 按照圈选的配置自动进行用户行为数据的采集和发送。

    全埋点是通过在产品中嵌入SDK,前端自动采集页面上的全部用户行为事件,上报埋点数据,相当于做了一个统一的埋点。然后再通过界面配置哪些数据需要在系统里面进行分析。

    3.2.2 埋点数据上报时机

    埋点数据上报时机包括两种方式。

    方式一,在离开该页面时,上传在这个页面产生的所有数据(页面、事件、曝光、错误等)。优点,批处理,减少了服务器接收数据压力。缺点,不是特别及时。

    方式二,每个事件、动作、错误等,产生后,立即发送。优点,响应及时。缺点,对服务器接收数据压力比较大。

    本次项目采用方式一埋点。

    3.2.3 埋点数据日志结构

    我们的日志结构大致可分为两类,一是普通页面埋点日志,二是启动日志。

    普通页面日志结构如下,每条日志包含了,当前页面的页面信息,所有事件(动作)、所有曝光信息以及错误信息。除此之外,还包含了一系列公共信息,包括设备信息,地理位置,应用信息等,即下边的common字段。

    (1)普通页面埋点日志格式

    {
      "common": {                  -- 公共信息
        "ar": "230000",              -- 地区编码
        "ba": "iPhone",              -- 手机品牌
        "ch": "Appstore",            -- 渠道
        "is_new": "1",--是否首日使用,首次使用的当日,该字段值为1,过了24:00,该字段置为0。
        "md": "iPhone 8",               -- 手机型号
        "mid": "YXfhjAYH6As2z9Iq",      -- 设备id
        "os": "iOS 13.2.9",             -- 操作系统
        "uid": "485",                   -- 会员id
        "vc": "v2.1.134"                -- app版本号
      },
    "actions": [                        --动作(事件)  
        {
          "action_id": "favor_add",     --动作id
          "item": "3",                  --目标id
          "item_type": "sku_id",        --目标类型
          "ts": 1585744376605           --动作时间戳
        }
      ],
      "displays": [
        {
          "displayType": "query",        -- 曝光类型
          "item": "3",                   -- 曝光对象id
          "item_type": "sku_id",         -- 曝光对象类型
          "order": 1,                    --出现顺序
          "pos_id": 2                    --曝光位置
        },
        {
          "displayType": "promotion",
          "item": "6",
          "item_type": "sku_id",
          "order": 2,
          "pos_id": 1
        },
        {
          "displayType": "promotion",
          "item": "9",
          "item_type": "sku_id",
          "order": 3,
          "pos_id": 3
        },
        {
          "displayType": "recommend",
          "item": "6",
          "item_type": "sku_id",
          "order": 4,
          "pos_id": 2
        },
        {
          "displayType": "query ",
          "item": "6",
          "item_type": "sku_id",
          "order": 5,
          "pos_id": 1
        }
      ],
      "page": {                     --页面信息
        "during_time": 7648,        -- 持续时间毫秒
        "item": "3",                -- 目标id
        "item_type": "sku_id",      -- 目标类型
        "last_page_id": "login",    -- 上页类型
        "page_id": "good_detail",   -- 页面ID
        "sourceType": "promotion"   -- 来源类型
      },
    "err":{                     --错误
    "error_code": "1234",       --错误码
        "msg": "***********"    --错误信息
    },
      "ts": 1585744374423  --跳入时间戳
    }

        (2)启动日志格式
    启动日志结构相对简单,主要包含公共信息,启动信息和错误信息。

    {
      "common": {
        "ar": "370000",
        "ba": "Honor",
        "ch": "wandoujia",
        "is_new": "1",
        "md": "Honor 20s",
        "mid": "eQF5boERMJFOujcp",
        "os": "Android 11.0",
        "uid": "76",
        "vc": "v2.1.134"
      },
      "start": {   
        "entry": "icon",         --icon手机图标  notice 通知   install 安装后启动
        "loading_time": 18803,  --启动加载时间
        "open_ad_id": 7,        --广告页ID
        "open_ad_ms": 3449,    -- 广告总共播放时间
        "open_ad_skip_ms": 1989   --  用户跳过广告时点
      },
    "err":{                     --错误
    "error_code": "1234",      --错误码
        "msg": "***********"       --错误信息
    },
      "ts": 1585744304000
    }

    3.3 服务器和JDK准备

    分别安装hadoop102、hadoop103、hadoop104三台主机。

    相关内容参考:https://www.cnblogs.com/wkfvawl/p/15369416.html

    3.4 模拟数据

    3.4.1 使用说明

    1)将application.yml、gmall2020-mock-log-2021-01-22.jar、path.json、logback.xml上传到hadoop102的/opt/module/applog目录下

    (1)创建applog路径

    [atguigu@hadoop102 module]$ mkdir /opt/module/applog

    (2)上传文件application.yml到/opt/module/applog目录

    2)配置文件
    (1)application.yml文件
    可以根据需求生成对应日期的用户行为日志。

    [atguigu@hadoop102 applog]$ vim application.yml
    修改如下内容

    # 外部配置打开
    logging.config: "./logback.xml"
    #业务日期  注意:并不是Linux系统生成日志的日期,而是生成数据中的时间
    mock.date: "2020-06-14"
    
    #模拟数据发送模式
    #mock.type: "http"
    #mock.type: "kafka"
    mock.type: "log"
    
    #http模式下,发送的地址
    mock.url: "http://hdp1/applog"
    
    #kafka模式下,发送的地址
    mock:
      kafka-server: "hdp1:9092,hdp2:9092,hdp3:9092"
      kafka-topic: "ODS_BASE_LOG"
    
    #启动次数
    mock.startup.count: 200
    #设备最大值
    mock.max.mid: 500000
    #会员最大值
    mock.max.uid: 100
    #商品最大值
    mock.max.sku-id: 35
    #页面平均访问时间
    mock.page.during-time-ms: 20000
    #错误概率 百分比
    mock.error.rate: 3
    #每条日志发送延迟 ms
    mock.log.sleep: 10
    #商品详情来源  用户查询,商品推广,智能推荐, 促销活动
    mock.detail.source-type-rate: "40:25:15:20"
    #领取购物券概率
    mock.if_get_coupon_rate: 75
    #购物券最大id
    mock.max.coupon-id: 3
    #搜索关键词  
    mock.search.keyword: "图书,小米,iphone11,电视,口红,ps5,苹果手机,小米盒子"

    (2)path.json,该文件用来配置访问路径
    根据需求,可以灵活配置用户点击路径。

    [
        {"path":["home","good_list","good_detail","cart","trade","payment"],"rate":20 },
        {"path":["home","search","good_list","good_detail","login","good_detail","cart","trade","payment"],"rate":40 },
        {"path":["home","mine","orders_unpaid","trade","payment"],"rate":10 },
        {"path":["home","mine","orders_unpaid","good_detail","good_spec","comment","trade","payment"],"rate":5 },
        {"path":["home","mine","orders_unpaid","good_detail","good_spec","comment","home"],"rate":5 },
        {"path":["home","good_detail"],"rate":10 },
        {"path":["home"  ],"rate":10 }
    ]

    (3)logback配置文件
    可配置日志生成路径,修改内容如下

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
        <property name="LOG_HOME" value="/opt/module/applog/log" />
        <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
            <encoder>
                <pattern>%msg%n</pattern>
            </encoder>
        </appender>
    
        <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <fileNamePattern>${LOG_HOME}/app.%d{yyyy-MM-dd}.log</fileNamePattern>
            </rollingPolicy>
            <encoder>
                <pattern>%msg%n</pattern>
            </encoder>
        </appender>
    
        <!-- 将某一个包下日志单独打印日志 -->
        <logger name="com.atgugu.gmall2020.mock.log.util.LogUtil"
                level="INFO" additivity="false">
            <appender-ref ref="rollingFile" />
            <appender-ref ref="console" />
        </logger>
    
        <root level="error"  >
            <appender-ref ref="console" />
        </root>
    </configuration>

    3)生成日志
    (1)进入到/opt/module/applog路径,执行以下命令

    [atguigu@hadoop102 applog]$ java -jar gmall2020-mock-log-2021-01-22.jar

    (2)在/opt/module/applog/log目录下查看生成日志

    [atguigu@hadoop102 log]$ ll

    3.4.2 集群日志生成脚本

        在hadoop102的/home/atguigu目录下创建bin目录,这样脚本可以在服务器的任何目录执行。

    [atguigu@hadoop102 ~]$ echo $PATH
    /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/atguigu/.local/bin:/home/atguigu/bin

        (1)在/home/atguigu/bin目录下创建脚本lg.sh

    [atguigu@hadoop102 bin]$ vim lg.sh

        (2)在脚本中编写如下内容

    #!/bin/bash
    for i in hadoop102 hadoop103; do
        echo "========== $i =========="
        ssh $i "cd /opt/module/applog/; java -jar gmall2020-mock-log-2021-01-22.jar >/dev/null 2>&1 &"
    done 

    注:
    ①/opt/module/applog/为jar包及配置文件所在路径
    /dev/null代表Linux的空设备文件,所有往这个文件里面写入的内容都会丢失,俗称“黑洞”。

    标准输入0:从键盘获得输入 /proc/self/fd/0
    标准输出1:输出到屏幕(即控制台) /proc/self/fd/1
    错误输出2:输出到屏幕(即控制台) /proc/self/fd/2

    这里意思是 1给黑洞,2给1

    (3)修改脚本执行权限

    [atguigu@hadoop102 bin]$ chmod u+x lg.sh

    (4)将jar包及配置文件上传至hadoop103的/opt/module/applog/路径
    (5)启动脚本

    [atguigu@hadoop102 module]$ lg.sh 

    (6)分别在hadoop102、hadoop103的/opt/module/applog/log目录上查看生成的数据

    [atguigu@hadoop102 logs]$ ls
    app.2021-01-22.log
    [atguigu@hadoop103 logs]$ ls
    app.2020-01-22.log

    第四章 数据采集模块

    相关内容参考:https://www.cnblogs.com/wkfvawl/p/15369416.html

    4.1 Hadoop 安装

    集群规划

     

    服务器hadoop102

    服务器hadoop103

    服务器hadoop104

    HDFS

    NameNode

    DataNode

    DataNode

    DataNode

    SecondaryNameNode

    Yarn

    NodeManager

    Resourcemanager

    NodeManager

    NodeManager


    注意:尽量使用离线方式安装
    安装过程参见:https://www.cnblogs.com/wkfvawl/p/15369416.html

    4.2 Zookeeper 安装

    集群规划

     

    服务器hadoop102

    服务器hadoop103

    服务器hadoop104

    Zookeeper

    Zookeeper

    Zookeeper

    Zookeeper

    安装过程参见:https://www.cnblogs.com/wkfvawl/p/15539847.html

    4.3 Kafka 安装

    集群规划

     

    服务器hadoop102

    服务器hadoop103

    服务器hadoop104

    Kafka

    Kafka

    Kafka

    Kafka

    安装过程参见:https://www.cnblogs.com/wkfvawl/p/15579066.html

    4.3.1 创建Kafka Topic

    进入到/opt/module/kafka/目录下创建日志主题
    [atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181,hadoop103:2181,hadoop104:2181  --create --replication-factor 1 --partitions 1 --topic topic_log

    4.4 采集日志Flume

    4.4.1 Flume安装

     

    服务器hadoop102

    服务器hadoop103

    服务器hadoop104

    Flume(采集日志)

    Flume

    Flume

     

    安装过程参见:https://www.cnblogs.com/wkfvawl/p/15603589.html

    4.4.2 Flume组件选型

    1)Source

    (1)Taildir Source相比Exec Source、Spooling Directory Source的优势

    TailDir Source:断点续传、多目录。Flume1.6以前需要自己自定义Source记录每次读取文件位置,实现断点续传。不会丢数据,但是有可能会导致数据重复。

    Exec Source可以实时搜集数据,但是在Flume不运行或者Shell命令出错的情况下,数据将会丢失。

    Spooling Directory Source监控目录,支持断点续传。

    (2)batchSize大小如何设置?

    答:Event 1K左右时,500-1000合适(默认为100)

    2)Channel
    采用Kafka Channel

    省去了Sink,提高了效率。KafkaChannel数据存储在Kafka里面,所以数据是存储在磁盘中。

    注意在Flume1.7以前,Kafka Channel很少有人使用,因为发现parseAsFlumeEvent这个配置起不了作用。也就是无论parseAsFlumeEvent配置为true还是false,都会转为Flume Event。这样的话,造成的结果是,会始终都把Flume的headers中的信息混合着内容一起写入Kafka的消息中,这显然不是我所需要的,我只是需要把内容写入即可。

    4.4.3 Flume 配置

    1)Flume配置分析

    Flume直接读log日志的数据,log日志的格式是app.yyyy-mm-dd.log。采集的数据可能不完整,需要用拦截器进行拦截,但这里只能进行简单的数据清洗。

    2)Flume的具体配置如下:   

    (1)在/opt/module/flume/conf目录下创建file-flume-kafka.conf文件

    [atguigu@hadoop102 conf]$ vim file-flume-kafka.conf

    在文件配置如下内容

    #为各组件命名
    a1.sources = r1
    a1.channels = c1
    
    #描述source
    a1.sources.r1.type = TAILDIR
    a1.sources.r1.filegroups = f1
    a1.sources.r1.filegroups.f1 = /opt/module/applog/log/app.*
    a1.sources.r1.positionFile = /opt/module/flume/taildir_position.json
    a1.sources.r1.interceptors =  i1
    a1.sources.r1.interceptors.i1.type = com.atguigu.flume.interceptor.ETLInterceptor$Builder
    
    #描述channel
    a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
    a1.channels.c1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092
    a1.channels.c1.kafka.topic = topic_log
    a1.channels.c1.parseAsFlumeEvent = false
    
    #绑定source和channel以及sink和channel的关系
    a1.sources.r1.channels = c1

        注意:com.atguigu.flume.interceptor.ETLInterceptor是自定义的拦截器的全类名。需要根据用户自定义的拦截器做相应修改。

    4.4.4 Flume拦截器

    1)创建Maven工程flume-interceptor

    2)创建包名:com.atguigu.flume.interceptor

    3)在pom.xml文件中添加如下配置

    <dependencies>
        <dependency>
            <groupId>org.apache.flume</groupId>
            <artifactId>flume-ng-core</artifactId>
            <version>1.9.0</version>
            <scope>provided</scope>
        </dependency>
    
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

        注意:scope中provided的含义是编译时用该jar包。打包时时不用。因为集群上已经存在flume的jar包。只是本地编译时用一下。

    4)在com.atguigu.flume.interceptor包下创建JSONUtils类

    package com.atguigu.flume.interceptor;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONException;
    
    public class JSONUtils {
        public static boolean isJSONValidate(String log){
            try {
                JSON.parse(log);
                return true;
            }catch (JSONException e){
                return false;
            }
        }
    }

    5)在com.atguigu.flume.interceptor包下创建LogInterceptor类

    package com.atguigu.flume.interceptor;
    
    import com.alibaba.fastjson.JSON;
    import org.apache.flume.Context;
    import org.apache.flume.Event;
    import org.apache.flume.interceptor.Interceptor;
    
    import java.nio.charset.StandardCharsets;
    import java.util.Iterator;
    import java.util.List;
    
    public class ETLInterceptor implements Interceptor {
    
        @Override
        public void initialize() {
    
        }
        @Override
        public Event intercept(Event event) {
    
            byte[] body = event.getBody();
            String log = new String(body, StandardCharsets.UTF_8);
    
            if (JSONUtils.isJSONValidate(log)) {
                return event;
            } else {
                return null;
            }
        }
        @Override
        public List<Event> intercept(List<Event> list) {
    
            Iterator<Event> iterator = list.iterator();
    
            while (iterator.hasNext()){
                Event next = iterator.next();
                if(intercept(next)==null){
                    iterator.remove();
                }
            }
            return list;
        }
        public static class Builder implements Interceptor.Builder{
    
            @Override
            public Interceptor build() {
                return new ETLInterceptor();
            }
            @Override
            public void configure(Context context) {
            }
        }
        @Override
        public void close() {
        }
    }

    6)打包

    7)需要先将打好的包放入到hadoop102的/opt/module/flume/lib文件夹下面。

    [atguigu@hadoop102 lib]$ ls | grep interceptor
    flume-interceptor-1.0-SNAPSHOT-jar-with-dependencies.jar

    8)分发Flume到hadoop103、hadoop104

    [atguigu@hadoop102 module]$ xsync flume/

    9)分别在hadoop102、hadoop103上启动Flume

    [atguigu@hadoop102 flume]$ bin/flume-ng agent --name a1 --conf-file conf/file-flume-kafka.conf &
    
    [atguigu@hadoop103 flume]$ bin/flume-ng agent --name a1 --conf-file conf/file-flume-kafka.conf &

    4.4.5 测试Flume-Kafka通道

    (1)生成日志

    [atguigu@hadoop102 ~]$ lg.sh

    (2)消费Kafka数据,观察控制台是否有数据获取到

    [atguigu@hadoop102 kafka]$ bin/kafka-console-consumer.sh \
    --bootstrap-server hadoop102:9092 --from-beginning --topic topic_log

    说明:如果获取不到数据,先检查Kafka、Flume、Zookeeper是否都正确启动。再检查Flume的拦截器代码是否正常。

    4.4.6 日志采集Flume启动停止脚本

    (1)在/home/atguigu/bin目录下创建脚本f1.sh

    [atguigu@hadoop102 bin]$ vim f1.sh

        在脚本中填写如下内容

    #! /bin/bash
    case $1 in
    "start"){
            for i in hadoop102 hadoop103
            do
                    echo " --------启动 $i 采集flume-------"
                    ssh $i "nohup /opt/module/flume/bin/flume-ng agent --conf-file /opt/module/flume/conf/file-flume-kafka.conf --name a1 -Dflume.root.logger=INFO,LOGFILE >/opt/module/flume/log1.txt 2>&1  &"
            done
    };;    
    "stop"){
            for i in hadoop102 hadoop103
            do
                    echo " --------停止 $i 采集flume-------"
                    ssh $i "ps -ef | grep file-flume-kafka | grep -v grep |awk  '{print \$2}' | xargs -n1 kill -9 "
            done
    
    };;
    esac

    说明1:nohup,该命令可以在你退出帐户/关闭终端之后继续运行相应的进程。nohup就是不挂起的意思,不挂断地运行命令。
    说明2:awk 默认分隔符为空格
    说明3:$2是在“”双引号内部会被解析为脚本的第二个参数,但是这里面想表达的含义是awk的第二个值,所以需要将他转义,用\$2表示。
    说明4:xargs 表示取出前面命令运行的结果,作为后面命令的输入参数。
    (2)增加脚本执行权限

    [atguigu@hadoop102 bin]$ chmod u+x f1.sh

    (3)f1集群启动脚本

    [atguigu@hadoop102 module]$ f1.sh start

    (4)f1集群停止脚本

    [atguigu@hadoop102 module]$ f1.sh stop

    4.5 消费Kafka数据Flume

    集群规划

     

    服务器hadoop102

    服务器hadoop103

    服务器hadoop104

    Flume(消费Kafka)

     

     

    Flume

    4.5.1 项目经验之Flume组件选型

    1)FileChannel和MemoryChannel区别

    MemoryChannel传输数据速度更快,但因为数据保存在JVM的堆内存中,Agent进程挂掉会导致数据丢失,适用于对数据质量要求不高的需求。

    FileChannel传输速度相对于Memory慢,但数据安全保障高,Agent进程挂掉也可以从失败中恢复数据。

    选型:

    金融类公司、对钱要求非常准确的公司通常会选择FileChannel

    传输的是普通日志信息(京东内部一天丢100万-200万条,这是非常正常的),通常选择MemoryChannel。

    2)FileChannel优化

    通过配置dataDirs指向多个路径,每个路径对应不同的硬盘,增大Flume吞吐量。
    官方说明如下:

    Comma separated list of directories for storing log files. Using multiple directories on separate disks can improve file channel peformance

    checkpointDir和backupCheckpointDir也尽量配置在不同硬盘对应的目录中,保证checkpoint坏掉后,可以快速使用backupCheckpointDir恢复数据。

    3)Sink:HDFS Sink

    (1)HDFS存入大量小文件,有什么影响?
    元数据层面:每个小文件都有一份元数据,其中包括文件路径,文件名,所有者,所属组,权限,创建时间等,这些信息都保存在Namenode内存中。所以小文件过多,会占用Namenode服务器大量内存,影响Namenode性能和使用寿命

    计算层面:默认情况下MR会对每个小文件启用一个Map任务计算,非常影响计算性能。同时也影响磁盘寻址时间。

    (2)HDFS小文件处理
    官方默认的这三个参数配置写入HDFS后会产生小文件,hdfs.rollInterval、hdfs.rollSize、hdfs.rollCount
    基于以上hdfs.rollInterval=3600,hdfs.rollSize=134217728,hdfs.rollCount =0几个参数综合作用,效果如下:

    ①文件在达到128M时会滚动生成新文件
    ②文件创建超3600秒时会滚动生成新文件

    4.5.2 消费者Flume配置

    1)Flume配置分析

    加上时间戳拦截器,用来解决零点漂移问题

    为什么会发生零点漂移?

    比如2022-01-24 23:59:59生成的日志文件,然后数据经过第一层的flume采集,加上kafka的缓冲,然后到 集群的另一台上的第二层的flume的时候,时间肯定就会到2022-01-25 00:00:XX了,这样一来,如果采用当前系统时间作为timestamp的话,2022-01-24 的日志数据就会上传到hdfs上的2022-01-25 的目录下。因为Kafka Source会为其加上该header,value为当前系统的时间戳Kafka Source会为其加上该header,value为当前系统的时间戳

    所以我们需要再第二层flume里面写这么一个加时间戳的拦截器,把日志文件里面的时间添加到event的header里面

    2)Flume的具体配置如下:
        (1)在hadoop104的/opt/module/flume/conf目录下创建kafka-flume-hdfs.conf文件

    [atguigu@hadoop104 conf]$ vim kafka-flume-hdfs.conf

    在文件配置如下内容

    ## 组件
    a1.sources=r1
    a1.channels=c1
    a1.sinks=k1
    
    ## source1
    a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
    a1.sources.r1.batchSize = 5000
    a1.sources.r1.batchDurationMillis = 2000
    a1.sources.r1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092,hadoop104:9092
    a1.sources.r1.kafka.topics=topic_log
    a1.sources.r1.interceptors = i1
    a1.sources.r1.interceptors.i1.type = com.atguigu.flume.interceptor.TimeStampInterceptor$Builder
    
    ## channel1
    a1.channels.c1.type = file
    a1.channels.c1.checkpointDir = /opt/module/flume/checkpoint/behavior1
    a1.channels.c1.dataDirs = /opt/module/flume/data/behavior1/
    
    
    ## sink1
    a1.sinks.k1.type = hdfs
    a1.sinks.k1.hdfs.path = /origin_data/gmall/log/topic_log/%Y-%m-%d
    a1.sinks.k1.hdfs.filePrefix = log-
    a1.sinks.k1.hdfs.round = false
    
    #控制生成的小文件
    a1.sinks.k1.hdfs.rollInterval = 10
    a1.sinks.k1.hdfs.rollSize = 134217728
    a1.sinks.k1.hdfs.rollCount = 0
    
    ## 控制输出文件是原生文件。
    a1.sinks.k1.hdfs.fileType = CompressedStream
    a1.sinks.k1.hdfs.codeC = lzop
    
    ## 拼装
    a1.sources.r1.channels = c1
    a1.sinks.k1.channel= c1

    4.5.3 Flume时间戳拦截器

    由于Flume默认会用Linux系统时间,作为输出到HDFS路径的时间。如果数据是23:59分产生的。Flume消费Kafka里面的数据时,有可能已经是第二天了,那么这部门数据会被发往第二天的HDFS路径。我们希望的是根据日志里面的实际时间,发往HDFS的路径,所以下面拦截器作用是获取日志中的实际时间。

    解决的思路:拦截json日志,通过fastjson框架解析json,获取实际时间ts。将获取的ts时间写入拦截器header头,header的key必须是timestamp,因为Flume框架会根据这个key的值识别为时间,写入到HDFS。
    1)在com.atguigu.flume.interceptor包下创建TimeStampInterceptor类

    package com.atguigu.flume.interceptor;
    
    import com.alibaba.fastjson.JSONObject;
    import org.apache.flume.Context;
    import org.apache.flume.Event;
    import org.apache.flume.interceptor.Interceptor;
    
    import java.nio.charset.StandardCharsets;
    import java.util.List;
    import java.util.Map;
    
    public class TimeStampInterceptor implements Interceptor {
        @Override
        public void initialize() {
        }
        @Override
        public Event intercept(Event event) {
            // 将日志拦下,取出header里面的key , 取出body里面的对应的日志时间;  将ts的值赋值给header的key  timestamp
            // 1 获取header头
            Map<String, String> headers = event.getHeaders();
            // 2 获取body中的ts
            byte[] body = event.getBody();
            String log = new String(body, StandardCharsets.UTF_8);
            JSONObject jsonObject = JSONObject.parseObject(log);
            String ts = jsonObject.getString("ts");
            // 3 将ts赋值给timestamp
            headers.put("timestamp", ts);
            return event;
        }
    
        @Override
        public List<Event> intercept(List<Event> list) {
            for (Event event : list) {
                intercept(event);
            }
            return list;
        }
    @Override
    public void close() { } public static class Builder implements Interceptor.Builder{ @Override public Interceptor build() { return new TimeStampInterceptor(); } @Override public void configure(Context context) { } } }

    2)重新打包
    3)需要先将打好的包放入到hadoop102的/opt/module/flume/lib文件夹下面。

    [atguigu@hadoop102 lib]$ ls | grep interceptor
    flume-interceptor-1.0-SNAPSHOT-jar-with-dependencies.jar

    4)分发Flume到hadoop103、hadoop104

    [atguigu@hadoop102 module]$ xsync flume/

    4.5.4 消费者Flume启动停    

    在脚本中填写如下内容

    #! /bin/bash
    
    case $1 in
    "start"){
            for i in hadoop104
            do
                    echo " --------启动 $i 消费flume-------"
                    ssh $i "nohup /opt/module/flume/bin/flume-ng agent --conf-file /opt/module/flume/conf/kafka-flume-hdfs.conf --name a1 -Dflume.root.logger=INFO,LOGFILE >/opt/module/flume/log2.txt   2>&1 &"
            done
    };;
    "stop"){
            for i in hadoop104
            do
                    echo " --------停止 $i 消费flume-------"
                    ssh $i "ps -ef | grep kafka-flume-hdfs | grep -v grep |awk '{print \$2}' | xargs -n1 kill"
            done
    
    };;
    esac

    (2)增加脚本执行权限

    [atguigu@hadoop102 bin]$ chmod u+x f2.sh

    (3)f2集群启动脚本

    [atguigu@hadoop102 module]$ f2.sh start

    (4)f2集群停止脚本

    [atguigu@hadoop102 module]$ f2.sh stop

    4.3.5 项目经验之Flume内存优化

    1)问题描述:如果启动消费Flume抛出如下异常

    ERROR hdfs.HDFSEventSink: process failed
    java.lang.OutOfMemoryError: GC overhead limit exceeded

    2)解决方案步骤
    (1)在hadoop102服务器的/opt/module/flume/conf/flume-env.sh文件中增加如下配置

    export JAVA_OPTS="-Xms100m -Xmx2000m -Dcom.sun.management.jmxremote"

    (2)同步配置到hadoop103、hadoop104服务器

    [atguigu@hadoop102 conf]$ xsync flume-env.sh

    3)Flume内存参数设置及优化

    JVM heap一般设置为4G或更高

    -Xmx与-Xms最好设置一致,减少内存抖动带来的性能影响,如果设置不一致容易导致频繁fullgc。

    -Xms表示JVM Heap(堆内存)最小尺寸,初始分配;-Xmx 表示JVM Heap(堆内存)最大允许的尺寸,按需分配。如果不设置一致,容易在初始化时,由于内存不够,频繁触发fullgc。

    4.6 采集通道启动/停止脚本

    (1)在/home/atguigu/bin目录下创建脚本cluster.sh

    [atguigu@hadoop102 bin]$ vim cluster.sh

        在脚本中填写如下内容

    #!/bin/bash
    
    case $1 in
    "start"){
            echo ================== 启动 集群 ==================
    
            #启动 Zookeeper集群
            zk.sh start
    
            #启动 Hadoop集群
            hdp.sh start
    
            #启动 Kafka采集集群
            kf.sh start
    
            #启动 Flume采集集群
            f1.sh start
    
            #启动 Flume消费集群
            f2.sh start
    
            };;
    "stop"){
            echo ================== 停止 集群 ==================
    
            #停止 Flume消费集群
            f2.sh stop
    
            #停止 Flume采集集群
            f1.sh stop
    
            #停止 Kafka采集集群
            kf.sh stop
    
            #停止 Hadoop集群
            hdp.sh stop
    
            #停止 Zookeeper集群
            zk.sh stop
    
    };;
    esac

    (2)增加脚本执行权限

    [atguigu@hadoop102 bin]$ chmod u+x cluster.sh    

    (3)cluster集群启动脚本

    [atguigu@hadoop102 module]$ cluster.sh start

    (4)cluster集群停止脚本

    [atguigu@hadoop102 module]$ cluster.sh stop

     产生日志

    [atguigu@hadoop102 module]$ lg.sh

    查看HDFS

  • 相关阅读:
    网络日志流量分析-第一部分.doc
    Azkaban.Sqoop_网站流量日志分析2
    飞机加油问题
    9个点画10条直线,要求每条直线上至少3个点
    vector
    Selenium VS Webdriver
    B/S测试与C/S测试之区别
    几款代码比较工具
    单元测试-圈复杂度计算
    为什么并行测试很困难以及如何使用 ConTest 辅助测试
  • 原文地址:https://www.cnblogs.com/wkfvawl/p/15834034.html
Copyright © 2020-2023  润新知