我們現在所看到的大型網站或者架構,都是從小的網站和簡單的架構一步步發展起來的,當然,也有一些是基于已有的分布式架構來構建的,也是看業務發展的情況而定,在架構的迭代演進的程序中,會遇到很多問題,就像升級打怪一樣,等級越高,遇到的怪獸越強,
之前有個學員問了我,什么是架構,我是這么回答的,比如我們要建一棟房子,那建房子之前,一定要有一個建筑圖紙,這個圖紙描述了建筑的形狀、內部結構、材料、設備等資訊,工程實施的時候會基于這個圖紙進行構建,軟體架構也是如此,軟體架構相當于軟體系統的一個設計圖紙,這個圖紙上描述了各個組件之間的連接方式和詳細的描述了組件之間的通訊機制, 而程式員在實施階段,就是將這些抽象圖紙細化為實際組件,比如具體的介面定義,類的定義等
那么我們接下來基于純技術角度模擬一個簡單的案例來看看架構迭代帶來的問題和解決方案,通過這樣一個迭代讓大家更清晰的理解架構,整個程序,重點關注的是資料量和訪問量的變化帶來架構的變化,不具體關注業務功能
從一個電商網站開始
為了更好的理解,我們用電商網站來舉例,作為一個交易型別的網站,一定會具備
用戶(用戶注冊、用戶管理)、商品(商品展示、商品管理)、交易(下單、支付)這些功能
假如我們只需要支持這幾個基本功能,那么我們最開始的架構應該可能是這樣的

這個地方要注意的是,各個功能模塊之間是通過JVM內部的方法呼叫來進行互動的,而應用和資料庫之間是通過JDBC進行訪問,
單機負載告警,資料庫與應用分離
隨著網站的開放,訪問量不斷增大,那么這個時候服務器的負載勢必會持續升高,必須要才需一些辦法來應付,這里先不考慮更換機器和各種軟體層面的優化,先從架構的結構上來做一些調整,我們可以把資料庫與應用從一臺機器分到兩臺機器

變化:
網站從一臺變成了2臺,這個變化對我們來說影響非常小,單機的情況下,我們應用采用JDBC的方式來和資料庫進行連接,現在資料庫與應用分開了,我們只需要在組態檔中把資料庫的地址從本機改成資料庫服務器的ip地址就行,對于開發、測驗、部署都沒有影響
調整以后我們能夠緩解當前的系統壓力,不過隨著時間的退役,訪問量繼續增大的話,我們的系統還是需要做改造
為什么這么分呢?從計算機本身的角度來考慮的話,一個請求的訪問到處理最終到回傳,性能瓶頸只會是:CPU、檔案IO、網路IO、記憶體、等因素,而一臺計算機中這些緯度是有性能瓶頸的,如果某個資源消耗過多,通常會造成系統的回應速度較慢,所以增加一臺機器,使得資料庫的IO和CPU資源獨占一臺機器從而增加性能,
這個地方插入一點題外話,就是簡單說一下各個資源的消耗原因,
CPU/IO/****記憶體:
-
主要是背景關系的切換,因為每個CPU核心在同一時刻只能執行一個執行緒,而CPU的調度有幾種方式,比如搶占式和輪詢等,以搶占式為例,每個執行緒會分配一定的執行時間,當達到執行時間、執行緒中有IO阻塞或者有高優先級的執行緒要執行時,CPU會切換執行其他執行緒,而在切換的程序中,需要存盤當前執行緒的執行狀態并恢復要執行的執行緒狀態,這個程序就是背景關系切換,比如IO、鎖等待等場景下也會觸發背景關系切換,當背景關系切換過多時會造成內核占用比較多的CPU,
-
檔案IO,比如頻繁的日志寫入,磁盤本身的處理速度較慢、都會造成IO性能問題
-
網路IO,帶寬不夠
-
記憶體,包括記憶體溢位、記憶體泄漏、記憶體不足
實際上不管是應用層的調優也好,還是硬體的升級也好,其實無非就是這幾個因素的調整,
應用服務器復雜告警,如何讓應用服務器走向集群
假如說這個時候應用服務器的壓力變大了,根據對應用的檢測結果,可以針對性的對性能壓力大的地方進行優化,我們這里考慮通過水平擴容來進行優化,把單機變為集群

應用服務器從一臺變為兩臺,這兩個應用服務器之間沒有直接的互動,他們都依賴資料庫對外提供服務,那么這個時候會拋出兩個問題
- 最終用戶對應兩個應用服務器訪問的選擇
對于這個問題,可以采用DNS解決,也可以通過負載均衡設備來解決
- session的問題?
水平和垂直擴容
對于大型的分布式架構而言,我們一直在追求一種簡單、優雅的方式來應對訪問量和資料量的增長,而這種方式通常指的是不需要改動軟體程式,僅僅通過硬體升級或者增加機器就可以解決,而這種就是分布式架構下的伸縮設計
伸縮分為垂直伸縮和水平伸縮兩種
垂直伸縮:表示通過升級或者增加單臺機器的硬體來支撐訪問量以及資料量增長的方式,垂直伸縮的好處在于技術難度比較低,運營和改動成本也相對較低,但是缺點是機器性能是有瓶頸的,同時升級高性能的小型機或者大型機,成本是非常大的,這也是阿里去IOE的一個原因之一
增加CPU核心數:增加CPU后系統的服務能力能夠得到大的增長,比如回應速度、同時可以處理的執行緒數,但是引入CPU后也會帶來一些顯著的問題
-
1.鎖競爭加劇;多個執行緒同時運行訪問某個共享資料,那么就涉及到鎖競爭,鎖競爭激烈時會導致很多執行緒都在等待鎖,所以即時增加CPU也無法讓執行緒得到更快的處理,當然這里是有調優手段的,可以通過調優手段來降低鎖競爭*
-
2.支撐并發請求的執行緒數是固定的,那么即時增加CPU,系統的服務能力也不會得到提升*
-
3.對于單執行緒任務,多核心CPU是沒有太大的作用的*
*增加記憶體:增加記憶體可以直接提成系統的回應速度,當然,也有可能達不到效果,就是如果JVM堆記憶體是固定的,
水平伸縮:通過增加機器來支撐訪問量及資料量增長的方式,成為水平伸縮,水平伸縮理論上來說沒有瓶頸,但是缺點是技術要求比較高,同時給運維帶來了更大的挑戰
垂直伸縮和水平伸縮都有各自的有點,我們在實際使用程序中都會對兩者做結合,一方面要考慮硬體升級的成本,一方面要考慮軟體改造的成本,
引入負載均衡設備
服務路由,基于負載均衡設備來實作

引入負載均衡器以后,會帶來session相關的問題
負載均衡演算法
輪詢(Round Robin)法
將請求按順序輪流分配到后臺服務器上,均衡的對待每一臺服務器,而不關心服務器實際的連接數和當前的系統負載
缺點:當集群中服務器硬體配置不同、性能差別大時,無法區別對待
隨機法
通過系統隨機函式,根據后臺服務器串列的大小值來隨機選取其中一臺進行訪問,隨著呼叫量的增大,其實際效果越來越接近于平均分配流量到后臺的每一臺服務器,也就是輪詢法的效果
優點:簡單使用,不需要額外的配置和演算法,
缺點:亂數的特點是在資料量大到一定量時才能保證均衡,所以如果請求量有限的話,可能會達不到均衡負載的要求,
源地址哈希法
根據服務消費者請求客戶端的IP地址,通過哈希函式計算得到一個哈希值,將這個哈希值和服務器串列的大小進行取模運算,得到的結果便是要訪問的服務器地址的序號,采用源地址哈希法進行負載均衡,相同的IP客戶端,如果服務器串列不變,將映射到同一個后臺服務器進行訪問,
加權輪詢(Weight Round Robin)法
不同的后臺服務器可能機器的配置和當前系統的負載并不相同,因此它們的抗壓能力也不一樣,跟配置高、負載低的機器分配更高的權重,使其能處理更多的請求,而配置低、負載高的機器,則給其分配較低的權重,降低其系統負載,加權輪詢很好的處理了這一問題,并將請求按照順序且根據權重分配給后端
最小連接數法
前面幾種方式都是通過對請求次數的合理分配最大可能提高服務器的利用率,但是實際上,請求次數的均衡并不能代表負載的均衡,所以,引入了最小連接數法,它正是根據后端服務器當前的連接情況,動態的選取其中當前積壓連接數最少的一臺服務器來處理當前請求,盡可能的提高后臺服務器利用率,將負載合理的分流到每一臺服務器,
session問題
我們打開一個網頁,基本上需要瀏覽器和web服務器進行多次互動,我們都知道Http協議本身是無狀態的,這也是http協議設計的初衷,客戶端只需要簡單的向服務器請求下載某些檔案,無論是客戶端還是服務器都沒必要記錄彼此過去的行為,每一次請求之間是獨立的,好比一個顧客和一個自動售歡訓之間的關系一樣.
而實際上,我們很多的場景都需要帶有狀態的特性,因此聰明的我們引入了session+cookie機制來記住每次請求的會話,
在會話開始時,給當前會話分配一個唯一的會話標識(sessionid),然后通過cookie把這個標識告訴瀏覽器,以后在每次請求的時候,瀏覽器都會帶上這個會話標識來告訴web服務器請求屬于哪個會話,在web服務器上,各個會話有獨立的存盤,保存不同會話的資訊,
如果遇到禁用cookie的情況,一般的做法就是把這個會話標識放到URL的引數中,

而我們應用服務器從一臺變成兩臺后,就會遇到session問題
分布式環境下的session共享
Session共享在當前這個互聯網背景下,已經不是一個新鮮的話題了,而且如何解決session共享其實也有很多非常成熟的方案
服務器實作的session復制或session共享,這型別的共享session是和服務器緊密相關的
我們在Web服務器之間增加了會話資料的同步,通過同步就保證了不同Web服務器之間Session資料的一致,一般應用容器都支持Session Replication方式
存在問題:
-
同步Session資料造成了網路帶寬的開銷,只要Session資料有變化,就需要將資料同步到所有其他機器上,機器越多,同步帶來的網路帶寬開銷就越大,
-
每臺Web服務器都要保存所有Session資料,如果整個集群的Session資料很多(很多人同時訪問網站)的話,每臺機器用于保存Session資料的內容占用會很嚴重,
這個方案是靠應用容器來完成Session的復制從而解決Session的問題的,應用本身并不關心這個事情,這個方案不適合集群機器數多的場景,
利用成熟的技術做session復制,比如12306使用的gemfire,比如常見的記憶體資料庫如Redis

Session資料不保存到本機而且存放到一個集中存盤的地方,修改Session也是發生在集中存盤的地方,Web服務器使用Session從集中存盤的地方讀取,這樣保證了不同Web服務器讀取到的Session資料都是一樣的,存盤Session的具體方式可以是資料庫
存在問題:
-
讀寫Session資料引入了網路操作,這相對于本機的資料讀取來說,問題就在于存在時延和不穩定性,不過我們的通訊基本都是發生在內網,問題不大,
-
如果集中存盤Session的機器或者集群有問題,就會影響到我們的應用,
相對于Session Replication,當Web服務器數量比較大、Session數比較多的時候,這個集中存盤方案的優勢是非常明顯的,
將session維護在客戶端
很容易想到就是利用cookie,但是客戶端存在風險,資料不安全,而且可以存放的資料量比較小,所以將session維護在客戶端還要對session中的資訊加密,
我們的Session資料放到Cookie中,然后在Web服務器上從Cookie中生成對應的Session資料,這就好比我們每次都把自己的碗筷帶在身上,這樣去那家飯店就可以隨意選擇了,相對前面的集中存盤方案,不會依賴外部的存盤系統,也就不存在從外部系統獲取、寫入Session資料的網路時延、不穩定性了,
存在問題:
安全性,Session資料本來都是服務端資料,而這個方案是讓這些服務端資料到了外部網路及客戶端,因此存在安全性上的問題,我們可以對寫入的Cookie的Session資料做加密,不過對于安全來說,物理上不能接觸才是安全的,
資料庫壓力變大,讀寫分離吧
隨著業務的繼續增長,資料量和訪問量持續增加,對于大型網站來說,有不少業務是讀多寫少,這個情況也會直接反饋到資料庫上,那么對于這種情況來說,我們可以考慮采用讀寫分離的方式來優化資料庫的壓力

這個結構的變化會帶來兩個問題
- 資料如何同步
我們希望通過讀庫來分擔主庫上讀的壓力,那么首先需要解決的是怎么復制到讀庫的問題,資料庫系統一般都提供了資料復制的功能,我們可以直接使用資料庫系統自身的機制,不同的資料庫系統有不同的支持,比如Mysql支持Master+slave的結構提供資料復制機制
- 應用對資料源如何路由
對于應用來說,增加一個讀庫對結構變化產生了一定的影響,也就是我們的應用需要根據不同的情況來選擇不同的資料庫源
搜索引擎其實是一個讀庫
搜索引擎其實可以理解成一個讀庫,我們的商品存盤在資料庫中,而網站需要提供用戶實時檢索的功能,尤其是在商品搜索這塊,對于這樣的讀請求,如果全部走讀庫,其實性能也會存在一個瓶頸,而使用搜索引擎,不僅僅能大大提高檢索速度,還能減輕讀資料庫的壓力
而搜索引擎最重要的作業,就是需要根據被搜索的資料來構建索引,而隨著被搜索的資料的變化,索引也需要相應變化,

搜索集群的使用方式和讀庫的使用方式是一樣的,只是構建索引的程序基本都是需要我們自己來實作,可以從兩個緯度對搜索引擎構建索引的方式進行規劃,一個是按照全量/增量劃分,一種是按照實時/非實時劃分,
全量方式用于第一次建立索引,可能是新建,也可能是重建,而增量的方式是在全量的基礎上持續更新索引,
實時和非實時提現在索引更新的時間上,實時是最好的,非實時主要考慮到對資料源頭的保護
總的來說,搜索引擎技術解決了站內搜索時某些場景下的讀的問題,提供了更好的查詢效率,
加速資料讀取的利器-快取及分布式存盤
在大型網站中,基本上就是在解決存盤和計算的問題,所以存盤是一個很重要的支撐系統,網站建設初期我們都是從關系型資料庫開始的,而且很多時候為了方便,我們會把一些業務邏輯放在資料庫里面去做,比如觸發器、存盤程序,雖然在前期能夠很方便的解決問題,但是在未來的發展程序中會帶來很多的麻煩,比如資料量大了以后,要做分庫分表操作等. 同時,業務發展到一定的體量以后,對存盤的需求不能完全通過關系型資料庫來滿足
分布式檔案系統
對一些圖片、大文本,使用資料庫就不合適了,所以我們會采用分布式檔案系統來實作檔案存盤,分布式檔案系統有很多產品、比如淘寶的TFS、google的GFS,還有開源的HDFS
NoSQL
NoSQL 我們可以理解成Not Only SQL、或者是No SQL, 兩種意思都是為了表達在大型網站中,關系型資料庫可以解決大部分問題,但是對于不同內容的特征、訪問特征、事務特征等對存盤的要求是不一樣的,NoSQL是定位于是檔案系統和SQL關系型資料庫之間的范疇,
資料快取都是為了更好的服務
大型網站內部都會用到一些資料快取,主要用于分擔資料庫的讀的壓力,快取系統一般是用來保存和查詢鍵值對的,應用系統中一般會把熱點資料放入到快取,而快取的填充也應該是由應用系統完成,如果資料不存在,則從資料庫獨處資料后放入快取,隨著時間的推移,當快取容量不夠需要清除資料時,最近不被訪問的資料就會被清理掉,還有一種方式就是在資料庫的資料發生變化后,主動把資料放入到快取系統中,這樣的好處是資料變化時能夠及時更新快取的資料,不會造成讀取失效

頁面快取
除了資料快取外,我們還可以對頁面做快取,資料快取可以加速應用在回應請求時的資料讀取數度,但是最終展示給用戶的還是頁面,有些動態產生的頁面或者訪問量特別高的頁面,我們會對頁面或者內容做一些快取,
彌補關系型資料庫的不足,引入分布式存盤
我們應用最多的主要還是關系型資料庫,但是在有些場景中,關系型資料庫不是很合適,所以我們會引入分布式存盤系統,比如redis、mongoDB、cassandra、HBase等,
根據不同的場景和資料結構型別,選擇合適的分布式存盤系統可以極大提高性能,分布式系統通過集群提供一個高容量、高并發訪問、資料冗余融債的支持,

讀寫分離后,資料庫又遇到瓶頸
通過讀寫分離以及在某些場景用分布式存盤系統替換關系型資料庫的方式,能夠降低主庫的壓力,解決資料存盤方面的問題,不過隨著業務的發展,我們的主庫也會遇到瓶頸,推演到現在,我們的網站各個模塊:交易、商品、用戶資料都還是存盤在一個資料庫,盡管增加了快取、讀寫分離的方式,但是資料庫的壓力仍然在持續增加,因此我們可以對資料垂直拆分和水平拆分來解決資料庫壓力問題
專庫專用,資料垂直拆分
垂直拆分的意思是把資料庫中不同的業務資料拆分到不同的資料庫中,那么根據我們推演的例子,把用戶、交易、商品的資料分開

不同業務的資料從原來的一個資料庫拆分到了多個資料庫中,那么就需要考慮到如何處理原來單機跨業務的事務
-
使用分布式事務解決
-
去掉事務或者不追求強事務的支持
對資料進行垂直拆分后,解決了把所有業務資料放在一個資料庫中的壓力問題,并且也可以根據不同業務的特點進行更多的優化
垂直拆分后,遇到瓶頸,資料水平拆分
與垂直拆分對應的還有資料水平拆分,資料水平拆分就是把同一個表的資料拆分到兩個資料庫中,產生資料水平拆分的原因是某個業務的資料表的資料量或者更新量達到了單個資料庫的瓶頸,這個時候就可以把表拆到兩個或者多個資料庫中,
資料水平拆分與讀寫分離的區別是,讀寫分離解決的是讀壓力大的問題,對于資料量大或者更新量大的情況并不起作用,
資料水平拆分與資料垂直拆分的區別是,垂直拆分是把不同的表拆分到不同的資料庫,而水平拆分是把同一個表拆分到不同的資料庫中,
我們可以進一步把用戶表拆分到兩個資料庫中,它們擁有結構一模一樣的用戶表,而且每個庫中的用戶表都只涵蓋了一部分的用戶,兩個資料庫的用戶和在一起就相當于沒有拆分之前的用戶表

水平拆分帶來的影響
-
sql路由問題,需要根據一個條件來決定當前請求發到那個資料庫中
-
主鍵的處理,不能采用自增id,需要全域id
由于同一個業務的資料被拆分到不同的資料庫,因此涉及到一些查詢需要跨兩個資料庫獲取,如果資料量太大并且需要分頁,就比較難處理了
資料庫問題解決后,應用面對的挑戰
前面講的讀寫分離、分布式存盤、資料垂直拆分和水平拆分都是解決資料方面的問題,接下來我們要看看應用方面的變化
隨著業務的發展,應用的功能會越來越多,應用也會越來越大,我們需要思考如何不讓應用持續變大,這就需要把應用拆開,從一個應用變為兩個甚至是多個,
第一種方式
根據業務的特性把應用拆分,在我們的例子中,主要業務功能分三個部分、用戶、商品、交易,我們可以把原來的一個應用拆成分別以交易和商品為主的兩個應用,對于交易和商品都會有設計使用用戶的地方,我們讓這兩個系統自己完成涉及用戶的作業,而類似用戶注冊、登錄等基礎的用戶作業,可以暫時交給兩個系統之一來完成

我們還可以按照用戶注冊、用戶登錄、用戶資訊維護等再拆分,變成三個系統,不過這樣拆分后在不同系統中會有一些相似的代碼,比如用戶相關的代碼,如何能夠保障這部分代碼的一致以及如何對其他模塊提供復用也是需要解決的問題,而且,這樣拆分出來的新系統之間沒有直接的相互呼叫
服務化的道路
我們在來看一下服務化的做法,我們把應用分為三層,處于最上端的是web系統,用于完成不同的業務功能,處于中間的是一些服務中心,不同的服務中心提供不同的業務服務;處于最下層的則是業務的資料庫

與之前相比有幾個重要的變化,首先業務功能之間的訪問不僅僅是單機內部的方法呼叫,還引入了遠程的服務呼叫,其次,共享代碼不再是散落在不同的應用中,這些實作被放在各個服務中心,最后,資料庫的連接也發生了一些變化,我們把資料庫的互動作業放到了服務中心,讓前端的web應用更加注重與瀏覽器的互動作業,而不必過多關注業務邏輯的事情,鏈接資料庫的任務交給回應的業務服務中心了,這樣可以降低資料庫的連接數,
而服務中心不僅把一些可以共用的代碼集中管理,而且還使得這些代碼變得更好維護,
服務化的方式會帶來很多好處,首先,從結構上來看,系統架構更加清晰了,比原本的架構更加立體,從穩定性上來看,一些散落在多個應用系統中的代碼變成了服務并且由專門的團隊進行統一維護,一方面可以提高代碼的質量,另一方面由于基礎核心模塊相對穩定,修改和發布的頻次相對于業務系統來說會少很多,這也會提高整個架構的穩定性,最后,更加底層的資源由服務層統一管理,結構更加清晰,對于團隊開發效率來說有比較大的提高
服務化的方式,對于研發也會有很大的影響,以前的開發模式是幾個大團隊負責幾個大應用,隨著服務化的落地,我們的應用數量會飛速增長,系統內部的依賴關系也會變的錯綜復雜,同時團隊也進行了拆分,每個小團隊專注于某個具體的服務或者應用上,迭代效率也會更高
著作權宣告:本博客所有文章除特別宣告外,均采用 CC BY-NC-SA 4.0 許可協議,轉載請注明來自
Mic帶你學架構!
如果本篇文章對您有幫助,還請幫忙點個關注和贊,您的堅持是我不斷創作的動力,歡迎關注「跟著Mic學架構」公眾號公眾號獲取更多技術干貨!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/355441.html
標籤:其他
上一篇:難頂!面試官問我G1垃圾收集器
