在前面的笔记一和笔记二中,我们已经分别将用户行为数据和业务数据采集到kafka中了:
在实时数仓中,由于Flink会从kafka中读取数据,也无需我们再同步数据了,因此同步数据是我们离线数仓的事。
离线数仓同步数据
对于用户行为数据,由Flume从Kafka直接同步到HDFS,没错,又来一个Flume...... 按照规划,该Flume需将Kafka中topic_log的数据发往HDFS。并且对每天产生的用户行为日志进行区分,将不同天的数据发往HDFS不同天的路径。这里我们用天作为粒度区分。此处的Flume我们选择KafkaSource、FileChannel、HDFSSink (这里也是,关于Source,Channel和Sink专门写一篇文章详细说)。
这里Flume的配置文件就不放全了,但有几点需要注意:
a1.sinks.k1.hdfs.rollInterval = 10
a1.sinks.k1.hdfs.rollSize = 134217728
a1.sinks.k1.hdfs.rollCount = 0
如果使用官方默认的这三个参数配置写入HDFS后会产生小文件,默认情况下MR会对每个小文件启用一个Map任务计算,非常影响计算性能。同时也影响磁盘寻址时间。基于以上hdfs.rollInterval=3600,hdfs.rollSize=134217728,hdfs.rollCount =0几个参数综合作用,使得文件在达到128M时会滚动生成新文件,文件创建超3600秒时会滚动生成新文件,这样既减少了小文件的可能,又不会因为新的文件迟迟不来而浪费时间,属于是折中的办法。
数据漂移
在上游flume配置中,我们设置parseAsEvent=false;意思是不以flume event方式来进行存储,所以在上游kafkachannel写入kafka的数据只有body,没有header。那么现在下游flume再从kafka读数据,它也不会把上游的header拿过来,而是自己封装一个header,发送到hdfsink。
好了,那么既然header是它自己封装的,那么header就没有自己的时间,当我们的数据23:59:59的数据从文件到Kafka再经过Flume采集,又过了几秒钟,变成第二天的时间,结果放入HDFS第二天的数据分区了,那不就乱套了吗?因此需要在source阶段编写日期拦截器,注意这里不能直接将kafkachannel数据写到hdfsink中,因为没有source我们就写不了拦截器。
package com.atguigu.gmall.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) {
//1、获取header和body的数据
Map<String, String> headers = event.getHeaders();
String log = new String(event.getBody(), StandardCharsets.UTF_8);
//2、将body的数据类型转成jsonObject类型(方便获取数据)
JSONObject jsonObject = JSONObject.parseObject(log);
//3、header中timestamp时间字段替换成日志生成的时间戳(解决数据漂移问题)
String ts = jsonObject.getString("ts");
headers.put("timestamp", ts);
return event;
}
@Override
public List<Event> intercept(List<Event> list) {
for (Event event : list) {
intercept(event);
}
return list;
}
@Override
public void close() {
}
public static class Builder implements Interceptor.Builder {
@Override
public Interceptor build() {
return new TimestampInterceptor();
}
@Override
public void configure(Context context) {
}
}
}
重新打包
将打好的包放入到hadoop104的/opt/module/flume/lib文件夹下面。