Hadoop常見的面試問題
1 . 談談資料傾斜,它如何發生的,並給出優化方案!
首先談一下什麼是資料傾斜?
答:map /reduce程式執行時,reduce節點大部分執行完畢,但是有一個或者幾個reduce節點執行很慢,導致整個程式的處理時間很長。
現象是 : 進度長時間維持在99%(或100%),檢視任務監控頁面,發現只有少量(1個或幾個)reduce子任務未完成;檢視未完成的子任務,可以看到本地讀寫資料量積累非常大,通常超過10GB可以認定為發生資料傾斜。
如何發生的?
資料的傾斜主要是兩個的資料相差的數量不在一個級別上 ,這是因為某一個key的條數比其他key多很多(有時是百倍或者千倍之多),這條key所在的reduce節點所處理的資料量比其他節點就大很多,從而導致某幾個節點遲遲執行不完。
優化方案 :
方式一 : reduce 本身的計算需要以合適的記憶體作為支援,在硬體環境容許的情況下,增加reduce 的JVM記憶體大小顯然有改善資料傾斜的可能,這種方式尤其適合資料分佈第一種情況,單個值有大量記錄, 這種值的所有紀錄已經超過了分配給reduce 的記憶體,無論你怎麼樣分割槽這種情況都不會改變. 當然這種情況的限制也非常明顯, 1.記憶體的限制存在,2.可能會對叢集其他任務的執行產生不穩定的影響。
方式二 : 這個對於資料分佈第二種情況有效,情況(一值較多,單個唯一值的記錄數不會超過分配給reduce 的記憶體). 如果發生了偶爾的資料傾斜情況,增加reduce 個數可以緩解偶然情況下的某些reduce 不小心分配了多個較多記錄數的情況. 但是對於第一種資料分佈無效。
方式三 : 一種情況是某個領域知識告訴你資料分佈的顯著型別,比如<<hadoop權威指南>> 裡面的溫度問題,一個固定的組合(觀測站點的位置和溫度) 的分佈是固定的, 對於特定的查詢如果前面兩種方式都沒用,實現自己的partitioner 也許是一個好的方式。
總結 : 資料傾斜沒有一勞永逸的方式可以解決,瞭解你的資料集的分佈情況,然後瞭解你所使用計算框架的執行機制和瓶頸,針對特定的情況做特定的優化,做多種嘗試,觀察是否有效。
2 . datanode 首次加入 cluster 的時候,如果 log 報告不相容檔案版本,那需要namenode 執行格式化操作,這樣處理的原因是?(可以當成工作中遇到的問題!)
這樣處理是不合理的,因為那麼 namenode 格式化操作,是對檔案系統進行格式化,namenode 格式化時清空 dfs/name 下空兩個目錄下的所有檔案,之後,會在目錄 dfs.name.dir 下建立檔案。文字不相容,有可能時 namenode 與 datanode 的 資料裡的 namespaceID、clusterID 不一致,找到兩個 ID 位置,修改為一樣即可解決。
3 . 簡述HDFS資料儲存機制?
HDFS是解決海量資料儲存問題的儲存系統。具有高可靠,讀寫效率高等特點。通過將大檔案拆分成一個一個block塊,Hadoop2.x預設是128M,分散式的儲存到各個DataNode節點中並且備份,通過橫向擴充套件解決了縱向擴充套件的問題,大大提升了讀寫的效率和降低了成本。同時,通過設定NameNode主節點來記錄每個block的元資料資訊,包括塊名,所在DataNode節點,備份所在位置,大小等等資訊,實現檔案的高可靠儲存和高效率讀取。而且在Hadoop2.0以上版本,通過HA解決了NameNode的單點故障問題,使得HDFS更為可靠。
4 . 如何實現小檔案的合併?
先將這些小檔案儲存到本地的一個路徑中同一個檔案中,通過shell指令碼,可以設定這個新檔案達到多大再上傳,一般設定為128M,上傳到HDFS中,這樣就實現了小檔案上傳之前的合併。還有,一般當天的日誌和資料都存在一個HDFS路徑中,如果沒有達到上傳大小,可以設定每天凌晨對前一天的本地檔案路徑的掃描,如果發現有檔案,不管多大,都上傳到前一天的HDFS檔案目錄下。
5 . Hadoop的Shuffer過程
map ----> partition(分割槽預設,可修改) ----> sort(排序預設,可修改) -----> combiner(map階段排序,可選) -----> spill (溢寫,預設不可改) -----> meger(合併檔案,預設,不可改) -----> compress(壓縮,可選) -----> Copy 階段----->Merge 階段----->Sort 階段。 這是整個MapReduce階段 , 而Map 產生輸出開始到Reduce取得資料作為輸入之前的過程稱shuffle階段
1. Collect階段 : 將MapTask的結果輸出到預設大小為100M的環形緩衝區 . 儲存的是key/value ,Partition分割槽資訊等。
2. Spill階段 : 當記憶體中的資料量達到一定的閾值的時候,就會將資料寫入到本地磁碟 , 在將資料寫入到磁碟之前需要對資料進行一次排序的操作。如果配置了combiner,還會將相同分割槽號和key的資料進行排序。
3. Merge階段 : 把所有的溢位的臨時檔案進行一次合併操作 , 以確保一個MapTask最終只產生一箇中間資料檔案。
4 . Copy=階段 : ReduceTask啟動Fetcher執行緒到已經完成MapTask的節點上覆制一份屬於自己的資料 , 這些資料預設會儲存到記憶體的緩衝區中 , 當記憶體的緩衝區達到一定的閾值的時候,就會將資料寫到磁碟之上
5 . 在reduceTask遠端複製資料的同時 , 會在後臺開啟兩個執行緒對記憶體到本地的資料檔案進行合併操作
6 . Sort階段 : 在對資料進行合併的同時 , 會進行排序操作 , 由於MapTask階段已經對資料進行了區域性的排序 , ReduceTask只需要保證Copy的資料最終整體有效性即可。
Shuffer中的緩衝區大小會影響到MapReduce程式的執行效率 , 原則上說 , 緩衝區越大 , 磁碟IO的次數越少,執行速度越快.
6 . 兩個檔案合併的問題
給定a、b兩個檔案,各存放50億個url,每個url各佔用64位元組,記憶體限制是4G,如何找出a、b檔案共同的url?主要的思想是把檔案分開進行計算,在對每個檔案進行對比,得出相同的URL,因為以上說是含有相同的URL所以不用考慮資料傾斜的問題。詳細的解題思路為:可以估計每個檔案的大小為5G*64=300G,遠大於4G。所以不可能將其完全載入到記憶體中處理。考慮採取分而治之的方法。
遍歷檔案a,對每個url求取hash(url)%1000,然後根據所得值將url分別儲存到1000個小檔案(設為a0,a1,...a999)當中。這樣每個小檔案的大小約為300M。遍歷檔案b,採取和a相同的方法將url分別儲存到1000個小檔案(b0,b1....b999)中。這樣處理後,所有可能相同的url都在對應的小檔案(a0 vs b0, a1 vs b1....a999 vs b999)當中,不對應的小檔案(比如a0 vs b99)不可能有相同的url。然後我們只要求出1000對小檔案中相同的url即可。 比如對於a0 vs b0,我們可以遍歷a0,將其中的url儲存到hash_map當中。然後遍歷b0,如果url在hash_map中,則說明此url在a和b中同時存在,儲存到檔案中即可。
如果分成的小檔案不均勻,導致有些小檔案太大(比如大於2G),可以考慮將這些太大的小檔案再按類似的方法分成小小檔案即可。
7 . 當場java或者scala手寫word count。(會一種)
8 . YARN的排程策略
FIFO: 先進先出. 小任務會被大任務阻塞
- Capacity: 容量排程器(預設).
按照你的佇列的形式!給不同的佇列劃分不同的百分比,當然如果一個佇列空閒!那麼另外一個佇列就會不斷的“吞噬”剩下的資源直至達到(自己設定的最大“吞噬”量的百分比),如果這個時候被吞噬的資源又開始忙的話!那麼就會釋放資源!最終達到自己設定的那個動態的平衡點!
例子 :
Root
佇列一 : prod 40% 剩餘25%
佇列二 :env 60% 剩餘75%
佇列二的子佇列 : mapreduce 50%
佇列二的子佇列 : spark 50%
動態平衡點 : 佇列一 :40% 佇列二佔 60% 當佇列一空閒 或者佇列二空閒的時候 , 另外一個不空閒的佇列最多吞噬到75% 。
而佇列二的子佇列 的50%+50% 等於佇列二的60%
- FAIR: 公平排程器
- 預設排程器是Capacity
- 1、容量排程器
- Vi yarn-site.xml中配置
- <property>
- <name>yarn.resourcemanager.scheduler.class</name>
- <value>org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler</value>
- </property>
- 然後在/etc/hadoop/capacity-scheduler.xml中配置容量排程器的引數
- 1、
- Name:yarn.scheduler.capacity.maximum-applications
- Value:10000
- 叢集或者佇列中(all)同時處於等待和執行狀態的應用程式數目上限,這是一個強限制,一旦叢集中應用程式數目超過該上限,後續提交的應用程式將被拒絕,預設值為10000
- 2、
- Name:yarn.scheduler.capacity.maximum-am-resource-percent
- Value:0.1
- 叢集中可用於執行application master的資源比例上限,這通常用於限制併發執行的應用程式數目
- 3、
- Name:yarn.scheduler.capacity.resource-calculator
- Value:org.apache.hadoop.yarn.util.resource.DefaultResourceCalculator
- 資源排程器resourceManager在做計算的時候,會考慮資源的排程。預設的方式只考慮到記憶體。如果使用DominantResourceCalculator則進行多維度比較:CPU,記憶體等
- 4、
- Name:yarn.scheduler.capacity.root.queues
- Value:default或者Q1,Q2,Q3(以,分割)
- 用來指定佇列的。容量排程器預先給root開啟了一個佇列,如果是預設的話,就表示只有一個佇列。可以指定當前root下有那些佇列的,不指定就一個
- 5、
- Name:yarn.scheduler.capacity.root.default.capacity
- Value:100
- 指定預設佇列的容量
- 如果要制定其他佇列容量的話只需要:
- Name:yarn.scheduler.capacity.root.Q1.capacity
- Value:100
- 6、
- Name:yarn.scheduler.capacity.root.default.user-limit-factor
- Value:1
- 每個佇列可使用的資源的限制
- 7、
- Name:yarn.scheduler.capacity.root.default.maximum-capacity
- Value:100
- Default佇列可使用的資源上限.
- 當第一個任務提交的時候,假如只有這一個任務,就會把叢集所有資源給這一個任務。第二個任務提交的時候,RM會分配給一半的資源給第二個任務。但是第二個任務從提交到執行會有一定的延遲,需要等待第一個任務釋放了container容器。但有個好處就是,能夠充分的讓每一個任務利用叢集的資源
9 . MR相關的調優(結合自己的專案場景,記住三四個)
主要使用的硬體 : CPU+memory
如果當前程序是進行儲存的 , 對CPU和記憶體要求並不是很高。如果當前程序是進行計算的,那麼對CPU和memory的要求都是很高的
硬體的主要配置有 :memory 當CPU不夠的話 : 程式執行運算會慢一點,當記憶體不夠的 : 會導致heap溢位,GC。因為在記憶體不夠的時候,Yarn裡面有一個動態的執行緒就會檢測到 , 然後就給你殺死了
二、Linux層面的一些調優 :
1、調整Linux最大檔案開啟數和最大程序數
vi /etc/security/limits.conf
soft nofile 65535 單個使用者可用的最大程序數量(軟限制)
hard nofile 65535 單個使用者可用的最大程序數量(硬限制)
soft nproc 65535 可開啟的檔案描述符的最大數(軟限制)
hard nproc 65535 可開啟的檔案描述符的最大數(硬限制)
2、網路引數net.core.somaxconn(定義每一個埠最大的監聽佇列的長度,預設值是128)
more /etc/sysct1.conf | grep net.core.somaxconn
sysct1 -w net.core.somaxconn=37628
echo vm.swappiness=0 >> /etc/sysct1.conf
3、設定swap(虛擬記憶體)分割槽 ->0 使用記憶體,這樣會更快
改成0不是禁用swap交換,而是優先考慮記憶體
More /etc/sysctl.conf |vm.swappiness
Echo vm.swappiness=0 >> /etc/sysctl.conf
三、NameNode上JVM的引數調優 :
假想一種情況 ,當namnode掛掉以後再次重啟的時候 , 會載入元資料到記憶體中 , 那麼在我們的堆存中還分年輕代和老年代。我們建立的物件之處都是在年輕帶(新生代)中的。在年輕帶中還分:eden、from space、to space。最開始的物件是在eden區中,當eden區放滿以後JVM會觸發GC,就會將物件放在生還區1(From space)和生還區2(to space)。然後在經過多次GC以後還能存活的物件放在老年代。(下面是新生代的詳解,跟本次釣友無關)
(-------新生代 (轉載)
新建立的物件分配的新生代中,經歷多次GC之後進入老年代。新生代具體細分有分為Eden區和survivor區,survivor區又包含S0和S1兩個區域。一般來說,我們建立了一個物件,它首先被放到新生代的Eden區。當Eden填滿時執行一次Minor GC,此時存活下來的物件進入survivor區第一塊survivor space S0,並且年齡加1。Eden被清空;等Eden區再滿了,就再觸發一次Minor GC,Eden和S0中的存活物件又會被複制送入第二塊survivor space S1(這個過程非常重要,因為這種複製演算法保證了S1中來自S0和Eden兩部分的存活物件佔用連續的記憶體空間,避免了碎片化的發生)。S0和Eden被清空,然後下一輪S0與S1交換角色,如此迴圈往復。如果物件的複製次數達到16次,該物件就會被送到老年代中。-------)
對於調優我們應該怎麼樣namenode上JVM的引數:
/etc/hadoop/hadoop-env.sh
找到 export HADOOP_NAMAENODE_OPTS="${HADOOP_NAMENODE_OPTS}
-Xms10240m -Xmx10240m
-XX:+UseParNewGC 設定年輕代為多執行緒並行收集
-XX:+UseConcMarkSweepGC 年老代啟用CMS收集器(標記演算法),可以儘量減少fullGC
-XX:+CMSConcurrentMTEnabled 當該標誌被啟用時,併發的CMS階段將以多執行緒執行
-XX:CMSInitiatingOccupancyFraction=70 當年老代空間被佔用70%的時候觸發CMS垃圾收集
-XX:+CMSClassUnloadingEnabled 設定這個引數表示對永久帶進行垃圾回收,CMS預設不對永久代進行垃圾回收"
總結 : 把年輕帶中的記憶體分配點給老年代
--XX:OldSize : 設定JVM啟動分配的老年代記憶體大小, 新生代記憶體的初始值大小 -XX:NewSize
我們的namenode啟動後,所有的元資料資訊一直載入到年老代中。name年老代的壓力會比較大,同時年輕帶那邊會剩餘很多的空間,所以有時候我們需要將年老代記憶體調大。 官方推薦的是3/8或者(1/4--1/3)的記憶體給年輕帶。剩下的給年老代。如果記憶體足夠全部調大也無所謂。比如 : Namenode節點分配的記憶體是15個G。年輕代給了5個G。年老代給了10G。實際年輕代可能1G都用不上,還空餘了4G。反倒年老代已經觸發了警戒線。這時候就要適當的調整。 當然這是基於儲存來說的
Mr/Hive的調優 : 這個剛好與(namenode相反):我們做mr/hive計算時候,建立的物件就會頻繁的發生在年輕代。因為年老代的物件可能就不怎麼用了。而且我們經常會出現井噴式的計算,會不斷的觸發GC。這樣就導致了頻繁的垃圾回收。所以我們在mr的時候,需要將年輕代給調大了。
四、配置方面的調優 :
一、core-site.xml 的調整
1、
Ipc.server.listen.queue.size 控制了服務端socket的監聽佇列長度,預設值128.這個要和
net.core.somaxconn 配合使用(定義每一個埠最大的監聽佇列的長度,預設值是128)
<property>
<name>Ipc.server.listen.queue.size</name>
<value>65535</value>
</property>
2、
Io.file.buffer.size預設值4096(4K),作為hadoop的緩衝區,用於hdfs檔案的讀寫。還有map的輸出都用到這個緩衝區,較大的快取能提高資料傳輸這是讀寫sequence file的buffer size,可減少I/O次數。如果叢集比較大,建議把引數調到65535-131072
Name :Io.file.buffer.size
Value:65535
3、
Hdfs的回收站,一但操作事物,刪掉了HDFS中的資料,可以找回。預設不開啟
Name:fs.trash.interval Value:1440(分鐘)1天
恢復方式:
Hdfs的回收站在: /user/$USER/.Trash/Current/user/$USER/目錄下
Hadoop dfs -mv /xxxxxx/xxxx /oooo/oooo
二、hdfs-site.xml 配置檔案的調整:
1、#HDFS中的block塊大小的設定
#Name:dfs.blockSize
#Value:134217728(128M)
#但是有時候我們場景中可能出現大量小檔案,這時候我們可以適當的調小,比如16M
#雖然可以用hadoop的壓縮方案,但是壓縮比例太高了,幾百M的東西能壓縮成幾百K,
#所以把block塊變小
2、頻寬:預設是1M(在balancer的時候,設定hdfs中資料移動速度)
叢集一般是千M路由器,所以儘量改大
<property>
<name>dfs.datanode.balance.bandwidthPerSec</name>
<value>1048576</value>
</property>
3、真正datanode資料儲存路徑,可以寫多塊硬碟。主要用來實現IO平衡,因此會顯著改進磁碟IO效能
Name:dfs.datanode.data.dir
Value:/sda,/sda2/sda3
但是這樣做,會導致慢磁碟的現象。
4、Namenode server RPC 的處理執行緒數,預設是10,namenode執行緒通過RPC的方式跟datanode通訊,如果datanode數量太多時可能出現RPC timeout
解決:
提升網路速度或者提高這個值。但是thread數量增多也代表著namenode消耗的記憶體也隨著增加
Dfs.namenode.hadler.count 30
Datanode serverRPC 處理的執行緒數,預設10
Dfs.datanode.hadler.count 20
5、
Datanode上負責檔案操作的執行緒數
Dfs.datanode.maxtrasfer.threads 8192
6、
datanode所保留的空間大小,需要設定一些,預設是不保留
Name:dfs.datanode.du.reserved
Value:位元組為單位
三、MapReduce層面的調優; mapred-site.xml的引數
1、
Mapreduce.framework.name yarn 已yarn為基礎來排程
Mapreduce.job.reduces 3 預設開啟的reduce數量,多臺機器分擔一臺機器的壓力
2、
Mapreduce環形緩衝區所佔記憶體大小預設100M
Mapreduce.task.io.sort.mb 200
3、
環形緩衝區的閾值 預設的是0.8
Mapreduce.map.sort.spill.percent 0.6
4、
Reduce Task中合併小檔案時,一次合併的檔案資料,每次合併的時候選擇最小的前10(預設值)進行合併。
Mapreduce.task.io.sort.factor 50
5、
map輸出是否進行壓縮,如果壓縮就會多耗cpu,但是減少傳輸時間,如果不壓縮,就需要較多的傳輸頻寬。需要配 合mapreduce.map.output.compress.codec(指定壓縮方式)
Name:Mapreduce.map.output.compress
Value:true
Name:Mapreduce.map.output.compress.codec
Value:org.apache.hadoop.io.compress.SnappyCodec(犧牲CPU換IO和磁碟的方式)
6、
Reduce shuffle階段並行傳輸的數量。根據叢集大小可調(牛人做的事)
Name:mapreduce.reduce.shuffle.parallelcopies
Value:20
7、
Map和reduce是通過http傳輸的,這個是設定傳輸的並行數
Name:Mapreduce.tasktracker.http.threads
Value:40
8、還有一點效率穩定性引數
(1) mapreduce.map.speculative: 是否為 Map Task 開啟推測執行機制,預設為 true, 如果為 true,則可以並行執行一些 Map 任務的多個例項。
(2) mapreduce.reduce.speculative: 是否為 Reduce Task 開啟推測執行機制,預設為 true
(3) mapreduce.input.fileinputformat.split.minsize: FileInputFormat做切片時最小切片大小,預設 1。
(5)mapreduce.input.fileinputformat.split.maxsize: FileInputFormat做切片時最大切片大小
推測執行機制(Speculative Execution):它根據一定的法則推測出“拖後腿”的任務,併為這樣的任務啟動一個備份任務,讓該任務與原始任務同時處理同一份資料,並最終選用最先成功執行完成任務的計算結果作為最終結果。
mapreduce的計數器 :
在實際生產程式碼中,常常需要將資料處理過程中遇到的不合規資料行進行全域性計數,類似這種需求可以藉助mapreduce 框架中提供的全域性計數器來實現。
9、容錯性相關的引數 :
(1) mapreduce.map.maxattempts: 每個 Map Task 最大重試次數,一旦重試引數超過該值,則認為 Map Task 執行失敗,預設值:4。
(2) mapreduce.reduce.maxattempts: 每個Reduce Task最大重試次數,一旦重試引數超過該值,則認為 Map Task 執行失敗,預設值:4。
(3) mapreduce.map.failures.maxpercent: 當失敗的 Map Task 失敗比例超過該值,整個作業則失敗,預設值為 0.
如果你的應用程式允許丟棄部分輸入資料,則該該值設為一個大於0的值,比如5,表示如果有低於 5%的 Map Task 失敗(如果一個 Map Task 重試次數超過mapreduce.map.maxattempts,則認為這個 Map Task 失敗,其對應的輸入資料將不會產生任何結果),整個作業扔認為成功。
(4) mapreduce.reduce.failures.maxpercent: 當失敗的 Reduce Task 失敗比例超過該值為,整個作業則失敗,預設值為 0.
(5) mapreduce.task.timeout:如果一個task在一定時間內沒有任何進入,即不會讀取新的資料,也沒有輸出資料,則認為該 task 處於 block 狀態,可能是臨時卡住,也許永遠會卡住。
為了防止因為使用者程式永遠 block 不退出,則強制設定了一個超時時間(單位毫秒),預設是600000,值為 0 將禁用超時。
--------------------------------重要引數配置--------------------------
1、
Map階段,map端的記憶體分配多少 個 其實就是 : Map Task 可使用的記憶體上限(單位:MB),預設為 1024。如果 Map Task 實際使用的資源量超過該值,則會被強制殺死。可以適當的調大
Name:mapreduce.map.memory.mb
Value:1280
2、
Map端啟用JVM所佔用的記憶體大小 (官方----->200M heap over/GC)
-XX:-UseGCOverheadLimit(禁用GC,我們使用parNew+CMS方式)
Name:Mapreduce.map.java.opts
Value:-Xmx1024m -XX:-UseGCOverheadLimit
3、
reduce階段,reduce端的記憶體分配多少 一個 Reduce Task 可使用的資源上限(單位:MB),預設為 1024。如果 Reduce Task 實際使用的資源量超過該值,則會被強制殺死。
Name:mapreduce.reduce.memory.mb
Value:1280
4、
reduce端啟用JVM所佔用的記憶體大小
-XX:-UseGCOverheadLimit(禁用GC)
Name:Mapreduce.reduce.java.opts
Value:-Xmx1024m -XX:-UseGCOverheadLimit
【注意:java的堆疊肯定要小於memory的,因為還要考慮非堆和其他】上面最大能分配多少,還取決於yarn的配置。
mapreduce.map.cpu.vcores: 每個 Maptask 可用的最多 cpu core 數目, 預設值: 1
mapreduce.reduce.cpu.vcores: 每個 Reducetask 可用最多 cpu core 數 預設值: 1
(這兩個引數調整的是核數)
Yarn-site.xml配置 //以下在yarn啟動之前就配置在伺服器的配置檔案中才能生效 :
1、
給nodemanager可用的實體記憶體
Name:yarn.nodemanager.resource.memory-mb
Value:8192
注意,如果你的節點記憶體資源不夠 8GB,則需要調減小這個值,而 YARN不會智慧的探測節點的實體記憶體總量。
2、
單個任務可申請的最少記憶體 --》 也就是RM 中每個容器請求的最小配置,以 MB 為單位,預設 1024。
Name:Yarn.scheduler.minimum-allocation-mb
Value:1024
3、
單個任務可申請的最大記憶體--》也就是RM 中每個容器請求的最大分配,以 MB 為單位,預設 8192。
Name:yarn.scheduler.maximum-allocation-mb
Value:8192
4、
Yarn這個節點可使用的虛擬CPU個數,預設是8.但我們通常配置成跟物理CPU個數一樣
Name:Yarn.nodemanager.resource.cpu-vcores
Value:4