第一章 數據倉庫概念
數據倉庫( Data Warehouse ),是爲企業制定決策,提供數據支持的。可以幫助企業,改進業務流程、提高產品質量等。
數據倉庫的輸入數據通常包括:業務數據、用戶行爲數據和爬蟲數據等
業務數據:就是各行業在處理事務過程中產生的數據。比如用戶在電商網站中登錄、下單、支付等過程中,需要和網站後臺數據庫進行增刪改查交互,產生的數據就是業務數據。業務數據通常存儲在MySQL、Oracle等數據庫中。
用戶行爲數據:用戶在使用產品過程中,通過埋點收集與客戶端產品交互過程中產生的數據,併發往日誌服務器進行保存。比如頁面瀏覽、點擊、停留、評論、點贊、收藏等。用戶行爲數據通常存儲在日誌文件中。
爬蟲數據:通常事通過技術手段獲取其他公司網站的數據。不建議同學們這樣去做。
第二章 項目需求及架構設計
2.1 項目需求分析
2.2 項目框架
2.2.1 技術選型
2.2.2 系統數據流程設計
2.2.3 框架版本選型
2.2.4服務器選型
2.2.5 集羣規模
2.2.6 集羣資源規劃設計
在企業中通常會搭建一套生產集羣和一套測試集羣。生產集羣運行生產任務,測試集羣用於上線前代碼編寫和測試。
1)生產集羣
(1)消耗內存的分開
(2)數據傳輸數據比較緊密的放在一起(Kafka 、Zookeeper)
(3)客戶端儘量放在一到兩臺服務器上,方便外部訪問
(4)有依賴關係的儘量放到同一臺服務器(例如:Hive和Azkaban Executor)
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
nn |
nn |
dn |
dn |
dn |
dn |
dn |
dn |
dn |
dn |
|
|
rm |
rm |
nm |
nm |
nm |
nm |
nm |
nm |
|
|
nm |
nm |
|
|
|
|
|
|
|
|
|
|
|
|
|
zk |
zk |
zk |
|
|
|
|
|
|
|
kafka |
kafka |
kafka |
|
|
|
|
|
|
|
Flume |
Flume |
flume |
|
|
Hbase |
Hbase |
Hbase |
|
|
|
|
|
hive |
hive |
|
|
|
|
|
|
|
|
mysql |
mysql |
|
|
|
|
|
|
|
|
spark |
spark |
|
|
|
|
|
|
|
|
Azkaban |
Azkaban |
|
|
|
ES |
ES |
|
|
2)測試集羣服務器規劃
服務名稱 |
子服務 |
服務器 hadoop102 |
服務器 hadoop103 |
服務器 hadoop104 |
HDFS |
NameNode |
√ |
|
|
DataNode |
√ |
√ |
√ |
|
SecondaryNameNode |
|
|
√ |
|
Yarn |
NodeManager |
√ |
√ |
√ |
Resourcemanager |
|
√ |
|
|
Zookeeper |
Zookeeper Server |
√ |
√ |
√ |
Flume(採集日誌) |
Flume |
√ |
√ |
|
Kafka |
Kafka |
√ |
√ |
√ |
Flume(消費Kafka) |
Flume |
|
|
√ |
Hive |
Hive |
√ |
|
|
MySQL |
MySQL |
√ |
|
|
Sqoop |
Sqoop |
√ |
|
|
Presto |
Coordinator |
√ |
|
|
Worker |
|
√ |
√ |
|
Azkaban |
AzkabanWebServer |
√ |
|
|
AzkabanExecutorServer |
√ |
|
|
|
Spark |
|
√ |
|
|
Kylin |
|
√ |
|
|
HBase |
HMaster |
√ |
|
|
HRegionServer |
√ |
√ |
√ |
|
Superset |
|
√ |
|
|
Atlas |
|
√ |
|
|
Solr |
Jar |
√ |
|
|
服務數總計 |
|
19 |
8 |
8 |
第三章 數據生成模塊
3.1 目標數據
我們要收集和分析的數據主要包括頁面數據、事件數據、曝光數據、啓動數據和錯誤數據。
3.1.1 頁面
頁面數據主要記錄一個頁面的用戶訪問情況,包括訪問時間、停留時間、頁面路徑等信息。
字段名稱 |
字段描述 |
page_id |
頁面id home("首頁"), category("分類頁"), discovery("發現頁"), top_n("熱門排行"), favor("收藏頁"), search("搜索頁"), good_list("商品列表頁"), good_detail("商品詳情"), good_spec("商品規格"), comment("評價"), comment_done("評價完成"), comment_list("評價列表"), cart("購物車"), trade("下單結算"), payment("支付頁面"), payment_done("支付完成"), orders_all("全部訂單"), orders_unpaid("訂單待支付"), orders_undelivered("訂單待發貨"), orders_unreceipted("訂單待收貨"), orders_wait_comment("訂單待評價"), mine("我的"), activity("活動"), login("登錄"), register("註冊"); |
last_page_id |
上頁id |
page_item_type |
頁面對象類型 sku_id("商品skuId"), keyword("搜索關鍵詞"), sku_ids("多個商品skuId"), activity_id("活動id"), coupon_id("購物券id"); |
page_item |
頁面對象id |
sourceType |
頁面來源類型 promotion("商品推廣"), recommend("算法推薦商品"), query("查詢結果商品"), activity("促銷活動"); |
during_time |
停留時間(毫秒) |
ts |
跳入時間 |
3.1.2 事件
事件數據主要記錄應用內一個具體操作行爲,包括操作類型、操作對象、操作對象描述等信息。
字段名稱 |
字段描述 |
action_id |
動作id favor_add("添加收藏"), favor_canel("取消收藏"), cart_add("添加購物車"), cart_remove("刪除購物車"), cart_add_num("增加購物車商品數量"), cart_minus_num("減少購物車商品數量"), trade_add_address("增加收貨地址"), get_coupon("領取優惠券"); 注:對於下單、支付等業務數據,可從業務數據庫獲取。 |
item_type |
動作目標類型 sku_id("商品"), coupon_id("購物券"); |
item |
動作目標id |
ts |
動作時間 |
3.1.3 曝光
曝光數據主要記錄頁面所曝光的內容,包括曝光對象,曝光類型等信息。
字段名稱 |
字段描述 |
displayType |
曝光類型 promotion("商品推廣"), recommend("算法推薦商品"), query("查詢結果商品"), activity("促銷活動"); |
item_type |
曝光對象類型 sku_id("商品skuId"), activity_id("活動id"); |
item |
曝光對象id |
order |
曝光順序 |
3.1.4 啓動
啓動數據記錄應用的啓動信息。
字段名稱 |
字段描述 |
entry |
啓動入口 icon("圖標"), notification("通知"), install("安裝後啓動"); |
loading_time |
啓動加載時間 |
open_ad_id |
開屏廣告id |
open_ad_ms |
廣告播放時間 |
open_ad_skip_ms |
用戶跳過廣告時間 |
ts |
啓動時間 |
3.1.5 錯誤
錯誤數據記錄應用使用
過程中的錯誤信息,包括錯誤編號及錯誤信息。
字段名稱 |
字段描述 |
error_code |
錯誤碼 |
msg |
錯誤信息 |
3.2 數據埋點
3.2.1 主流埋點方式(瞭解)
目前主流的埋點方式,有代碼埋點(前端/後端)、可視化埋點、全埋點三種。
代碼埋點是通過調用埋點SDK函數,在需要埋點的業務邏輯功能位置調用接口,上報埋點數據。例如,我們對頁面中的某個按鈕埋點後,當這個按鈕被點擊時,可以在這個按鈕對應的 OnClick 函數裏面調用SDK提供的數據發送接口,來發送數據。
可視化埋點只需要研發人員集成採集 SDK,不需要寫埋點代碼,業務人員就可以通過訪問分析平臺的“圈選”功能,來“圈”出需要對用戶行爲進行捕捉的控件,並對該事件進行命名。圈選完畢後,這些配置會同步到各個用戶的終端上,由採集 SDK 按照圈選的配置自動進行用戶行爲數據的採集和發送。
全埋點是通過在產品中嵌入SDK,前端自動採集頁面上的全部用戶行爲事件,上報埋點數據,相當於做了一個統一的埋點。然後再通過界面配置哪些數據需要在系統裏面進行分析。
3.2.2 埋點數據上報時機
埋點數據上報時機包括兩種方式。
方式一,在離開該頁面時,上傳在這個頁面產生的所有數據(頁面、事件、曝光、錯誤等)。優點,批處理,減少了服務器接收數據壓力。缺點,不是特別及時。
方式二,每個事件、動作、錯誤等,產生後,立即發送。優點,響應及時。缺點,對服務器接收數據壓力比較大。
本次項目採用方式一埋點。
3.2.3 埋點數據日誌結構
我們的日誌結構大致可分爲兩類,一是普通頁面埋點日誌,二是啓動日誌。
普通頁面日誌結構如下,每條日誌包含了,當前頁面的頁面信息,所有事件(動作)、所有曝光信息以及錯誤信息。除此之外,還包含了一系列公共信息,包括設備信息,地理位置,應用信息等,即下邊的common字段。
(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 }, { "displayType": "recommend", "item": "6", "item_type": "sku_id", "order": 4, "pos_id": 2 }, { "displayType": "query ", "item": "6", "item_type": "sku_id", "order": 5, "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" -- 來源類型 }, "err":{ --錯誤 "error_code": "1234", --錯誤碼 "msg": "***********" --錯誤信息 }, "ts": 1585744374423 --跳入時間戳 }
(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 }
3.3 服務器和JDK準備
分別安裝hadoop102、hadoop103、hadoop104三臺主機。
相關內容參考:https://www.cnblogs.com/wkfvawl/p/15369416.html
3.4 模擬數據
3.4.1 使用說明
1)將application.yml、gmall2020-mock-log-2021-01-22.jar、path.json、logback.xml上傳到hadoop102的/opt/module/applog目錄下
(1)創建applog路徑
[atguigu@hadoop102 module]$ mkdir /opt/module/applog
(2)上傳文件application.yml到/opt/module/applog目錄
2)配置文件
(1)application.yml文件
可以根據需求生成對應日期的用戶行爲日誌。
[atguigu@hadoop102 applog]$ vim application.yml
修改如下內容
# 外部配置打開 logging.config: "./logback.xml" #業務日期 注意:並不是Linux系統生成日誌的日期,而是生成數據中的時間 mock.date: "2020-06-14" #模擬數據發送模式 #mock.type: "http" #mock.type: "kafka" mock.type: "log" #http模式下,發送的地址 mock.url: "http://hdp1/applog" #kafka模式下,發送的地址 mock: kafka-server: "hdp1:9092,hdp2:9092,hdp3:9092" kafka-topic: "ODS_BASE_LOG" #啓動次數 mock.startup.count: 200 #設備最大值 mock.max.mid: 500000 #會員最大值 mock.max.uid: 100 #商品最大值 mock.max.sku-id: 35 #頁面平均訪問時間 mock.page.during-time-ms: 20000 #錯誤概率 百分比 mock.error.rate: 3 #每條日誌發送延遲 ms mock.log.sleep: 10 #商品詳情來源 用戶查詢,商品推廣,智能推薦, 促銷活動 mock.detail.source-type-rate: "40:25:15:20" #領取購物券概率 mock.if_get_coupon_rate: 75 #購物券最大id mock.max.coupon-id: 3 #搜索關鍵詞 mock.search.keyword: "圖書,小米,iphone11,電視,口紅,ps5,蘋果手機,小米盒子"
(2)path.json,該文件用來配置訪問路徑
根據需求,可以靈活配置用戶點擊路徑。
[ {"path":["home","good_list","good_detail","cart","trade","payment"],"rate":20 }, {"path":["home","search","good_list","good_detail","login","good_detail","cart","trade","payment"],"rate":40 }, {"path":["home","mine","orders_unpaid","trade","payment"],"rate":10 }, {"path":["home","mine","orders_unpaid","good_detail","good_spec","comment","trade","payment"],"rate":5 }, {"path":["home","mine","orders_unpaid","good_detail","good_spec","comment","home"],"rate":5 }, {"path":["home","good_detail"],"rate":10 }, {"path":["home" ],"rate":10 } ]
(3)logback配置文件
可配置日誌生成路徑,修改內容如下
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="LOG_HOME" value="/opt/module/applog/log" /> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender"> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/app.%d{yyyy-MM-dd}.log</fileNamePattern> </rollingPolicy> <encoder> <pattern>%msg%n</pattern> </encoder> </appender> <!-- 將某一個包下日誌單獨打印日誌 --> <logger name="com.atgugu.gmall2020.mock.log.util.LogUtil" level="INFO" additivity="false"> <appender-ref ref="rollingFile" /> <appender-ref ref="console" /> </logger> <root level="error" > <appender-ref ref="console" /> </root> </configuration>
3)生成日誌
(1)進入到/opt/module/applog路徑,執行以下命令
[atguigu@hadoop102 applog]$ java -jar gmall2020-mock-log-2021-01-22.jar
(2)在/opt/module/applog/log目錄下查看生成日誌
[atguigu@hadoop102 log]$ ll
3.4.2 集羣日誌生成腳本
在hadoop102的/home/atguigu目錄下創建bin目錄,這樣腳本可以在服務器的任何目錄執行。
[atguigu@hadoop102 ~]$ echo $PATH
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/atguigu/.local/bin:/home/atguigu/bin
(1)在/home/atguigu/bin目錄下創建腳本lg.sh
[atguigu@hadoop102 bin]$ vim lg.sh
(2)在腳本中編寫如下內容
#!/bin/bash for i in hadoop102 hadoop103; do echo "========== $i ==========" ssh $i "cd /opt/module/applog/; java -jar gmall2020-mock-log-2021-01-22.jar >/dev/null 2>&1 &" done
注:
①/opt/module/applog/爲jar包及配置文件所在路徑
②/dev/null代表Linux的空設備文件,所有往這個文件裏面寫入的內容都會丟失,俗稱“黑洞”。
標準輸入0:從鍵盤獲得輸入 /proc/self/fd/0
標準輸出1:輸出到屏幕(即控制檯) /proc/self/fd/1
錯誤輸出2:輸出到屏幕(即控制檯) /proc/self/fd/2
這裏意思是 1給黑洞,2給1
(3)修改腳本執行權限
[atguigu@hadoop102 bin]$ chmod u+x lg.sh
(4)將jar包及配置文件上傳至hadoop103的/opt/module/applog/路徑
(5)啓動腳本
[atguigu@hadoop102 module]$ lg.sh
(6)分別在hadoop102、hadoop103的/opt/module/applog/log目錄上查看生成的數據
[atguigu@hadoop102 logs]$ ls app.2021-01-22.log [atguigu@hadoop103 logs]$ ls app.2020-01-22.log
第四章 數據採集模塊
相關內容參考:https://www.cnblogs.com/wkfvawl/p/15369416.html
4.1 Hadoop 安裝
集羣規劃
服務器hadoop102 |
服務器hadoop103 |
服務器hadoop104 |
|
HDFS |
NameNode DataNode |
DataNode |
DataNode SecondaryNameNode |
Yarn |
NodeManager |
Resourcemanager NodeManager |
NodeManager |
注意:儘量使用離線方式安裝
安裝過程參見:https://www.cnblogs.com/wkfvawl/p/15369416.html
4.2 Zookeeper 安裝
集羣規劃
服務器hadoop102 |
服務器hadoop103 |
服務器hadoop104 |
|
Zookeeper |
Zookeeper |
Zookeeper |
Zookeeper |
安裝過程參見:https://www.cnblogs.com/wkfvawl/p/15539847.html
4.3 Kafka 安裝
集羣規劃
|
服務器hadoop102 |
服務器hadoop103 |
服務器hadoop104 |
Kafka |
Kafka |
Kafka |
Kafka |
安裝過程參見:https://www.cnblogs.com/wkfvawl/p/15579066.html
4.3.1 創建Kafka Topic
進入到/opt/module/kafka/目錄下創建日誌主題
[atguigu@hadoop102 kafka]$ bin/kafka-topics.sh --zookeeper hadoop102:2181,hadoop103:2181,hadoop104:2181 --create --replication-factor 1 --partitions 1 --topic topic_log
4.4 採集日誌Flume
4.4.1 Flume安裝
服務器hadoop102 |
服務器hadoop103 |
服務器hadoop104 |
|
Flume(採集日誌) |
Flume |
Flume |
|
安裝過程參見:https://www.cnblogs.com/wkfvawl/p/15603589.html
4.4.2 Flume組件選型
1)Source
(1)Taildir Source相比Exec Source、Spooling Directory Source的優勢
TailDir Source:斷點續傳、多目錄。Flume1.6以前需要自己自定義Source記錄每次讀取文件位置,實現斷點續傳。不會丟數據,但是有可能會導致數據重複。
Exec Source可以實時蒐集數據,但是在Flume不運行或者Shell命令出錯的情況下,數據將會丟失。
Spooling Directory Source監控目錄,支持斷點續傳。
(2)batchSize大小如何設置?
答:Event 1K左右時,500-1000合適(默認爲100)
2)Channel
採用Kafka Channel
省去了Sink,提高了效率。KafkaChannel數據存儲在Kafka裏面,所以數據是存儲在磁盤中。
注意在Flume1.7以前,Kafka Channel很少有人使用,因爲發現parseAsFlumeEvent這個配置起不了作用。也就是無論parseAsFlumeEvent配置爲true還是false,都會轉爲Flume Event。這樣的話,造成的結果是,會始終都把Flume的headers中的信息混合着內容一起寫入Kafka的消息中,這顯然不是我所需要的,我只是需要把內容寫入即可。
4.4.3 Flume 配置
1)Flume配置分析
Flume直接讀log日誌的數據,log日誌的格式是app.yyyy-mm-dd.log。採集的數據可能不完整,需要用攔截器進行攔截,但這裏只能進行簡單的數據清洗。
2)Flume的具體配置如下:
(1)在/opt/module/flume/conf目錄下創建file-flume-kafka.conf文件
[atguigu@hadoop102 conf]$ vim file-flume-kafka.conf
在文件配置如下內容
#爲各組件命名 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.* a1.sources.r1.positionFile = /opt/module/flume/taildir_position.json a1.sources.r1.interceptors = i1 a1.sources.r1.interceptors.i1.type = com.atguigu.flume.interceptor.ETLInterceptor$Builder #描述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以及sink和channel的關係 a1.sources.r1.channels = c1
注意:com.atguigu.flume.interceptor.ETLInterceptor是自定義的攔截器的全類名。需要根據用戶自定義的攔截器做相應修改。
4.4.4 Flume攔截器
1)創建Maven工程flume-interceptor
2)創建包名:com.atguigu.flume.interceptor
3)在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>
注意:scope中provided的含義是編譯時用該jar包。打包時時不用。因爲集羣上已經存在flume的jar包。只是本地編譯時用一下。
4)在com.atguigu.flume.interceptor包下創建JSONUtils類
package com.atguigu.flume.interceptor; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONException; public class JSONUtils { public static boolean isJSONValidate(String log){ try { JSON.parse(log); return true; }catch (JSONException e){ return false; } } }
5)在com.atguigu.flume.interceptor包下創建LogInterceptor類
package com.atguigu.flume.interceptor; import com.alibaba.fastjson.JSON; 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) { byte[] body = event.getBody(); String log = new String(body, StandardCharsets.UTF_8); if (JSONUtils.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() { } }
6)打包
7)需要先將打好的包放入到hadoop102的/opt/module/flume/lib文件夾下面。
[atguigu@hadoop102 lib]$ ls | grep interceptor
flume-interceptor-1.0-SNAPSHOT-jar-with-dependencies.jar
8)分發Flume到hadoop103、hadoop104
[atguigu@hadoop102 module]$ xsync flume/
9)分別在hadoop102、hadoop103上啓動Flume
[atguigu@hadoop102 flume]$ bin/flume-ng agent --name a1 --conf-file conf/file-flume-kafka.conf & [atguigu@hadoop103 flume]$ bin/flume-ng agent --name a1 --conf-file conf/file-flume-kafka.conf &
4.4.5 測試Flume-Kafka通道
(1)生成日誌
[atguigu@hadoop102 ~]$ lg.sh
(2)消費Kafka數據,觀察控制檯是否有數據獲取到
[atguigu@hadoop102 kafka]$ bin/kafka-console-consumer.sh \
--bootstrap-server hadoop102:9092 --from-beginning --topic topic_log
說明:如果獲取不到數據,先檢查Kafka、Flume、Zookeeper是否都正確啓動。再檢查Flume的攔截器代碼是否正常。
4.4.6 日誌採集Flume啓動停止腳本
(1)在/home/atguigu/bin目錄下創建腳本f1.sh
[atguigu@hadoop102 bin]$ 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 --conf-file /opt/module/flume/conf/file-flume-kafka.conf --name a1 -Dflume.root.logger=INFO,LOGFILE >/opt/module/flume/log1.txt 2>&1 &" done };; "stop"){ for i in hadoop102 hadoop103 do echo " --------停止 $i 採集flume-------" ssh $i "ps -ef | grep file-flume-kafka | grep -v grep |awk '{print \$2}' | xargs -n1 kill -9 " done };; esac
說明1:nohup,該命令可以在你退出帳戶/關閉終端之後繼續運行相應的進程。nohup就是不掛起的意思,不掛斷地運行命令。
說明2:awk 默認分隔符爲空格
說明3:$2是在“”雙引號內部會被解析爲腳本的第二個參數,但是這裏面想表達的含義是awk的第二個值,所以需要將他轉義,用\$2表示。
說明4:xargs 表示取出前面命令運行的結果,作爲後面命令的輸入參數。
(2)增加腳本執行權限
[atguigu@hadoop102 bin]$ chmod u+x f1.sh
(3)f1集羣啓動腳本
[atguigu@hadoop102 module]$ f1.sh start
(4)f1集羣停止腳本
[atguigu@hadoop102 module]$ f1.sh stop
4.5 消費Kafka數據Flume
集羣規劃
服務器hadoop102 |
服務器hadoop103 |
服務器hadoop104 |
|
Flume(消費Kafka) |
|
|
Flume |
4.5.1 項目經驗之Flume組件選型
1)FileChannel和MemoryChannel區別
MemoryChannel傳輸數據速度更快,但因爲數據保存在JVM的堆內存中,Agent進程掛掉會導致數據丟失,適用於對數據質量要求不高的需求。
FileChannel傳輸速度相對於Memory慢,但數據安全保障高,Agent進程掛掉也可以從失敗中恢復數據。
選型:
金融類公司、對錢要求非常準確的公司通常會選擇FileChannel
傳輸的是普通日誌信息(京東內部一天丟100萬-200萬條,這是非常正常的),通常選擇MemoryChannel。
2)FileChannel優化
通過配置dataDirs指向多個路徑,每個路徑對應不同的硬盤,增大Flume吞吐量。
官方說明如下:
Comma separated list of directories for storing log files. Using multiple directories on separate disks can improve file channel peformance
checkpointDir和backupCheckpointDir也儘量配置在不同硬盤對應的目錄中,保證checkpoint壞掉後,可以快速使用backupCheckpointDir恢復數據。
3)Sink:HDFS Sink
(1)HDFS存入大量小文件,有什麼影響?
元數據層面:每個小文件都有一份元數據,其中包括文件路徑,文件名,所有者,所屬組,權限,創建時間等,這些信息都保存在Namenode內存中。所以小文件過多,會佔用Namenode服務器大量內存,影響Namenode性能和使用壽命
計算層面:默認情況下MR會對每個小文件啓用一個Map任務計算,非常影響計算性能。同時也影響磁盤尋址時間。
(2)HDFS小文件處理
官方默認的這三個參數配置寫入HDFS後會產生小文件,hdfs.rollInterval、hdfs.rollSize、hdfs.rollCount
基於以上hdfs.rollInterval=3600,hdfs.rollSize=134217728,hdfs.rollCount =0幾個參數綜合作用,效果如下:
①文件在達到128M時會滾動生成新文件
②文件創建超3600秒時會滾動生成新文件
4.5.2 消費者Flume配置
1)Flume配置分析
加上時間戳攔截器,用來解決零點漂移問題。
爲什麼會發生零點漂移?
比如2022-01-24 23:59:59生成的日誌文件,然後數據經過第一層的flume採集,加上kafka的緩衝,然後到 集羣的另一臺上的第二層的flume的時候,時間肯定就會到2022-01-25 00:00:XX了,這樣一來,如果採用當前系統時間作爲timestamp的話,2022-01-24 的日誌數據就會上傳到hdfs上的2022-01-25 的目錄下。因爲Kafka Source會爲其加上該header,value爲當前系統的時間戳Kafka Source會爲其加上該header,value爲當前系統的時間戳
所以我們需要再第二層flume裏面寫這麼一個加時間戳的攔截器,把日誌文件裏面的時間添加到event的header裏面
2)Flume的具體配置如下:
(1)在hadoop104的/opt/module/flume/conf目錄下創建kafka-flume-hdfs.conf文件
[atguigu@hadoop104 conf]$ vim kafka-flume-hdfs.conf
在文件配置如下內容
## 組件 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.atguigu.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/ ## 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.round = false #控制生成的小文件 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 = lzop ## 拼裝 a1.sources.r1.channels = c1 a1.sinks.k1.channel= c1
4.5.3 Flume時間戳攔截器
由於Flume默認會用Linux系統時間,作爲輸出到HDFS路徑的時間。如果數據是23:59分產生的。Flume消費Kafka裏面的數據時,有可能已經是第二天了,那麼這部門數據會被髮往第二天的HDFS路徑。我們希望的是根據日誌裏面的實際時間,發往HDFS的路徑,所以下面攔截器作用是獲取日誌中的實際時間。
解決的思路:攔截json日誌,通過fastjson框架解析json,獲取實際時間ts。將獲取的ts時間寫入攔截器header頭,header的key必須是timestamp,因爲Flume框架會根據這個key的值識別爲時間,寫入到HDFS。
1)在com.atguigu.flume.interceptor包下創建TimeStampInterceptor類
package com.atguigu.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) { // 將日誌攔下,取出header裏面的key , 取出body裏面的對應的日誌時間; 將ts的值賦值給header的key timestamp // 1 獲取header頭 Map<String, String> headers = event.getHeaders(); // 2 獲取body中的ts byte[] body = event.getBody(); String log = new String(body, StandardCharsets.UTF_8); JSONObject jsonObject = JSONObject.parseObject(log); String ts = jsonObject.getString("ts"); // 3 將ts賦值給timestamp 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) { } } }
2)重新打包
3)需要先將打好的包放入到hadoop102的/opt/module/flume/lib文件夾下面。
[atguigu@hadoop102 lib]$ ls | grep interceptor
flume-interceptor-1.0-SNAPSHOT-jar-with-dependencies.jar
4)分發Flume到hadoop103、hadoop104
[atguigu@hadoop102 module]$ xsync flume/
4.5.4 消費者Flume啓動停 在腳本中填寫如下內容
#! /bin/bash case $1 in "start"){ for i in hadoop104 do echo " --------啓動 $i 消費flume-------" ssh $i "nohup /opt/module/flume/bin/flume-ng agent --conf-file /opt/module/flume/conf/kafka-flume-hdfs.conf --name a1 -Dflume.root.logger=INFO,LOGFILE >/opt/module/flume/log2.txt 2>&1 &" done };; "stop"){ for i in hadoop104 do echo " --------停止 $i 消費flume-------" ssh $i "ps -ef | grep kafka-flume-hdfs | grep -v grep |awk '{print \$2}' | xargs -n1 kill" done };; esac
(2)增加腳本執行權限
[atguigu@hadoop102 bin]$ chmod u+x f2.sh
(3)f2集羣啓動腳本
[atguigu@hadoop102 module]$ f2.sh start
(4)f2集羣停止腳本
[atguigu@hadoop102 module]$ f2.sh stop
4.3.5 項目經驗之Flume內存優化
1)問題描述:如果啓動消費Flume拋出如下異常
ERROR hdfs.HDFSEventSink: process failed
java.lang.OutOfMemoryError: GC overhead limit exceeded
2)解決方案步驟
(1)在hadoop102服務器的/opt/module/flume/conf/flume-env.sh文件中增加如下配置
export JAVA_OPTS="-Xms100m -Xmx2000m -Dcom.sun.management.jmxremote"
(2)同步配置到hadoop103、hadoop104服務器
[atguigu@hadoop102 conf]$ xsync flume-env.sh
3)Flume內存參數設置及優化
JVM heap一般設置爲4G或更高
-Xmx與-Xms最好設置一致,減少內存抖動帶來的性能影響,如果設置不一致容易導致頻繁fullgc。
-Xms表示JVM Heap(堆內存)最小尺寸,初始分配;-Xmx 表示JVM Heap(堆內存)最大允許的尺寸,按需分配。如果不設置一致,容易在初始化時,由於內存不夠,頻繁觸發fullgc。
4.6 採集通道啓動/停止腳本
(1)在/home/atguigu/bin目錄下創建腳本cluster.sh
[atguigu@hadoop102 bin]$ vim cluster.sh
在腳本中填寫如下內容
#!/bin/bash case $1 in "start"){ echo ================== 啓動 集羣 ================== #啓動 Zookeeper集羣 zk.sh start #啓動 Hadoop集羣 hdp.sh start #啓動 Kafka採集集羣 kf.sh start #啓動 Flume採集集羣 f1.sh start #啓動 Flume消費集羣 f2.sh start };; "stop"){ echo ================== 停止 集羣 ================== #停止 Flume消費集羣 f2.sh stop #停止 Flume採集集羣 f1.sh stop #停止 Kafka採集集羣 kf.sh stop #停止 Hadoop集羣 hdp.sh stop #停止 Zookeeper集羣 zk.sh stop };; esac
(2)增加腳本執行權限
[atguigu@hadoop102 bin]$ chmod u+x cluster.sh
(3)cluster集羣啓動腳本
[atguigu@hadoop102 module]$ cluster.sh start
(4)cluster集羣停止腳本
[atguigu@hadoop102 module]$ cluster.sh stop
產生日誌
[atguigu@hadoop102 module]$ lg.sh
查看HDFS