4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)

东软跨境电商数仓项目–数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)

文章目录

  • 东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)
    • 1.用户行为日志介绍
    • 2.用户行为日志主要内容
      • 2.1页面浏览记录
      • 2.2动作记录
      • 2.3曝光记录
      • 2.4启动记录
      • 2.5错误记录
    • 3.用户行为日志格式
      • 3.1页面日志
      • 3.2启动日志
    • 4.模拟生成用户行为日志
    • 5.用户行为日志采集通道架构回顾
    • 6.环境准备
      • 6.1编写查看集群所有java进程的脚本
      • 6.2Hadoop安装
      • 6.3Zookeepr安装
      • 6.4Kafka安装
      • 6.5Flume安装
      • 6.6环境测试
    • 7.日志采集(Flume采集日志到Kafka过程)
      • 7.1 为什么要选择**TaildirSource**不选择execSource和SpoolingDirectorySource呢?
      • 7.2为什么采用KafkaChannel呢?
      • 7.3 Flume配置文件编写
      • 7.3 Flume拦截器编写
      • 7.4 日志采集Flume(File->Kafka)测试
      • 7.5 分发日志采集Flume配置文件和拦截器以及编写日志采集Flume进程启停脚本
    • 8.日志消费(Flume监控Kafka日志传至HDFS上)
      • 8.1为什么不使用KafkaChannel+HDFSSink?
      • 8.2Flume配置文件编写
      • 8.3如何将不同天的数据发往HDFS不同天的路径
      • 8.4对应Flume配置的优化
      • 8.5Flume日志消费零点漂移问题(项目中所遇到的问题)
      • 8.5 Flume拦截器编写
      • 8.6日志消费Flume(Kafka->HDFS)测试
    • 9.编写采集通道启停脚本
    • 10.用户行为数据采集通道使用总结

1.用户行为日志介绍

用户行为日志的内容,主要包括的是用户的各项行为信息以及在进行该行为时所处的环境信息

为什么我们需要收集这些信息呢?

因为这些信息可以帮助我们优化产品(东软睿购跨境电商项目)和为统计各项指标提供数据支撑。

获取这些信息的手段有代码埋点(前端/后端)、可视化埋点以及全埋点等。

2.用户行为日志主要内容

通过和进行前后端项目开发的同学进行沟通,本项目收集和分析的用户行为信息主要有**页面的浏览记录、动作记录、曝光记录、启动记录以及错误记录。**接下来,就上述这五种记录,我们进行了分析,沟通好了有哪些数据需要采集。

2.1页面浏览记录

页面浏览记录,记录的是访客对页面的浏览行为,该行为的环境信息主要有用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息及页面信息等。

上图当中就是我们最为常见的一张用户在进行页面浏览时的情况,那么我们这对该种情况下,可以收集到的信息就有以下几种:

信息类型 具体描述
用户信息 用户ID、设备ID
时间信息 用户跳入页面的时间
地理位置信息 用户浏览页面时所处的地理位置
设备信息 设备品牌、设备型号、设备系统
应用信息 指用户访问的应用信息,例如应用的版本信息
渠道信息 指该应用下载的渠道
页面信息 页面ID、页面对象等

其中上述的信息都会使用json的形式来接收,示例如下所示

{
  "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版本号
  },
  "page": {                       --页面信息
    "during_time": 7648,        -- 持续时间毫秒
    "item": "3",                  -- 目标id
    "item_type": "sku_id",      -- 目标类型
    "last_page_id": "login",    -- 上页类型
    "page_id": "good_detail",   -- 页面ID
    "sourceType": "promotion"   -- 来源类型
  },
  "ts": 1585744374423  --跳入时间戳
}

2.2动作记录

动作记录,记录的是用户的业务操作行为,该行为的环境信息主要有用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息及动作目标对象信息等。

上图中,当我们点击领卷购买时,就会产生动作记录,那么我们这对该种情况下,可以收集到的信息就有以下几种:

信息类型 具体描述
用户信息 用户ID、设备ID
时间信息 动作时间
地理位置信息 动作发生时所处的地理位置
设备信息 设备品牌、设备型号、设备系统
应用信息 指用户访问的应用信息,如应用版本
渠道信息 指应用下载的渠道
动作目标信息 动作目标对象相关信息,包括对象类型,对象ID

其中上述的信息都会使用json的形式来接收,示例如下所示

{
  "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           --动作时间戳
    }
  ],
  "page": {                       --页面信息
    "during_time": 7648,        -- 持续时间毫秒
    "item": "3",                  -- 目标id
    "item_type": "sku_id",      -- 目标类型
    "last_page_id": "login",    -- 上页类型
    "page_id": "good_detail",   -- 页面ID
    "sourceType": "promotion"   -- 来源类型
  },
  "ts": 1585744374423  --跳入时间戳
}

2.3曝光记录

曝光记录,记录的是曝光行为,该行为的环境信息主要有用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息及曝光对象信息等。

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第1张图片

上图的绿框中就是页面当中的曝光信息。那么我们这对该种情况下,可以收集到的信息就有以下几种:

信息类型 具体描述
用户信息 用户ID、设备ID
时间信息 动作时间
地理位置信息 动作发生时所处的地理位置
设备信息 设备品牌、设备型号、设备系统
应用信息 指用户访问的应用信息,如应用版本
渠道信息 指应用下载的渠道
曝光对象信息 曝光对象相关信息,包括对象类型,对象ID

其中上述的信息都会使用json的形式来接收,示例如下所示

{
  "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版本号
  },
  "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
    }
  ],
  "page": {                       --页面信息
    "during_time": 7648,        -- 持续时间毫秒
    "item": "3",                  -- 目标id
    "item_type": "sku_id",      -- 目标类型
    "last_page_id": "login",    -- 上页类型
    "page_id": "good_detail",   -- 页面ID
    "sourceType": "promotion"   -- 来源类型
  },
  "ts": 1585744374423  --跳入时间戳
}

2.4启动记录

启动日志以启动为单位,及一次启动行为,生成一条启动日志。一条完整的启动日志包括一个启动记录,一个本次启动时的报错记录,以及启动时所处的环境信息,包括用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息等。

上图就是页面当中的启动信息。那么我们这对该种情况下,可以收集到的信息就有以下几种:

信息类型 具体描述
用户信息 用户ID、设备ID
时间信息 动作时间
地理位置信息 动作发生时所处的地理位置
设备信息 设备品牌、设备型号、设备系统
应用信息 指用户访问的应用信息,如应用版本
渠道信息 指应用下载的渠道
开屏广告信息 包括广告ID等信息

其中上述的信息都会使用json的形式来接收,示例如下所示

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

2.5错误记录

错误记录,记录的是用户在使用应用过程中的报错行为,该行为的环境信息主要有用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息、以及可能与报错相关的页面信息、动作信息、曝光信息和动作信息。

示例如下所示:

"err":{                     --错误
"error_code": "1234",      --错误码
    "msg": "***********"       --错误信息
},

3.用户行为日志格式

通过上述分析,我们可以大致将这五种记录归结为在用户使用的过程当中可以产生两种日志,分别是页面日志和启动日志。

3.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
    }
  ],
  "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  --跳入时间戳
}

3.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
}

4.模拟生成用户行为日志

对于用户行为日志的生成,使用的是已经提供好的模拟用户行为日志生成的工具,其中包括一个用sparingboot写的模拟用户行为日志生成的程序,一个yml配置文件,一个logback配置文件以及一个path.json文件,我们可以通过配置文件来更改相关的用户行为日志生成的信息,比如生成的用户行为日志中每一条记录发生的时间等信息。

下面介绍一下日志如何通过该脚本生成:

1)首先,我们将这些文件上传到服务器上的指定文件夹下(我存到的是/opt/module/applog下):

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第2张图片

2)我们在该路径下执行 java - jar mock_log_data.jar 便可以运行该程序生成对应的日志信息,日志信息存储在log文件当中:
请添加图片描述

因为本次我们共有三台服务器用于开发该项目,我们选择两台服务器模拟日志服务器来模拟生成日志(hadoop102和hadoop103),因此我们需要在这两台服务器上的’/opt/module/applog’位置处都添加日志生成工具,日志产生的位置在‘/opt/module/applog/log’下。

为了方便集群当中两台服务器上的日志的生成,进行集群日志生成脚本的编写,本次项目中,我们的脚本统一放在了hadoop102这台服务器的/home/hadoop/bin的目录下,我们把该路径设置为系统路径:
请添加图片描述

接下来,我们开始在该目录下进行脚本的编写,该脚本lg.sh 的作用是同时运行hadoop102和hadoop103上模拟生成日志的工具,脚本的内容如下所示:

#!/bin/bash
for i in hadoop102 hadoop103; do #遍历两台服务器,分别执行对应的命令
    echo "========== $i =========="
    ssh $i "cd /opt/module/applog/; java -jar mock_log_data.jar >/dev/null 2>&1 &"
done 

为了不将日志信息打印到控制台上,使用了 >/dev/null 2>&1将标准输出和错误输出都丢失掉。

之后我们再使用 chmod u+x lg.sh 来修改脚本的权限,使脚本可执行。

至此为止,我们模拟业务生成部分已经完成,可以正式开始搭建用户行为日志数据采集通道。

5.用户行为日志采集通道架构回顾

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第3张图片

在本次项目中,我们在hadoop102和hadoop103两台服务器上分别使用Flume来监听日志文件的变化,将产生的用户行为日志数据发送到Kafka的topic_log主题当中。之后,我们在hadoop104上启动flume来监听Kafka中主题topic_log,将其中的数据存放到指定的目录下。为了合理的存放数据,我们将数据存放到了该行为所发送的那一天的目录下。

6.环境准备

6.1编写查看集群所有java进程的脚本

为了方便我们查询集群中hadoop,zookeeper等集群进程的启动情况,我们编写相关脚本(/home/hadoop/bin目录下)xcall.sh。具体内容如下:

#! /bin/bash

for i in hadoop102 hadoop103 hadoop104
do 
	echo "========== $i =========="
	ssh $i "$*"
done

之后我们在对应的目录下修改该脚本的目录权限

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

如果我们想使用该脚本查看所有的java进行信息,只需要输入:

[root@hadoop102 bin]$ xcall.sh jps

具体效果如下所示:
4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第4张图片

6.2Hadoop安装

本次hadoop的版本使用的是3.1.3版本,集群的部署规划如下图所示:

hadoop102 hadoop103 hadoop104
HDFS NameNode DataNode DataNode SecondaryNameNode DataNode
YARN NodeManager ResourceManager NodeManager NodeManager

在将hadoop安装包解压到指定文件目录(/opt/module/hadoop-3.1.3)下后,我们需要对集群进行相关配置。

1)首先我们对核心文件进行配置

vim /opt/module/hadoop-3.1.3/etc/hadoop/core-site.xml

文件的内容如下图所示:




<configuration>
	
    <property>
        <name>fs.defaultFSname>
        <value>hdfs://hadoop102:8020value>
	property>
	
    <property>
        <name>hadoop.tmp.dirname>
        <value>/opt/module/hadoop-3.1.3/datavalue>
	property>
	
    <property>
        <name>hadoop.http.staticuser.username>
        <value>rootvalue>
	property>

	
    <property>
        <name>hadoop.proxyuser.root.hostsname>
        <value>*value>
	property>
	
    <property>
        <name>hadoop.proxyuser.root.groupsname>
        <value>*value>
	property>
	
    <property>
        <name>hadoop.proxyuser.root.usersname>
        <value>*value>
	property>
configuration>

2)我们对HDFS的配置文件进行配置

vim /opt/module/hadoop-3.1.3/etc/hadoop/core-site.xml

具体内容如下所示:




<configuration>
	
	<property>
        <name>dfs.namenode.http-addressname>
        <value>hadoop102:9870value>
    property>
    
	
    <property>
        <name>dfs.namenode.secondary.http-addressname>
        <value>hadoop104:9868value>
    property>
    
    
    <property>
        <name>dfs.replicationname>
        <value>3value>
    property>
configuration>

3)我们对yarn的配置文件进行配置

vim /opt/module/hadoop-3.1.3/etc/hadoop/yarn-site.xml

具体内容如下所示:




<configuration>
	
    <property>
        <name>yarn.nodemanager.aux-servicesname>
        <value>mapreduce_shufflevalue>
    property>
    
    
    <property>
        <name>yarn.resourcemanager.hostnamename>
        <value>hadoop103value>
    property>
    
    
    <property>
        <name>yarn.nodemanager.env-whitelistname>    <value>JAVA_HOME,HADOOP_COMMON_HOME,HADOOP_HDFS_HOME,HADOOP_CONF_DIR,CLASSPATH_PREPEND_DISTCACHE,HADOOP_YARN_HOME,HADOOP_MAPRED_HOMEvalue>
    property>
    
    
    <property>
        <name>yarn.scheduler.minimum-allocation-mbname>
        <value>512value>
    property>
    <property>
        <name>yarn.scheduler.maximum-allocation-mbname>
        <value>8192value>
    property>
    
    
    <property>
        <name>yarn.nodemanager.resource.memory-mbname>
        <value>8192value>
    property>
    
    
    <property>
        <name>yarn.nodemanager.pmem-check-enabledname>
        <value>falsevalue>
    property>
    <property>
        <name>yarn.nodemanager.vmem-check-enabledname>
        <value>falsevalue>
    property>
configuration>

4)MapReduce的配置文件

vim /opt/module/hadoop-3.1.3/etc/hadoop/yarn-site.xml

具体内容如下:




<configuration>
	
    <property>
        <name>mapreduce.framework.namename>
        <value>yarnvalue>
    property>
configuration>

5)配置workers

vim /opt/module/hadoop-3.1.3/etc/hadoop/workers

文件内容:

hadoop102
hadoop103
hadoop104

之后,为了查看程序的历史运行情况,需要配置一下历史服务器。具体配置步骤如下:

1)配置mapred-site.xml

在文件当中添加如下内容:

	
	<property>
		<name>mapreduce.jobhistory.addressname>
		<value>hadoop102:10020value>
	property>

	
	<property>
		<name>mapreduce.jobhistory.webapp.addressname>
		<value>hadoop102:19888value>
	property>

2)配置日志的聚集(yarn-site.xml)


	
		yarn.log-aggregation-enable
		true
	

	
	  
		yarn.log.server.url  
		http://hadoop102:19888/jobhistory/logs
	

	
	
		yarn.log-aggregation.retain-seconds
		604800
	

最后,我们需要将hadoop分发到三台服务器当中:

xsync /opt/module/hadoop-3.1.3/

为了我们可以在后续启动hadoop集群方便,接下来我们编写Hadoop的群起脚本(/home/hadoop/bin/)myhadoop.sh:

#! /bin/bash


if [$# -lt 1 ]
then 
	echo "No Args Input..."
	exit;
fi

case $1 in
"start")
	echo " =================== 启动 hadoop集群 ==================="
        echo " --------------- 启动 hdfs ---------------"
        ssh hadoop102 "/opt/module/hadoop-3.1.3/sbin/start-dfs.sh"
        echo " --------------- 启动 yarn ---------------"
        ssh hadoop103 "/opt/module/hadoop-3.1.3/sbin/start-yarn.sh"
        echo " --------------- 启动 historyserver ---------------"
        ssh hadoop102 "/opt/module/hadoop-3.1.3/bin/mapred --daemon start historyserver"
;;
"stop")
	 echo " =================== 关闭 hadoop集群 ==================="
        echo " --------------- 关闭 historyserver ---------------"
        ssh hadoop102 "/opt/module/hadoop-3.1.3/bin/mapred --daemon stop historyserver"
        echo " --------------- 关闭 yarn ---------------"
        ssh hadoop103 "/opt/module/hadoop-3.1.3/sbin/stop-yarn.sh"
        echo " --------------- 关闭 hdfs ---------------"
        ssh hadoop102 "/opt/module/hadoop-3.1.3/sbin/stop-dfs.sh"
*)
	    echo "Input Args Error..."
;;
esac

之后我们授予该脚本执行的权限

 chmod 777 myhadoop.sh

6.3Zookeepr安装

我们本次项目中zookeeper使用的版本是3.5.7,具体的集群规划如下所示:

服务器hadoop102 服务器hadoop103 服务器hadoop104
Zookeeper Zookeeper Zookeeper Zookeeper

对于zookeeper的安装,我们首先将压缩包解压到指定的目录,之后配置服务器的编号,hadoop102对应的服务器的编号为2,hadoop103对应的编号为3,hadoop104对应的编号为4。

注:编号配置在/opt/module/zookeeper-3.5.7/zkData/myid文件中

之后,我们对zoo.cfg文件进行配置:

1)首先,我们重命名opt/module/zookeeper-3.5.7/conf目录下的zoo_sample.cfg为zoo.cfg

mv zoo_sample.cfg zoo.cfg

2)之后,我们打开zoo.cfg文件,修改数据存储路径配置以及添加相关配置。

修改数据存储路径配置:

dataDir=/opt/module/zookeeper-3.5.7/zkData

之后添加相关配置:

#######################cluster##########################
server.2=hadoop102:2888:3888
server.3=hadoop103:2888:3888
server.4=hadoop104:2888:3888

为了后续方便我们启停ZK集群,我们接下来编写Zookeeper的启停脚本:

脚本地址:/home/hadoop/bin/zk.sh

#! /bin/bash

case $1 bin
"start"){
	for i in hadoop102 hadoop103 hadoop104
	do
		echo "---------- zookeeper $i 启动 ------------"
		ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh start"
	done
};;
"stop"){
	for i in hadoop102 hadoop103 hadoop104
	do
        echo ---------- zookeeper $i 停止 ------------    
		ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh stop"
	done
};;
"status"){
	for i in hadoop102 hadoop103 hadoop104
	do
        echo ---------- zookeeper $i 状态 ------------    
		ssh $i "/opt/module/zookeeper-3.5.7/bin/zkServer.sh status"
	done
};;
esac

之后,我们为脚本增加执行权限:

chmod u+x zk.sh

测试该脚本:
4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第5张图片

6.4Kafka安装

6.5Flume安装

6.6环境测试

上述环境安装成功之后,我们接下来对环境进行测试,查看是否能使用脚本启动停止Hadoop、Zookeeper、Kafka

接下来按以下顺序执行脚本:

myhadoop.sh start
zk.sh start
kafka.sh start

具体效果如下所示:
4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第6张图片
集群正常运行,说明我们的环境已经搭建成功。

7.日志采集(Flume采集日志到Kafka过程)

在环境都搭建好之后,我们首先需要进行的任务是将服务器本地磁盘上的日志文件中的数据通过flume监控采集到Kafka当中。

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第7张图片

按照我们的规划,需要采集的用户行为日志文件分布在hadoop102,hadoop103两台日志服务器上,因此我们需要在hadoop102,hadoop103两台节点配置日志采集Flume。日志采集Flume需要采集日志文件内容,并对日志格式(JSON)进行校验,然后将校验通过的日志发送到Kafka。

在此处source,channel和sink的选择中,我们选择使用TaildirSource和KafkaChannel,并且配置了日志校验拦截器。

7.1 为什么要选择TaildirSource不选择execSource和SpoolingDirectorySource呢?

为什么要选择TaildirSource呢?因为TailDirSource相比于ExecSource、SpoolingDirectorySource有以下这些优点:

TailDirSourceSpoolingDirectorySource监控目录,支持断点续传。:断点续传、多目录。Flume1.6以前需要自己自定义Source记录每次读取文件位置,实现断点续传。

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

SpoolingDirectorySource监控目录,支持断点续传。Spooling Directory Source可监听一个目录,同步目录中的新文件到sink,被同步完的文件可被立即删除或被打上标记。适合用于同步新文件,但不适合对实时追加日志的文件进行监听并同步。

7.2为什么采用KafkaChannel呢?

在对接flume到kafka时,一种常见的策略是使用TailDirSource->MemoryChannel->KafkaSink这种方式,将采集到的数据先经过内存管道,然后输送到Kafka当中,但是这种方式会有以下这些弊端:

1.MemoryChannel数据会有堆积,内存可能溢出;

2.这种方式经历了多个组件,传输的效率变低,出现问题的概率也会对应着变大。

而如果我们使用了KafkaChannel来替代MemoryChannel+KafkaSink,将KafkaChannel作为缓存,省去Sink,效率就会变高。在这种方式下,TailDirSource来监控日志文件,之后将数据输送到KafkaChannel当中,然后KafkaChannel直接将数据输送到Kafka集群当中,因为没有Sink,KafkaChannel在此处就相当于Kafka的生产者,即使数据量很大,Kafka也完全可以承受的住。

7.3 Flume配置文件编写

1)首先,我们现在Flume的job目录下创建file_to_kafka.conf配置文件

vim job/file_to_kafka.conf

2)编写配置文件内容:

#为各组件命名
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.*   #采集该目录下以app.开头的文件中的数据
a1.sources.r1.positionFile = /opt/module/flume/taildir_position.json
a1.sources.r1.interceptors =  i1
a1.sources.r1.interceptors.i1.type = com.neu.flume.interceptor.ETLInterceptor$Builder #自定义flume拦截器

#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的关系
a1.sources.r1.channels = c1

7.3 Flume拦截器编写

之后,我们需要编写一个Flume的拦截器,来将异常的数据(不是json格式的数据)在上游过滤出去。

1)首先,我们需要创建一个maven项目并导入依赖:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    
    <groupId>com.neugroupId>
    <artifactId>flume-interceptorartifactId>
    <version>1.0-SNAPSHOTversion>

    <dependencies>
        <dependency>
            <groupId>org.apache.flumegroupId>
            <artifactId>flume-ng-coreartifactId>
            <version>1.9.0version>
            <scope>providedscope>
        dependency>

        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.62version>
        dependency>
    dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-pluginartifactId>
                <version>2.3.2version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
            <plugin>
                <artifactId>maven-assembly-pluginartifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependenciesdescriptorRef>
                    descriptorRefs>
                configuration>
                <executions>
                    <execution>
                        <id>make-assemblyid>
                        <phase>packagephase>
                        <goals>
                            <goal>singlegoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    build>
project>

2)之后,我们在com.neu.flume.interceptor包下创建一个ETLInterceptor类。我们第一步要实现interceptor接口;第二步我们需要实现四个抽象方法:initialize()、intercept(Event event)、intercept(List events)、close(),我们主要的代码编写工作在intercept两个接口当中进行;第三步我们需要构建一个静态内部类来实现接口Builder,重写它的bulid()方法即可。

3)对于具体的编写逻辑,我们需要在intercept(Event event)中通过event.body获取当前的数据,之后将数据转换成字符串类型log,然后我们通过fastjson中的JSONObject.parseObject(log)方法来判断当前的log数据是否是json对象,如果是json,则返回当前event,如果不是json,则返回空。之后,我们再在intercept(List events)方法中将null值过滤掉即可。

具体的代码实现如下:

ETLInterceptor类:

package com.neu.flume.interceptor;

import com.alibaba.fastjson.JSONException;
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.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.List;

/**
 * @author majie
 * 1.实现接口interceptor
 * 2.实现四个抽象方法
 * 3.构建一个静态内部类来实现接口Builder,重写它的bulid()方法
 */
public class ETLInterceptor implements Interceptor {
    @Override
    public void initialize() {

    }

    @Override
    public Event intercept(Event event) {
        //需求:过滤event中的数据是否时json格式
        byte[] body = event.getBody();
        String log = new String(body, StandardCharsets.UTF_8);

        boolean flag = JSONUtils.isJson(log);

        return flag ? event : null;
    }

    @Override
    public List<Event> intercept(List<Event> events) {

        //将处理过之后为null的event删除掉
        Iterator<Event> iterator = events.iterator();

        while (iterator.hasNext()){
            Event event = iterator.next();
            if(intercept(event) == null){
                iterator.remove();
            }
        }

        return events;
    }

    @Override
    public void close() {

    }

    public static class Builder implements Interceptor.Builder{

        @Override
        public Interceptor build() {
            return new ETLInterceptor();
        }

        @Override
        public void configure(Context context) {

        }
    }
}

JSONUtils类:

package com.neu.flume.interceptor;

import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;

public class JSONUtils {
    public static boolean isJson(String log){
        boolean flag = false;
        //判断log是否时json
        try {
            JSONObject.parseObject(log);
            flag = true;
        }catch (JSONException e){

        }

        return flag;
    }
}

在代码编写完成之后,我们需要将代码使用maven中的package来将我们的代码进行打包,为了可以将打包的代码中携带有依赖文件,我们添加了插件到pom.xml文件中:

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-pluginartifactId>
                <version>2.3.2version>
                <configuration>
                    <source>1.8source>
                    <target>1.8target>
                configuration>
            plugin>
            <plugin>
                <artifactId>maven-assembly-pluginartifactId>
                <configuration>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependenciesdescriptorRef>
                    descriptorRefs>
                configuration>
                <executions>
                    <execution>
                        <id>make-assemblyid>
                        <phase>packagephase>
                        <goals>
                            <goal>singlegoal>
                        goals>
                    execution>
                executions>
            plugin>
        plugins>
    build>

之后,我们将打好的jar包放到hadoop102的/opt/module/flume/lib文件夹下:

7.4 日志采集Flume(File->Kafka)测试

在编写完成相关代码以及flume配置文件之后,接下来我们进行功能的测试,检测是否可以使用我们编写的代码来实现将日志采集到flume当中的功能。

1)首先启动Zookeeper、Kafka集群

zk.sh start
kafka.sh start

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第8张图片
2)之后,启动hadoop102的日志采集flume

[root@hadoop102 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/file_to_kafka.conf -Dflume.root.logger=info,console

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第9张图片

3)启动一个Kafka的Console-Consumer

[root@hadoop102 kafka]$ bin/kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic topic_log

请添加图片描述

4)生成模拟数据

[root@hadoop102 ~]$ lg.sh 

5)查看Kafka消费者是否已经消费到了数据
4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第10张图片

我们可以查看到,已经成功消费到了数据,说明我们该部分的功能已经测试成功。

7.5 分发日志采集Flume配置文件和拦截器以及编写日志采集Flume进程启停脚本

我们需要将hadoop102节点的Flume的配置文件和拦截器jar包,向另一台日志服务器发送一份。

[root@hadoop102 flume]$ scp -r job hadoop103:/opt/module/flume/
[root@hadoop102 flume]$ scp lib/flume-interceptor-1.0-SNAPSHOT-jar-with-dependencies.jar hadoop103:/opt/module/flume/lib/

为了方便开启日志采集Flume进程,我们在hadoop102节点的/home/root/bin目录下创建脚本f1.sh

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 -n a1 -c /opt/module/flume/conf/ -f /opt/module/flume/job/file_to_kafka.conf >/dev/null 2>&1 &"
        done
};; 
"stop"){
        for i in hadoop102 hadoop103
        do
                echo " --------停止 $i 采集flume-------"
                ssh $i "ps -ef | grep file_to_kafka.conf | grep -v grep |awk  '{print \$2}' | xargs -n1 kill -9 "
        done

};;
esac

之后,我们为脚本增加执行权限:

chmod x f1.sh

测试f1启动:

[root@hadoop102 module]$ f1.sh start

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第11张图片
测试f1停止:

[root@hadoop102 module]$ f1.sh stop

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第12张图片

8.日志消费(Flume监控Kafka日志传至HDFS上)

接下来,我们需要将Kafka当中汇聚的日志的数据通过Flume采集到HDFS的指定目录当中。按照规划,该Flume需将Kafka中topic_log的数据发往HDFS。并且对每天产生的用户行为日志进行区分,将不同天的数据发往HDFS不同天的路径
4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第13张图片

8.1为什么不使用KafkaChannel+HDFSSink?

此处选择KafkaSource、FileChannel、HDFSSink。这里我们也可以将FileChannel换成MemoryChannel,如果内存较为充足的话。

对于KafkaSource+FileChannel的组合,我们也可以使用KafkaChannel来进行替换,而且性能会更好,那么为什么我们不使用KafkaChannel呢?

因为我们需要在source端编写一个拦截器,用来提取每个事件中对应的事件戳,从而确定该条json数据的事件时间。

8.2Flume配置文件编写

1)首先,我们现在Flume的job目录下创建file_to_kafka.conf配置文件

vim job/kafka_to_hdfs.conf

2)编写配置文件内容:

## 组件命名
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.root.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/
a1.channels.c1.maxFileSize = 2146435071
a1.channels.c1.capacity = 1000000
a1.channels.c1.keep-alive = 6


## 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.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 = gzip #使用gzip压缩

## 拼装
a1.sources.r1.channels = c1
a1.sinks.k1.channel= c1

8.3如何将不同天的数据发往HDFS不同天的路径

在配置文件当中的“%Y-%m-%d”是占位符,只要在event的header中有key为“timestamp”,value值为时间戳,我们就可以使用占位符,来将该时间戳转换过来的时间作为变量在配置文件当中使用。

8.4对应Flume配置的优化

1)FileChannel优化

通过配置dataDirs指向多个路径,每个路径对应不同的硬盘,增大Flume吞吐量。

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

2)HDFS Sink优化

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

(1)文件在达到128M时会滚动生成新文件

(2)文件创建超3600秒时会滚动生成新文件

我们后续可以更改这几个参数来进行调整小文件的数量。

8.5Flume日志消费零点漂移问题(项目中所遇到的问题)

由于最开始,在使用flume采集Kafka主题topic_log中的数据到HDFS上去时,在指定存放路径的地方,我们使用的是系统当前的时间,也就是在Flume采集该条数据时系统所处的时间,那么就会出现一个问题。可能我们在2022年6月1日23点59分采集到了一部分数据从日志中,但是由于网络的拥堵,可能到了2022年6月2日0点1分才进入到了flume日志消费的采集通道当中,这时,我们前一天的数据便被错误的归到了下一天当中。
4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第14张图片

解决方案:

因此,我们不能使用系统当前的数据(处理时间),而应该加一个拦截器,提取处event当中的时间戳加到header当中去,从而使用事件时间来作为存放路径的依据。

8.5 Flume拦截器编写

由于我们已经在数据进入到kafka之前使用拦截器剔除掉了不是json格式的数据,因此在此处,我们只需要先将event.body的数据转换为json格式,然后再提取出时间戳放到header中即可。我们在com.neu.flume.interceptor包下创建TimeStampInterceptor类进行编写。

具体代码如下所示:

TimeStampInterceptor类:

package com.neu.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) {

        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> events) {
        for (Event event : events) {
            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) {
        }
    }
}

之后我们只需要重新打包,并将打好的包放入到hadoop104的/opt/module/flume/lib文件夹下面即可。

8.6日志消费Flume(Kafka->HDFS)测试

在编写完成相关代码以及flume配置文件之后,接下来我们进行功能的测试,检测是否可以使用我们编写的代码来实现将数据从kafka当中采集到hdfs上。

1)首先启动Zookeeper、Kafka集群

zk.sh start
kafka.sh start

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第15张图片

2)之后,启动hadoop102的日志采集flume

[root@hadoop102 flume]$ f1.sh start

3)启动hadoop104上的日志消费flume

[root@hadoop104 flume]$ bin/flume-ng agent -n a1 -c conf/ -f job/kafka_to_hdfs.conf -Dflume.root.logger=info,console

4)生成2022年5月1日的模拟数据

[root@hadoop102 ~]$ lg.sh 

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第16张图片

5)观察HDFS上是否有出现数据
4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第17张图片
我们可以查看到,已经成功将日志数据采集到HDFS上,说明我们该部分的功能已经测试成功。

9.编写采集通道启停脚本

由于采集通道中需要执行的脚本过多,因此,我们最终再次编写一个脚本,去按序执行之前的那些脚本。在/home/root/bin目录下创建脚本cluster.sh:

[root@hadoop102 bin]$ vim cluster.sh

脚本中的内容如下所示:

#!/bin/bash

case $1 in
"start"){
        echo ================== 启动 集群 ==================

        #启动 Zookeeper集群
        zk.sh start

        #启动 Hadoop集群
        myhadoop.sh start

        #启动 Kafka采集集群
        kafka.sh start

        #启动 Flume采集集群
        f1.sh start

        #启动 Flume消费集群
        f2.sh start

        };;
"stop"){
        echo ================== 停止 集群 ==================

        #停止 Flume消费集群
        f2.sh stop

        #停止 Flume采集集群
        f1.sh stop

        #停止 Kafka采集集群
        kafka.sh stop

        #停止 Hadoop集群
        myhadoop.sh stop

        #停止 Zookeeper集群
        zk.sh stop

};;
esac

之后,我们增加脚本的执行权限:

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

通过上述脚本,我们便可以轻松快捷的打开日志采集的通道,用来采集用户行为日志数据到HDFS上。

1)测试cluster集群启动脚本

[root@hadoop102 module]$ cluster.sh start

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第18张图片
2)测试cluster集群关停脚本:

[root@hadoop102 module]$ cluster.sh stop

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第19张图片

至此,用户行为数据采集通道全部搭建完成。

10.用户行为数据采集通道使用总结

4.东软跨境电商数仓项目--数据采集通道搭建之用户行为数据采集通道搭建(2022.6.1-2022.6.4)_第20张图片
我们在进行日志采集时,只需要启动Flume1,Kafka以及Flume2即可,由于我们编写了采集通道的启停脚本,因此我们在进行用户行为数据采集时,只需要输入以下命令就可以将这些集群启动:

[root@hadoop102 module]$ cluster.sh start

之后,我们使用模拟生成日志的脚本生成对应天的日志数据,该采集通道就会自动的将数据采集并传输到HDFS上指定的路径上去。

你可能感兴趣的:(东软睿购跨境电商数仓项目,hadoop,hive)