【面經】頭條-2017年8月30日,散招實習生

頭條是這批次面試中的一個理想公司,基礎架構部。從兩輪面試的情況來看,面試官的素質非常高,面試經驗也比較豐富。一方面提問抓的準,不會在你明確表示準備不足的方面硬扣;一方面深度廣度、代碼風格均有涉及。不過總共只讓我寫了兩道代碼題,希望不是放水。

一面

一面應該還問了其他內容,但是兩次面試問的太多,想不起來了。

項目

兩個項目都是簡單介紹,沒有深入問。

基礎

并發

  • 條件變量內部有鎖,為什么在wait條件變量時,最外層還需要加鎖(跪)
  • 除了notify,wait還有什么情況下可能被喚醒(跪)

書到用時方恨少,題到問時才知淺。并發復習的太少,這部分完全忘卻了。

算法

判斷二叉樹是否是二叉搜索樹

題目很熟悉了,倒是也寫過。

我有點記不清二叉搜索樹(BST)的定義,于是先跟面試官簡單確認了下。接下來,向面試官明確題目:

  • 假設不包含重復值
  • 給出明確的BST的定義:
    1. l 是BST
    2. r 是BST
    3. 滿足l < root < r

向面試官表示用分治法,然后開始寫代碼。寫到一半,面試官又跟我說可以考慮二叉樹的先序、中序、后續遍歷這些。我反應過來可以直接中序遍歷,遍歷的同時判斷序列是否遞增。說了下思路,詢問面試官是否可以先寫我原來的解法,這樣就不用換思路了,省的出錯。面試官統一后我才繼續寫:

private class Result {
  public boolean isBST;
  public int min;
  public int max;
  public Result(boolean isBST, int min, int max) {
    this.isBST = isBST;
    this.min = min;
    this.max = max;
  }
}

public isBST(TreeNode root) {
  if (root == null) {
    return true;
  }
  
  Result result = dc(root);
  return result.isBST;
}

private Result dc(TreeNode root) {
  if (root == null) {
    return null;
  }
  if (root.left == null && root.right == null) {
    return new Result(true, root.val, root.val);
  }
  
  Result lResult = dc(root.left);
  Result rResult = dc(root.right);
  if (lResult != null && (!lResult.isBST || lResult.max >= root.val) {
    return new Result(false, 0, 0);
  }
  if (rResult != null && (!rResult.isBST || rResult.min >= root.val)) {
    return new Result(false, 0, 0);
  }
  
  // 忘記了這里的check,no face, no bug free
  int min = root.val;
  int max = root.val;
  if (lResult != null) {
    min = lResult.min;
  }
  if (rResult != null) {
    max = rResult.max;
  }
  return new Result(true, min, max);
}

分治法代碼寫出來有點長,不過多長也得堅持編碼風格bug free

but,我第一次寫的還是有bug,剛把紙交給面試官我就想起來了——最后一句return沒有檢查空指針,,,媽的真是蠢。還好面試官沒有介意,感謝感謝。

中序遍歷的話,簡單做可以完全保存下序列,再去check,但是浪費空間;復雜做就得一邊遍歷一邊檢查,我還沒想清楚足夠簡潔的寫法。(待補充)

二面

項目

冷數據壓縮與存儲——vulture

可能是我講的越來越能抓住key point了?這是我唯一一次把vulture中的兩個難點都講了。特別是異常恢復,把壓縮過程抽象成狀態機還是有點講頭的。

  • 為什么要依賴外部配置,而沒有跟HDFS整合在一起

我表示也考慮過這種方案,但當時的需求是盡量簡單的搞定壓縮,如果跟HDFS整合的話,可能需要將冷熱溫的生存期、溫度等都寫進FileStatus,而改動這種基礎類的影響太大了。

Hadoop集群監控——hawk

表示項目本身很簡單,難在如何確定所監控指標的準確含義。

話說一半,意在引導面試官提問指標相關的內容,秀源碼。

  • 準確含義指什么

我把ContainerExitStatus=137時的case講了一下。

源碼

HDFS里的Federation怎么實現的

客戶端掛載,如何映射 balabala。

一般Federation里還要使用HA,講一下HA的原理

Zookeeper保障唯一時刻只有至多一個active節點;唯一的active向journalNode寫數據,其他standby從journalNode讀數據。

HA中兩個節點同步哪些內容(跪)

開始以為跟SecondaryNameNode機制一樣,后來仔細一想:standby既然能從journalNode讀數據,也就不需要像SecondaryNameNode那樣從active拉取editlog,自然也不需要在stanby上合并日志并同步回active。

所以,坦白自己沒看過這一塊內容,也沒什么想法。

Yarn的源碼你比較了解哪塊

狀態機、事件調度

container是怎么啟動的

說的比較糙:

  1. NM收到指令
  2. 創建Container狀態機、初始化資源等
  3. ContainerLaucher服務收到LAUNCH_CONTAINER事件
  4. 創建launch_container腳本
  5. 創建container_executor腳本,該腳本里會啟動該java進程

container運行需要下載一些資源,了解這個過程嗎

先講三個資源等級,假設最簡單的場景,剛說到ResourceLocalizer狀態機,面試官就表示,“好,不用說了,我知道你看過這塊”。

系統設計

用客戶端掛載實現Federation的缺點

  • 掛載表配置在客戶端,所以修改掛載表后,客戶端也必須作出同步的修改,無法做到用戶無感知或低感知
  • Federation的在擴展NameNode的同時也有負載均衡的目的,但是客戶端掛載相當于人工維護負載均衡,流量發生任何變化都無法維持均衡

只想出來這兩點。待完善

不使用掛載表,如何實現Federation(半跪)

我不了解這方面的內容,嘗試以客戶端掛載方案為baseline進行改進。

Federation的核心是目錄到NameNode的映射關系。而客戶端掛載的本質缺點在于將映射關系保存在客戶端,因此所有功能都依賴于客戶端完成。所以優化的第一步是,將映射關系保存在服務端。下面給出兩種方案:

  • 將映射保存在服務端。進一步的,如想訪問/logdata目錄,就創建/logdata文件,文件中保存/logdata目錄映射的NameNode。每次客戶端都先訪問/logdata文件,獲取映射的NameNode,再到該NameNode上訪問/logdata目錄。效果相當于把客戶端掛載移到了服務端。
  • 在NameNode前架設Proxy服務,由Proxy在內存中維護映射關系。甚至可基于Proxy實現新的均衡器和自動化掛載,實現不同標準(跨NameNode、同NameNode)的數據均衡和流量均衡。

我回答的不完全是面試官想要的——面試官問我看過Hdaoop 3.0的源碼嗎,我表示完全沒看過,面試官表示沒看過的話應該答不上來。

如果用Proxy的方案,估計一下qps

1w個節點,每個節點都是 40核+256G,全部跑MR,每個container1核+2G。估計Proxy需要承受的qps。

假設就跑了一個AM,它申請了集群所有的container,全部跑mapper,可忽略該AM。假設每個mapper在1min(60s)內需要訪問5次NameNode,則至少需要訪問5次Proxy(獲取映射)。則:

qps = (10E4 * 40 / 1) * 5次/60s = 3.67 * 10E4 次/s

面試官問我這么大的規模單機扛得住嗎,,,我表示現在的技術扛萬級qps不是很輕松嗎。恩,,可能我還是沒有get到考點吧。

基礎

并發(半跪)

我一面中并發答的很差,就主動跟二面面試官表示并發這塊復習的不好。面試官心領神會,也沒有出很難的題目。萬謝萬謝。

  • Java中可以通過哪些方式使用鎖

synchronized、ReentrantLock、用AQS裸寫一個鎖。

這里我本來也寫了Condition、CountDownLatch那些并發工具,后來覺得這更屬于同步,所以又把它們刪了。這種屬于偏概念的套路題,需要刷面經才能知道套路答案

  • 如何讓鎖實現“可重入”(半跪)

我一開始覺得先不用說重入次數,就只回答了需要在鎖內部記錄owner。結果面試官提醒多重入的場景我才說還要記錄重入次數,搞得好像面試官提醒我才想到。

所以說下次不能耍小聰明,能想到的點盡量說出來。如果面試官不攔著你,你就由簡到難,一直說下去,說到一個完整且你說不下去的地方為止

  • 講一個你熟悉的并發的內容

對比講了HashTable和ConcurrentHashMap。

估計面試官問我這個問題只是想看看我是不是并發一點都不會,畢竟我前面并發答的那么差。

問個簡單的,Object類中有哪些方法(半跪)

  • wait, notify, notifyAll
  • toString
  • hashCode, equals

然后就說不上來了。

現補充如下:

public class Object {
    private static native void registerNatives();
    static {
        registerNatives();
    }
    
    public final native Class<?> getClass();
    
    public native int hashCode();
    public boolean equals(Object obj) {
        return (this == obj);
    }
    
    protected native Object clone() throws CloneNotSupportedException;
    
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
    
    public final native void notify();
    public final native void notifyAll();
    public final native void wait(long timeout) throws InterruptedException;
    public final void wait(long timeout, int nanos) throws InterruptedException {...}
    public final void wait() throws InterruptedException {
        wait(0);
    }
    protected void finalize() throws Throwable { }
}

主要漏答的是clone, getClass。finalize, registerNatives也盡量記住。

覆寫hashCode時有什么需要注意的

主要是需要判斷相等的時候,比如HashMap的get方法,對hashCode和equals的調用順序有要求:

if (table[pos].key.hashCode() == key.hashCode 
  && (table[pos].key == key || table[pos].equals(key))) {
  return table[pos];
}

只有hashCode相等的時候,equals才有可能相等,即,“覆寫后,如果equals相等,那么hashCode也必須相等”。

同時,對于HashMap還要保證插入前后,key的hashCode相等。

總結如下:

  1. 如果equals相等,那么hashCode也必須相等
  2. 對同一個對象,將其插入HashMap前后,hashCode不可變

工程代碼

按需求實現新的hash表(跪)

我們知道NameNode中保存了BlockId到BlockInfo的映射,以滿足快速查找的目的。如果直接用HashMap實現(當然,實際用的也不是HashMap,用的Google的LightWeightGSet),由于HashMap內部用拉鏈發實現,每個節點都有最有兩個指針,假設每個指針占2字節,那么1億個Block最少要占用4億字節,假設造成很大的空間浪費。你需要實現一個新的hash表,接口定義:

interface SimpleMap<K, V> {
 K get(K key);
 V put(K key, V value);
 V remove(K key);
}

要求:

  • 盡量提高空間利用率
  • 可以犧牲部分性能

當即想到了線性探測、rehash等方法,覺得so easy,確認可以用線性探測后,就開始打草稿了,然后謄抄。需要注意的是del方法,我采取的思路是刪除后將tail節點填充到被刪除的位置。啪啪啪一頓寫,結果交了答案——我又意識到了大bug,思路就錯了;還發現了一個put方法中的一個死循環。不過這兩點都屬于我基本思路上的問題,就不秀錯誤代碼了。

回來自己想,現在補充正確代碼,未實現的非核心代碼參照HashMap:

public class LinearProbingHashMap<K, V> implements SimpleMap<K, V> {

  @Override
  public V get(Object key) {
    if (key == null) {
      return null;
    }

    int pos = indexFor(hash(key));
    for (int i = 0;
         i < table.length && table[pos] != null;
         i++, pos = (pos + 1) % table.length) {
      if (table[pos] == Entry.DELETED_ENTRY) {
        continue;
      }
      if (table[pos].keyHash == key.hashCode()
          && (table[pos].key == key || table[pos].key.equals(key))) {
        return table[pos].value;
      }
    }
    return null;
  }

  @Override
  public V put(K key, V value) {
    if (key == null) {
      return null;
    }

    int pos = indexFor(hash(key));
    int lastEmptyPos = -1;
    for (int i = 0;
         i < table.length && table[pos] != null;
         i++, pos = (pos + 1) % table.length) {
      if (table[pos] == Entry.DELETED_ENTRY) {
        lastEmptyPos = pos;
      }
      if (table[pos].keyHash == key.hashCode()
          && (table[pos].key == key || table[pos].key.equals(key))) {
        V oldValee = table[pos].value;
        table[pos].value = value;
        return oldValee;
      }
    }

    if (size + 1 < threshold) {
      if (lastEmptyPos == -1) {
        // assert table[pos] == null;
        lastEmptyPos = pos;
      }
      table[lastEmptyPos] = new Entry<>(key, value);
      size++;
      return null;
    }

    LinearProbingHashMap<K, V> newMap = new LinearProbingHashMap<>(table.length * 2);
    for (Entry<K, V> entry : table) {
      // TODO: 2017/8/31 remove extra cost on copy entry
      if (entry != null && entry != Entry.DELETED_ENTRY) {
        newMap.put(entry.key, entry.value);
      }
    }
    init(newMap); // update table, size, loadFactor, threshold, and etc.
    put(key, value);

    return null;
  }

  @Override
  public V remove(Object key) {
    if (key == null) {
      return null;
    }

    int pos = indexFor(hash(key));
    for (int i = 0;
         i < table.length && table[pos] != null;
         i++, pos = (pos + 1) % table.length) {
      if (table[pos] == Entry.DELETED_ENTRY) {
        continue;
      }
      if (table[pos].keyHash == key.hashCode()
          && (table[pos].key == key || table[pos].key.equals(key))) {
        V oldValue = table[pos].value;
        table[pos] = (Entry<K, V>) Entry.DELETED_ENTRY;
        size--;
        return oldValue;
      }
    }

    return null;
  }
}

提問

  • 如果有幸加入,入職后可能負責的工作內容
  • 總共幾輪面試
  • 面試評價

恩,我以為到提問環節就是最后一輪技術面呢,問了問題1,面試官表示這是三面才會聊的。總共有三輪面試,一面二面水平差不多,三面是技術leader。

正面評價:

  • 整體表現不錯
  • 代碼風格挺好

負面評價:

  • 最后一道題有死循環是大問題(也就是思路可以原諒,但是死循環不能原諒)
  • 這道題看起來簡單,但我面試的人沒有一個能寫的沒有問題。以后應該想好再動手
  • 你要搞分布式的話,并發是基礎。本來最后一道題也想考你并發,但是說并發掌握的不好,就沒考你

三面(HR面)

等了三面的面試官很久都沒有來,我實在憋不住就寫了個紙條,把門開著去上廁所了。。。結果回來——握草女面試官!再看正臉,握草化妝了!怎么看都不像是技術leader,一問才知道是HR。HR表示,技術leader在開會,剛才跟他碰了一下,他說看前面兩面面試官評價都不錯,他不需要面了,所以直接跳到了HR面。

給我整的分不清是好消息還是壞消息。聽說這個leader是90年的,天大本科畢業就工作了,帶著一群80、90后,非常屌,本來想借著面試瞻仰一下,真是可惜。后面就是正常的HR面,不過感覺頭條的HR問的真多啊,各種興趣愛好,評價,家庭什么的。第一次經歷這陣仗,學到了學到了。

最后問我有沒有拿到其他offer,老實回答了。我個人覺得,面試是個相互選擇的過程,希望公司和面試者都能坦誠相待。

總結

面試完,又分別跟大學同學(頭條暑期實習)和濤神聊了聊,兩個人都反應,頭條的壓力非常大,但是同時也主動表示食堂好。看來只要公司愿意給高價,什么加班什么壓力大都可以放一邊。學到了學到了。

上次阿里面試官簡歷的面試三原則非常管用,這次溝通明顯順暢了許多。頭條面試有一個好處,要不要你都會給通知,,,but,拒信和offer都要等好幾天啊,,,HR姐姐說盡量本周五之前給通知,希望一切順利!!

給自己的建議:

  • bug free!bug free!bug free!
  • 耐心學習分布式系統的理論知識,研究更多分布式系統的優秀案例
  • 很多看起來簡單的問題,還是不要掉以輕心,耐心想想再動筆
  • 學過的東西不要丟下,原來看書雖然快,但很難吸收;這段時間建議少看書,多通過實踐把知識固化。特別是并發部分,這是搞分布式的基本功,必須吃到肚子里

本文鏈接:【面經】頭條-2017年8月30日,散招實習生
作者:猴子007
出處:https://monkeysayhi.github.io
本文基于 知識共享署名-相同方式共享 4.0 國際許可協議發布,歡迎轉載,演繹或用于商業目的,但是必須保留本文的署名及鏈接。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 首先感謝熱心助人的崔同學,耐心給我講解猿題庫的面試風格,讓我能安心只準備了算法和system design。不過算...
    猴子007閱讀 1,518評論 6 5
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,269評論 25 708
  • 又一個父親節,每年六月的第三個星期天它都會如約而至。不出意外,朋友圈又是一篇篇感謝父愛如山的文章,或滿懷對父親的深...
    金幣慢慢長大閱讀 162評論 0 0
  • 近期很多城市房價猛漲,北京也在列。回想起年初的買房決定,不覺間倒吸幾口涼氣。若猶豫至今,我勢必得離開北京了。 留守...
    一個寫字的閱讀 498評論 4 4
  • 一,關鍵點原則。我們一定要找到能讓我們的一生價值最大,最能收獲幸福的那個事業。前邊講過在熱愛且擅長的領域拼搏是最容...
    田玉洲閱讀 270評論 0 0