数据仓库( Data Warehouse ),是为企业制定决策,提供数据支持的。可以帮助企业,改进业务流程、提高产品质量等。
通常数据仓库的输入数据有三种:业务数据、用户行为数据和爬虫数据等;
业务数据:比如用户在电商网站中登录、下单、支付等过程中,需要和网站后台数据库进行增删改查交互,产生的数据就是业务数据。通常存储在MySQL、Oracle等数据库中。
用户行为数据:用户在使用产品过程中,通过埋点收集与客户端产品交互过程中产生的数据,并发往日志服务器进行保存。比如页面浏览、点击、停留、评论、点赞、收藏等。用户行为数据通常存储在日志文件中。
用户行为数据
首先我们来看用户行为数据,收集这些信息的主要目的是优化产品和为各项分析统计指标提供数据支撑。
用户行为日志
我们的日志结构大致可分为两类,一是页面日志,二是启动日志。
页面日志,以页面浏览为单位,即一个页面浏览记录,生成一条页面埋点日志。一条完整的页面日志包含,一个页面浏览记录,若干个用户在该页面所做的动作记录,若干个该页面的曝光记录,以及一个在该页面发生的报错记录。除上述行为信息,页面日志还包含了这些行为所处的各种环境信息,包括用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息等。
启动日志,以启动为单位,及一次启动行为,生成一条启动日志。一条完整的启动日志包括一个启动记录,一个本次启动时的报错记录,以及启动时所处的环境信息,包括用户信息、时间信息、地理位置信息、设备信息、应用信息、渠道信息等。
它们均为JSON格式,如下面这种格式:
{
"name": "John",
"age": 30,
"city": "New York",
"isStudent": false,
"hobbies": ["reading", "music", "sports"],
"address": {
"street": "123 Main St",
"city": "New York",
"zipCode": "10001"
}
}
模拟用户数据
一共有4个配置文件:application.yml、gmall2020-mock-log-2021-10-10.jar、path.json、logback.xml;
application.yml文件:可以根据需求生成对应日期的用户行为日志;
path.json:该文件用来配置访问路径;
logback.xml:配置文件;
然后编写执行jar包的脚本:
#!/bin/bash
for i in hadoop102 hadoop103; do
echo "========== $i =========="
ssh $i "cd /opt/module/applog/; java -jar gmall2020-mock-log-2021-10-10.jar >/dev/null 2>&1 &"
done
脚本使用循环遍历两个远程主机 hadoop102
和 hadoop103
。在每次迭代中,它会输出一个带有远程主机名称的提示信息。
然后,通过 SSH 连接到远程主机,并在远程主机上执行以下命令:
cd /opt/module/applog/; java -jar gmall2020-mock-log-2021-10-10.jar >/dev/null 2>&1 &
这条命令首先进入远程主机的 /opt/module/applog/
目录,然后执行 java -jar gmall2020-mock-log-2021-10-10.jar
命令。这个命令是在远程主机上运行 Java 可执行 JAR 文件 gmall2020-mock-log-2021-10-10.jar
。>/dev/null
是将标准输出重定向到 /dev/null
,即丢弃输出。2>&1
是将标准错误输出重定向到标准输出,即将错误输出也丢弃。
最后的 &
符号表示在后台运行命令,即使 SSH 连接关闭,命令也会继续运行。当然我们要将这个脚本放在admin/bin目录下,这样脚本可以在服务器的任何目录执行。
用户行为数据采集模块
此时我们已经在hadoop102和hadoop103上分别生成了用户行为数据,数据通过Flume先传输到Kafka集群,达到削峰填谷的作用。
按照规划,需要采集的用户行为日志文件分布在hadoop102,hadoop103两台日志服务器,故需要在hadoop102,hadoop103两台节点配置日志采集Flume。日志采集Flume需要采集日志文件内容,并对日志格式(JSON)进行校验,然后将校验通过的日志发送到Kafka。此处我们选择TaildirSource和KafkaChannel,并配置日志校验拦截器。
为什么使用TaildirSource?
TailDirSource:断点续传、多目录。Flume1.6以前需要自己自定义Source记录每次读取文件位置,实现断点续传。
为什么选择KafkaChannel?
我们需要将数据写入Kafka时,可以不需要Sink,直接使用KafkaChannel就可以将数据写入Kafka,省去了Sink的开销。
这里先一笔带过,等后面有时间再详细写一篇文章。
编写Flume日志拦截器:
创建maven工程:
在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>
在com.atguigu.gmall.flume.utils包下创建JSONUtil类
package com.atguigu.gmall.flume.utils;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.JSONException;
public class JSONUtil {
/*
* 通过异常判断是否是json字符串
* 是:返回true 不是:返回false
* */
public static boolean isJSONValidate(String log){
try {
JSONObject.parseObject(log);
return true;
}catch (JSONException e){
return false;
}
}
}
在com.atguigu.gmall.flume.interceptor包下创建ETLInterceptor类
package com.atguigu.gmall.flume.interceptor;
import com.atguigu.gmall.flume.utils.JSONUtil;
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) {
//1、获取body当中的数据并转成字符串
byte[] body = event.getBody();
String log = new String(body, StandardCharsets.UTF_8);
//2、判断字符串是否是一个合法的json,是:返回当前event;不是:返回null
if (JSONUtil.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() {
}
}
配置Flume启停脚本
方便起见,此处编写一个日志采集Flume进程的启停脚本:
在bin目录下创建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 | grep -v grep |awk '{print \$2}' | xargs -n1 kill -9 "
done
};;
esac
如果传入的参数为 "start",则执行以下操作:
- 使用循环遍历主机列表中的每个主机(hadoop102和
hadoop103
)。 - 输出消息指示正在启动采集 flume(Flume 是一个用于大规模数据采集的分布式系统)。
通过 SSH 连接到远程主机,并在远程主机上执行以下命令:
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 &
这个命令是在远程主机上启动 Flume 的代理进程,通过指定配置件 /opt/module/flume/job/file_to_kafka.conf
来采集文件数据并发送到 Kafka(一种分布式消息队列)中。nohup
命令用于在后台运行进程,并将输出重定向到 /dev/null
,即丢弃输出。
如果传入的参数为 "stop",则执行以下操作:
- 使用循环遍历主机列表中的每个主机(
hadoop102
和hadoop103
)。 - 输出消息指示正在停止采集 flume。
- 通过 SSH 连接到远程主机,并执行以下命令:
ps -ef | grep file_to_kafka | grep -v grep | awk '{print $2}' | xargs -n1 kill -9
这个命令会查找包含 "file_to_kafka" 的进程,并使用 kill -9
命令终止这些进程。ps -ef
命令用于查看进程列表,grep
命令用于过滤包含 "file_to_kafka" 的行,grep -v grep
用于排除掉 grep 命令本身的行,awk '{print $2}'
用于提取进程的 PID,xargs -n1 kill -9
用于终止进程,它接受前面的进程PID作为参数,kill掉这个PID。