從0到100——知乎架構變遷史

也許不少人還不知道,知乎在規模上是僅次於百度貼吧和豆瓣的中文互聯網最大的UGC(用戶生成內容)社區。知乎創業三年來,從0開始,到如今已經有了100多臺服務器。目前知乎的註冊用戶超過了1100萬,每月有超過8000萬人使用;網站每月的PV 超過2.2億,差很少每秒鐘的動態請求超過2500。node

在ArchSummit全球架構師峯會上,知乎聯合創始人兼CTO李申申帶來了知乎創業三年多來的首次全面技術分享(演講視頻)。本文系根據演講內容整理而成。數據庫

初期架構選型

知乎的主力開發語言是Python。由於Python簡單且強大,可以快速上手,開發效率高,並且社區活躍,團隊成員也比較喜歡。瀏覽器

知乎使用的是Tornado框架。由於它支持異步,很適合作實時Comet應用,並且簡單輕量,學習成本低,再就是有FriendFeed的成熟案例,Facebook的社區支持。知乎的產品有個特性,就是但願跟瀏覽器端創建一個長鏈接,便於實時推送Feed和通知,因此Tornado比較合適。服務器

最初整個團隊的精力所有放在產品功能的開發上,而其餘方面,基本上能節約時間、能省的都用最簡單的方法來解決,固然這在後期也帶來了一些問題。網絡

最初的想法是用雲主機,節省成本。知乎的第一臺服務器是512MB內存的Linode主機。可是網站上線後,內測受歡迎程度超出預期,不少用戶反饋網站很慢。跨國網絡延遲比想象的要大,特別是國內的網絡不均衡,全國各地用戶訪問的狀況都不太同樣。數據結構

買了機器、找了機房以後又遇到了新的問題,服務常常宕掉。當時服務商的機器內存老是出問題,動不動就重啓。終於有一次機器宕掉起不來了,這時知乎就作了Web和數據庫的高可用。架構

當時那個階段的架構圖,Web和數據庫都作了主從。當時的圖片服務託管在又拍雲上。除了主從,爲了性能更好還作了讀寫分離。框架

爲解決同步問題,又添加了一個服務器來跑線腳本,避免對線上服務形成響應延遲。另外,爲改進內網的吞吐量延遲,還更換了設備,使整個內網的吞吐量翻了20倍。異步

在2011年上半年時,知乎對Redis已經很依賴。除了最開始的隊列、搜索在用,後來像Cache也開始使用,單機存儲成爲瓶頸,因此引入了分片,同時作了一致性。分佈式

知乎團隊是一個很相信工具的團隊,相信工具能夠提高效率。工具實際上是一個過程,工具並無所謂的最好的工具,只有最適合的工具。並且它是在整個過程當中,隨着整個狀態的變化、環境的變化在不斷髮生變化的。知乎本身開發或使用過的工具包括Profiling(函數級追蹤請求,分析調優)、Werkzeug(方便調試的工具)、Puppet(配置管理)和Shipit(一鍵上線或回滾)等。

日誌系統

知乎最初是邀請制的,2011年下半年,知乎上線了申請註冊,沒有邀請碼的用戶也能夠經過填寫一些資料申請註冊知乎。用戶量又上了一個臺階,這時就有了一些發廣告的帳戶,須要掃除廣告。日誌系統的需求提上日程。

這個日誌系統必須支持分佈式收集、集中存儲、實時、可訂閱和簡單等特性。當時調研了一些開源系統,好比Scribe整體不錯,可是不支持訂閱。Kafka 是Scala開發的,可是團隊在Scala方面積累較少,Flume也是相似,並且比較重。因此開發團隊選擇了本身開發一個日誌系統——Kids(Kids Is Data Stream)。顧名思義,Kids是用來聚集各類數據流的。

Kids參考了Scribe的思路。Kdis在每臺服務器上能夠配置成Agent 或Server。Agent直接接受來自應用的消息,把消息聚集以後,能夠打給下一個Agent 或者直接打給中心Server。訂閱日誌時,能夠從Server上獲取,也能夠從中心節點的一些Agent上獲取。

具體細節以下圖所示:
知乎還基於Kids作了一個Web小工具(Kids Explorer),支持實時看線上日誌,如今已經成爲調試線上問題最主要的工具。Kids 已經開源,放到了Github上。

事件驅動的架構

知乎這個產品有一個特色,最先在添加一個答案後,後續的操做其實只有更新通知、更新動態。可是隨着整個功能的增長,又多出了一些更新索引、更新計數、內容審查等操做,後續操做五花八門。若是按照傳統方式,維護邏輯會愈來愈龐大,維護性也會很是差。這種場景很適合事件驅動方式,因此開發團隊對整個架構作了調整,作了事件驅動的架構。

這時首先須要的是一個消息隊列,它應該能夠獲取到各類各樣的事件,並且對一致性有很高的要求。針對這個需求,知乎開發了一個叫Sink的小工具。它拿到消息後,先作本地的保存、持久化,而後再把消息分發出去。若是那臺機器掛掉了,重啓時能夠完整恢復,確保消息不會丟失。而後它經過Miller開發框架,把消息放到任務隊列。Sink更像是串行消息訂閱服務,但任務須要並行化處理, Beanstalkd就派上了用場,由其對任務進行全週期管理。

舉例而言,若是如今有用戶回答了問題,首先系統會把問題寫到MySQL 裏面,把消息塞到Sink,而後把問題返回給用戶。Sink經過Miller把任務發給Beanstalkd,Worker本身能夠找到任務並處理。

最開始上線時,每秒鐘有10個消息,而後有70個任務產生。如今每秒鐘有100個事件,有1500個任務產生,就是經過如今的事件驅動架構支撐的。

頁面渲染優化

知乎在2013年時天天有上百萬的PV,頁面渲染實際上是計算密集型的,另外由於要獲取數據,因此也有IO密集型的特色。這時開發團隊就對頁面進行了組件化,還升級了數據獲取機制。知乎按照整個頁面組件樹的結構,自上而下分層地獲取數據,當上層的數據已經獲取了,下層的數據就不須要再下去了,有幾層基本上就有幾回數據獲取。

結合這個思路,知乎本身作了一套模板渲染開發框架——ZhihuNode。

經歷了一系列改進以後,頁面的性能大幅度提高。問題頁面從500ms減小到150ms,Feed頁面從1s減小到600ms。

面向服務的架構(SOA)

隨着知乎的功能愈來愈龐雜,整個系統也愈來愈大。知乎是怎麼作的服務化呢?

首先須要一個最基本的RPC框架,RPC框架也經歷了好幾版演進。

初版是Wish,它是一個嚴格定義序列化的模型。傳輸層用到了STP,這是本身寫的很簡單的傳輸協議,跑在TCP上。一開始用的還不錯,由於一開始只寫了一兩個服務。可是隨着服務增多,一些問題開始出現,首先是ProtocolBuffer會 生成一些描述代碼,很冗長,放到整個庫裏顯得很醜陋。另外嚴格的定義使其不便使用。這時有位工程師開發了新的RPC框架——Snow。它使用簡單的JSON作數據序列化。可是鬆散的數據定義面對的問題是,好比說服務要去升級,要改寫數據結構,很難知道有哪幾個服務在使用,也很難通知它們,每每錯誤就發生了。因而又出了第三個RPC 框架,寫RPC框架的工程師,但願結合前面兩個框架的特色,首先保持Snow簡單,其次須要相對嚴格的序列化協議。這一版本引入了Apache Avro。同時加入了特別的機制,在傳輸層和序列化協議這一層都作成了可插拔的方式,既能夠用JSON,也能夠用Avro,傳輸層能夠用STP,也能夠用二進制協議。

再就是搭了一個服務註冊發現,只須要簡單的定義服務的名字就能夠找到服務在哪臺機器上。同時,知乎也有相應的調優的工具,基於Zipkin開發了本身的Tracing系統。

按照調用關係,知乎的服務分紅了3層:聚合層、內容層和基礎層。按屬性又能夠分紅3類:數據服務、邏輯服務和通道服務。數據服務主要是一些要作特殊數據類型的存儲,好比圖片服務。邏輯服務更多的是CPU密集、計算密集的操做,好比答案格式的定義、解析等。通道服務的特色是沒有存儲,更可能是作一個轉發,好比說Sink。

這是引入服務化以後總體的架構。

演講中還介紹了基於AngularJS開發知乎專欄的新實踐,感興趣的讀者能夠觀看視頻。

相關文章
相關標籤/搜索