本公眾號(五分鍾學大數據)將推出大數據面試系列文章—五分鍾小面試,此系列文章將會深入研究各大廠筆面試真題,並根據筆面試題擴展相關的知識點,助力大家都能夠成功入職大廠!
大數據筆面試系列文章分為兩種類型:混合型(即一篇文章中會有多個框架的知識點—融會貫通);專項型(一篇文章針對某個框架進行深入解析—專項演練)。
此篇文章為系列文章的第一篇(混合型)
第一題:大數據筆試題-Java相關(美菜網)
寫出下列程序的輸出:
class Father{
static {
System.out.println("Static Father");
}
{
System.out.println("Non-static Father");
}
public Father(){
System.out.println("Constructor Father");
}
}
public class Son extends Father{
static {
System.out.println("Static Son");
}
{
System.out.println("Non-static Son");
}
public Son(){
System.out.println("Constructor Son");
}
public static void main(String[] args) {
System.out.println("First Son");
new Son();
System.out.println("Second Son");
new Son();
}
}
運行結果:
Static Father
Static Son
First Son
Non-static Father
Constructor Father
Non-static Son
Constructor Son
Second Son
Non-static Father
Constructor Father
Non-static Son
Constructor Son
分析:
這道程序題考察的是Java中的靜態代碼塊、構造代碼塊、構造函數的概念。
- 靜態代碼塊 static {}:
隨着類的加載而執行,即JVM加載類后就執行,而且只執行一次,執行優先級高於非靜態的初始化塊,它會在類初始化的時候執行一次,執行完成便銷毀,它僅能初始化類變量,即static修飾的數據成員。
- 非靜態代碼塊,也稱構造代碼塊 {}:
執行的時候如果有靜態代碼塊,先執行靜態代碼塊再執行非靜態代碼塊,在每個對象生成時都會被執行一次,它可以初始化類的實例變量。非靜態代碼塊會在構造函數執行時,在構造函數主體代碼執行之前被運行。
- 構造函數:
對象一建立,就會調用與之相應的構造函數,也就是說,不建立對象,構造函數是不會運行的。
一個對象建立,構造函數只運行一次,而一般方法可以被該對象調用多次。
再來看本題,程序運行,執行main()方法會先加載main()方法所在的類,加載 Son 類,但是 Son 類繼承自 Father 類,所以先加載父類,同時父類的靜態代碼塊執行,然后加載 Son 類本身,Son 類自己的靜態代碼塊開始執行,類加載完成之后執行main()方法內部的語句,打印 First Son,然后 new Son(),在創建對象時先構造父類的對象,因為靜態代碼塊只在類加載時執行一次,所以不再執行,然后執行父類的構造代碼塊,構造函數,構造代碼塊的優先級大於構造函數。然后在執行 Son 對象本身。完成之后打印 Second Son,接着又 new Son(),重復以上步驟。構造代碼塊和構造函數在每次new的時候都會執行一次。
第二題:大數據面試題-JVM相關(豐巢科技)
問:解釋內存中的棧(stack)、堆(heap)和靜態存儲區的用法?
答:通常我們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用內存中的棧空間;而通過new關鍵字和構造器創建的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、“hello”和常量都是放在靜態存儲區中。棧空間操作最快但是也很小,通常大量的對象都是放在堆空間,整個內存包括硬盤上的虛擬內存都可以被當成堆空間來使用。
String str = new String(“hello”);
上面的語句中str放在棧上,用new創建出來的字符串對象放在堆上,而“hello”這個字面量放在靜態存儲區。
補充:較新版本的Java中使用了一項叫“逃逸分析“的技術,可以將一些局部對象放在棧上以提升對象的操作性能。(在 Java SE 6u23+ 開始支持,並默認設置為啟用狀態,可以不用額外加這個參數。)
第三題:大數據面試題-海量數據相關(騰訊)
問:給 40 億個不重復的 unsigned int 的整數,沒排過序的,然后再給一個數,如何快速判斷這個數是否在那 40 億個數當中?
答:方案 1:申請 512M 的內存,512M是42億多 bit,一個 bit 位代表一個 unsigned int 值。讀入 40 億個數,設置相應的 bit 位,讀入要查詢的數,查看相應 bit 位是否為 1,為 1 表示存在,為 0 表示不存在。
方案 2:這個問題在《編程珠璣》里有很好的描述,大家可以參考下面的思路,探討一下: 因為 2^32 為 42 億多,所以給定一個數可能在,也可能不在其中; 這里我們把 40 億個數中的每一個用 32 位的二進制來表示 ,假設這 40 億個數開始放在一個文件中。 然后將這 40 億個數分成兩類:
- 最高位為 0
- 最高位為 1
並將這兩類分別寫入到兩個文件中,其中一個文件中數的個數<=20 億,而另一個>=20 億(相當於折半); 與要查找的數的最高位比較並接着進入相應的文件再查找
然后再把這個文件為又分成兩類:
- 次最高位為 0
- 次最高位為 1
並將這兩類分別寫入到兩個文件中,其中一個文件中數的個數<=10 億,而另一個>=10 億(相當於折半); 與要查找的數的次最高位比較並接着進入相應的文件再查找。
.....
以此類推,就可以找到了,而且時間復雜度為 O(logn)。
第四題:大數據面試題-Hadoop相關(阿里)
問:MapReduce 中排序發生在哪幾個階段?這些排序是否可以避免?為什么?
答:
-
一個 MapReduce 作業由 Map 階段和 Reduce 階段兩部分組成,這兩階段會對數據排序,從這個意義上說,MapReduce 框架本質就是一個 Distributed Sort。
-
在 Map 階段,Map Task 會在本地磁盤輸出一個按照 key 排序(采用的是快速排序)的文件(中間可能產生多個文件,但最終會合並成一個),在 Reduce 階段,每個 Reduce Task 會對收到的數據排序(采用的是歸並排序),這樣,數據便按照 Key 分成了若干組,之后以組為單位交給 reduce 處理。
-
很多人的誤解在 Map 階段,如果不使用 Combiner 便不會排序,這是錯誤的,不管你用不用 Combiner,Map Task 均會對產生的數據排序(如果沒有 Reduce Task,則不會排序,實際上 Map 階段的排序就是為了減輕 Reduce端排序負載)。
-
由於這些排序是 MapReduce 自動完成的,用戶無法控制,因此,在hadoop 1.x 中無法避免,也不可以關閉,但 hadoop2.x 是可以關閉的。
第五題:大數據面試題-Kafka相關(商湯科技)
問:kafka數據分區和消費者的關系,kafka的數據offset讀取流程,kafka內部如何保證順序
答:
-
kafka數據分區和消費者的關系:1個partition只能被同組的⼀一個consumer消費,同組的consumer則起到均衡效果
-
kafka的數據offset讀取流程:
- 連接ZK集群,從ZK中拿到對應topic的partition信息和partition的Leader的相關信息
- 連接到對應Leader對應的broker
- consumer將⾃自⼰己保存的offset發送給Leader
- Leader根據offset等信息定位到segment(索引⽂文件和⽇日志⽂文件)
- 根據索引⽂文件中的內容,定位到⽇日志⽂文件中該偏移量量對應的開始位置讀取相應⻓長度的數據並返回給consumer
-
kafka內部如何保證順序:kafka只能保證partition內是有序的,但是partition間的有序是沒辦法的。愛奇藝的搜索架構,是從業務上把需要有序的打到同一個partition。
第六題:大數據面試題-分布式相關(阿里)
問:說說三種分布式鎖?
答:
- 基於數據庫實現分布式鎖:(性能較差,鎖表的風險,非阻塞,失敗需要輪詢耗CPU)
- 悲觀鎖
利用select … where … for update 排他鎖
注意: 其他附加功能與實現基本一致,這里需要注意的是“where name=lock ”,name字段必須要走索引,否則會鎖表。有些情況下,比如表不大,mysql優化器會不走這個索引,導致鎖表問題。
- 樂觀鎖
所謂樂觀鎖與悲觀鎖最大區別在於基於CAS思想,是不具有互斥性,不會產生鎖等待而消耗資源,操作過程中認為不存在並發沖突,只有update version失敗后才能覺察到。我們的搶購、秒殺就是用了這種實現以防止超賣。
樂觀鎖是通過增加遞增的版本號字段實現的。
- 基於緩存(Redis等)實現分布式鎖:(過期時間不好控制,非阻塞,失敗需要輪詢耗CPU)
-
獲取鎖的時候,使用setnx加鎖,並使用expire命令為鎖添加一個超時時間,超過該時間則自動釋放鎖,鎖的value值為一個隨機生成的UUID,通過此在釋放鎖的時候進行判斷。
-
獲取鎖的時候還設置一個獲取的超時時間,若超過這個時間則放棄獲取鎖。
-
釋放鎖的時候,通過UUID判斷是不是該鎖,若是該鎖,則執行delete進行鎖釋放。
- 基於Zookeeper實現分布式鎖:(高可用、可重入、阻塞鎖)
大致思想:每個客戶端對某個功能加鎖時,在zookeeper上的與該功能對應的指定節點的目錄下,⽣生成⼀個唯一的瞬時有序節點。判斷是否獲取鎖的方式很簡單,只需要判斷有序節點中序號最小的一個。當釋放鎖的時候,只需將這個瞬時節點刪除即可。同時,其可以避免服務宕機導致的鎖無法釋放,⽽而產生的死鎖問題。
-
優點:鎖安全性高,zk可持久化,且能實時監聽獲取鎖的客戶端狀態。一旦客戶端宕機,則瞬時節點隨之消失,zk因⽽而能第一時間釋放鎖。這也省去了用分布式緩存實現鎖的過程中需要加入超時時間判斷的這一邏輯。
-
缺點:性能開銷⽐比較高。因為其需要動態產生、銷毀瞬時節點來實現鎖功能。所以不太適合直接提供給高並發的場景使用。
-
實現:可以直接采用zookeeper第三方庫curator即可方便地實現分布式鎖。
-
適用場景:對可靠性要求非常高,且並發程度不高的場景下使用。如核心數據的定時全量/增量同步等。
第七題:大數據面試題-Hadoop、Spark相關(京東金融)
問:Hadoop 和 Spark 的相同點和不同點?
答:
Hadoop底層使用MapReduce計算架構,只有map和reduce兩種操作,表達能力比較欠缺,而且在MR過程中會重復的讀寫hdfs,造成大量的磁盤io讀寫操作,所以適合高時延環境下批處理計算的應用;
Spark是基於內存的分布式計算架構,提供更加豐富的數據集操作類型,主要分成轉化操作和行動操作,包括map、reduce、filter、flatmap、groupbykey、reducebykey、union和join等,數據分析更加快速,所以適合低時延環境下計算的應用;
spark與hadoop最大的區別在於迭代式計算模型。基於mapreduce框架的Hadoop主要分為map和reduce兩個階段,兩個階段完了就結束了,所以在一個job里面能做的處理很有限;spark計算模型是基於內存的迭代式計算模型,可以分為n個階段,根據用戶編寫的RDD算子和程序,在處理完一個階段后可以繼續往下處理很多個階段,而不只是兩個階段。所以spark相較於mapreduce,計算模型更加靈活,可以提供更強大的功能。
但是spark也有劣勢,由於spark基於內存進行計算,雖然開發容易,但是真正面對大數據的時候,在沒有進行調優的情況下,可能會出現各種各樣的問題,比如OOM內存溢出等情況,導致spark程序可能無法運行起來,而mapreduce雖然運行緩慢,但是至少可以慢慢運行完。
第八題:大數據面試題-Yarn相關(特斯拉)
問:一個應用程序是如何在 Yarn 集群上執行的?
答:
當 jobclient 向YARN提交一個應用程序后,YARN將分兩個階段運行這個應用程序:一是啟動ApplicationMaster;第二個階段是由ApplicationMaster創建應用程序,為它申請資源,監控運行直到結束。
具體步驟如下:
-
用戶向YARN提交一個應用程序,並指定ApplicationMaster程序、啟動ApplicationMaster的命令、用戶程序。
-
RM為這個應用程序分配第一個Container,並與之對應的NM通訊,要求它在這個Container中啟動應用程序ApplicationMaster。
-
ApplicationMaster向RM注冊,然后拆分為內部各個子任務,為各個內部任務申請資源,並監控這些任務的運行,直到結束。
-
AM采用輪詢的方式向RM申請和領取資源。
-
RM為AM分配資源,以Container形式返回。
-
AM申請到資源后,便與之對應的NM通訊,要求NM啟動任務。
-
NodeManager為任務設置好運行環境,將任務啟動命令寫到一個腳本中,並通過運行這個腳本啟動任務。
-
各個任務向AM匯報自己的狀態和進度,以便當任務失敗時可以重啟任務。
-
應用程序完成后,ApplicationMaster向ResourceManager注銷並關閉自己。
第九題:大數據面試題-數據質量相關(螞蟻金服)
問:數據質量怎么監控?
答:
如一張表的記錄數在一個已知的范圍內,或者上下浮動不會超過某個閾值:
-
SQL結果:var 數據量 = select count(*)from 表 where 時間等過濾條件
-
報警觸發條件設置:如果數據量不在[數值下限, 數值上限], 則觸發報警
-
同比增加:如果((本周的數據量 -上周的數據量)/上周的數據量*100)不在 [比例下線,比例上限],則觸發報警
-
環比增加:如果((今天的數據量 - 昨天的數據量)/昨天的數據量*100)不在 [比例下線,比例上限],則觸發報警
-
報警觸發條件設置一定要有。如果沒有配置的閾值,不能做監控
日活、周活、月活、留存(日周月)、轉化率(日、周、月)GMV(日、周、月)
復購率(日周月)
單表空值檢測
某個字段為空的記錄數在一個范圍內,或者占總量的百分比在某個閾值范圍內
-
目標字段:選擇要監控的字段,不能選“無”
-
SQL結果:var 異常數據量 = select count(*) from 表 where 目標字段 is null
-
單次檢測:如果(異常數據量)不在[數值下限, 數值上限],則觸發報警
單表重復值檢測
一個或多個字段是否滿足某些規則
-
目標字段:第一步先正常統計條數;select count(*) form 表;
-
第二步,去重統計;select count(*) from 表 group by 某個字段
-
第一步的值和第二步的值做減法,看是否在上下線閥值之內
-
單次檢測:如果(異常數據量)不在[數值下限, 數值上限], 則觸發報警
跨表數據量對比
主要針對同步流程,監控兩張表的數據量是否一致
-
SQL結果:count(本表) - count(關聯表)
-
閾值配置與“空值檢測”相同
第十題:大數據面試題-海量數據相關(百度)
問:在海量日志數據中,提取出某日訪問百度次數最多的那個IP
答:這類問題都歸為求Top K的問題,解決方法都差不多。
將這一天訪問百度的日志的IP取出來,逐個寫入到一個大文件中。注意到IP是32位的,最多有個2^32個IP。同樣可以采用映射的方法,比如模1000,把整個大文件映射為1000個小文件,再找出每個小文中出現頻率最大的IP(可以采用 HashMap 進行頻率統計,然后再找出頻率最大的幾個)及相應的頻率。然后再在這1000個最大的IP中找出那個頻率最大的IP,即為所求。
算法思想:分而治之+Hash
-
IP地址最多有2^32=4G種取值情況,所以不能完全加載到內存中處理;
-
可以考慮采用分而治之的思想,按照IP地址的Hash(IP) % 1024值,把海量IP日志分別存儲到1024個
小文件中,這樣每個小文件最多包含4MB個IP地址; 這里解釋一下為什么用Hash(IP) % 1024值,如果不用,而直接分類的話,可能會出現這樣一種情況,就是有個IP在每個小文件中都存在,而且這個IP並不一定在那個小文件中是數量最多的,那么最終可能選擇的結果會有問題,所以這里用了Hash(IP)%1024值,這樣的話,通過計算IP的Hash值,相同IP肯定會放到一個文件中,當然了不同的IP的Hash值也可能相同,就存在一個小文件中。 -
對於每一個小文件,可以構建一個IP為key,出現的次數為value的HashMap,同時記錄當前出現次數最多的那個IP地址;
-
可以得到1024個小文件中的出現次數最多的那個IP,再依據常規的排序算法得出總體上出現次數最多的IP。