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

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

一面

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

項目

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

基礎

併發

  • 條件變量內部有鎖,爲何在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 freeapp

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
出處:monkeysayhi.github.io
本文基於 知識共享署名-相同方式共享 4.0 國際許可協議發佈,歡迎轉載,演繹或用於商業目的,可是必須保留本文的署名及連接。

相關文章
相關標籤/搜索