1. flume是個什麼東西
- 正如上面看到的Logo這樣,他就是我們古時候由於交通並不發達,我們在一條河流的上游砍了很多的巨木(注意是又多又重),
這麼多笨重的木頭我們不可能靠人力將他們運送到指定的地方吧,用交通工具又不現實(你想叢林中這麼多樹,你的交通工
具怎麼在這裏面開展開來),所以聰明的古人就想到了一個法子,既然是木頭,它還可以浮在水面上,何不直接把砍下來的樹丟
進河裏,隨着水流順勢而下,下游派一些人接收不就行了嗎; - 這就是flume的設計初衷,簡單而又高效,你的一端只管丟數據,另一端只管收數據,至於中間的運輸過程你完全不用知道.
- Flume 呢是由 Cloudera 公司開發的 一個高可用、高可靠、分佈式海量日誌收集、聚合和傳輸的系統,目前已是 Apache
下的 一個頂級項目。 Flume 提供了很多與之對接的組件,可以很方便地進行數據傳輸 。 現在Flume的最新版本爲 1.9.0
Flume l.x 版本相對 Flume 0 . 9 .x 版本被稱爲 Flume next generation ,簡稱爲 Flume NG .這裏所說的就是flume-ng版本
下圖呢就是他的一個架構示意圖:
- 可以看圖發現Flume的核心就是Agent(代理),它是Flume中最基本的一個單元了,在Agent的內部有三個組件
- Source (事件源) 就是上面故事中的伐木工
Source 是負責接收數據到 Flume Agent的組件.Source 可以從其他系統中接收數據,像kafka,FileSystem… Source 也
可以接收其他Flume Agent 中Sink發送出來的數據,Source甚至還可以生產數據 - Channel (通道) 故事中的河流
Channel 是位於Source 和 Sink 之間的緩衝區.因此,Channel允許Source和Sink運作在不同的速率上.Channel 是Flume
保證數據不丟失的關鍵.Souce 寫入數據一個或多個Channel中,再由一個或多個Sink讀取.Sink只能從一個Channel讀取
數據,而多個Sink可以從相同的Channel讀取以獲得更好的性能. - Sink (接收器) 故事中下游接收木頭的工人
這個就主要是將前面Channel中的數據讀取出來然後轉移到其他地方,一般是一些存儲系統上
2.flume的安裝配置
- 關於Flume的安裝就太簡單了
- 打開Flume的官網
官網地址 - 找到需要下載的版本後進行下載
- 下載後進如下載目錄進行解壓到安裝目錄
- 然後配置環境變量
export FLUME_HOME=/xxxx/xxxx/apache-flume-x.x.x
# 這裏bin主要是一些啓動腳本,lib呢主要是必要的jar文件
export PATH=$FLUME_HOME/bin:$FLUME_HOME/lib:$PATH
- 進入$FLUME_HOME/conf目錄
cp flume-env.sh.template flume-env.sh
vim flume-env.sh
在文件內修改JAVA_HOME即可
到此Flume的安裝就完成了
3.監聽以時間分隔的日誌文件
- 對flume有所瞭解的應該都知道在flume中我們監聽某一個日誌文件很簡單,就是利用一個 tail -f 進行監聽
- 但是這裏我們的日誌文件是以時間進行分隔的就像這種
- 而且文件的數量還不是固定的,會隨着時間進行變化,這裏是以小時爲單位變化的,也就是每個小時生成一個日誌文件
- 在flume 1.6以前我們要實現監聽這樣的日誌就有些困難了,可以通過編寫Shell腳本來實現,最後在配置Source時 command指定你的 watch_log.sh /target_log_dir/ 這樣即可,又或者自定義Source也行.
- 但是到了1.6版本以後這就變得簡單了,我們有了Taildir Source
它是個什麼東西,官網已經解釋得很詳細了,常用的用法使用黑體進行註明了的.今天這裏就講如何使用
4.模擬日誌數據
- 由於我們這裏只是測試其的用法,就必須模擬以每個小時生成一個日誌文件的場景,這裏我們把一份日誌文件中的日誌信息按照裏面的時間分隔成多個日誌文件.博主是編寫的一個Python腳本進行實現的.爲了邏輯簡單就以最簡單的方式呈現的,如不嫌棄就可以拿來用,代碼如下:
import re
import time
# 打開單個的日誌文件
with open("../logs/RS_Http_Server", "r") as f:
# 以每行分隔讀取成一個列表
log_lines = f.readlines()
# 遍歷這個列表
for line in log_lines:
# 正則提取裏面日誌的時間信息(可能格式跟你的會不太一樣,自行修改即是)
str_time = re.search(r"\d{4}-\d\d-\d\d \d\d", line).group(0)
# 將字符串時間轉換的time_struct格式
t = time.strptime(str_time, "%Y-%m-%d %H")
# 又將時間映射爲文件名稱
file_name = time.strftime("%Y-%m-%d_%H:%M:%S", t) + ".log"
# 寫入文件(這裏就沒管那麼多文件打開關閉的效率,一切還是以簡單爲主)
with open("./logTest/{filename}".format(filename=file_name), "a+") as fin:
fin.write(line)
# 睡眠0.5模擬日誌的生成
time.sleep(0.5)
5.配置Flume任務
- 進入$FLUME_HOME/conf目錄
- 新建一個duration_hour_log.conf
- 然後在這個文件中加入下面配置
# agent的名稱爲a1
a1.sources=source1
a1.channels=channel1
a1.sinks=sink1
# set source
a1.sources.source1.type=TAILDIR
a1.sources.source1.filegroups=f1
a1.sources.source1.filegroups.f1=/home/zh123/PycharmProjects/RS_HttpServer/Test/logTest/.*log
a1sources.source1.fileHeader=flase
# 設置sink
a1.sinks.sink1.type =org.apache.flume.sink.kafka.KafkaSink
a1.sinks.sink1.brokerList=localhost:9092
a1.sinks.sink1.topic=duration_hour_log
a1.sinks.sink1.kafka.flumeBatchSize=20
a1.sinks.sink1.kafka.producer.acks=1
a1.sinks.sink1.kafka.producer.linger.ms=1
a1.sinks.sink1.kafka.producer.compression.type=snappy
# 設置 channel
a1.channels.channel1.type=memory
a1.channels.channel1.capacity=1000
a1.channels.channel1.transactionCapacity=1000
# 進行綁定
a1.sources.source1.channels=channel1
a1.sinks.sink1.channel=channel1
6.初始化Kafka
- 創建主題.
(1) 方法一直接只用命令進行創建主題:
kafka-topics.sh -- create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic duration_hour_log
(2) 用API進行創建:
import conf.KafkaProperties;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import java.util.Collection;
import java.util.Collections;
public class KafkaTopic {
private static final String ZK_CONNECT="localhost:2181";
/** Session 過期時間 */
private static final int SESSION_TIMEOUT = 30000;
/** 連接超時時間 */
private static final int CONNECT_TIMEOUT = 30000;
public static void createTopic(Collection<NewTopic> topics){
try (AdminClient adminClient = AdminClient.create(KafkaProperties.getTopicProperties())) {
adminClient.createTopics(topics);
for (String s : adminClient.listTopics().names().get()) {
System.out.println(s);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deleteTopic(Collection<String> topics){
try (AdminClient adminClient = AdminClient.create(KafkaProperties.getTopicProperties())){
adminClient.deleteTopics(topics);
for (String s : adminClient.listTopics().names().get()) {
System.out.println(s);
}
}catch (Exception e){
e.printStackTrace();
}
}
public static void main(String[] args) {
/* 創建主題*/
createTopic(Collections.singletonList(new NewTopic("duration_hour_log", 1, Short.decode("1"))));
/* 刪除主題*/
// deleteTopic(Collections.singletonList("duration_hour_log"));
}
}
7.準備消費者接收數據
消費者代碼:
import conf.KafkaProperties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class CustomerDemon {
public static void main(String[] args) {
Properties properties = KafkaProperties.getCustomerProperties("group1","c1");
try(KafkaConsumer<String ,String> kafkaConsumer = new KafkaConsumer<>(properties)) {
kafkaConsumer.subscribe(Collections.singletonList("duration_hour_log"));
while(true){
ConsumerRecords<String, String> records = kafkaConsumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records)
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
}catch (Exception e){
e.printStackTrace();
}
}
}
8.啓動整個流程,並在消費者端查看數據
- 啓動Flume任務
flume-ng agent --conf-file ~/opt/flume/conf/single_agent.conf --name a1 -Dfume.root.logger=INFO,console
- 啓動消費者程序,查看數據
- 啓動模擬日誌的python程序啓動
python flumeLogOutTest.py
- 最後查看運行情況
消費者端接收到的數據:
flume運行情況: