一個社交App是如何構建高伸縮性的互動式系統
一個社交App需實現的功能
使用者關注的常規社交功能、活動、地理位置、探索功能、新鮮事、影片照片分享等等,需要提供的功能不勝列舉,所以從技術角度來說,開發者需要解決的問題也是異常複雜的。
當一款社交App釋出之初,使用者訪問量比較小,使用一臺伺服器就能夠支撐全部的訪問壓力和資料儲存需求,但是網際網路應用具有病毒式的傳播特點。一款App很可能會面臨一夜爆紅的現象,訪問量和資料量在短時間內呈現爆發式增長,這時候會面臨的局面是每天上億PV、數百萬新增使用者和活躍使用者、流量飆升至每秒數百兆。這些對於一個只部署了簡單後端架構的應用來講是無法支撐的,會直接導致伺服器響應緩慢甚至超時,以及在高峰期時服務呈現癱瘓狀態,使得後端的服務完全無法使用,使用者體驗急劇下降。本文將會透過一個真實的案例來分享一個社交應用如何構建一個具備高伸縮性的後端系統。
社交App最初部署的後端架構解析
社交App在最初的時候,後端架構相對比較簡單,最初是部署在基礎網路之上。最前面放置一臺繫結了公網IP的nginx伺服器作負載均衡,後面放置3臺應用伺服器來負責處理所有業務上的請求,最後面搭建一臺MySQL Database資料庫。
構建私有網路
隨著產品的不斷迭代、使用者數的持續增長、資料量的積累,App就需要改進自己的後端架構,即開始構建私有網路。使用者可以使用私有網路構建自己的網路拓撲——建立路由器和私有網路,將後續加入的用於執行內部服務的主機放置在私用網路中,可以有效地和雲平臺其他使用者主機,在網路上實現100%二層隔離。主機對外開放的僅僅只有80埠,這樣系統安全性上多了一層保障。
在上面的架構圖中,最前面的是防火牆,後面接負載均衡器,然後接路由器和私有網路,很多網際網路應用都存在讀多寫少的情況,這個比例有時可以達到8:2,所以我們首先透過引入快取分攤資料庫讀壓力。其次,引入負載均衡器,替換最初架構中的nginx proxy,負責均衡器在這裡其主要用於分發請求到後端多臺應用伺服器,,當其中一臺應用伺服器掛掉,負載均衡器可以進行自動隔離。
業務分割槽與擴充套件
App隨著併發訪問量和資料量不斷增大,首先想到橫向擴容Web服務。水平擴容業務伺服器的前提是要保證每臺伺服器都是無狀態的,將session資訊下放到快取或資料庫中儲存,保證請求被負載到任何一臺伺服器可以正常處理。
從上圖中看到,在前一步「構建私有網路」之後,增加了一個新的私有網路來擴充套件網路層,這裡可以利用自有映像功能,將原有的應用伺服器製作成模板,後續就可以基於這個模板快速啟動新的主機。另外可以利用Auto-scaling(自動橫向擴充套件)功能,根據後端伺服器的負載請求,動態調整伺服器的數量。
一個社交應用的後端會提供很多服務請求介面,比如新增好友、重新整理新鮮事、瀏覽頁面等,可以透過日誌分析每一個介面的耗時,將耗時長但非重要業務的請求分到單獨的Web伺服器上進行處理,從而給主Web伺服器留出更多資源去處理關鍵業務的請求。
面向服務的架構
隨著產品功能的不斷迭代,業務程式碼會越來越複雜,出現故障的可能性也在加大,當一個區域性功能出現問題時,都會影響整個服務的可用性。此時可以構建面向服務的架構,將一個完整且龐大的服務拆分為一個個的子服務,服務之間透過介面互動。如下圖所示:
社交App的服務被拆分成了四個子服務——新鮮事(News Feed)、使用者資料(Profile)、廣告(Ads)和探索(Explore),不同的服務之間透過訊息通訊框架(例如ZeroMQ)來進行互動。把一個大服務拆分為幾個小的子服務的好處不言而喻,主要是:
- 故障隔離:子服務出現故障不會影響全域性,比如廣告業務出現問題並不會讓整個App不能使用,依然可以檢視新鮮事等;
- 獨立擴充套件:每一個被拆分出的子服務有著不同的訪問壓力,比如新鮮事的呼叫相比一些二級頁面的使用者資料要高很多,所以前者會被分配更多的Web 伺服器;
- 獨立部署:一個大服務的配置因功能過多會異常複雜,一旦被拆分就可根據不同的特性需求定製配置項,從而提高可管理性;
- 團隊協作開發:開發者都有著自己精通的方向,從而提高開發效率;
- 抽象出資料訪問:在後續進行資料層面(資料庫、快取)擴充套件時,可透過修改子服務的Data Service,實現對下層資料的透明。
資料庫Replication
業務增長也會給資料庫帶來諸多問題,當最初架構中單臺資料庫(資料庫同時提供讀和寫)不足已支撐起App訪問壓力時,首先需要做資料副本Replication。市面上常見的MySQL、MongoDB等資料庫都提供Replication功能,以MySQL為例,從高層來看,Replication可分成三步:
- Master將改變記錄到二進位制日誌(binary log)中(這些記錄叫做二進位制日誌事件,binary log events);
- Slave將Master的binary log events複製到它的中繼日誌(relay log);
- Slave重做中繼日誌中的事件,將改變反映它自己的資料。
具體實現該過程的第一部分就是Master記錄二進位制日誌。在每個事務更新資料完成之前,Master在二進位制日誌記錄這些改變。MySQL將事務序列的寫入二進位制日誌,即使事務中的語句都是交叉執行的。在事件寫入二進位制日誌完成後,Master通知儲存引擎提交事務。
下一步就是Slave將Master的binary log複製到它自己的中繼日誌。首先,Slave開始一個工作執行緒——I/O執行緒。I/O執行緒在Master上開啟一個普通的連線,然後開始binlog dump process。Binlog dump process從Master的二進位制日誌中讀取事件,如果已經跟上Master,它會睡眠並等待Master產生新的事件。I/O執行緒將這些事件寫入中繼日誌。
SQL slave thread處理該過程的最後一步。SQL執行緒從中繼日誌讀取事件,更新Slave的資料,使其與Master中的資料一致。只要該執行緒與I/O執行緒保持一致,中繼日誌通常會位於OS的快取中,所以中繼日誌的開銷很小。
此外,在Master中也有一個工作執行緒:和其它MySQL的連線一樣,Slave在Master中開啟一個連線也會使得Master開始一個執行緒。複製過程有一個很重要的限制——複製在Slave上是序列化的,也就是說Master上的並行更新操作不能在Slave上並行操作。
對於雲端計算使用者來說,只需要知道資料庫的IP和埠即可進行使用。具體實現見下圖:
第一步要做的是擴充Slave,將單機Master變成Master+3臺Slave的架構,而在其中的Slave上搭建一個內網的負載均衡器(Load Balancer),對於最上層的Data Service來說,只要配置一個MySQL Master節點和一個LB節點即可,今後因業務變化進行增減Slave對上層來說完全是透明的。
此做法可以帶來兩個好處,第一是提高可用性,若是一臺Master出現錯誤,則可以提升某一臺的Slave作為Master繼續提供服務,從而保證資料可用性;第二個是分攤讀壓力,對於一個社交App來說,讀寫分離是在資料層最佳化第一步要做的事情,利用上面的架構可以很輕易地做到將讀的請求分擔到MySQL Slave上進行查詢,而寫留給Master。但是讀寫分離時會有資料庫一致性的問題,即在資料寫至Master之後同步到Slave有一個延遲的時間,對於社交應用來說,這是可以接受的,只要保證資料的最終一致性即可。
在上圖的最下面有一個Snapshot,即定期對資料進行冷備份,這不同於單純對MySQL Master進行復制的Slave,因為線上bug或誤操作會刪除Master上的資料,這時會立即同步到slave上造成資料丟失這時冷備份Snapshot就會起到資料保護作用。
執行過程中肯定需要監控,使用者可以利用Linux上的工具進行統計分析top / iotop / df / free / netstat等工具去監控系統裡的各個服務和元件是否正常執行,以及透過日誌的資訊(http access log / application log / database slow log )分析各個服務的效能瓶頸。
資料分割槽與擴容
下一步業務的調整要進行資料庫的分割槽和擴容。第一,構建快取叢集,在開始的架構中引用了Memcached快取,是單機資料庫快取。當資料量增長,,需要把資料分散到多臺快取伺服器上,常用的是HashRing演算法,好處在於不管是新增結點還是刪除結點時,只會使得少部分資料失效。還可以引用NoSQL資料庫,這裡用到了Redis把社交資料裡對於關係要求不強但對查詢效率要求很高的資料從MySQL裡拿到Redis裡存。Redis尤其適合儲存列表類資料,比如好友關係列表、排行榜資料等。
除此以外可以考慮做資料分割槽對於MySQL第一步是垂直拆分,把原來單獨的資料庫按照功能模組分別拆分成:好友新鮮事、使用者資料、廣告資料以及探索資料。對於Redis也同樣,將原來的單臺Redis按照功能模組拆成四個,分別為:排行榜資料、好友、廣告資料、探索資料。
接下來會遇到的瓶頸是單表過大的問題,這時候我們需要做水平拆分——把一個表拆分成多個表,需要選取一個分割槽Key,比如對使用者表做拆分時,通常選取User ID。分割槽key的選擇主要是看所有的查詢語句頻繁使用哪個查詢欄位,就選擇那個欄位作為分割槽key這樣能保證大部分的查詢可以落在單個資料表上,少量沒有帶分割槽Key的查詢語句,可能要遍歷一遍所有切分後的資料表。
構建完整的測試環境
構建完整測試伺服器時需要建立新的路由器和私有網路、獨立的網路環境和頻寬資源、內網GRE隧道打通路由器、VPN撥入網路和SSH金鑰管理。
這個過程你可以建立一個包含所有系統服務的all-in-one的環境,將其製作成自有映像。如果後續你的團隊來新的人,需要獨立的完整開發環境,只需基於自有映象快速建立主機即可;還可以利用User Data定製化功能,在主機啟動執行一段你上傳的指令碼,來初始化環境。你可以將這兩個功能結合起來用,把所有你所需要用的服務全部安裝部署完畢後做成映像,並用User Data指令碼從程式碼庫裡更新程式碼。因為程式碼的變動相對於環境的更新更加頻繁,不可能每次程式碼的更新都要構建一個新的自有映象。透過這種方式構建起一個完整的測試伺服器,讓每個工程師都可以有自己獨立的測試伺服器。
在App釋出上線時需要連到線上環境怎麼辦?這兩個網路本身完全100%隔離,可利用GRE隧道的功能,把兩個路由器打通,實現測試環境網路和線上生產環境網路的完全連通。
多機房部署與混合組網
為了讓後端架構更可靠和業務更穩定,就需要實施多機房部署和混合組網。具體原因有以下三點:
- 異地容災:在複雜的網路環境下,機房可能會出現網路狀況,導致一些比較關鍵性的業務的可用性降低,備份機房後可保證服務不會出現明顯的長時間中斷;
- 負載分攤:單獨一個機房可能不足以支撐全部的請求,這時可以把一部分的請求壓力分擔到另一個機房;
- 加速區域訪問:在國內網路環境下,南方和北方相互之間網路訪問時有較高的延遲。透過做多機房部署實現加速區域使用者的訪問。
如上所示,有三個機房,中間是QingCloud北京1區機房,負責主營業務。左邊是亞太1區機房,主要服務亞太和海外的客戶。這兩個機房都使用了QingCloud私有網路部署,利用路由器,透過GRE隧道或者IPsec加密隧道的方式進行互通。如果對資料傳輸過程的安全性要求較高,可以用IPsec的方式把兩個機房相互打通,這時的訪問只能透過內網IP進行訪問。右邊是辦公室機房,工程師在這個環境下進行開發。
在實現混合組網時,只要機房路由器或者網寬裝置支援標準的GRE隧道協議、IP隧道協議,就可以將傳統物理世界的機房與路由器連通,並最終打通公有云環境。多機房部署通常見的方案有這些:
異地冷備份
把主機房全套業務在異地重新構建一遍,且不需要提供線上服務,只有在主機房出現故障的時候才切換到備用機房,部署相對要簡單一些。但有兩方面缺點,一是成本比較高,需要雙倍的費用且只是用來做冷備份,平時完全用不上;另外,當主機房突然掛掉時,備用機房再起動起來提供服務,資料需要預熱,這是非常緩慢的過程,可能會出現服務響應慢,甚至不能正常提供服務。
異地多活
從易到難有三階段:第一,反向代理,使用者請求到第二個機房,但不做任何處理被轉向第一個機房這樣會對兩地的延時有一定的要求。第二,在第二個機房部署應用伺服器和快取,大部分的資料請求可以從快取中讀取,不用進行跨機房請求,但當快取失效時,依然落到第一個機房的資料庫去查詢。所以,這個方式不太徹底;第三,全套服務的部署,包括HTTP伺服器、業務伺服器、快取和資料庫的 slave。此方式使得進入第二個機房的請求,只需要在機房內就可以完成請求處理,速度更快,但會遇到資料一致性和快取一致性的問題,針對這點也會有一些解決方法。除了資料同步過程中的不一致問題,還需要面對快取。
好的系統架構不是設計出來的,而是進化而來的
構建穩定可靠的業務系統需要注意以下這些:
- 分析使用者行為,理解你的業務,如社交、電商、影片;
不同的業務有不同的行業屬性和特點,對於社交來講,比較典型的特點是資料量龐大、資料查詢維度多,比如查詢6月11日-7月15日在xx咖啡廳我所有好友裡拍過照片的人,查詢條件包括好友維度、照片維度、地點維度、隱私狀態維度等,這時就需要合理的做資料層面的擴充套件。
電商的特點是定期舉辦大促銷活動,屆時會需要大量的計算資源、應用伺服器來扛流量峰值,此時可利用雲端計算平臺的彈性實現快速擴充套件業務,而在自己業務壓力、促銷來臨時呼叫API介面,及AutoScaling擴充套件後端計算資源。影片業務有非常明顯的流量高峰期和低峰期,流量高峰期通常是白天或者大家晚上下班回家那段時間,晚上2點到早上6點是流量非常低的時候,可利用雲端計算彈性優勢,來呼叫API方式調整業務頻寬資源,從而達到節省成本目的。
- 合理規劃系統,預估系統容量,如 10w / 100w / 1000w PV(DAU):不同的系統容量有可能對應不同架構的部署方式,找到最適合自己的那一個;
- 系統是可橫向擴充套件的 scalable;
- 不遺餘力地解決單點問題;
- 為出錯而設計design for failure:App的後端架構在開發支出就要為可能出現的各種問題進行準備,比如異地備份等;
- 設計面向服務的架構,拆分子系統,API互動,非同步處理;
- 構建無處不在的快取:頁面快取、介面快取、物件快取、資料庫快取;
- 避免過度設計,好的系統架構不是設計出來的,而是進化而來的。
相關文章
- 基於 Raft 構建彈性伸縮的儲存系統的一些實踐Raft
- 可伸縮性和重/輕量,誰是實用系統的架構主選?架構
- 如何構建一個系統?
- 分散式系統「伸縮性」大招之——「水平&垂直切分」詳解分散式
- 大型網站的可伸縮性架構如何設計?網站架構
- 彈性伸縮:高可用架構利器(架構+演算法+思維)架構演算法
- 網站架構的伸縮性設計網站架構
- 基於 HBase 構建可伸縮的分散式事務佇列分散式佇列
- Twitter如何使用Redis提高可伸縮性Redis
- 構建高效且可伸縮的結果快取快取
- 15個構建互動式圖表的最佳 jQuery 外掛jQuery
- 如何構建分散式系統的知識體系分散式
- Facebook 是如何構建第一個跨平臺的 React Native APPReact NativeAPP
- Node.js的可伸縮性Node.js
- 高可用可伸縮架構實用經驗談架構
- 如何互動式地建立一個Docker容器Docker
- 用Akka構建一個簡易的分散式檔案系統分散式
- 彈性伸縮服務實戰:我是如何節省80%的機器成本的
- EMQX Operator 如何快速建立彈性伸縮的 MQTT 叢集MQQT
- 大型網站技術架構(六)--網站的伸縮性架構網站架構
- OpenStack的高可用系統構建(1)
- OpenStack的高可用系統構建(2)
- 在一個成熟的分散式系統中 如何下手做高可用?分散式
- 深度探索MMO生態構建——社交系統
- 一個高效能,高併發,高可用的系統是如何演變來的
- Facebook:我們是如何構建第一個跨平臺的 React Native APPReact NativeAPP
- 實戰:構建可伸縮Hadoop叢集的方法步驟Hadoop
- 可伸縮的微服務告警系統設計指南微服務
- 如何寫一個作用域安全的建構函式函式
- 如何構建高擴充套件性網站?套件網站
- 彈性佈局(伸縮佈局)
- Serverless:基於個性化服務畫像的彈性伸縮實踐Server
- JVM效能優化,提高Java的伸縮性JVM優化Java
- [IDS培訓文件]第一章 Informix動態可伸縮體系結構ORM
- .NET 實現的互動式 OA 系統
- 如何使用 Kubernetes 實現應用程式的彈性伸縮
- 構建高併發&高可用&安全的IT系統-高併發部分
- Docker Swarm + Harbor + Portainer 打造高可用,高伸縮,叢集自動化部署,更新。DockerSwarmAI