大數據開發面試的總結-第二篇

(1)反轉二叉樹的實現;
可用遞歸實現,代碼包含遞歸,根節點作爲遍歷條件,左右子節點分別進行兌換。

class Node(object):                #定義樹的結點
    def __init__(self, data=-1, lchild=None, rchild=None):
        self.data = data
        self.lchild = lchild
        self.rchild = rchild
class Binsearchtree(object):
    def __init__(self, data):
        self.root = Node(data) 
    def inverttree(self, treenode):      #真正的翻轉只有這8行代碼
        if treenode == None:
            return None
        temp = treenode.lchild
        treenode.lchild = treenode.rchild
        treenode.rchild = temp
        self.inverttree(treenode.lchild)

樹的翻轉其實就是遞歸遍歷每一個結點,把所有結點的左孩子和右孩子互換,因爲沒有定義的孩子的data爲None,所以只有一個孩子的結點也不會出現問題
樹的結構如下:
在這裏插入圖片描述

(2)兩個文本文件A和文本B,A中的每一行去B中查找與之相似度最大的N條;
思路:針對A,B文件,選取其中一個相對較小的文件進行廣播join,例如廣播A去joinB,然後根據計算的每一個部分的相似度進行排序,取前N條,最終再進行全排序;(這個地方是先局部有序,再全局有序取最大的N條)

擴展:還有一種劃分分區的方式,暫時還未用到這種場景:針對每一個分區,實現總體在每個分區上的邊界是有序的,分區內部不一定有序–先從總體再到局部的思想,當然此時的分區邊界值元素即是每個分區的最大值;

(3) 給定一列,如何取該數的第2大的數,第n大的數呢?(高頻,好幾家公司的面試官都有考這個)
有很多種思路(涉及通過查詢或刪除的方式):
思路1:先查詢出最大,然後查詢小於該值的最大值;
思路2:先查詢出最大的,去掉該最大值,然後將數據進行排序(降序),查詢出當前的第一條數據。
思路3: 給每一個記錄一個行號,使用RANK函數,去掉身高值最高的記錄(重複值的rank函數獲得值相同)。
其中rank函數考慮到了over子句中排序字段值相同的情況,值相同的時候RANK函數的到的值相等。

查詢出可能與最大相同的第二名
思路1:將數據按照(降序)排列,查詢出前面的兩項,然後將這兩項按照身高升序,查詢第一項目。
思路2:=給每一個記錄一個行號ROW_NUMBER使用函數,去掉最大的記錄(重複值的ROW_NUMBER函數獲得值不同)。

(4)排序算法,熟悉哪些?哪些是穩定的;
快排:不穩定,Nlog(N)
二分類排序:穩定,Nlog(N)

(5)hive sql和spark sql的區別
hive sql底層是基於mapreduce框架計算實現的,mapreduce框架與spark計算框架的區別:
Hadoop MapReduce採用了多進程模型,而Spark採用了多線程模型:

Apache Spark的高性能一定程度上取決於它採用的異步併發模型(這裏指server/driver 端採用的模型),這與Hadoop 2.0(包括YARN和MapReduce)是一致的。Hadoop 2.0自己實現了類似Actor的異步併發模型,實現方式是epoll+狀態機,而Apache Spark則直接採用了開源軟件Akka,該軟件實現了Actor模型,性能非常高。儘管二者在server端採用了一致的併發模型,但在任務級別(特指 Spark任務和MapReduce任務)上卻採用了不同的並行機制:Hadoop MapReduce採用了多進程模型,而Spark採用了多線程模型。

注意,本文的多進程和多線程,指的是同一個節點上多個任務的運行模 式。無論是MapReduce和Spark,整體上看,都是多進程:MapReduce應用程序是由多個獨立的Task進程組成的;Spark應用程序的 運行環境是由多個獨立的Executor進程構建的臨時資源池構成的。

多進程模型便於細粒度控制每個任務佔用的資源,但會消耗較多的啓動時間,不適合運行低延遲類型的作業,這是MapReduce廣爲詬病的原因之一。而多線程模型則相反,該模型使得Spark很適合運行低延遲類型的作業。總之,Spark同節點上的任務以多線程的方式運行在一個JVM進程中,可帶來以下好處:

1)任務啓動速度快,與之相反的是MapReduce Task進程的慢啓動速度,通常需要1s左右;

2)同節點上所有任務運行在一個進程中,有利於共享內存。這非常適合內存密集型任務,尤其對於那些需要加載大量詞典的應用程序,可大大節省內存。

3) 同節點上所有任務可運行在一個JVM進程(Executor)中,且Executor所佔資源可連續被多批任務使用,不會在運行部分任務後釋放掉,這避免 了每個任務重複申請資源帶來的時間開銷,對於任務數目非常多的應用,可大大降低運行時間。與之對比的是MapReduce中的Task:每個Task單獨 申請資源,用完後馬上釋放,不能被其他任務重用,儘管1.0支持JVM重用在一定程度上彌補了該問題,但2.0尚未支持該功能。

儘管Spark的過線程模型帶來了很多好處,但同樣存在不足,主要有:

1)由於同節點上所有任務運行在一個進程中,因此,會出現嚴重的資源爭用,難以細粒度控制每個任務佔用資源。與之相 反的是MapReduce,它允許用戶單獨爲Map Task和Reduce Task設置不同的資源,進而細粒度控制任務佔用資源量,有利於大作業的正常平穩運行。

下面簡要介紹MapReduce的多進程模型和Spark的多線程模型。

MapReduce多進程模型

mapreduce採用多進程與spark採用多線程比較

1) 每個Task運行在一個獨立的JVM進程中;

2) 可單獨爲不同類型的Task設置不同的資源量,目前支持內存和CPU兩種資源;

3) 每個Task運行完後,將釋放所佔用的資源,這些資源不能被其他Task複用,即使是同一個作業相同類型的Task。也就是說,每個Task都要經歷“申請資源—> 運行Task –> 釋放資源”的過程。

Spark多線程模型

mapreduce採用多進程與spark採用多線程比較

1) 每個節點上可以運行一個或多個Executor服務;

2) 每個Executor配有一定數量的slot,表示該Executor中可以同時運行多少個ShuffleMapTask或者ReduceTask;

3) 每個Executor單獨運行在一個JVM進程中,每個Task則是運行在Executor中的一個線程;

4) 同一個Executor內部的Task可共享內存,比如通過函數SparkContext#broadcast廣播的文件或者數據結構只會在每個Executor中加載一次,而不會像MapReduce那樣,每個Task加載一次;

5) Executor一旦啓動後,將一直運行,且它的資源可以一直被Task複用,直到Spark程序運行完成後才釋放退出。

總體上看,Spark採用的是經典的scheduler/workers模式, 每個Spark應用程序運行的第一步是構建一個可重用的資源池,然後在這個資源池裏運行所有的ShuffleMapTask和ReduceTask(注 意,儘管Spark編程方式十分靈活,不再侷限於編寫Mapper和Reducer,但是在Spark引擎內部只用兩類Task便可表示出一個複雜的應用 程序,即ShuffleMapTask和ReduceTask),而MapReduce應用程序則不同,它不會構建一個可重用的資源池,而是讓每個 Task動態申請資源,且運行完後馬上釋放資源。

(6)設計模式,他們的實現方式;
傳統課本上單例模式分兩種,一種餓漢式,一種懶漢式。對應的代碼如下:
懶漢式
/**

  • 懶漢模式

  • 單例實例在第一次使用時進行創建
    */
    public class SingletonExample1 {

    // 私有構造函數
    private SingletonExample1() {

    }

    // 單例對象
    private static SingletonExample1 instance = null;

    // 靜態的工廠方法
    public static SingletonExample1 getInstance() {
    if (instance == null) {
    instance = new SingletonExample1();
    }
    return instance;
    }
    }
    懶漢式的實例是在第一次使用時創建的,相應的靜態工廠辦法會先判斷有沒有實例,沒有實例在進行創建。
    然而這種創建方法時線程不安全的,如果有兩個線程,同一時刻拿到單例對象,要去靜態工廠辦法訪問,由於工廠辦法沒有鎖,那麼很有可能這兩個線程最終會拿到兩個實例。
    餓漢式
    /**

  • 餓漢模式

  • 單例實例在類裝載時進行創建
    */
    public class SingletonExample2 {

    // 私有構造函數
    private SingletonExample2() {

    }

    // 單例對象
    private static SingletonExample2 instance = new SingletonExample2();

    // 靜態的工廠方法
    public static SingletonExample2 getInstance() {
    return instance;
    }
    }
    相對於上面那種懶漢式,餓漢式是線程安全的。直接將單例對象用static修飾,把實例對象放到堆內存中,保證了多個線程在訪問時的可見性。但是缺點也是很大的,正是由於把實例對象放到堆內存中,這樣應用一加載就會看到對應實例,極大浪費內存。

(7)spark job的數量,stage的數量,以及task的數量是怎麼確定的;(描述他們的流程:從job提交到task任務的分配的過程)

首先job的劃分是遇到action操作時,被發現後經過sparkcontext的runjob方法來到DAGscheduler,這個類中它會通過依賴關係劃分出stage,一個stage是一個taskset,裏面的每個task對應着rdd的一個分區。task可以理解爲並行的分片。一個job任務可以有一個或多個stage,一個stage又可以有一個或多個task。所以一個job的task數量是 (stage數量 * task數量)的總和。

擴展:
並行度:是指指令並行執行的最大條數。在指令流水中,同時執行多條指令稱爲指令並行。

理論上:每一個stage下有多少的分區,就有多少的task,task的數量就是我們任務的最大的並行度。

(一般情況下,我們一個task運行的時候,使用一個cores)

實際上:最大的並行度,取決於我們的application任務運行時使用的executor擁有的cores的數量。

(8)實際編程題
主要涉及實現效率,以及注意邊界條件判斷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章