一、第一层Flume(f1)
(1)Flume组件:Agent(Source + Channel + Sink)
(2)一个事务为event(Header + Body),body为存储数据,header是Flume自动加入的数据。
① 选用配置:taildir source -> etl interceptor -> kafka channel
taildir source实现断点续传,监控多目录
kafka channel可省去kafka sink
② 作用:采用了etl interceptor进行初步的数据清洗,去除不符合Json格式的数据。
第一层flume采集到kafka中数据是一个主题一个分区,主题名applog。
1、flume_to_kafka.conf
#taildir source -> etl interceptor -> kafka channel
#1、定义source、channel、agent名称
a1.sources = r1
a1.channels = c1#2、描述source
a1.sources.r1.type = TAILDIR
#指定监控的组名
a1.sources.r1.filegroups = f1
#指定f1组监控的路径
a1.sources.r1.filegroups.f1 = /opt/module/applog/log/app.*
#指定断点续传的文件
a1.sources.r1.positionFile = /opt/module/flume/position.json
#指定每个批次采集多少数据[batchSize<=事务的大小]
a1.sources.r1.batchSize = 100#2.1、指定自定义拦截器
#定义拦截器的名称
a1.sources.r1.interceptors = i1
#定义拦截器的全类名
a1.sources.r1.interceptors.i1.type = com.atguigu.interceptor.ETLInterceptor$Builder#3、描述channel
a1.channels.c1.type = org.apache.flume.channel.kafka.KafkaChannel
#指定kafka集群
a1.channels.c1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092
#指定数据写到kafka哪个topic
a1.channels.c1.kafka.topic = applog
#是否以Event对象的形式写入kafka
a1.channels.c1.parseAsFlumeEvent = false#4、关联source->channel
a1.sources.r1.channels = c1
2、pom.xml文件
导入两个依赖和两个插件
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>data0821</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.9.0</version>
<scope>provided</scope>
</dependency>
<!-- 解析Json格式 -->
<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>
</project>
3、解析Json格式数据的示例代码
package com.fromguigu.interceptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
public class JsonDemo {
public static void main(String[] args) {
String js = "{\"name\":\"zhangsan\",\"age\":20";
JSONObject object = JSON.parseObject(js);
System.out.println(object.getString("name"));
}
}
4、ETLInterceptor.java代码
package com.fromguigu.interceptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;
import java.util.Iterator;
import java.util.List;
public class ETLInterceptor implements Interceptor {
/**
* 用于初始化
*/
@Override
public void initialize() {
}
/**
* 对单个数据进行处理
* @param event
* @return
*/
@Override
public Event intercept(Event event) {
//1、取出数据
byte[] body = event.getBody();
String json = new String(body);
//2、判断是否为json数据,如果是json数据保留,如果不是json数据就返回null
try{
JSON.parseObject(json);
return event;
}catch (Exception e){
return null;
}
}
/**
* 对一个批次的数据进行处理
* @param list
* @return
*/
@Override
public List<Event> intercept(List<Event> list) {
// 遍历循环若使用for没用,只能创建迭代器
Iterator<Event> it = list.iterator();
while (it.hasNext()){
Event event = it.next();
if(intercept(event)==null) it.remove();
}
return list;
}
/**
* 关闭
*/
@Override
public void close() {
}
public static class Builder implements org.apache.flume.interceptor.Interceptor.Builder{
/**
* 用于flume创建自定义拦截器对象
* @return
*/
@Override
public Interceptor build() {
return new ETLInterceptor();
}
@Override
public void configure(Context context) {
}
}
}
二、第二层Flume(f2)
(1)Flume组件:Agent(Source + Channel + Sink)
(2)一个事务为event(Header + Body),body为存储数据,header是Flume自动加入的数据。
① 选用配置:kafka source -> timestamp interceptor -> file channel -> hdfs sink
注意:hdfs sink有可能出现较多小文件的问题
1)为什么不直接使用kafka channel -> hdfs sink
直接拉取数据到hdfs,会有时间漂移问题,所以使用kafka source,并引进了timestamp interceptor,因为flume默认是以系统时间为时间戳,如果第一层flume采集到kafka中的某条数据时间是23:59:59,当第二层Flume采集该条数据到hdfs中时正好系统时间到了第二天凌晨,那么这条数据会被保存到第二天的数据里,即昨天的数据保存到了今天,出现了时间漂移。
2)使用file channel 基于磁盘,保证数据的安全,不丢失
memory channel 基于内存,断电丢失
file channel有磁盘空间和内存空间,磁盘保存source采集的数据,内存则保存指向还没有被消费的event的指针,为了安全起见,需要指定checkpointDir来存储这些指针。
② 作用:解析event中body存放的ts事件时间,即使用fastJson获取到body数据,parseObject(json),再把获取到的时间写到header中参数timestamp做时间戳便于将当天的数据能正确地以当天的日期作为目录进行落盘。
第二层Flume采集到的数据仍是以text数据格式保存在hdfs,另外,在第二层Flume中还指定了以lzop进行压缩,后缀名为.lzo,创建索引后则支持mr切片,若不另外创建索引,mr会将其作为一个大文件进行处理,不切片。所以在搭建数仓时,ods层的数据格式还是text,在ods层建表不能指定存储格式为parquet,因为要保持数据的原始性,用于备份,只有在dwd层开始才指定parquet格式创建表,stored as parquet,这是因为列式存储便于频繁地查询。所以到ads层数据格式还是parquet,如果要用sqoop把存储在ads层中指标分析数据导出到mysql,注意将parquet转为text,sqoop默认导出的是textfile。
1、kafka_to_hdfs.conf
#1、定义agent、source、channel、sink的名称
a1.sources = r1
a1.channels = c1
a1.sinks = k1#2、描述source
a1.sources.r1.type = org.apache.flume.source.kafka.KafkaSource
#指定flume消费哪个topic的数据
a1.sources.r1.kafka.topics = applog
#指定kafka集群地址
a1.sources.r1.kafka.bootstrap.servers = hadoop102:9092,hadoop103:9092
#定义消费者组的名称
a1.sources.r1.kafka.consumer.group.id = applog_consumer
#指定flume一个批次从kafka采集多少数据
a1.sources.r1.batchSize = 100
#指定kafka中的数据的类型是否为Event格式
a1.sources.r1.useFlumeEventFormat = false
#指定消费者组第一次消费topic数据的时候从哪里开始消费
a1.sources.r1.kafka.consumer.auto.offset.reset = earliest#2.1、定义拦截器,使用数据的时间戳作为目录的生成时间
a1.sources.r1.interceptors = i1
a1.sources.r1.interceptors.i1.type = com.atguigu.interceptor.MyTimeStampInterceptor$Builder#3、描述channel
a1.channels.c1.type = file
#指定file channel数据存储在本地磁盘哪个目录
a1.channels.c1.dataDirs = /opt/module/flume/datas
#指定checkpoint的路径
a1.channels.c1.checkpointDir = /opt/module/flume/checkpoint
#设置channel事务容量
a1.channels.c1.transactionCapacity = 100
#设置channel的容量
a1.channels.c1.capacity = 1000000#4、描述sink
a1.sinks.k1.type = hdfs
#设置数据保存在hdfs哪个路径
a1.sinks.k1.hdfs.path = /applog/events/%Y%m%d
#设置hdfs文件前缀
a1.sinks.k1.hdfs.filePrefix = event-
#设置多久生成新文件
a1.sinks.k1.hdfs.rollInterval = 30
#设置旧文件写入数据量多大之后生成新文件
a1.sinks.k1.hdfs.rollSize = 133169152
#设置文件写入多少个Event之后生成新文件
a1.sinks.k1.hdfs.rollCount = 0
#设置sink每个批次从channel中拉取多少数据
a1.sinks.k1.hdfs.batchSize = 100
#指定数据的压缩格式
a1.sinks.k1.hdfs.codeC= lzop
#指定写入的文件类型
a1.sinks.k1.hdfs.fileType= CompressedStream
#是否按照指定的时间间隔生成文件夹
a1.sinks.k1.hdfs.round= true
#指定时间间隔的值
a1.sinks.k1.hdfs.roundValue= 24
#指定时间间隔的单位
a1.sinks.k1.hdfs.roundUnit= hour#5、关联source->channel->sink
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
2、pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<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.0</modelVersion>
<groupId>com.atguigu</groupId>
<artifactId>data0821</artifactId>
<version>1.0-SNAPSHOT</version>
<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>
</project>
3、MyTimeStampInterceptor.java代码
package com.fromguigu.interceptor;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.flume.Context;
import org.apache.flume.Event;
import org.apache.flume.interceptor.Interceptor;
import java.util.List;
import java.util.Map;
public class MyTimeStampInterceptor implements Interceptor {
/**
* 初始化
*/
@Override
public void initialize() {
}
/**
* 单个数据的处理
* @param event
* @return
*/
@Override
public Event intercept(Event event) {
//1、取出数据
String json = new String(event.getBody());
//2、取出时间戳
JSONObject obj = JSON.parseObject(json);
Long ts = obj.getLong("ts");
//3、将时间戳写入header
Map<String, String> headers = event.getHeaders();
// 要求传入字符串,但解析得到的ts为Long类型,故使用+"",即用字符串拼接方法转换成字符串
headers.put("timestamp",ts+"");
//4、数据返回
return event;
}
/**
* 批次数据的处理
* @param list
* @return
*/
@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 org.apache.flume.interceptor.Interceptor.Builder{
/**
* 用于Flume创建拦截器对象
* @return
*/
@Override
public Interceptor build() {
return new MyTimeStampInterceptor();
}
@Override
public void configure(Context context) {
}
}
}
三、flume_kafka.sh一键采集脚本
第一层Flume部署hadoop102、hadoop103
第二层Flume部署hadoop104,只有一台机器,没必要写个脚本,可直接发送命令启动Flume
执行脚本:flume_kafka.sh start
#! /bin/bash
#1、判断是否有参数传入
if [ $# -lt 1 ]
then
echo "必须传入一个参数..."
exit
fi
#2、根据参数执行对应的逻辑
case $1 in
"start")
for host in hadoop102 hadoop103
do
echo "===============start $host data==============="
ssh $host "nohup /opt/module/flume/bin/flume-ng agent -n a1 -c /opt/module/flume/conf/ -f /opt/module/flume/job/flume-to-kafka.conf -Dflume.root.logger=INFO,console >/opt/module/flume/logs/flume.log 2>&1 &"
done
;;
"stop")
for host in hadoop102 hadoop103
do
echo "===============start $host data==============="
ssh $host "ps -ef| grep flume-to-kafka.conf | grep -v grep|awk '{print \$2}' | xargs kill -9"
done
;;
"status")
for host in hadoop102 hadoop103
do
echo "===============start $host data==============="
pid=$(ssh $host "ps -ef| grep flume-to-kafka.conf | grep -v grep")
[ "$pid" ] && echo "flume进程正常" || echo "flume进程不存在"
done
;;
*)
echo "参数传输错误...."
;;
esac
四、Flume优化
设置参数,把-Xms、-Xmx都调到4个G左右。