微博併發這麼牛逼!看他架構如何設計的?

1

架構以及我理解中架構的本質

在開始談我對架構本質的理解之前,先談談自己的個人見解,千萬級規模的網站感覺數量級是非常大的,對這個數量級我們戰略上要重視它 ,戰術上又要藐視它。

先舉個例子感受一下千萬級到底是什麼數量級?現在的優步 (Uber),從媒體公佈的信息看,它每天接單量平均在百萬左右,假如每天有 10 個小時的服務時間,平均 QPS 只有 30 左右。

對於一個後臺服務器,單機的平均 QPS 可以到達 800-1000,單獨看寫的業務量很簡單 。爲什麼我們又不能說輕視它?

第一,我們看它的數據存儲,每天一百萬的話,一年數據量的規模是多少?其次,剛纔說的訂單量,每一個訂單要推送給附近的司機、司機要併發搶單,後面業務場景的訪問量往往是前者的上百倍,輕鬆就超過上億級別了。

今天我想從架構的本質談起之後,希望大家理解在做一些建構設計的時候,它的出發點以及它解決的問題是什麼。

架構,剛開始的解釋是我從知乎上看到的。什麼是架構?有人講, 說架構並不是一 個很懸乎的 東西 , 實際 上就是一個架子 ,放一些 業務 和算法,跟我們的生活中的晾衣架很像。更抽象一點,說架構其實是對我們重複性業務的抽象和我們未來業務拓展的前瞻,強調過去的經驗和你對整個行業的預見。

我們要想做一個架構的話需要哪些能力?我覺得最重要的是架構師一個最重要的能力就是你要有戰略分解能力。這個怎麼來看呢:

第一,你必須要有抽象的能力,抽象的能力最基本就是去重,去重在整個架構中體現在方方面面,從定義一個函數,到定義一個類,到提供的一個服務,以及模板,背後都是要去重提高可複用率。

第二, 分類能力。做軟件需要做對象的解耦,要定義對象的屬性和方法,做分佈式系統的時候要做服務的拆分和模塊化,要定義服務的接口和規範。

第三, 算法(性能),它的價值體現在提升系統的性能,所有性能的提升,最終都會落到 CPU,內存,IO 和網絡這 4 大塊上。

這一頁 PPT 舉了一些例子來更深入的理解常見技術背後的架構理念。

第一個例子,在分佈式系統我們會做 MySQL 分庫分表,我們要從不同的庫和表中讀取數據,這樣的抽象最直觀就是使用模板,因爲絕大多數 SQL 語義是相同的,除了路由到哪個庫哪個表,如果不使用 Proxy 中間件,模板就是性價比最高的方法。

第二看一下加速網絡的 CDN,它是做速度方面的性能提升,剛纔我們也提到從 CPU、內存、IO、網絡四個方面來考慮,CDN 本質上一個是做網絡智能調度優化,另一個是多級緩存優化。

第三個看一下服務化,剛纔已經提到了,各個大網站轉型過程中一定會做服務化,其實它就是做抽象和做服務的拆分。第四個看一下消息隊列,本質上還是做分類,只不過不是兩個邊際清晰的類,而是把兩個邊際不清晰的子系統通過隊列解構並且異步化。

2

新浪微博整體架構是什麼樣的 

接下我們看一下微博整體架構,到一定量級的系統整個架構都會變成三層,客戶端包括 WEB、安卓和 IOS,這裏就不說了。

接着還都會有一個接口層, 有三個主要作用:

第一個作用,要做 安全隔離,因爲前端節點都是直接和用戶交互,需要防範各種惡意攻擊;

第二個還充當着一個 流量控制的作用,大家知道,在 2014 年春節的時候,微信紅包,每分鐘 8 億多次的請求,其實真正到它後臺的請求量,只有十萬左右的數量級(這裏的數據可能不準),剩餘的流量在接口層就被擋住了;

第三,我們看對 PC 端和移動端的需求不一樣的,所以我們可以進行拆分。接口層之後是後臺,可以看到微博後臺有三大塊:

一個是平臺服務,

第二, 搜索,

第三, 大數據。

到了後臺的各種服務其實都是處理的數據。像平臺的業務部門,做的就是數據存儲和讀取,對搜索來說做的是 數據的檢索,對大數據來說是做的數據的挖掘。微博其實和淘寶是很類似

微博其實和淘寶是很類似的。一般來說,第一代架構,基本上能支撐到用戶到 百萬 級別,到第二代架構基本能支撐到 千萬 級別都沒什麼問題,當業務規模到 億級別時,需要第三代的架構。

從 LAMP 的架構到面向服務的架構,有幾個地方是非常難的,首先不可能在第一代基礎上通過簡單的修修補補滿足用戶量快速增長的,同時線上業務又不能停,這是我們常說的在飛機上換引擎的問題。

前兩天我有一個朋友問我,說他在內部推行服務化的時候,把一個模塊服務化做完了,其他部門就是不接。我建議在做服務化的時候,首先更多是偏向業務的梳理,同時要找準一個很好的切入點,既有架構和服務化上的提升,業務方也要有收益,比如提升性能或者降低維護成本同時升級過程要平滑,建議開始從原子化服務切入,比如基礎的用戶服務, 基礎的短消息服務,基礎的推送服務。

第二,就是可 以做無狀 態 服 務,後面會詳細講,還有數據量大了後需要做數據 Sharding,後面會將。第三代 架構 要解決的 問題,就是用戶量和業務趨於穩步增加(相對爆發期的指數級增長),更多考慮技術框架的穩定性, 提升系統整體的性能,降低成本,還有對整個系統監控的完善和升級。

大型網站的系統架構是如何演變的

我們通過通過數據看一下它的挑戰,PV 是在 10 億級別,QPS 在百萬,數據量在千億級別。我們可用性,就是 SLA 要求 4 個 9,接口響應最多不能超過 150 毫秒,線上所有的故障必須得在 5 分鐘內解決完。

如果說 5 分鐘沒處理呢?那會影響你年終的績效考覈。2015 年微博 DAU 已經過億。我們系統有上百個微服務,每週會有兩次的常規上線和不限次數的緊急上線。我們的挑戰都一樣,就是數據量,bigger and bigger,用戶體驗是 faster and faster,業務是 more and more。

互聯網業務更多是產品體驗驅動,技術在產品體驗上最有效的貢獻 ,就是你的性能越來越好 。每次降低加載一個頁面的時間,都可以間接的降低這個頁面上用戶的流失率。

3

微博的技術挑戰和正交分解法解析架構

下面看一下第三代的架構圖以及我們怎麼用正交分解法闡述。

我們可以看到我們從兩個維度,橫軸和縱軸可以看到。一個維度是水平的分層 拆分,第二從垂直的維度會做拆分。水平的維度從接口層、到服務層到數據存儲層。垂直怎麼拆分,會用業務架構、技術架構、監控平臺、服務治理等等來處理。

我相信到第二代的時候很多架構已經有了業務架構和技術架構的拆分。我們看一下, 接口層有 feed、用戶關係、通訊接口;服務層,SOA 裏有基層服務、原子服務和組合服務,在微博我們只有原子服務和組合服務。原子服務不依賴於任何其他服務,組合服務由幾個原子服務和自己的業務邏輯構建而成 ,資源層負責海量數據的存儲(後面例子會詳細講)。

技術框架解決獨立於業務的海量高併發場景下的技術難題,由衆多的技術組件共同構建而成 。在接口層,微博使用 JERSY 框架,幫助你做參數的解析,參數的驗證,序列化和反序列化;資源層,主要是緩存、DB 相關的各類組件,比如 Cache 組件和對象庫組件。監 控平臺和服 務 治理 ,完成系統服務的像素級監控,對分佈式系統做提前診斷、預警以及治理。包含了 SLA 規則的制定、服務監控、服務調用鏈監控、流量監控、錯誤異常監控、線上灰度發佈上線系統、線上擴容縮容調度系統等。

下面我們講一下常見的設計原則。

第一個,首先是系統架構三個利器:

第二個 , 無狀態接口層最重要的就是無狀態。

我們在電商網站購物,在這個過程中很多情況下是有狀態的,比如我瀏覽了哪些商品,爲什麼大家又常說接口層是無狀態的,其實我們把狀態從接口層剝離到了數據層。像用戶在電商網站購物,選了幾件商品,到了哪一步,接口無狀態後,狀態要麼放在緩存中,要麼放在數據庫中, 其實它並不是沒有狀 態 , 只是在這個過程中我們要把一些有狀態的東西抽離出來到了數據層。

第三個, 數據 層 比服 務層 更需要 設計,這是一條非常重要的經驗。對於服務層來說,可以拿 PHP 寫,明天你可以拿 JAVA 來寫,但是如果你的數據結構開始設計不合理,將來數據結構的改變會花費你數倍的代價,老的數據格式向新的數據格式遷移會讓你痛不欲生,既有工作量上的,又有數據遷移跨越的時間週期,有一些甚至需要半年以上。

第四,物理結構與邏輯結構的映射,上一張圖看到兩個維度切成十二個區間,每個區間代表一個技術領域,這個可以看做我們的邏輯結構。另外,不論後臺還是應用層的開發團隊,一般都會分幾個垂直的業務組加上一個基礎技術架構組,這就是從物理組織架構到邏輯的技術架構的完美的映射,精細化團隊分工,有利於提高溝通協作的效率 。

第五, www .sanhao.com 的訪問過程,我們這個架構圖裏沒有涉及到的,舉個例子,比如當你在瀏覽器輸入 www.sanhao 網址的時候,這個請求在接口層之前發生了什麼?首先會查看你本機 DNS 以及 DNS 服務,查找域名對應的 IP 地址,然後發送 HTTP 請求過去。這個請求首先會到前端的 VIP 地址(公網服務 IP 地址),VIP 之後還要經過負載均衡器(Nginx 服務器),之後纔到你的應用接口層。在接口層之前發生了這麼多事,可能有用戶報一個問題的時候,你通過在接口層查日誌根本發現不了問題,原因就是問題可能發生在到達接口層之前了。

第六,我們說分佈式系統,它最終的瓶頸會落在哪裏呢?前端時間有一個網友跟我討論的時候,說他們的系統遇到了一個瓶頸, 查遍了 CPU,內存,網絡,存儲,都沒有問題。我說你再查一遍,因爲最終你不論用上千臺服務器還是上萬臺服務器,最終系統出瓶頸的一定會落在某一臺機(可能是葉子節點也可能是核心的節點),一定落在 CPU、內存、存儲和網絡上,最後查出來問題出在一臺服務器的網卡帶寬上。

 

4

微博多級雙機房緩存架構

接下來我們看一下微博的 Feed 多級緩存。我們做業務的時候,經常很少做業務分析,技術大會上的分享又都偏向技術架構。其實大家更多的日常工作是需要花費更多時間在業務優化上。

這張圖是統計微博的信息流前幾頁的訪問比例,像前三頁佔了 97%,在做緩存設計的時候,我們最多隻存最近的 M 條數據。這裏強調的就是做系統設計 要基於用 戶 的 場 景 , 越細緻越好 。

舉了一個例子,大家都會用電商,電商在雙十一會做全國範圍內的活動,他們做設計的時候也會考慮場景的,一個就是購物車,我曾經跟相關開發討論過,購物車是在雙十一之前用戶的訪問量非常大,就是不停地往裏加商品。在真正到雙十一那天他不會往購物車加東西了,但是他會頻繁的瀏覽購物車。針對這個場景,活動之前重點設計優化購物車的寫場景, 活動開始後優化購物車的讀場景。

你看到的微博是由哪些部分聚合而成的呢?最右邊的是 Feed,就是微博所有關注的人,他們的微博所組成的。微博我們會按照時間順序把所有關注人的順序做一個排序。

隨着業務的發展,除了跟時間序相關的微博還有非時間序的微博,就是會有廣告的要求,增加一些廣告,還有粉絲頭條,就是拿錢買的,熱門微博,都會插在其中。分發控制,就是說和一些推薦相關的,我推薦一些相關的好友的微博,我推薦一些你可能沒有讀過的微博,我推薦一些其他類型的微博。

當然對非時序的微博和分發控制微博,實際會起多個並行的程序來讀取,最後同步做統一的聚合。這裏稍微分享一下,從 SNS 社交領域來看,國內現在做的比較好的三個信息流:

信息流的聚合,體現在很多很多的產品之中,除了 SNS,電商裏也有信息流的聚合的影子。比如搜索一個商品後出來的列表頁,它的信息流基本由幾部分組成:第一,打廣告的;第二個,做一些推薦,熱門的商品,其次,纔是關鍵字相關的搜索結果。信息流 開始的時候很簡單 , 但是到後期會發現 , 你的這 個流如何做控制分發 , 非常複雜, 微博在最近一兩年一直在做這樣的工作。

剛纔我們是從業務上分析,那麼技術上怎麼解決高併發,高性能的問題?微博訪問量很大的時候,底層存儲是用 MySQL 數據庫,當然也會有其他的。

對於查詢請求量大的時候,大家知道一定有緩存,可以複用可重用的計算結果。可以看到,發一條微博,我有很多粉絲,他們都會來看我發的內容,所以 微博是最適合使用緩存的系統,微博的讀寫比例基本在幾十比一。

微博使用了雙層緩存,上面是 L1,每個 L1 上都是一組(包含 4-6 臺機器),左邊的框相當於一個機房,右邊又是一個機房。在這個系統中 L1 緩存所起的作用是什麼?

首先,L1 緩存增加整個系 統 的 QPS, 其次以低成本靈活擴容的方式增加系統 的 帶寬 。想象一個極端場景,只有一篇博文,但是它的訪問量無限增長,其實我們不需要影響 L2 緩存,因爲它的內容存儲的量小,但它就是訪問量大。這種場景下,你就需要使用 L1 來擴容提升 QPS 和帶寬瓶頸。

另外一個場景,就是 L2 級緩存發生作用,比如我有一千萬個用戶,去訪問的是一百萬個用戶的微博 ,這個時候,他不只是說你的吞吐量和訪問帶寬,就是你要緩存的博文的內容也很多了,這個時候你要考慮緩存的容量, 第二 級緩 存更多的是從容量上來 規劃,保證請求以較小的比例 穿透到後端的數據庫中 ,根據你的用戶模型你可以估出來,到底有百分之多少的請求不能穿透到 DB, 評估這個容量之後,才能更好的評估 DB 需要多少庫,需要承擔多大的訪問的壓力。

另外,我們看雙機房的話,左邊一個,右邊一個。兩個機房是互爲主備 , 或者互 爲熱備 。如果兩個用戶在不同地域,他們訪問兩個不同機房的時候,假設用戶從 IDC1 過來,因爲就近原理,他會訪問 L1,沒有的話纔會跑到 Master,當在 IDC1 沒找到的時候纔會跑到 IDC2 來找。

同時有用戶從 IDC2 訪問,也會有請求從 L1 和 Master 返回或者到 IDC1 去查找。IDC1 和 IDC2 ,兩個機房都有全量的用戶數據,同時在線提供服務,但是緩存查詢又遵循最近訪問原理。

還有哪些多級緩存的例子呢?CDN 是典型的多級緩存。CDN 在國內各個地區做了很多節點,比如在杭州市部署一個節點時,在機房裏肯定不止一臺機器,那麼對於一個地區來說,只有幾臺服務器到源站回源,其他節點都到這幾臺服務器回源即可,這麼看 CDN 至少也有兩級。

Local Cache+ 分佈式緩存,這也是常見的一種策略。有一種場景,分佈式緩存並不適用, 比如單點資源的爆發性峯值流量,這個時候使用 Local Cache + 分佈式緩存,Local Cache 在應用服務器上用很小的內存資源擋住少量的極端峯值流量,長尾的流量仍然訪問分佈式緩存,這樣的 Hybrid 緩存架構通過複用衆多的應用服務器節點,降低了系統的整體成本。

我們來看一下 Feed 的存儲架構,微博的博文主要存在 MySQL 中。首先來看內容表,這個比較簡單,每條內容一個索引,每天建一張表,其次看索引表,一共建了兩級索引。首先想象一下用戶場景,大部分用戶刷微博的時候,看的是他關注所有人的微博,然後按時間來排序。

仔細分析發現在這個場景下, 跟一個用戶的自己的相關性很小了。所以在一級索引的時候會先根據關注的用戶,取他們的前條微博 ID,然後聚合排序。我們在做哈希(分庫分表)的時候,同時考慮了按照 UID 哈希和按照時間維度。很業務和時間相關性很高的,今天的熱點新聞,明天就沒熱度了,數據的冷熱非常明顯,這種場景就需要按照時間維度做分表,

首先冷熱數據做了分離(可以對冷熱數據採用不同的存儲方案來降低成本),其次, 很容止控制我數據庫表的爆炸。像微博如果只按照用戶維度區分,那麼這個用戶所有數據都在一張表裏,這張表就是無限增長的,時間長了查詢會越來越慢。

二級索引,是我們裏面一個比較特殊的場景,就是我要快速找到這個人所要發佈的某一時段的微博時,通過二級索引快速定位。

5

分佈式服務追蹤系統

分佈式追蹤服務系統,當系統到千萬級以後的時候,越來越龐雜,所解決的問題更偏向穩定性,性能和監控。剛纔說用戶只要有一個請求過來,你可以依賴你的服務 RPC1、RPC2,你會發現 RPC2 又依賴 RPC3、RPC4。分佈式服務的時候一個痛點,就是說一個請求從用戶過來之後,在後臺不同的機器之間不停的調用並返回。

當你發現一個問題的時候,這些日誌落在不同的機器上,你也不知道問題到底出在哪兒,各個服務之間互相隔離,互相之間沒有建立關聯。所以導致排查問題基本沒有任何手段,就是出了問題沒法兒解決。

我們要解決的問題,我們剛纔說日誌互相隔離,我們就要把它建立聯繫。建立聯繫我們就有一個請求 ID,然後結合 RPC 框架, 服務治理功能。假

設請求從客戶端過來,其中包含一個 ID 101,到服務 A 時仍然帶有 ID 101,然後調用 RPC1 的時候也會標識這是 101 ,所以需要一個唯一的請求 ID 標識遞歸迭代的傳遞到每一個相關節點。

第二個,你做的時候,你不能說每個地方都加,對業務系統來說需要一個框架來完成這個工作, 這個框架要對業務系統是最低侵入原則 , 用 JAVA 的 話 就可以用 AOP,要做到零侵入的原則,就是對所有相關的中間件打點,從接口層組件(HTTP Client、HTTP Server)至到服務層組件(RPC Client、RPC Server),還有數據訪問中間件的,這樣業務系統只需要少量的配置信息就可以實現全鏈路監控 。

爲什麼要用日誌?服務化以後,每個服務可以用不同的開發語言, 考慮多種開發語言的兼容性 , 內部定義標準化的日誌是唯一且有效的辦法。

最後,如何構建基於 GPS 導航的路況監控?我們剛纔講分佈式服務追蹤。

分佈式服務追蹤能解決的問題, 如果單一用戶發現問題後 ,可以通過請求 ID 快速找到發生問題的節點在什麼,但是並沒有解決如何發現問題。

我們看現實中比較容易理解的道路監控,每輛車有 GPS 定位,我想看北京哪兒擁堵的時候,怎麼做?第一個 , 你肯定要知道每個車在什麼位置,它走到哪兒了。

其實可以說每個車上只要有一個標識,加上每一次流動的信息,就可以看到每個車流的位置和方向。其次如何做監控和報警,我們怎麼能瞭解道路的流量狀況和負載,並及時報警。

我們要定義這條街道多寬多高,單位時間可以通行多少輛車,這就是道路的容量。有了道路容量,再有道路的實時流量,我們就可以基於實習路況做預警?

對應於分佈式系統的話如何構建?

第一 , 你要定義每個服務節點它的 SLA A 是多少 ?SLA 可以從系統的 CPU 佔用率、內存佔用率、磁盤佔用率、QPS 請求數等來定義,相當於定義系統的容量。第二個 , 統計線上動態的流量,你要知道服務的平均 QPS、最低 QPS 和最大 QPS,有了流量和容量,就可以對系統做全面的監控和報警。

剛纔講的是理論,實際情況肯定比這個複雜。微博在春節的時候做許多活動,必須保障系統穩定,理論上你只要定義容量和流量就可以。但實際遠遠不行,爲什麼?有技術的因素,有人爲的因素,因爲不同的開發定義的流量和容量指標有主觀性,很難全局量化標準,所以真正流量來了以後,你預先評估的系統瓶頸往往不正確。

實際中我們在春節前主要採取了三個措施:

第一,最簡單的就是有降級的預案,流量超過系統容量後,先把哪些功能砍掉,需要有明確的優先級 。

第二個, 線上全鏈路壓測,就是把現在的流量放大到我們平常流量的五倍甚至十倍(比如下線一半的服務器,縮容而不是擴容),看看系統瓶頸最先發生在哪裏。我們之前有一些例子,推測系統數據庫會先出現瓶頸,但是實測發現是前端的程序先遇到瓶頸。

第三,搭建在線 Docker 集羣 ,所有業務共享備用的 Docker 集羣資源,這樣可以極大的避免每個業務都預留資源,但是實際上流量沒有增長造成的浪費。

6

總結

接下來說的是如何不停的學習和提升,這裏以 Java 語言爲例,

首先,一定要理解 JAVA;

第二步,JAVA 完了以後,一定要理解 JVM;

其次,還要理解操作系統;

再次還是要了解一下 DesignPattern,這將告訴你怎麼把過去的經驗抽象沉澱供將來借鑑;

還要學習 TCP/IP、 分佈式系統、數據結構和算法。

最後就是我想說的就是今天我所說的可能一切都是錯的!大家通過不停的學習、練習和總結, 形成自己的一套架構設計原則和方法,謝謝大家。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/QYpdFrAwpvXH06wD5djFhw