离线数仓-01-项目介绍和用户行为数据采集

数据仓库概念

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

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

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

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

爬虫数据:通常事通过技术手段获取其他公司网站的数据。

数仓:

项目需求和架构设计

项目需求分析

  1. 用户行为数据采集平台搭建
  2. 业务数据采集平台搭建
  3. 数据仓库维度建模
  4. 分析,设备、会员、商品、地区、活动等电商核心主题,统计的报表指标近100个
  5. 采用即席查询工具,随时进行指标分析
  6. 对集群性能进行监控,发生异常需要报警
  7. 元数据管理
  8. 质量监控
  9. 权限管理

技术选型

技术选型主要考虑因素:数据量大小、业务需求、行业内经验、技术成熟度、开发维护成本、总成本预算。

  • 数据采集传输:FlumeKaikaSqoop,Logstash,Daax,maxwell,canal
  • 数据存储:MySQLHDFSHBase,Redis, MongoDB
  • 数据计算:HiveTezSpark,Flink, Storm
  • 数据查询:PrestoKylin, Impala, Druid, ClickHouse, Doris
  • 数据可视化:EchartsSuperset,QuickBI, Datav
  • 任务调度:Azkabhan, Oozie, DolphinSchedulet, Airflow
  • 集群监控:Zabbix, Prometheus
  • 元数据管理:Atlas
  • 权限管理:Ranger,Sentry

系统数据流程设计

框架版本选型

如何选择Apache/CDH/HDP版本?

  • Apache:运维麻烦,组件间兼容性需要自己调研。(一般大厂使用,技术实力雄厚,有专业的运维人员) (建议使用)
  • DH: 国内使用最多的版本,但CM不开源,今年开始收费,一个节点1万美金/年。
  • HDP:开源,可以进行二次开发,但是没有CDH稳定,国内使用较少

云服务选择:

  • 阿里云的EMR、 MaxCompute、 DataWorks
  • 亚马逊云EMR
  • 腾讯云EMR
  • 华为云EMR

具体版本:

服务器选型

服务器选择物理机还是云主机?
(1)物理机:

  • 以128G内存,20核物理CPU,40线程,8THDD和2TSSD硬盘,戴尔品牌单台报价4W出头。一般物理机寿命5年左右。
  • 需要有专业的运维人员,平均一个月1万。电费也是不少的开销。

(2)云主机:

  • 云主机:以阿里云为例,差不多相同配置,每年5w
  • 很多运维工作都由阿里云完成,运维相对较轻松

(3)企业选择:

  • 金融有钱公司和阿里没有直接冲突的公司选择阿里云
  • 中小公司、为了融资上市,选择阿里云,拉倒融资后买物理机
  • 有长期打算,资金比较足,选择物理机

集群规模

(1)如何确认集群规模?(假设:每台服务器8T磁盘,128G内存)

  • 每天日活跃用户100万,每人一天平均100条:100万100条=1亿条
  • 每条日志1K左右,每天1亿条:100000000 /1024/1024一约100G
  • 半年内不扩容服务器来算:100G*180天=约18T
  • 保存3副本:18T*3=54T
  • 预留20%~30%Buf-54T/0.7=77T
  • 算到这:约8T*10台服务器

(2)如果考虑数仓分层?数据采用压缩?需要重新再计算

集群资源规划设计

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

(1)生产集群

  • 消耗内存的分开
  • 数据传输数据比较紧密的放在一起(Kafka 、Zookeeper)
  • 客户端尽量放在一到两台服务器上,方便外部访问
  • 有依赖关系的尽量放到同一台服务器(例如:Hive和Azkaban Executor)
12345678910
nnnndndndndndndndndn
rmrmnmnmnmnmnmnm
nmnm
zkzkzk
kafkakafkakafka
FlumeFlumeflume
HbaseHbaseHbase
hivehive
mysqlmysql
sparkspark
AzkabanAzkabanESES

(2)测试集群服务器规划

服务名称子服务服务器hadoop102服务器hadoop103服务器hadoop104
HDFSNameNode
DataNode
SecondaryNameNode
YarnNodeManager
Resourcemanager
ZookeeperZookeeper Server
Flume(采集日志)Flume
KafkaKafka
Flume(消费Kafka)Flume
HiveHive
MySQLMySQL
SqoopSqoop
PrestoCoordinator
Worker
AzkabanAzkabanWebServer
AzkabanExecutorServer
Spark
Kylin
HBaseHMaster
HRegionServer
Superset
Atlas
SolrJar
服务数总计1988

用户行为数据采集01-日志数据生成模块

目标数据

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

(1)页面数据

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

字段名称字段描述
page_id页面idhome(“首页”),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跳入时间

(2)事件数据

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

字段名称字段描述
action_id动作idfavor_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)曝光数据

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

字段名称字段描述
displayType曝光类型promotion(“商品推广”),recommend(“算法推荐商品”),query(“查询结果商品”),activity(“促销活动”);
item_type曝光对象类型sku_id(“商品skuId”),activity_id(“活动id”);
item曝光对象id
order曝光顺序

(4)启动

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

字段名称字段描述
entry启动入口icon(“图标”),notification(“通知”),install(“安装后启动”);
loading_time启动加载时间
open_ad_id开屏广告id
open_ad_ms广告播放时间
open_ad_skip_ms用户跳过广告时间
ts启动时间

(5)错误

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

字段名称字段描述
error_code错误码
msg错误信息

数据埋点

(1)主流埋点方式(了解)

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

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

  • 前端埋点:前端埋点能够收集更全面、精细的用户数据,尤其是不需要请求服务器的行为数据,如:页面停留时长、页面浏览深度、视频播放时长、用户鼠标轨迹、表单项停留及终止等等,只能通过前端埋点实现。但缺点在于,前端埋点的上报一般存在 15% 左右的延迟上报和漏报(客户端未联网、数据打包上报、用户删除行为数据等原因)。另外,如果客户端是 APP,每次上线新的埋点或者更新埋点时,需要发布新的版本才行,但是会存在部分用户不更新版本情况,影响数据质量。
  • 后端埋点:理论上,只要客户端向服务器发送过请求,服务端埋点能够收集到。相比于前端埋点,能实时采集数据,不存在延时上报,数据很准确;并且,服务端埋点支持与用户身份信息和行为附带属性信息整合;另外,每次上线新的埋点或者更新埋点时,发布后马上生效。

代码埋点适合精细化分析的场景,我们可以将各种细粒度的数据采集下来,后续做深度分析。当然这种埋点方式很低效,需要经历完整的埋点流程,包括业务梳理(产品运营)、埋点设计(产品运营/研发)、实施/测试/上线埋点(研发/测试)。整个过程需要多方协作,且要求产品运营也具备一定的专业水平,如果发生错漏无法快速补救。

全埋点,也指无埋点、无痕埋点、自动埋点,是通过在产品中嵌入SDK,前端自动采集页面上的全部用户行为事件,上报埋点数据,相当于做了一个统一的埋点。然后再通过界面配置哪些数据需要在系统里面进行分析。① 无埋点只能覆盖基本的点击、展示等用户行为;② 其次,全埋点采集的数据量非常大,随着数据量上升,可能会导致客户端崩溃的概率也会上升。尤其是移动端,更多的数据量意味着更多的电量、流量和内存消耗;③ 第三,即使全部行为数据都被收集回来了,具体分析时也不能避免二次梳理和加工,因为机器无法在采集时按照我们想要的方式对全部事件进行有意义的命名,甚至无法保证采集上来的事件都正好是正确的;④ 第四,现阶段全埋点对于用户身份信息和行为附带的属性信息也几乎无能为力。

可视化埋点,通常是指用户通过设备连接用户行为分析工具的数据接入管理界面,对可交互且交互后有效果的页面元素(如:图片、按钮、链接等),直接在界面上进行操作实现数据埋点,下发采集代码生效回数的埋点方式。这种方式所见即所得,跳过代码部署、测试验证和发版过程,极大提升生产力。

参考:

(2)埋点数据上报时机

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

  • 在离开该页面时,上传在这个页面产生的所有数据(页面、事件、曝光、错误等)。优点,批处理,减少了服务器接收数据压力。缺点,不是特别及时。
  • 每个事件、动作、错误等,产生后,立即发送。优点,响应及时。缺点,对服务器接收数据压力比较大。

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

(3)埋点数据日志结构

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

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

  • 普通页面埋点日志格式

    {
      "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  --跳入时间戳
    }
    
  • 启动日志格式

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

    {
      "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
    }
    

服务器和JDK准备

可以购买阿里云服务器准备(可选)

编写集群分发脚本xsync

(1)需求:循环复制文件到所有节点的相同目录下

(2)需求分析:

  • rsync命令原始拷贝:

    rsync -av /opt/module root@hadoop103:/opt/
    
  • 期望脚本:xsync要同步的文件名称

  • 说明:在/home/atguigu/bin这个目录下存放的脚本,atguigu用户可以在系统任何地方直接执行。

(3)脚本实现

  • 在用的家目录/home/atguigu下创建bin文件夹

    mkdir bin
    
  • 在/home/atguigu/bin目录下创建xsync文件,以便全局调用

    cd /home/atguigu/bin
    vim xsync
    

    在该文件中编写如下代码

    #!/bin/bash
    #1. 判断参数个数
    if [ $# -lt 1 ]
    then
      echo Not Enough Arguement!
      exit;
    fi
    #2. 遍历集群所有机器
    for host in hadoop102 hadoop103 hadoop104
    do
      echo ====================  $host  ====================
      #3. 遍历所有目录,挨个发送
      for file in $@
      do
        #4 判断文件是否存在
        if [ -e $file ]
        then
          #5. 获取父目录
          pdir=$(cd -P $(dirname $file); pwd)
          #6. 获取当前文件的名称
          fname=$(basename $file)
          ssh $host "mkdir -p $pdir"
          rsync -av $pdir/$fname $host:$pdir
        else
          echo $file does not exists!
        fi
      done
    done
    
  • 修改脚本xsync具有执行权限

    chmod +x xsync
    
  • 测试脚本

    xsync xsync
    
SSH无密登录配置

说明:这里面只配置了hadoop102、hadoop103到其他主机的无密登录;因为hadoop102未外配置的是NameNode,hadoop103配置的是ResourceManager,都要求对其他节点无密访问。

(1)hadoop102上生成公钥和私钥:

[atguigu@hadoop102 .ssh]$ ssh-keygen -t rsa

然后敲(三个回车),就会生成两个文件id_rsa(私钥)、id_rsa.pub(公钥)

(2)将hadoop102公钥拷贝到要免密登录的目标机器上

[atguigu@hadoop102 .ssh]$ ssh-copy-id hadoop102
[atguigu@hadoop102 .ssh]$ ssh-copy-id hadoop103
[atguigu@hadoop102 .ssh]$ ssh-copy-id hadoop104

(3)hadoop103上生成公钥和私钥:

[atguigu@hadoop103 .ssh]$ ssh-keygen -t rsa

然后敲(三个回车),就会生成两个文件id_rsa(私钥)、id_rsa.pub(公钥)

(4)将hadoop103公钥拷贝到要免密登录的目标机器上

[atguigu@hadoop103 .ssh]$ ssh-copy-id hadoop102
[atguigu@hadoop103 .ssh]$ ssh-copy-id hadoop103
[atguigu@hadoop103 .ssh]$ ssh-copy-id hadoop104
JDK准备

(1)卸载现有JDK(3台节点)

[atguigu@hadoop103 .ssh]# sudo rpm -qa | grep -i java | xargs -n1 sudo rpm -e --nodeps
[atguigu@hadoop103 .ssh]# sudo rpm -qa | grep -i java | xargs -n1 sudo rpm -e --nodeps
[atguigu@hadoop103 .ssh]# sudo rpm -qa | grep -i java | xargs -n1 sudo rpm -e --nodeps
  • rpm -qa:表示查询所有已经安装的软件包
  • grep -i:表示过滤时不区分大小写
  • xargs -n1:表示一次获取上次执行结果的一个值
  • rpm -e --nodeps:表示卸载软件

(2)用XShell工具将JDK导入到hadoop102的/opt/software文件夹下面

(3)在Linux系统下的opt目录中查看软件包是否导入成功

[atguigu@hadoop102 software]# ls /opt/software/

(4)解压JDK到/opt/module目录下

[atguigu@hadoop102 software]#tar -zxvf jdk-8u212-linux-x64.tar.gz -C /opt/module/

(5)配置JDK环境变量

  • 新建/etc/profile.d/my_env.sh文件

    [atguigu@hadoop102 module]# sudo vim /etc/profile.d/my_env.sh
    
  • 添加如下内容,然后保存(:wq)退出

    #JAVA_HOME
    export JAVA_HOME=/opt/module/jdk1.8.0_212
    export PATH=\$PATH:\$JAVA_HOME/bin
    
  • 让环境变量生效

    [atguigu@hadoop102 software]$ source /etc/profile.d/my_env.sh
    

(6)测试JDK是否安装成功

[atguigu@hadoop102 module]# java -version

如果能看到以下结果、则Java正常安装

java version "1.8.0_212"

(7)分发JDK

[atguigu@hadoop102 module]$ xsync /opt/module/jdk1.8.0_212/

(8)分发环境变量配置文件

[atguigu@hadoop102 module]$ sudo /home/atguigu/bin/xsync /etc/profile.d/my_env.sh

(9)分别在hadoop103、hadoop104上执行source

[atguigu@hadoop103 module]$ source /etc/profile.d/my_env.sh
[atguigu@hadoop104 module]$ source /etc/profile.d/my_env.sh
环境变量配置说明

Linux的环境变量可在多个文件中配置,如**/etc/profile,/etc/profile.d/*.sh,~/.bashrc,~/.bash_profile**等,下面说明上述几个文件之间的关系和区别。

bash的运行模式可分为login shell和non-login shell。

例如,我们通过终端,输入用户名、密码,登录系统之后,得到就是一个login shell。而当我们执行以下命令ssh hadoop103 command,在hadoop103执行command的就是一个non-login shell。

登录shell和非登录shell的区别:

这两种shell的主要区别在于,它们启动时会加载不同的配置文件,login shell启动时会加载/etc/profile,~/.bash_profile,~/.bashrc。non-login shell启动时会加载~/.bashrc。

而在加载~/.bashrc(实际是~/.bashrc中加载的/etc/bashrc)或/etc/profile时,都会执行如下代码片段,

因此不管是login shell还是non-login shell,启动时都会加载/etc/profile.d/*.sh中的环境变量。

模拟数据-日志生成

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

  • 创建applog路径

    [atguigu@hadoop102 module]$ mkdir /opt/module/applog
    
  • 上传文件application.yml到/opt/module/applog目录

(2)配置文件

  • 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,苹果手机,小米盒子"
    
  • 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 }
    ]
    
  • 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)生成日志

  • 进入到/opt/module/applog路径,执行以下命令

    [atguigu@hadoop102 applog]$ java -jar gmall2020-mock-log-2021-01-22.jar
    
  • 在/opt/module/applog/log目录下查看生成日志

    [atguigu@hadoop102 log]$ ll
    

(4)集群日志生成脚本

在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
  • 在/home/atguigu/bin目录下创建脚本lg.sh

    [atguigu@hadoop102 bin]$ vim lg.sh
    
  • 在脚本中编写如下内容

    #!/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

(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.2020-06-14.log
[atguigu@hadoop103 logs]$ ls
app.2020-06-14.log

用户行为数据采集02-数据采集模块

集群所有进程查看脚本

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

[atguigu@hadoop102 bin]$ vim xcall.sh

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

#! /bin/bash
for i in hadoop102 hadoop103 hadoop104
do
    echo --------- $i ----------
    ssh $i "$*"
done

(3)修改脚本执行权限

[atguigu@hadoop102 bin]$ chmod 777 xcall.sh

(4)启动脚本

[atguigu@hadoop102 bin]$ xcall.sh jps

Hadoop安装

集群规划:

服务器hadoop102服务器hadoop103服务器hadoop104
HDFSNameNodeDataNodeDataNodeDataNodeSecondaryNameNode
YarnNodeManagerResourcemanagerNodeManagerNodeManager

注意:尽量使用离线方式安装

项目经验之HDFS存储多目录

(1)给Linux系统新增加一块硬盘

参考:https://www.cnblogs.com/yujianadu/p/10750698.html

(2)生产环境服务器磁盘情况

(3)在hdfs-site.xml文件中配置多目录,注意新挂载磁盘的访问权限问题

HDFS的DataNode节点保存数据的路径由dfs.datanode.data.dir参数决定,其默认值为file://${hadoop.tmp.dir}/dfs/data,若服务器有多个磁盘,必须对该参数进行修改。如服务器磁盘如上图所示,则该参数应修改为如下的值。

<property>
    <name>dfs.datanode.data.dir</name>
    <value>file:///dfs/data1,file:///hd2/dfs/data2,file:///hd3/dfs/data3,file:///hd4/dfs/data4</value>
</property>

注意:因为每台服务器节点的磁盘情况不同,所以这个配置配完之后,不需要分发

集群数据均衡

(1)节点间数据均衡

  • 开启数据均衡命令

    start-balancer.sh -threshold 10
    

    对于参数10,代表的是集群中各个节点的磁盘空间利用率相差不超过10%,可根据实际情况进行调整。

  • 停止数据均衡命令

    stop-balancer.sh
    

    注意:于HDFS需要启动单独的Rebalance Server来执行Rebalance操作,所以尽量不要在NameNode上执行start-balancer.sh,而是找一台比较空闲的机器。

(2)磁盘间数据均衡

  • 生成均衡计划(我们只有一块磁盘,不会生成计划

    hdfs diskbalancer -plan hadoop103
    
  • 执行均衡计划

    hdfs diskbalancer -execute hadoop103.plan.json
    
  • 查看当前均衡任务的执行情况

    hdfs diskbalancer -query hadoop103
    
  • 取消均衡任务

    hdfs diskbalancer -cancel hadoop103.plan.json
    
项目经验之支持LZO压缩配置

(1)hadoop-lzo编译

hadoop本身并不支持lzo压缩,故需要使用twitter提供的hadoop-lzo开源组件。hadoop-lzo需依赖hadoop和lzo进行编译,编译步骤如下。

  • 下载、安装并编译LZO

    wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz
    tar -zxvf lzo-2.10.tar.gz
    cd lzo-2.10
    ./configure -prefix=/usr/local/hadoop/lzo/
    make
    make install
    
  • 编译hadoop-lzo源码

    a.下载hadoop-lzo的源码,下载地址:https://github.com/twitter/hadoop-lzo/archive/master.zip
    b.解压之后,修改pom.xml:<hadoop.current.version>3.1.3</hadoop.current.version>
    c.声明两个临时环境变量:
         export C_INCLUDE_PATH=/usr/local/hadoop/lzo/include
         export LIBRARY_PATH=/usr/local/hadoop/lzo/lib 
    d.编译
        进入hadoop-lzo-master,执行maven编译命令
        mvn package -Dmaven.test.skip=true   
    e.进入target,hadoop-lzo-0.4.21-SNAPSHOT.jar 即编译成功的hadoop-lzo组件
    

(2)将编译好后的hadoop-lzo-0.4.20.jar 放入hadoop-3.1.3/share/hadoop/common/

[atguigu@hadoop102 common]$ pwd
/opt/module/hadoop-3.1.3/share/hadoop/common
[atguigu@hadoop102 common]$ ls
hadoop-lzo-0.4.20.jar

(3)同步hadoop-lzo-0.4.20.jar到hadoop103、hadoop104

[atguigu@hadoop102 common]$ xsync hadoop-lzo-0.4.20.jar

(4)core-site.xml增加配置支持LZO压缩

<configuration>
    <property>
        <name>io.compression.codecs</name>
        <value>
            org.apache.hadoop.io.compress.GzipCodec,
            org.apache.hadoop.io.compress.DefaultCodec,
            org.apache.hadoop.io.compress.BZip2Codec,
            org.apache.hadoop.io.compress.SnappyCodec,
            com.hadoop.compression.lzo.LzoCodec,
            com.hadoop.compression.lzo.LzopCodec
        </value>
    </property>

    <property>
        <name>io.compression.codec.lzo.class</name>
        <value>com.hadoop.compression.lzo.LzoCodec</value>
    </property>
</configuration>

(5)同步core-site.xml到hadoop103、hadoop104

[atguigu@hadoop102 hadoop]$ xsync core-site.xml

(6)启动及查看集群

[atguigu@hadoop102 hadoop-3.1.3]$ sbin/start-dfs.sh
[atguigu@hadoop103 hadoop-3.1.3]$ sbin/start-yarn.sh

(7)测试-数据准备

[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -mkdir /input
[atguigu@hadoop102 hadoop-3.1.3]$ hadoop fs -put README.txt /input

(8)测试-压缩

[atguigu@hadoop102 hadoop-3.1.3]$ hadoop jar share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount -Dmapreduce.output.fileoutputformat.compress=true -Dmapreduce.output.fileoutputformat.compress.codec=com.hadoop.compression.lzo.LzopCodec  /input /output

项目经验之LZO创建索引

(1)创建LZO文件的索引

LZO压缩文件的可切片特性依赖于其索引,故我们需要手动为LZO压缩文件创建索引。若无索引,则LZO文件的切片只有一个。

hadoop jar /path/to/your/hadoop-lzo.jar com.hadoop.compression.lzo.DistributedLzoIndexer big_file.lzo

(2)测试

  • 将bigtable.lzo(200M)上传到集群的根目录

    [atguigu@hadoop102 module]$ hadoop fs -mkdir /input
    [atguigu@hadoop102 module]$ hadoop fs -put bigtable.lzo /input
    
  • 执行wordcount程序

    [atguigu@hadoop102 module]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount -Dmapreduce.job.inputformat.class=com.hadoop.mapreduce.LzoTextInputFormat /input /output1
    

  • 对上传的LZO文件建索引

    [atguigu@hadoop102 module]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/common/hadoop-lzo-0.4.20.jar  com.hadoop.compression.lzo.DistributedLzoIndexer /input/bigtable.lzo
    
  • 再次执行WordCount程序

    [atguigu@hadoop102 module]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar wordcount -Dmapreduce.job.inputformat.class=com.hadoop.mapreduce.LzoTextInputFormat /input /output2
    

(3)注意:如果以上任务,在运行过程中报如下异常

Container \[pid=8468,containerID=container_1594198338753_0001_01_000002\] is running 318740992B beyond the 'VIRTUAL' memory limit. Current usage: 111.5 MB of 1 GB physical memory used; 2.4 GB of 2.1 GB virtual memory used. Killing container.

Dump of the process-tree for container_1594198338753_0001_01_000002 :

解决办法:在hadoop102的/opt/module/hadoop-3.1.3/etc/hadoop/yarn-site.xml文件中增加如下配置,然后分发到hadoop103、hadoop104服务器上,并重新启动集群。

<!--是否启动一个线程检查每个任务正使用的虚拟内存量,如果任务超出分配值,则直接将其杀掉,默认是true -->
<property>
   <name>yarn.nodemanager.vmem-check-enabled</name>
   <value>false</value>
</property>
项目经验之基准测试

在企业中非常关心每天从Java后台拉取过来的数据,需要多久能上传到集群?消费者关心多久能从HDFS上拉取需要的数据?为了搞清楚HDFS的读写性能,生产环境上非常需要对集群进行压测。

HDFS的读写性能主要受网络和磁盘影响比较大。为了方便测试,将hadoop102、hadoop103、hadoop104虚拟机网络都设置为100mbps。

100Mbps单位是bit;10M/s单位是byte ; 1byte=8bit,100Mbps/8=12.5M/s。

测试网速:

(1)来到hadoop102的/opt/module目录,创建一个

[atguigu@hadoop102 software]$ python -m SimpleHTTPServer

(2)在Web页面上访问

hadoop102:8000

(1)测试HDFS写性能

  • 写测试底层原理

  • 测试内容:向HDFS集群写10个128M的文件

    [atguigu@hadoop102 mapreduce]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-3.1.3-tests.jar TestDFSIO -write -nrFiles 10 -fileSize 128MB
    
    2021-02-09 10:43:16,853 INFO fs.TestDFSIO: ----- TestDFSIO ----- : write
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:             Date & time: Tue Feb 09 10:43:16 CST 2021
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:         Number of files: 10
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:  Total MBytes processed: 1280
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:       Throughput mb/sec: 1.61
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:  Average IO rate mb/sec: 1.9
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:   IO rate std deviation: 0.76
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:      Test exec time sec: 133.05
    2021-02-09 10:43:16,854 INFO fs.TestDFSIO:
    

    注意:nrFiles n为生成mapTask的数量,生产环境一般可通过hadoop103:8088查看CPU核数,设置为(CPU核数 - 1)

    • Number of files:生成mapTask数量,一般是集群中(CPU核数 - 1),我们测试虚拟机就按照实际的物理内存-1分配即可。(目标,让每个节点都参与测试)

    • Total MBytes processed:单个map处理的文件大小

    • Throughput mb/sec:单个mapTak的吞吐量

      计算方式:处理的总文件大小/每一个mapTask写数据的时间累加

      集群整体吞吐量:生成mapTask数量*单个mapTak的吞吐量

    • Average IO rate mb/sec::平均mapTak的吞吐量

      计算方式:每个mapTask处理文件大小/每一个mapTask写数据的时间全部相加除以task数量

    • IO rate std deviation:方差、反映各个mapTask处理的差值,越小越均衡

    注意:如果测试过程中,出现异常:

    ①可以在yarn-site.xml中设置虚拟内存检测为false

    <!--是否启动一个线程检查每个任务正使用的虚拟内存量,如果任务超出分配值,则直接将其杀掉,默认是true -->
    <property>
        <name>yarn.nodemanager.vmem-check-enabled</name>
        <value>false</value>
    </property>
    

    ②分发配置并重启Yarn集群

  • 测试结果分析

    ①由于副本1就在本地,所以该副本不参与测试:

    一共参与测试的文件:10个文件 * 2个副本 = 20个

    压测后的速度:1.61

    实测速度:1.61M/s * 20个文件 ≈ 32M/s

    三台服务器的带宽:12.5 + 12.5 + 12.5 ≈ 30m/s

    所有网络资源都已经用满。

    如果实测速度远远小于网络,并且实测速度不能满足工作需求,可以考虑采用固态硬盘或者增加磁盘个数。

    ②如果客户端不在集群节点,那就三个副本都参与计算:

(2)测试HDFS读性能

  • 测试内容:读取HDFS集群10个128M的文件

    [atguigu@hadoop102 mapreduce]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-3.1.3-tests.jar TestDFSIO -read -nrFiles 10 -fileSize 128MB
    
    2021-02-09 11:34:15,847 INFO fs.TestDFSIO: ----- TestDFSIO ----- : read
    2021-02-09 11:34:15,847 INFO fs.TestDFSIO:             Date & time: Tue Feb 09 11:34:15 CST 2021
    2021-02-09 11:34:15,847 INFO fs.TestDFSIO:         Number of files: 10
    2021-02-09 11:34:15,847 INFO fs.TestDFSIO:  Total MBytes processed: 1280
    2021-02-09 11:34:15,848 INFO fs.TestDFSIO:       Throughput mb/sec: 200.28
    2021-02-09 11:34:15,848 INFO fs.TestDFSIO:  Average IO rate mb/sec: 266.74
    2021-02-09 11:34:15,848 INFO fs.TestDFSIO:   IO rate std deviation: 143.12
    2021-02-09 11:34:15,848 INFO fs.TestDFSIO:      Test exec time sec: 20.83
    
  • 删除测试生成数据

    [atguigu@hadoop102 mapreduce]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-3.1.3-tests.jar TestDFSIO -clean
    
  • 测试结果分析:为什么读取文件速度大于网络带宽?由于目前只有三台服务器,且有三个副本,数据读取就近原则,相当于都是读取的本地磁盘数据,没有走网络。

(3)使用Sort程序评测MapReduce

  • 使用RandomWriter来产生随机数,每个节点运行10个Map任务,每个Map产生大约1G大小的二进制随机数

    [atguigu@hadoop102 mapreduce]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar randomwriter random-data
    
  • 执行Sort程序

    [atguigu@hadoop102 mapreduce]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-examples-3.1.3.jar sort random-data sorted-data
    
  • 验证数据是否真正排好序了

    [atguigu@hadoop102 mapreduce]$ hadoop jar /opt/module/hadoop-3.1.3/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-3.1.3-tests.jar testmapredsort -sortInput random-data -sortOutput sorted-data
    
项目经验之Hadoop参数调优

(1)HDFS参数调优hdfs-site.xml

The number of Namenode RPC server threads that listen to requests from clients. If dfs.namenode.servicerpc-address is not configured then Namenode RPC server threads listen to requests from all nodes.
NameNode有一个工作线程池,用来处理不同DataNode的并发心跳以及客户端并发的元数据操作。
对于大集群或者有大量客户端的集群来说,通常需要增大参数dfs.namenode.handler.count的默认值10。
<property>
    <name>dfs.namenode.handler.count</name>
    <value>10</value>
</property>

dfs.namenode.handler.count= 20 × log ⁡ e C l u s t e r   S i z e 20 \times \log_{e}^{Cluster\ Size} 20×logeCluster Size,比如集群规模为8台时,此参数设置为41。可通过简单的python代码计算该值,代码如下。

[atguigu@hadoop102 ~]$ python
Python 2.7.5 (default, Apr 11 2018, 07:36:10) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-28)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import math
>>> print int(20*math.log(8))
41
>>> quit()

(2)YARN参数调优yarn-site.xml

(1)情景描述:总共7台机器,每天几亿条数据,数据源->Flume->Kafka->HDFS->Hive

面临问题:数据统计主要用HiveSQL,没有数据倾斜,小文件已经做了合并处理,开启的JVM重用,而且IO没有阻塞,内存用了不到50%。但是还是跑的非常慢,而且数据量洪峰过来时,整个集群都会宕掉。基于这种情况有没有优化方案。

(2)解决办法:

NodeManager内存和服务器实际内存配置尽量接近,如服务器有128g内存,但是NodeManager默认内存8G,不修改该参数最多只能用8G内存。NodeManager使用的CPU核数和服务器CPU核数尽量接近。

  • yarn.nodemanager.resource.memory-mb NodeManager使用内存数
  • yarn.nodemanager.resource.cpu-vcores NodeManager使用CPU核数

Zookeeper安装

集群规划

服务器hadoop102服务器hadoop103服务器hadoop104
ZookeeperZookeeperZookeeperZookeeper

Kafka安装

集群规划:

服务器hadoop102服务器hadoop103服务器hadoop104
KafkaKafkaKafkaKafka
项目经验之Kafka机器数量计算

Kafka机器数量(经验公式)= 2 *(峰值生产速度 * 副本数 / 100)+ 1

先拿到峰值生产速度,再根据设定的副本数,就能预估出需要部署Kafka的数量。

  • 峰值生产速度:峰值生产速度可以压测得到。
  • 副本数:副本数默认是1个,在企业里面2-3个都有,2个居多。副本多可以提高可靠性,但是会降低网络传输效率。

比如我们的峰值生产速度是50M/s。副本数为2。Kafka机器数量 = 2 *(50 * 2 / 100)+ 1 = 3台

项目经验之Kafka压力测试

(1)Kafka压测

用Kafka官方自带的脚本,对Kafka进行压测。

  • kafka-consumer-perf-test.sh
  • kafka-producer-perf-test.sh

Kafka压测时,在硬盘读写速度一定的情况下,可以查看到哪些地方出现了瓶颈(CPU,内存,网络IO)。一般都是网络IO达到瓶颈。

(2)Kafka Producer压力测试

  • 压测环境准备

    ①hadoop102、hadoop103、hadoop104的网络带宽都设置为100mbps。

    ②关闭hadoop102主机,并根据hadoop102克隆出hadoop105(修改IP和主机名称)

    ③hadoop105的带宽不设限

    ④创建一个test topic,设置为3个分区2个副本

    [atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181,hadoop103:2181,hadoop104:2181/kafka --create --replication-factor 2 --partitions 3 --topic test
    
  • 在/opt/module/kafka/bin目录下面有这两个文件。我们来测试一下

    [atguigu@hadoop102 kafka]$ bin/kafka-producer-perf-test.sh  --topic test --record-size 100 --num-records 10000000 --throughput -1 --producer-props bootstrap.servers=hadoop102:9092,hadoop103:9092,hadoop104:9092
    

    说明:

    record-size是一条信息有多大,单位是字节。

    num-records是总共发送多少条信息。

    throughput 是每秒多少条信息,设成-1,表示不限流,尽可能快的生产数据,可测出生产者最大吞吐量。

  • Kafka会打印下面的信息

    699884 records sent, 139976.8 records/sec (13.35 MB/sec), 1345.6 ms avg latency, 2210.0 ms max latency.
    713247 records sent, 141545.3 records/sec (13.50 MB/sec), 1577.4 ms avg latency, 3596.0 ms max latency.
    773619 records sent, 153862.2 records/sec (14.67 MB/sec), 2326.8 ms avg latency, 4051.0 ms max latency.
    773961 records sent, 154206.2 records/sec (15.71 MB/sec), 1964.1 ms avg latency, 2917.0 ms max latency.
    776970 records sent, 154559.4 records/sec (15.74 MB/sec), 1960.2 ms avg latency, 2922.0 ms max latency.
    776421 records sent, 154727.2 records/sec (15.76 MB/sec), 1960.4 ms avg latency, 2954.0 ms max latency.
    

    参数解析:Kafka的吞吐量15m/s左右是否符合预期呢?

    hadoop102、hadoop103、hadoop104三台集群的网络总带宽30m/s左右,由于是两个副本,所以Kafka的吞吐量30m/s ➗ 2(副本) = 15m/s

    结论:网络带宽和副本都会影响吞吐量。

  • 调整batch.size

    batch.size默认值是16k。

    batch.size较小,会降低吞吐量。比如说,批次大小为0则完全禁用批处理,会一条一条发送消息);

    batch.size过大,会增加消息发送延迟。比如说,Batch设置为64k,但是要等待5秒钟Batch才凑满了64k,才能发送出去。那这条消息的延迟就是5秒钟。

    [atguigu@hadoop102 kafka]$ bin/kafka-producer-perf-test.sh  --topic test --record-size 100 --num-records 10000000 --throughput -1 --producer-props bootstrap.servers=hadoop102:9092,hadoop103:9092,hadoop104:9092 batch.size=500
    

    输出结果:

    69169 records sent, 13833.8 records/sec (1.32 MB/sec), 2517.6 ms avg latency, 4299.0 ms max latency.
    105372 records sent, 21074.4 records/sec (2.01 MB/sec), 6748.4 ms avg latency, 9016.0 ms max latency.
    113188 records sent, 22637.6 records/sec (2.16 MB/sec), 11348.0 ms avg latency, 13196.0 ms max latency.
    108896 records sent, 21779.2 records/sec (2.08 MB/sec), 12272.6 ms avg latency, 12870.0 ms max latency.
    
  • linger.ms

    如果设置batch size为64k,但是比如过了10分钟也没有凑够64k,怎么办?

    可以设置,linger.ms。比如linger.ms=5ms,那么就是要发送的数据没有到64k,5ms后,数据也会发出去。

  • 总结:同时设置batch.size和 linger.ms,就是哪个条件先满足就都会将消息发送出去。Kafka需要考虑高吞吐量与延时的平衡。

(3)Kafka Consumer压力测试

  • Consumer的测试,如果这四个指标(IO,CPU,内存,网络)都不能改变,考虑增加分区数来提升性能。

    [atguigu@hadoop102 kafka]$ bin/kafka-consumer-perf-test.sh --broker-list hadoop102:9092,hadoop103:9092,hadoop104:9092 --topic test --fetch-size 10000 --messages 10000000 --threads 1
    

    ①参数说明:

    –broker-list指定Kafka集群地址

    –topic 指定topic的名称

    –fetch-size 指定每次fetch的数据的大小

    –messages 总共要消费的消息个数

    ②测试结果说明:

    start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg**, nMsg.sec**

    2021-08-03 21:17:21:778, 2021-08-03 21:18:19:775, 514.7169, 8.8749, 5397198, 93059.9514

    开始测试时间,测试结束数据,共消费数据514.7169MB,吞吐量8.8749MB/s

  • 调整fetch-size

    ①增加fetch-size值,观察消费吞吐量。

    [atguigu@hadoop102 kafka]$ bin/kafka-consumer-perf-test.sh --broker-list hadoop102:9092,hadoop103:9092,hadoop104:9092 --topic test --fetch-size 100000 --messages 10000000 --threads 1
    

    ②测试结果说明:

    start.time, end.time, data.consumed.in.MB, MB.sec, data.consumed.in.nMsg**, nMsg.sec**

    2021-08-03 21:22:57:671, 2021-08-03 21:23:41:938, 514.7169, 11.6276, 5397198, 121923.7355

  • 总结:吞吐量受网络带宽和fetch-size的影响

项目经验值Kafka分区数计算

(1)创建一个只有1个分区的topic

(2)测试这个topic的producer吞吐量和consumer吞吐量。

(3)假设他们的值分别是Tp和Tc,单位可以是MB/s。

(4)然后假设总的目标吞吐量是Tt,那么分区数 = Tt / min(Tp,Tc)

例如:producer吞吐量 = 20m/s;consumer吞吐量 = 50m/s,期望吞吐量100m/s;

分区数 = 100 / 20 = 5分区

https://blog.csdn.net/weixin_42641909/article/details/89294698

分区数一般设置为:3-10个

采集日志Flume

集群规划:

服务器hadoop102服务器hadoop103服务器hadoop104
Flume(采集日志)FlumeFlume
项目经验之Flume组件选型

(1)Source

  • Taildir Source相比Exec Source、Spooling Directory Source的优势

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

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

    Spooling Directory Source监控目录,支持断点续传,不可以实时搜集数据。

  • 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的消息中,这显然不是我所需要的,我只是需要把内容写入即可。

日志采集Flume配置

(1)Flume配置分析

Flume直接读log日志的数据,log日志的格式是app.yyyy-mm-dd.log。

(2)Flume的具体配置如下

在/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是自定义的拦截器的全类名。需要根据用户自定义的拦截器做相应修改。

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 &
测试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的拦截器代码是否正常。

日志采集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

消费Kafka数据Flume

集群规划:

服务器hadoop102服务器hadoop103服务器hadoop104
Flume(消费Kafka)Flume
项目经验之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

  • HDFS存入大量小文件,有什么影响?

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

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

  • HDFS小文件处理:

    官方默认的这三个参数配置写入HDFS后会产生小文件,hdfs.rollInterval、hdfs.rollSize、hdfs.rollCount

    基于以上hdfs.rollInterval=3600,hdfs.rollSize=134217728,hdfs.rollCount =0几个参数综合作用,效果如下:

    ①文件在达到128M时会滚动生成新文件

    ②文件创建超3600秒时会滚动生成新文件

消费者Flume配置

Flume配置分析

在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
  • round相关的字段作用对象是时间戳(创建文件夹):

    Round(false):时间戳需不需要四舍五入(不影响%t)

    roundValue(1):时间戳四舍五入的倍数,要小于当前时间

    roundUnit(second):时间戳四舍五入的单位

  • roll相关的字段作用对象是文件(小文件问题):

    a1.sinks.k1.hdfs.rollInterval(30):每隔30秒截断一个文件。设置为0表示不会因为时间间隔截断文件

    a1.sinks.k1.hdfs.rollSize(1024):文件字节数超过1024截断一个文件。设置为0就不因为文件大小截断文件

    a1.sinks.k1.hdfs.rollCount(10):每10个event截断一个文件。设置为0就不因为event数量截断文件

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.ArrayList;
import java.util.List;
import java.util.Map;

public class TimeStampInterceptor implements Interceptor {
    private ArrayList<Event> events = new ArrayList<>();

    @Override
    public void initialize() {

    }

    @Override
    public Event intercept(Event event) {
        Map<String, String> headers = event.getHeaders();
        String log = new String(event.getBody(), StandardCharsets.UTF_8);

        JSONObject jsonObject = JSONObject.parseObject(log);

        String ts = jsonObject.getString("ts");
        headers.put("timestamp", ts);

        return event;
    }

    @Override
    public List<Event> intercept(List<Event> list) {
        events.clear();
        for (Event event : list) {
            events.add(intercept(event));
        }
        return events;
    }

    @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/
消费者Flume启动停止脚本

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

[atguigu@hadoop102 bin]$ vim f2.sh

在脚本中填写如下内容

#! /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
项目经验之Flume内存优化

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

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

(2)解决方案步骤

  • 在hadoop102服务器的/opt/module/flume/conf/flume-env.sh文件中增加如下配置

    export JAVA_OPTS="-Xms100m -Xmx2000m -Dcom.sun.management.jmxremote"
    
  • 同步配置到hadoop103、hadoop104服务器

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

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

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

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

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

采集通道启动/停止脚本

(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
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值