...

.NET技術+25台服務器怎樣支撐世界第54大網站

2021-06-29

英文原文:StackOverflow Update: 560M Pageviews A Month, 25 Servers, And It's All About Performance

StackOverflow 是一個 IT 技術問答網站,用戶可以在網站上提交和回答問題。當下的 StackOverflow 已擁有 400 萬個用戶,4000 萬個回答,月 PV5.6 億,世界排行第 54。然而值得關注的是,支撐他們網站的全部服務器隻有 25 台,并且都(dōu)保持著(zhe)非常低的資源使用率,這(zhè)是一場高有效性、負載均衡、緩存、數據庫、搜索及高效代碼上的較量。近日,High Scalability 創始人 Todd Hoff 根據 Marco Cecconi 的演講視頻“ The architecture of StackOverflow”以及 Nick Craver 的博文“ What it takes to run Stack Overflow”總結了 StackOverflow 的成(chéng)功原因。

意料之中,也是意料之外,Stack Overflow 仍然重度使用著(zhe)微軟的産品。他們認爲既然微軟的基礎設施可以滿足需求,又足夠便宜,那麼(me)沒(méi)有什麼(me)理由去做根本上的改變。而在需要的地方,他們同樣使用了 Linux。究其根本,一切都(dōu)是爲了性能(néng)。

另一個值得關注的地方是,Stack Overflow 仍然使用著(zhe)縱向(xiàng)擴展策略,沒(méi)有使用雲。他們使用了 384GB 的内存和 2TB 的 SSD 來支撐 SQL Servers,如果使用 AWS 的話,花費可想而知。沒(méi)有使用雲的另一個原因是 Stack Overflow 認爲雲會(huì)一定程度上的降低性能(néng),同時(shí)也會(huì)給優化和排查系統問題增加難度。此外,他們的架構也并不需要橫向(xiàng)擴展。峰值期間是橫向(xiàng)擴展的殺手級應用場景,然而他們有著(zhe)豐富的系統調整經(jīng)驗去應對(duì)。該公司仍然堅持著(zhe) Jeff Atwood 的名言——硬件永遠比程序員便宜。

Marco Ceccon 曾提到,在談及系統時(shí),有一件事(shì)情必須首先弄明白——需要解決問題的類型。首先,從簡單方面(miàn)著(zhe)手,StackExchange 究竟是用來做什麼(me)的——首先是一些主題,然後(hòu)圍繞這(zhè)些主題建立社區,最後(hòu)就(jiù)形成(chéng)了這(zhè)個令人敬佩的問答網站。

其次則是規模相關。StackExchange 在飛速增長(cháng),需要處理大量的數據傳輸,那麼(me)這(zhè)些都(dōu)是如何完成(chéng)的,特别是隻使用了 25 台服務器,下面(miàn)一起(qǐ)追根揭底:

狀态

  • StackExchange 擁有 110 個站點,以每個月 3 到 4 個的速度增長(cháng)。

  • 400 萬用戶

  • 800 萬問題

  • 4000 萬答案

  • 世界排名 54 位

  • 每年增長(cháng) 100%

  • 月 PV 5.6 億萬

  • 大多數工作日期間峰值爲 2600 到 3000 請求每秒,作爲一個編程相關網站,一般情況下工作日的請求都(dōu)會(huì)高于周末

  • 25 台服務器

  • SSD 中儲存了 2TB 的 SQL 數據

  • 每個 web server 都(dōu)配置了 2 個 320G 的 SSD,使用 RAID 1

  • 每個 ElasticSearch 主機都(dōu)配備了 300GB 的機械硬盤,同時(shí)也使用了 SSD

  • Stack Overflow 的讀寫比是 40:60

  • DB Server 的平均 CPU 利用率是 10%

  • 11 個 web server,使用 IIS

  • 2 個負載均衡器,1 個活躍,使用 HAProxy

  • 4 個活躍的數據庫節點,使用 MS SQL

  • 3 台實現了 tag engine 的應用程序服務器,所有搜索都(dōu)通過(guò) tag

  • 3 台服務器通過(guò) ElasticSearch 做搜索

  • 2 台使用了 Redis 的服務器支撐分布式緩存和消息

  • 2 台 Networks(Nexus 5596 + Fabric Extenders)

  • 2 Cisco 5525-X ASAs 

  • 2 Cisco 3945 Routers

  • 主要服務 Stack Exchange API 的 2 個隻讀 SQL Servers

  • VM 用于部署、域控制器、監控、運維數據庫等場合

平台

  • ElasticSearch

  • Redis

  • HAProxy

  • MS SQL

  • Opserver

  • TeamCity

  • Jil——Fast .NET JSON Serializer,建立在 Sigil 之上

  • Dapper——微型的 ORM

UI

  • UI 擁有一個信息收件箱,用于新徽章獲得、用戶發(fā)送信息、重大事(shì)件發(fā)生時(shí)的信息收取,使用 WebSockets 實現,并通過(guò) Redis 支撐。

  • 搜索箱通過(guò) ElasticSearch 實現,使用了一個 REST 接口。

  • 因爲用戶提出問題的頻率很高,因此很難顯示最新問題,每秒都(dōu)會(huì)有新的問題産生,從而這(zhè)裡(lǐ)需要開(kāi)發(fā)一個關注用戶行爲模式的算法,隻給用戶顯示感興趣的問題。它使用了基于 Tag 的複雜查詢,這(zhè)也是開(kāi)發(fā)獨立 Tag Engine 的原因。

  • 服務器端模闆用于生成(chéng)頁面(miàn)。

服務器

  • 25 台服務器并沒(méi)有滿載,CPU 使用率并不高,單計算 SO(Stack Overflow)隻需要 5 台服務器。

  • 數據庫服務器資源利用率在 10% 左右,除下執行備份時(shí)。

  • 爲什麼(me)會(huì)這(zhè)麼(me)低?因爲數據庫服務器足足擁有 384GB 内存,同時(shí) web server 的 CPU 利用率也隻有 10%-15%。

  • 縱向(xiàng)擴展還(hái)沒(méi)有遇到瓶頸。通常情況下,如此流量使用橫向(xiàng)擴展大約需要 100 到 300 台服務器。

  • 簡單的系統。基于 .Net,隻用了 9 個項目,其他系統可能(néng)需要 100 個。之所以使用這(zhè)麼(me)少系統是爲了追求極限的編譯速度,這(zhè)點需要從系統開(kāi)始時(shí)就(jiù)進(jìn)行規劃,每台服務器的編譯時(shí)間大約是 10 秒。

  • 11 萬行代碼,對(duì)比流量來說(shuō)非常少。

  • 使用這(zhè)種(zhǒng)極簡的方式主要基于幾個原因。首先,不需要太多測試,因爲 Meta.stackoverflow 本來就(jiù)是一個問題和 bug 讨論社區。其次,Meta.stackoverflow 還(hái)是一個軟件的測試網站,如果用戶發(fā)現問題的話,往往會(huì)提出并給予解決方案。

  • 紐約數據中心使用的是 Windows 2012,已經(jīng)向(xiàng) 2012 R2 升級(Oregon 已經(jīng)完成(chéng)了升級),Linux 系統使用的是 Centos 6.4。

SSD

  • 默認使用的是 Intel 330(Web 層等)

  • Intel 520 用于中間層寫入,比如 Elastic Search

  • 數據層使用 Intel 710 和 S3700

  • 系統同時(shí)使用了 RAID 1 和 RAID 10(任何4+ 以上的磁盤都(dōu)使用 RAID 10)。不畏懼故障發(fā)生,即使生産環境中使用了上千塊 2.5 英寸 SSD,還(hái)沒(méi)碰到過(guò)一塊失敗的情景。每個模型都(dōu)使用了 1 個以上的備件,多個磁盤發(fā)生故障的情景不在考慮之中。

  • ElasticSearch 在 SSD 上表現的異常出色,因爲 SO writes/re-indexes 的操作非常頻繁。

  • SSD 改變了搜索的使用方式。因爲鎖的問題,Luncene.net 并不能(néng)支撐 SO 的并發(fā)負載,因此他們轉向(xiàng)了 ElasticSearch。在全 SSD 環境下,并不需要圍繞 Binary Reader 建立鎖。

高可用性

  • 異地備份——主數據中心位于紐約,備份數據中心在 Oregon。

  • Redis 有兩(liǎng)個從節點,SQL 有 2 個備份,Tag Engine 有 3 個節點,elastic 有 3 個節點,冗餘一切,并在兩(liǎng)個數據中心同時(shí)存在。

  • Nginx 是用于 SSL,終止 SSL 時(shí)轉換使用 HAProxy。

  • 并不是主從所有,一些臨時(shí)的數據隻會(huì)放到緩存中

  • 所有 HTTP 流量發(fā)送隻占總流量的 77%,還(hái)存在 Oregon 數據中心的備份及一些其他的 VPN 流量。這(zhè)些流量主要由 SQL 和 Redis 備份産生。

數據庫

  • MS SQL Server

  • Stack Exchange 爲每個網站都(dōu)設置了數據庫,因此 Stack Overflow 有一個、Server Fault 有一個,以此類推。

  • 在紐約的主數據中心,每個集群通常都(dōu)使用 1 主和 1 隻讀備份的配置,同時(shí)還(hái)會(huì)在 Oregon 數據中心也設置一個備份。如果是運行的是 Oregon 集群,那麼(me)兩(liǎng)個在紐約數據中心的備份都(dōu)會(huì)是隻讀和同步的。

  • 爲其他内容準備的數據庫。這(zhè)裡(lǐ)還(hái)存在一個“網絡範圍”的數據庫,用于儲存登陸憑證和聚合數據(大部分是 stackexchange.com 用戶文件或者 API)。

  • Careers Stack Overflow、stackexchange.com 和 Area 51 等都(dōu)擁有自己獨立的數據庫模式。

  • 模式的變化需要同時(shí)提供給所有站點的數據庫,它們需要向(xiàng)下兼容,舉個例子,如果需要重命名一個列,那麼(me)將(jiāng)非常麻煩,這(zhè)裡(lǐ)需要進(jìn)行多個操作:增加一個新列,添加作用在兩(liǎng)個列上的代碼,給新列寫數據,改變代碼讓新列有效,移除舊列。

  • 并不需要分片,所有事(shì)情通過(guò)索引來解決,而且數據體積也沒(méi)那麼(me)大。如果有 filtered indexes 需求,那麼(me)爲什麼(me)不更高效的進(jìn)行?常見模式隻在 DeletionDate = Null 上做索引,其他則通過(guò)爲枚舉指定類型。每項 votes 都(dōu)設置了 1 個表,比如一張表給 post votes,1 張表給 comment votes。大部分的頁面(miàn)都(dōu)可以實時(shí)渲染,隻爲匿名用戶緩存,因此,不存在緩存更新,隻有重查詢。

  • Scores 是非規範化的,因此需要經(jīng)常查詢。它隻包含 IDs 和 dates,post votes 表格當下大約有 56454478 行,使用索引,大部分的查詢都(dōu)可以在數毫秒内完成(chéng)。

  • Tag Engine 是完全獨立的,這(zhè)就(jiù)意味著(zhe)核心功能(néng)并不依賴任何外部應用程序。它是一個巨大的内存結構數組結構,專爲 SO 用例優化,并爲重負載組合進(jìn)行預計算。Tag Engine 是個簡單的 windows 服務,冗餘的運行在多個主機上。CPU 使用率基本上保持在2-5%,3 個主機專門用于冗餘,不負責任何負載。如果所有主機同時(shí)發(fā)生故障,網絡服務器將(jiāng)把 Tag Engine 加載到内存中持續運行。

  • 關于 Dapper 無編譯器校驗查詢與傳統 ORM 的對(duì)比。使用編譯器有很多好(hǎo)處,但在運行時(shí)仍然會(huì)存在 fundamental disconnect 問題。同時(shí)更重要的是,由于生成(chéng) nasty SQL,通常情況還(hái)需要去尋找原始代碼,而 Query Hint 和 parameterization 控制等能(néng)力的缺乏更讓查詢優化變得複雜。

編碼

  • 流程

  • 大部分程序員都(dōu)是遠程工作,自己選擇編碼地點

  • 編譯非常快

  • 然後(hòu)運行少量的測試

  • 一旦編譯成(chéng)功,代碼即轉移至開(kāi)發(fā)交付準備服務器

  • 通過(guò)功能(néng)開(kāi)關隐藏新功能(néng)

  • 在相同硬件上作爲其他站點測試運行

  • 然後(hòu)轉移至 Meta.stackoverflow 測試,每天有上千個程序員在使用,一個很好(hǎo)的測試環境

  • 如果通過(guò)則上線,在更廣大的社區進(jìn)行測試

  • 大量使用靜态類和方法,爲了更簡單及更好(hǎo)的性能(néng)

  • 編碼過(guò)程非常簡單,因爲複雜的部分被打包到庫裡(lǐ),這(zhè)些庫被開(kāi)源和維護。.Net 項目數量很低,因爲使用了社區共享的部分代碼。

  • 開(kāi)發(fā)者同時(shí)使用 2 到 3 個顯示器,多個屏幕可以顯著提高生産效率。

緩存

  • 緩存一切

  • 5 個等級的緩存

  • 1 級是網絡級緩存,緩存在浏覽器、CDN 以及代理服務器中。

  • 2 級由 .Net 框架 HttpRuntime.Cache 完成(chéng),在每台服務器的内存中。

  • 3 級 Redis,分布式内存鍵值存儲,在多個支撐同一個站點的服務器上共享緩存項。

  • 4 級 SQL Server Cache,整個數據庫,所有數據都(dōu)被放到内存中。

  • 5 級 SSD。通常隻在 SQL Server 預熱後(hòu)才生效。

  • 舉個例子,每個幫助頁面(miàn)都(dōu)進(jìn)行了緩存,訪問一個頁面(miàn)的代碼非常簡單:

  • 使用了靜态的方法和類。從 OOP 角度來看确實很糟,但是非常快并有利于簡潔編碼。

  • 緩存由 Redis 和 Dapper 支撐,一個微型 ORM

  • 爲了解決垃圾收集問題,模闆中 1 個類隻使用 1 個副本,被建立和保存在緩存中。監測一切,包括 GC 操。據統計顯示,間接層增加 GC 壓力達到了某個程度時(shí)會(huì)顯著的降低性能(néng)。

  • CDN Hit 。鑒于查詢字符串基于文件内容進(jìn)行哈希,隻在有新建立時(shí)才會(huì)被再次取出。每天 3000 萬到 5000 萬 Hit,帶寬大約爲 300GB 到 600GB。

  • CDN 不是用來應對(duì) CPU 或I/O負載,而是幫助用戶更快的獲得答案

部署

  • 每天 5 次部署,不去建立過(guò)大的應用。主要因爲

  • 可以直接的監視性能(néng)

  • 盡可能(néng)最小化建立,可以工作才是重點

  • 産品建立後(hòu)再通過(guò)強大的腳本拷貝到各個網頁層,每個服務器的步驟是:

  • 通過(guò) POST 通知 HAProxy 下架某台服務器

  • 延遲 IIS 結束現有請求(大約 5 秒)

  • 停止網站(通過(guò)同一個 PSSession 結束所有下遊)

  • Robocopy 文件

  • 開(kāi)啓網站

  • 通過(guò)另一個 POST 做 HAProxy Re-enable

  • 幾乎所有部署都(dōu)是通過(guò) puppet 或 DSC,升級通常隻是大幅度調整 RAID 陣列并通過(guò) PXE boot 安裝,這(zhè)樣做非常快速。

協作

  • 團隊

  • SRE (System Reliability Engineering):5 人

  • Core Dev(Q&A site)6-7 人

  • Core Dev Mobile:6 人

  • Careers 團隊專門負責 SO Careers 産品開(kāi)發(fā):7 人

  • Devops 和開(kāi)發(fā)者結合的非常緊密

  • 團隊間變化很大

  • 大部分員工遠程工作

  • 辦公室主要用于銷售,Denver 和 London 除外

  • 一切平等,些許偏向(xiàng)紐約工作者,因爲面(miàn)對(duì)面(miàn)有助于工作交流,但是在線工作影響也并不大

  • 對(duì)比可以在同一個辦公室辦公,他們更偏向(xiàng)熱愛産品及有才華的工程師,他們可以很好(hǎo)的衡量利弊

  • 許多人因爲家庭而選擇遠程工作,紐約是不錯,但是生活并不寬松

  • 辦公室設立在曼哈頓,那是個人才的誕生地。數據中心不能(néng)太偏,因爲經(jīng)常會(huì)涉及升級

  • 打造一個強大團隊,偏愛極客。早期的微軟就(jiù)聚集了大量極客,因此他們征服了整個世界

  • Stack Overflow 社區也是個招聘的地點,他們在那尋找熱愛編碼、樂于助人及熱愛交流的人才。

編制預算

  • 預算是項目的基礎。錢隻花在爲新項目建立基礎設施上,如此低利用率的 web server 還(hái)是 3 年前數據中心建立時(shí)購入。

測試

  • 快速叠代和遺棄

  • 許多測試都(dōu)是發(fā)布隊伍完成(chéng)的。開(kāi)發(fā)擁有一個同樣的 SQL 服務器,并且運行在相同的 Web 層,因此性能(néng)測試并不會(huì)糟糕。

  • 非常少的測試。Stack Overflow 并沒(méi)有進(jìn)行太多的單元測試,因爲他們使用了大量的靜态代碼,還(hái)有一個非常活躍的社區。

  • 基礎設施改變。鑒于所有東西都(dōu)有雙份,所以每個舊配置都(dōu)有備份,并使用了一個快速故障恢複機制。比如,keepalived 可以在負載均衡器中快速回退。

  • 對(duì)比定期維護,他們更願意依賴冗餘系統。SQL 備份用一個專門的服務器進(jìn)行測試,隻爲了可以重存儲。計劃做每兩(liǎng)個月一次的全數據中心故障恢複,或者使用完全隻讀的第二數據中心。

  • 每次新功能(néng)發(fā)布都(dōu)做單元測試、集成(chéng)測試盒 UI 測試,這(zhè)就(jiù)意味著(zhe)可以預知輸入的産品功能(néng)測試後(hòu)就(jiù)會(huì)推送到孵化網站,即 meta.stackexchange(原 meta.stackoverflow)。

監視/日志

  • 當下正在考慮使用 http://logstash.net/做日志管理,目前使用了一個專門的服務將(jiāng) syslog UDP 傳輸到 SQL 數據庫中。網頁中爲計時(shí)添加 header,這(zhè)樣就(jiù)可以通過(guò) HAProxy 來捕獲并且融合到 syslog 傳輸中。

  • Opserver 和 Realog 用于顯示測量結果。Realog 是一個日志展示系統,由 Kyle Brandt 和 Matt Jibson 使用 Go 建立。

  • 日志通過(guò) HAProxy 負載均衡器借助 syslog 完成(chéng),而不是 IIS,因爲其功能(néng)比 IIS 更豐富。

關于雲

  • 還(hái)是老生常談,硬件永遠比開(kāi)發(fā)者和有效率的代碼便宜。基于木桶效應,速度肯定受限于某個短闆,現有的雲服務基本上都(dōu)存在容量和性能(néng)限制。

  • 如果從開(kāi)始就(jiù)使用雲來建設 SO 說(shuō)不定也會(huì)達到現在的水準。但毫無疑問的是,如果達到同樣的性能(néng),使用雲的成(chéng)本將(jiāng)遠遠高于自建數據中心。

性能(néng)至上

  • StackOverflow 是個重度的性能(néng)控,主頁加載的時(shí)間永遠控制在 50 毫秒内,當下的響應時(shí)間是 28 毫秒。

  • 程序員熱衷于降低頁面(miàn)加載時(shí)間以及提高用戶體驗。

  • 每個獨立的網絡提交都(dōu)予以計時(shí)和記錄,這(zhè)種(zhǒng)計量可以弄清楚提升性能(néng)需要修改的地方。

  • 如此低資源利用率的主要原因就(jiù)是高效的代碼。web server 的 CPU 平均利用率在5% 到 15% 之間,内存使用爲 15.5 GB,網絡傳輸在 20 Mb/s到 40 Mb/s。SQL 服務器的 CPU 使用率在5% 到 10% 之間,内存使用是 365GB,網絡傳輸爲 100 Mb/s到 200 Mb/s。這(zhè)可以帶來 3 個好(hǎo)處:給升級留下很大的空間;在嚴重錯誤發(fā)生時(shí)可以保持服務可用;在需要時(shí)可以快速回檔。

學(xué)到的知識

1. 爲什麼(me)使用 MS 産品的同時(shí)還(hái)使用 Redis?什麼(me)好(hǎo)用用什麼(me),不要做無必要的系統之争,比如 C# 在 Windows 機器上運行最好(hǎo),我們使用 IIS;Redis 在*nix 機器上可以得到充分發(fā)揮,我們使用*nix。

2. Overkill 即策略。平常的利用率并不能(néng)代表什麼(me),當某些特定的事(shì)情發(fā)生時(shí),比如備份、重建等完全可以將(jiāng)資源使用拉滿。

3. 堅固的 SSD。所有數據庫都(dōu)建立在 SSD 之上,這(zhè)樣可以獲得 0 延時(shí)。

4. 了解你的讀寫負載。

5. 高效的代碼意味著(zhe)更少的主機。隻有新項目上線時(shí)才會(huì)因爲特殊需求增加硬件,通常情況下是添加内存,但在此之外,高效的代碼就(jiù)意味著(zhe) 0 硬件添加。所以經(jīng)常隻讨論兩(liǎng)個問題:爲存儲增加新的 SSD;爲新項目增加硬件。

6. 不要害怕定制化。SO 在 Tag 上使用複雜查詢,因此專門開(kāi)發(fā)了所需的 Tag Engine。

7. 隻做必須做的事(shì)情。之所以不需要測試是因爲有一個活躍的社區支撐,比如,開(kāi)發(fā)者不用擔心出現“Square Wheel”效應,如果開(kāi)發(fā)者可以制作一個更更輕量級的組件,那就(jiù)替代吧。

8. 注重硬件知識,比如 IL。一些代碼使用 IL 而不是C#。聚焦 SQL 查詢計劃。使用 web server 的内存轉儲究竟做了些什麼(me)。探索,比如爲什麼(me)一個 split 會(huì)産生 2GB 的垃圾。

9. 切勿官僚作風。總有一些新的工具是你需要的,比如,一個編輯器,新版本的 Visual Studio,降低提升過(guò)程中的一切阻力。

10. 垃圾回收驅動編程。SO 在減少垃圾回收成(chéng)本上做了很多努力,跳過(guò)類似 TDD 的實踐,避免抽象層,使用靜态方法。雖然極端,但是确實打造出非常高效的代碼。

11. 高效代碼的價值遠遠超出你想象,它可以讓硬件跑的更快,降低資源使用,切記讓代碼更容易被程序員理解。


來源:cnblogs