門戶級UGC系統的技術進化路線——新浪新聞評論系統的架構演進和經驗總結(轉)

add by zhj:先收藏了前端

摘要:評論系統是全部門戶網站的核心標準服務組件之一。本文做者曾負責新浪網評論系統多年,這套系統不只服務於門戶新聞業務,還包括調查、投票等產品,經歷了從單機到多機再到集羣,從簡單到複雜再回歸簡單的過程。git

評論系統,或者稱爲跟帖、留言板,是全部門戶網站的核心標準服務組件之一。與論壇、博客等其餘互聯網UGC系統相比,評論系統雖然從產品功能角度衡量相對簡單,但由於須要可以在突發熱點新聞事件時,在沒有任何預警和準備的前提下支撐住短短几分鐘內上百倍甚至更高的訪問量暴漲,而評論系統既沒法像靜態新聞內容業務那樣經過CDN和反向代理等中間緩存手段化解衝擊,也不可能在平時儲備大量冗餘設備應對突發新聞,因此如何在有限的設備資源條件下提高系統的抗壓性和伸縮性,也是對一個貌似簡單的UGC系統的不小考驗。程序員

新聞評論系統的起源

新浪網很早就在新聞中提供了評論功能,最開始是使用Perl語言開發的簡單腳本,目前能找到的最先具有評論功能的新聞是2000年4月7日的,通過屢次系統升級,2014年前的評論地址已經失效了,但數據仍保存在數據庫中。直到今天,評論還是國內全部新聞網站的標配功能。github

評論系統3.0

2003年左右,我接手負責評論系統,系統版本爲3.0。當時的評論系統運行在單機環境,一臺x86版本Solaris系統的Dell 6300服務器提供了所有服務,包括MySQL和Apache,以及全部先後臺CGI程序,使用C++開發。web

圖1  3.0系統流程和架構算法

3.0系統的緩存模塊設計得比較巧妙,以顯示頁面爲單位緩存數據,由於評論頁面依照提交時間降序排列,每新增一條評論,全部帖子都須要向下移動一位,因此緩存格式設計爲每兩頁數據一個文件,先後相鄰的兩個文件有一頁數據重複,最新的緩存文件一般狀況下不滿兩頁數據。數據庫

圖2  頁面緩存算法示意圖跨域

圖2是假設評論總數95條,每頁顯示20條時的頁面緩存結構,此時用戶看到的第一頁數據讀取自「緩存頁4」的95~76,第二頁數據讀取自「緩存頁3」的75~56,以此類推。瀏覽器

這樣發帖動做對應的緩存更新可簡化爲一次文件追加寫操做,效率最高。並且可保證任意評論總量和顯示順序下的翻頁動做,均可在一個緩存文件中讀到所需的所有數據,而不須要跨頁讀取再合併。缺點是更新評論狀態時(如刪除),須要清空自被刪除帖子開始的全部後續緩存文件。緩存模塊採起主動+被動更新模式,發帖爲主動,每次發帖後觸發一次頁面緩存追加寫操做。更新評論狀態爲被動,所涉及緩存頁面文件會被清空,直到下一次用戶讀取頁面緩存時再鏈接數據庫完成查詢,而後更新頁面緩存,以備下次讀取。這個針對發帖優化的頁面緩存算法繼續沿用到了後續版本的評論系統中。緩存

此時的評論系統就已具有了將同一專題事件下全部新聞評論彙總顯示的能力,在很長一段時間內這都是新浪評論系統的獨有功能。

雖然3.0系統基本知足了當時的產品需求,但畢竟是單機系統,熱點新聞時瞬間涌來的大量發帖和讀取操做,常常會壓垮這臺當時已屬高配的4U服務器,頻繁顯示資源耗盡的錯誤頁面。我接手後的首要任務就是儘可能在最短期內最大限度下降系統的宕機頻率,經過觀察分析肯定主要性能瓶頸在數據庫層面。

3.0系統中,每一個新聞頻道的所有評論數據都保存在一張MyISAM表中,部分頻道的數據量已經超過百萬,在當時已屬海量規模,並且只有一個數據庫實例,讀寫競爭很是嚴重。一旦有評論狀態更新,就會致使不少緩存頁面失效,瞬間引起大量數據庫查詢,進一步加重了讀寫競爭。當全部CGI進程都阻塞在數據庫環節沒法退出時,殃及Apache,進而致使系統Load值急劇上升沒法響應任何操做,只有重啓才能恢復。

解決方案是增長了一臺FreeBSD系統的低配服務器用於數據庫分流,當時MySQL的版本是3.23,Replication主從同步還未發佈,採起的辦法是天天給數據表減肥,把超過一週的評論數據搬到2號服務器上,保證主服務器的評論表數據量維持在合理範圍,在這樣的臨時方案下,3.0系統又撐了幾個月。

如今看來,在至關簡陋的系統架構下,新浪評論系統3.0與中國互聯網產業的門戶時代一塊兒經歷了南海撞機、911劫機、非典、孫志剛等新聞事件。

評論系統4.0啓動

2004年左右,運行了近三年的3.0系統已沒法支撐新浪新聞流量的持續上漲,技術部門啓動了4.0計劃,核心需求就是三個字:不宕機。

由於當時我還負責了新浪聊天系統的工做,不得不分身應對新舊系統的開發維護和其餘項目任務,因此在現有評論系統線上服務不能中斷的前提下,制定了數據庫結構不變,歷史數據所有保留,雙系統逐步無縫切換,升級期間新舊系統並存的大方針。

第一階段:文件系統代替數據庫,基於ICE的分佈式系統

既然3.0系統數據庫結構不可變,除了把數據庫升級到MySQL 4.0啓用Repliaction分解讀寫壓力之外,最開始的設計重點是如何把數據庫與用戶行爲隔離開。

解決方案是在MySQL數據庫和頁面緩存模塊之間,新建一個帶索引的數據文件層,每條新聞的全部評論都單獨保存在一個索引文件和一個數據文件中,指望經過把對數據庫單一表文件的讀寫操做,分解爲文件系統上互不干涉可併發執行的讀寫操做,來提升系統併發處理能力。在新的索引數據模塊中,查詢評論總數、追加評論、更新評論狀態都是針對性優化過的高效率操做。從這時起,MySQL數據庫就降到了只提供歸檔備份和內部管理查詢的角色,再也不直接承載任何用戶更新和查詢請求了。

同時引入了數據庫更新隊列來緩解數據庫併發寫操做的壓力,由於當時消息隊列中間件遠不如如今百花齊放,自行實現了一個簡單的文件方式消息隊列模塊,逐步應用到4.0系統各個模塊間異步通訊場合中。

圖3  4.0系統流程

選用了ICE做爲RPC組件,用於全部的模塊間調用和網絡通訊,這大概是剛設計4.0系統時惟一沒作錯的選擇,在整個4.0系統項目生命週期,ICE的穩定性和性能表現從未成爲過問題。

圖4  4.0索引緩存結構

4.0系統開發語言仍爲C++,由於同時選用了MySQL 4.0、ICE、Linux系統和新文件系統等多項應用經驗不足的新技術,也爲後來的系統表現動盪埋下了伏筆(新浪到2005年左右才逐步從FreeBSD和Solaris遷移到了CentOS系統)。

圖5  4.0系統架構

此時的4.0評論系統已從雙機互備擴容到五機集羣,進入小範圍試用階段,雖然扛過了劉翔第一次奪金時創紀錄的發帖高峯,但倒在了2004年亞洲盃中國隊1 : 3敗於日本隊的那個夜晚。

當時系統在進入宕機以前的最高發帖速度大約是每分鐘千帖量級,在十年前還算得上是業界同類系統的峯值,最終確認問題出在文件系統的I/O負載上。

設計索引緩存模塊時的設想過於理想化,雖然把單一數據表的讀寫操做分解到了文件系統的多個文件上,但不可避免地帶來了對機械磁盤的大量隨機讀寫操做,在CentOS默認的Ext3文件系統上,每條新聞對應兩個文件的設計(2004年新浪新聞總量爲千萬左右),雖然已採起了128×256的兩層目錄HASH來預防單目錄下文件過多隱患,但剛上線時還表現良好的系統,稍過幾個月後就把文件系統完全拖垮了。

既然Ext3沒法應對大數量文件的頻繁隨機讀寫,當時咱們還能夠選擇使用B*樹數據結構專爲海量文件優化的ReiserFS文件系統,在與系統部同事配合反覆對比測試,解決了ReiserFS與特定Linux Kernel版本搭配時的kswapd進程大量消耗CPU資源的問題後,終於選定了能夠正常工做的Kernel和ReiserFS對應版本,固然這也埋下了ReiserFS做者殺妻入獄後新裝的CentOS服務器找不到可用的ReiserFS安裝包這個大隱患。

第二階段:全系統異步化,索引分頁算法優化

直到這個階段,新浪評論系統的前端頁面還是傳統的Apache+CGI模式,隨着剩餘頻道的逐步切換,新浪評論系統升級爲靜態HTML頁面使用XMLHTTP組件異步加載XML數據的AJAX模式,當時跨域限制更少的JSON還未流行。升級爲當時剛剛開始流行的AJAX模式並非盲目追新,而是爲了實現一個很是重要的目標:緩存被動更新的異步化。

隨着消息隊列的廣泛應用,4.0系統中全部的數據庫寫操做和緩存主動更新(即後臺程序邏輯觸發的更新)都異步化了,當時已在實踐中證實,系統訪問量大幅波動時,模塊間異步化通訊是解決系統伸縮性和保證系統響應性的惟一途徑。但在CGI頁面模式下,由用戶動做觸發的緩存被動更新,只能阻塞在等待狀態,直到查詢數據和更新緩存完成後才能返回,會致使前端服務器Apache CGI進程的堆積。

使用AJAX模式異步加載數據,可在幾乎不影響用戶體驗的前提下完成等待和循環重試動做,接收緩存更新請求的支持優先級的消息隊列還可合併對同一頁面的重複請求,也隔離了用戶行爲對前端服務器的直接衝擊,極大提升了前端服務器的伸縮性和適應能力,甚至連低硬件配置的客戶端電腦在AJAX模式加載數據時都明顯更順暢了。前端頁面靜態化還可將所有數據組裝和渲染邏輯,包括分頁計算都轉移到了客戶端瀏覽器上,充分借用用戶端資源,惟一的缺點是對SEO不友好。

經過以上各項措施,此時的4.0系統抗衝擊能力已有明顯改善,可是接下來出現了新的問題。在3.0系統時代,上萬條評論的新聞已屬少見,隨着業務的增加,相似2005年超女專題或者體育頻道NBA專題這樣千萬評論數級別的巨無霸留言板開始出現。

爲了提升分頁操做時定位和讀取索引的效率,4.0系統的算法是先經過mmap操做把一個評論的索引文件加載到內存,而後按照評論狀態(經過或者刪除)和評論時間進行快速排序,篩選出經過狀態的帖子並按時間降序排列,這樣讀取任意一頁的索引數據,都是內存中一次常量時間成本的偏移量定位和讀取操做。幾百條或者幾千條評論時,上述方案運做得很好,但在千萬留言數量的索引文件上進行全量排序,佔用大量內存和CPU資源,嚴重影響系統性能。咱們曾嘗試改用BerkeleyDB的Btree模式來存儲評論索引,但性能不升反降。

爲避免大數據量排序操做的成本,只能改成簡單遍歷方式,從頭開始依次讀取,直到獲取所需的數據。雖可經過從索引文件的兩端分別做爲起點,來提高較新和較早頁面的定位效率,但遍歷讀取自己就是一個隨着請求頁數增大愈來愈慢的線性算法,而且隨着4.0系統滑動翻頁功能的上線,本來用戶沒法輕易訪問到的中間頁面數據也開始被頻繁請求,所以最終改成了兩端精確分頁,中間模糊分頁的方式。模糊分頁就是根據評論帖子的經過比例,假設可顯示帖子均勻分佈,一步跳到估算的索引偏移位置。畢竟在數十萬甚至上百萬頁的評論裏,精確計算分頁偏移量沒有太大實際意義。

圖6  異步緩存更新流程

2005年很是受關注的日本申請加入聯合國常任理事國事件,引起了各家網站的民意沸騰,新浪推出了徵集反日入常簽名活動並在短短几天內徵集到2000多萬簽名。由於沒有預計到會有如此多的網民參與,最開始簡單實現的PHP+MySQL系統在很短期內就沒法響應了,而後基於4.0評論系統緊急加班開發了一個簽名請願功能,系統表現穩定。

評論系統4.0第三階段:簡化緩存策略,進一步下降文件系統I/O

到了這個階段,硬件資源進一步擴容,評論系統的服務器數量終於達到了兩位數,4.0系統已實現了當初的「不宕機」設計目標,隨着網站的改版,全部新聞頁面(包括網站首頁)都開始實時加載和顯示最新的評論數量和最新的帖子列表,此時4.0系統承受的Hits量級已接近新浪新聞靜態池的水平。從這時起,新浪評論系統再沒有由於流量壓力宕機或者暫停服務過。

前面提到,新裝的CentOS系統很難找到足夠新版本的ReiserFS安裝包,甚至不得不降級系統版本,一直困擾性能表現的文件系統也接近了優化的極限,這時候Memcached出現了。

圖7  系統架構

2006年左右Memcached取代了4.0系統中索引緩存模塊的實體數據部分(主要是評論正文),索引緩存模塊在文件系統上只存儲索引數據,評論文本都改用Memcached存儲,極大下降了文件系統的I/O壓力。由於系統流量與熱點事件的時間相關性,僅保存最近幾周的評論就足以保證系統性能,極少許過時數據訪問即便穿透到MySQL也問題不大,固然服務器宕機重啓和新裝服務器上線時要很是留意數據的加載預熱。

以後4.0系統進入穩定狀態,小修小補,又堅持服役了若干年,並逐步拓展到股票社區、簽名活動、三方辯論、專家答疑、觀點投票等產品線,直到2010年以後5.0系統的上線。

2008年5月12日,我發現不少網友在地震新聞評論中詢問親友信息,就當即開發了基於評論系統的地震尋親功能並於當晚上線。大約一週後爲了配合Google發起的尋親數據彙總項目,還專門爲Google爬蟲提供了非異步加載模式的數據頁面以方便其抓取。

2004年上線的4.0系統,2010~2011年後被5.0系統取代逐步下線,從上線到下線期間系統處理的用戶提交數據量變化趨勢如圖8所示。

圖8  系統流量變化圖

高訪問量UGC系統設計總結

縱觀整個4.0系統的設計和優化過程,在硬件資源有限的約束下,依靠過渡設計的多層緩衝,完成了流量劇烈波動時保障服務穩定的最基本目標,但也確實影響到了UGC系統最重要的數據更新實時性指標,數據更新的實時性也是以後5.0系統的重點改進方向。

總結下來,通常UGC系統的設計方針就是經過下降系統次要環節的實時一致性,在合理的成本範圍內,儘可能提升系統響應性能,而提升響應性能的手段歸根結底就是三板斧:隊列(Queue)、緩存(Cache)和分區(Sharding)。

 

  • 隊列:能夠緩解併發寫操做的壓力,提升系統伸縮性,同時也是異步化系統的最多見實現手段。
  • 緩存:從文件系統到數據庫再到內存的各級緩存模塊,解決了數據就近讀取的需求。
  • 分區:保證了系統規模擴張和長期數據積累時,頻繁操做的數據集規模在合理範圍。

 

關於數據庫,區分冷熱數據,按照讀寫操做規律合理拆分存儲,通常UGC系統近期數據纔是熱點,歷史數據是冷數據。

 

  • 區分索引和實體數據,索引數據是Key,易變,通常用於篩選和定位,要保證充分的拆分存儲,極端狀況下要把關係數據庫當NoSQL用;實體數據是Value,通常是正文文本,一般不變,通常業務下只按主鍵查詢;二者要分開。
  • 區分核心業務和附加業務數據,每一項附加的新業務數據都單獨存儲,與核心業務數據表分開,既可下降核心業務數據庫的變動成本,還可避免新業務頻繁調整上下線時影響核心業務。

 

目前的互聯網系統大都嚴重依賴MySQL的Replication主從同步來實現系統橫向擴展,雖然MySQL在新版本中陸續加入RBR複製和半同步等機制,但從庫的單線程寫操做限制仍是最大的制約因素,到如今尚未看到很理想的革新性解決方案。

關於緩存,從瀏覽器到文件系統不少環節都有涉及,這裏主要說的是應用系統本身的部分。

 

  • 最好的緩存方案是不用緩存,緩存帶來的問題每每多於它解決的問題。
  • 只有一次更新屢次讀取的數據纔有必要緩存,個性化的冷數據不必緩存。
  • 緩存分爲主動(Server推)和被動(Client拉)兩種更新方式,各自適用於不用場景。主動更新方式通常適用於更新頻率較高的熱數據,可保證緩存未命中時,失控的用戶行爲不會引起系統連鎖反應,致使雪崩。被動更新方式通常適用於更新頻率相對較低的數據,也能夠經過上文提到的異步更新模式,避免連鎖反應和雪崩。
  • 緩存的更新操做盡可能設計爲覆蓋方式,避免偶發數據錯誤的累積效應。

 

一個UGC系統流量剛開始上漲時,初期的表面性能瓶頸通常會表如今Web Server層面,而實際上大可能是數據庫的緣由,但經充分優化後,最終會落在文件系統或網絡通訊的I/O瓶頸上。直接承載用戶訪問衝擊的前端服務器最好儘可能設計爲無狀態模式,下降宕機重啓後的修復工做量。

順帶說起,我在新浪聊天和評論系統的開發過程當中,逐步積累了一個Web應用開發組件庫,在新浪全面轉向PHP以前,曾用於新浪的內容管理(CMS)、用戶註冊和通行證、日誌分析和論壇等使用C++的系統,目前發佈於github.com/pi1ot/webapplib。

評論系統5.0方案

2010年後針對4.0系統的缺陷,啓動了5.0系統工做。由於工做的交接,5.0系統我只負責了方案設計,具體開發是交給其餘同事負責的,線上的具體實現與原始設計方案可能會有區別。5.0系統極大簡化了系統層次,在保證抵抗突發流量波動性能的前提下,數據更新的及時性有了明顯提升。

圖9  4.5系統流程

圖10  5.0系統流程

設計方案上的主要變化有如下幾點。

 

  • 評論帖子ID從數據庫自增整數改成UUID,提交時便可肯定,消除了必須等待主庫寫入後才能肯定評論ID的瓶頸,對各個層面的緩存邏輯優化有極大幫助。
  • 從新設計數據庫結構,經過充分的數據切分,保證了全部高頻業務操做均可在一個有限數據量的數據表中的一次簡單讀取操做完成,索引和文本數據隔離存儲,在數據庫中實現了原4.0系統中索引模塊的功能,取消了4.0系統的索引緩存層。
  • 改用內存NoSQL緩存用戶頻繁讀取的最新10~20頁數據,取消了原4.0系統文件方式的頁面緩存層。
  • 系統運行環境遷移到新浪雲的內部版本:新浪動態平臺,設備資源富裕度有了極大改善。
  • 改成Python語言開發,不用再像4.0系統那樣每次更新時都要等待半個小時的編譯過程,也不用再打包幾百兆的執行文件同步到幾十臺服務器上,而語言層面的性能損失能夠忽略不計。

 

新聞評論產品總結

新聞評論做爲微博以前最能反映輿情民意的UGC平臺,長期承載了國內互聯網用戶對時事新聞的匿名錶達慾望,曾經一度成爲上到政府下到網民的關注焦點。雖然面臨了相對其餘社區系統更爲嚴厲的管控力度,也錯過了實施實名制改造時邁向社區化的最佳時機,但不管如何,在21世紀的前十年,國內門戶網站的新聞評論服務,都是中國互聯網產品和技術發展歷史上絕對不能錯過的一筆。

做者劉立,2000年畢業於哈爾濱工業大學計算機系,2000-2013年工做於新浪網研發中心和門戶技術部門,目前在一家社交電商平臺創業團隊任技術負責人。

相關文章
相關標籤/搜索