一個社交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交互,異步處理; 構建無處不在的緩存:頁面緩存、接口緩存、對象緩存、數據庫緩存; 避免過度設計,好的系統架構不是設計出來的,而是進化而來的。 ?app公司 ? m.fj256.com?