多終端接入、開放平臺給互聯網帶來了史無前例的用戶量級和訪問規模,SNS網站產生了海量的UGC(用戶產生內容),並且這些內容依託關 系鏈擴散速度之快、傳播範圍之廣是傳統網站不可思議的,海量數據的計算存儲也一直是近年互聯網領域的熱點。本文將從發展演進的層面探討互聯網的系統架構。前端
天下武功惟快不破程序員
網站初期的架構通常採用「短平快」的架構思路,架構以簡單清晰、容易開發爲第一衡量指標。算法
互聯網架構選型首先包括開發語言的選擇,目前PHP、Java是主力語言。開發語言的選擇通常從團隊人員的知識儲備、社區活躍度、商業應用的成熟度、招聘人才的人力成本等方面考量。數據庫
選擇語言以後,通常會選擇該語言的流行框架輔助研發,例如Java的SSH、Python的Django等。但這些框架並非一般意義上的架構,架構通常可 分爲物理架構、運行架構、邏輯架構、開發架構、數據架構等多個維度,框架每每只是代碼架構的一部分。代碼架構是指代碼的組織形式、規範、設計模式等,框架 實際上是經常使用設計模式的軟件化。例如Struts是MVC模式的實現,Hibernate、iBATIS是ORM模式的實現。後端
在架構視圖中,早期關注的主要是開發視圖和數據視圖,通常數據存儲採用DB,初期數據的關注點主要是安全和備份,MySQL的Master-Slave模式能夠知足該需求。設計模式
雞蛋不要放在一個籃子裏緩存
採用「短平快」三板斧將網站開發出來以後,急需解決的是網站的可用性問題。可用性最基本的要求是不能有單點,對程序節點而言,前端可採用LVS、HAProxy、Nginx等負載均衡/反向代理設備。安全
DB的可用性就複雜了不少,數據庫自然是有狀態的,狀態就是其中的數據,新增一個數據節點通常伴隨着大量的數據複製和遷移。對金融行業而言,昂貴的商用存儲是 解決之道,「IBM+Oracle+EMC」是該類系統的標配。互聯網企業則通常採用較爲廉價的方案,例如開源的DRDB+Heartbeat技術組合可 以在MySQL主庫宕機時實現備機接管,接管時間能夠控制在30秒內。性能優化
程序節點其實也可能存在狀態,例如Web服務中經常使用的Session 就是保存在容器中的狀態,這種狀態保持要求全部相同用戶的請求都在同一臺機器上處理,無狀態的程序節點才能水平擴展。無狀態通常有兩種設計思路,還以 Session爲例,一種思路是把用戶的狀態保存在客戶端Cookie,每次請求都把客戶端的用戶信息帶到服務器端,淘寶的分佈式Session就是該思 路的一種實現;另外一種思路是狀態保留在另一個服務中,例若有些公司將Session放在分佈式緩存中。服務器
性能是生命線
去除單點以後的系統就能夠水平擴展,架構如圖1所示。但隨着網站的推廣運營,系統的規模開始擴大,此時可能會出現服務訪問緩慢,甚至不可用的情況,如何提高系統性能就成了架構師的當務之急。
圖1 去除單點以後進行水平擴展
存儲架構和性能
互聯網系統全部的性能瓶頸中,數據存儲和訪問速度每每是最重要也是最難解決的,選擇合適的存儲是系統的關鍵。存儲的選擇通常須要從多個方面考量,如成本、內容、用途和模型。目前主流的存儲介質包括硬盤和內存兩種。
對機械硬盤來講,1秒能夠完成150次左右的隨機I/O。而結合設計優良的Hash算法,內存查找能夠每秒執行40萬次左右。硬盤的隨機讀寫能力決定了其讀 寫的最差性能,但操做系統在實現文件系統時會把最近讀寫過的數據緩存在內存中。因爲磁盤訪問和內存訪問性能量級的差距,從操做系統的Cache命中率就可 以簡單計算文件存儲的性能,若是內存命中率能夠達到80%,系統的I/O能力相較徹底隨機I/O將有5倍提高。
對於數據層服務器,大內存已成爲標配(通常爲100GB左右),若是DB中存儲200GB的數據,根據8/2原則,Cache命中率應爲87.5%,所以對MySQL而言,通常讀寫能夠達到每秒1千次以上。
對於讀寫頻率都很高、且可容忍數據丟失的場景,能夠採用內存做爲數據存儲的介質。可靠的內存存儲須要每次操做都記錄Biglog,即便數據丟失也能夠恢復,同時內存中的數據通常按期持久化到硬盤。
從功能角度考量,還能夠分爲持久化存儲和Cache。持久化存儲也可稱爲可靠存儲,Cache是爲了提高系統性能,在可靠存儲的基礎上創建的訪問性能更加高效的數據讀取節點,一般是內存存儲,其架構通常如圖2所示。
圖2 持久化存儲和Cache
存儲的數據模型通常分爲結構化存儲和NoSQL存儲。結構化存儲以各類傳統DB爲表明,NoSQL技術的表明系統則有HBase、Memcached、 Redis等。各類NoSQL系統雖然特性各異,但相對傳統DB而言,因爲結構化信息的缺失,每每不能作各類關聯查詢,適用場景更可能是主鍵查詢,並且通常 是寫少讀多的系統。
對於大型互聯網公司,爲了某些場景下的性能優化,也會定製個性化的文件系統,例如爲了適應大文件存儲的場景,Google開發了GFS;爲了更快讀取海量商品的描述圖片,TFS在阿里誕生。
雖然各種存儲快速涌現,但DB做爲結構化數據的傳統存儲設備,依然在架構中處於很是重要的地位。因爲隨機I/O的瓶頸,DB的性能天花板十分明顯。在大型系 統中一般須要分庫操做,分庫通常有兩個維度——水平切分和垂直切分。水平切分通常根據主鍵規則或某種規則將同類數據切分到不一樣的單元表中,原則是數據切分均勻,尤爲是熱點數據分佈均勻。
垂直切分是把大表中的字段拆分到多張表。垂直切分通常按照數據訪問頻率的不一樣。邏輯關係的差異進行切分,例如將大字段、kv字段、計數等高頻訪問字段單獨剝離存儲都是常見的垂直切分方案。
除了切庫以外, MySQL的分表也會有效減小單表大小,使數據變得更簡單,甚至能夠作到不下線變動,單表索引規模的降低也會帶來性能的提高。
分庫分表做爲DB架構中重要的一環,使DB更加穩健,但它給業務代碼帶來了額外的複雜性,最好經過中間件來屏蔽DB的底層分佈,對業務透明。
做爲高性能網站必不可少的組件,Cache在各類主流架構中也起着重要的做用。
從部署模式上看,它可分爲本地Cache和分佈式Cache。本地Cache是指在應用進程中的Cache,一般的數據結構是一個MAP,其優勢是結構簡 單,效率較分佈式Cache更高,缺點是通常應用程序服務器的內存有限,致使本地Cache容量受到侷限,並且數據冗餘度較高,每一個應用服務器都須要一份 數據,更新比較煩瑣,通常採用超時刪除機制。
分佈式Cache的容量較大,方便擴容和更新,其數據分佈可採用一致性Hash算法,減小節點變化帶來的數據遷移。
引入Cache不可避免的問題是服務器的宕機處理。Cache一般是一個集羣,數據分佈在多個節點,若是掛掉一個節點,只會影響部分數據,並且對於可靠性要求較高的系統,每一個節點均可以有備份。
做爲可靠存儲的數據備份,Cache在架構設計上每每承擔大部分讀訪問需求,其命中率尤其重要。Cache不命中有兩種狀況,一是數據在Cache中不存 在,二是在持久化存儲中也不存在。對於後者的頻繁訪問會致使請求直接壓在DB上,在設計時應儘可能避免,能夠經過維護Bitmap對持久化存儲中沒有的數據 進行攔截,直接返回,也能夠簡單地將這些數據對應空對象放進Cache。
Cache的存儲通常是將索引和數據分離,對於索引數據能夠全量緩存,對於體量較大的數據通常採用部分緩存的方式。
Cache的使用場景有必定的侷限,對於較爲靜態的數據纔有意義,這個臨界值通常是5分鐘。因爲當前存儲技術的進步,Cache也能夠用其餘高性能的存儲介質代替,例如SSD的引入使得硬盤的隨機讀寫能力提高數十倍,也會使得Cache的重要性有所降低。
程序架構和性能
對通常的系統而言,程序邏輯的主要做用是調用各類數據訪問接口,該操做一般須要等待,因此除搜索等少數系統外,程序邏輯通常是非CPU密集型。該類系統中「線程」是稀缺資源,線程數和接口耗時構成了系統的QPS能力。
大型互聯網系統的QPS可能爲幾萬甚至峯值達到幾十萬,此時增長機器能夠解決問題,但這些機器的利用率其實很低,由於大部分時間是在等待,此時引入異步變得很是重要,異步在一樣的時間能夠處理更多工做,擁有更好的性能。
圖3 同步調用和異步調用
利用Nio的多路複用方式可方便地實現異步系統,固然也可用協程令代碼更加清晰。業界流行的SEDA技術可將一次請求拆分爲粒度更細的Actor,每一個 Actor使用獨立隊列,前一個的輸出是後一個的輸入。SEDA經過該方式將請求中等待和非等待的環節分離,提高了系統的吞吐量,這種方式在小米等互聯網 公司有較多應用。
除了異步以外,並行對系統也很重要,它能夠有效縮短請求的響應時間。批量接口也能夠有效減小系統調用次數,使得系統線程消耗更少,從而提高系統吞吐量。
對線程而言,還有一個重要的參數是超時時間。響應快的服務,超時時間能夠長一些,對於響應慢的服務,超時時間能夠短一些,儘快失敗是保護本身的有效手段。
網絡架構和性能
大型網站的網絡接入通常是「DNS+負載均衡層+CDN」這種模式。對於大型互聯網公司,每每有多個IDC提供對外服務,中國互聯網的南北不互通使得解決不 同地域不一樣運營商的接入速度問題成了難題,該問題的解決通常須要公司本身開發DNS服務器,結合IP測速平臺,引流用戶請求到訪問速度最快的節點。
大系統小作
業務邏輯複雜多變,如何保證程序邏輯的代碼穩定是架構師須要解決的問題,良好的模塊劃分和擴展性強的接口設計都是解決這個問題的利器。
模塊是和領域模型相關的一個概念,其每每指系統中高內聚的一個數據訪問單元。例如對電商系統而言,最大的兩個領域模型分別是商品信息和交易信息,每一個領域模 型對應一系列數據,商品會有商品的基本信息、類目信息等,交易會包括交易的訂單,這些「領域模型+數據+業務方法」就構成了一個個的模塊,高度內聚的模塊 是數據的訪問的入口。例如交易時也須要去獲取商品信息,但通常不會被容許直接調用商品模塊的數據表,而是經過商品模塊提供的接口進行訪問,這樣作有下面一 些優勢。
對較小規模的應用,模塊可部署在一塊兒,但對大型系統而言,模塊通常是單獨部署,經過RPC交互,這樣能夠減小彼此之間的系統層面影響。例如Amazon就傾向將全部服務器程序暴露爲接口。
分佈式系統增長了系統交互的複雜性,也爲系統引入了更多潛在的失敗環節,但分佈式系統能夠帶來的好處更明顯。
邏輯層的接口設計如何作到穩定呢?首先接口的設計不該該是產品驅動的,而應該由數據驅動,儘可能考慮接口之後的發展,接口的參數儘可能是對象,參數不要採用boolean這種難以擴展的數據類型。
邏輯層的接口設計,通常有粗粒度和細粒度兩種模式。粗粒度接口的優勢是交互少,一次調用基本能夠知足需求,缺點是業務邏輯較多,因此不穩定性增長,這裏的不穩定性不只是系統穩定性,還包括業務邏輯的穩定性。細粒度接口交互較多,但更加有利於重用性。
細粒度接口屢次交互是否會帶來明顯的性能損耗呢?目前服務器1秒能夠執行近萬次TCP鏈接,網絡傳輸對於千兆網卡而言,通常也不會形成瓶頸,尤爲RPC框架般都會保持長鏈接。所以,一般狀況下咱們能夠忽略RPC調用帶來的性能損耗。
邏輯層接口儘可能採用大系統小作的原則,該原則是騰訊架構中重要的價值觀。一個接口不作太多事情,只作關鍵路徑,非關鍵邏輯能夠用消息隊列或事件通知等方式剝離出來,使得關鍵路徑更加穩定。這也體現了服務分級的思想,把最大的精力花在最重要的接口上。
除了按重要性劃分服務以外,也能夠按接入方劃分,避免不一樣終端的Bug影響。還能夠按快慢劃分,例如爲上傳文件等功能提供單獨的服務,避免長時間佔用線程,影響系統穩定性。
模塊化是程序的水平切分,有時也會採用垂直切分,這種架構有如下好處。
開放勢不可擋
系統發展每每會帶來平臺化需求,例如微博的大多數服務除了知足PC訪問,還要知足移動端及內外部平臺,此時系統一般會抽象出一個接入層。
接入層設計
接入層通常分爲:通訊、協議和路由模塊。
經常使用的通訊方式有TCP、UDP和HTTP等,對開放平臺等之外圍接入爲主的系統而言,HTTP因其簡單方即是最合適的通訊方式,而內部系統接入出於性能考量,能夠直接用TCP或者UDP。
目前的主流協議有JSON、XML、Hessian等,對外部調用通常採用XML或者JSON,內部系統能夠採用Hessian或其餘自定義的二進制協議。
路由層是根據用戶的信息將請求轉發到後端服務層。LVS可看做是路由層,根據IP協議將不一樣來源的請求轉發到不一樣IP Server,Nginx等具有反向代理的服務器也能夠看做路由,根據不一樣URL轉發到不一樣後端。咱們常常會自定義路由層,經過用戶調用的不一樣方法轉發到 不一樣Server,或者根據用戶的特徵,將用戶路由到咱們的灰度測試機。
雲平臺概念
具備了平臺化的接入功能,系統能夠方便地接入內部或者外部系統,此時就具備了「雲」的特徵,對各類公有云或私有云來講,系統的隔離和自動擴容都十分重要,虛擬化等技術在該類系統中有了充分應用,例如阿里雲等雲平臺都大量使用了虛擬化。
穩定壓倒一切
代碼穩定性
穩定性是系統架構中一以貫之的內容,能夠從圖4中理解它的含義。
正常狀況下,圖4左邊系統的性能明顯優於右邊,但從架構角度考慮,右圖要好於左圖,由於突起的毛刺使得系統的容量驟降,很容易引起雪崩。性能考量不只是系統的最優性能或者平均性能,最差性能每每也是系統出現問題的緣由。
圖4 兩種穩定性對比
容災
除了特別小型的系統,沒有100%可用的系統。通常須要根據系統的狀況制定合適的目標,該目標最通用的衡量維度是系統可用率。
系統可用率是能夠提供服務的時間與總時間的比率,經常使用的系統可用率如表1所示。
而對於災難,咱們有下面幾個環節能夠介入。
系統容量的冗餘和可水平擴展也是容災的必備要求,無狀態的系統對於系統擴容更友好