咱們以 Java Web
爲例,來搭建一個簡單的電商系統,看看這個系統能夠如何一步步演變。前端
該系統具有的功能:算法
網站的初期,咱們常常會在單機上跑咱們全部的程序和軟件。此時咱們使用一個容器,如Tomcat
、Jetty
、Jboss
,而後直接使用JSP/Servlet技術,或者使用一些開源的框架如Maven + Spring + Struts + Hibernate
、Maven + Spring + Spring MVC + Mybatis
。最後再選擇一個數據庫管理系統來存儲數據,如MySQL
、SqlServer
、Oracle
,而後經過JDBC
進行數據庫的鏈接和操做。數據庫
把以上的全部軟件包括數據庫、應用程序都裝載同一臺機器上,應用跑起來了,也算是一個小系統了。此時系統結果以下:編程
隨着網站的上線,訪問量逐步上升,服務器的負載慢慢提升,在服務器尚未超載的時候,咱們應該就要作好準備,提高網站的負載能力。假如咱們代碼層面已難以優化,在不提升單臺機器的性能的狀況下,採用增長機器是一個不錯的方式,不只能夠有效地提升系統的負載能力,並且性價比高。後端
增長的機器用來作什麼呢?此時咱們能夠把數據庫服務器和Web服務器拆分開來,這樣不只提升了單臺機器的負載能力,也提升了容災能力。瀏覽器
應用服務器與數據庫分開後的架構以下圖所示:緩存
隨着訪問量繼續增長,單臺應用服務器已經沒法知足需求了。在假設數據庫服務器沒有壓力的狀況下,咱們能夠把應用服務器從一臺變成了兩臺甚至多臺,把用戶的請求分散到不一樣的服務器中,從而提升負載能力。而多臺應用服務器之間沒有直接的交互,他們都是依賴數據庫各自對外提供服務。著名的作故障切換的軟件有KeepAlived
,KeepAlived
是一個相似於Layer三、四、7交換機制的軟件,他不是某個具體軟件故障切換的專屬品,而是能夠適用於各類軟件的一款產品。KeepAlived
配合上ipvsadm
又能夠作負載均衡,可謂是神器。安全
咱們以增長了一臺應用服務器爲例,增長後的系統結構圖以下:服務器
系統演變到這裏,將會出現下面四個問題:cookie
針對以上問題,經常使用的解決方案以下:
通常如下有5種解決方案:
一、HTTP重定向
HTTP
重定向就是應用層的請求轉發。用戶的請求其實已經到了HTTP
重定向負載均衡服務器,服務器根據算法要求用戶重定向,用戶收到重定向請求後,再次請求真正的集羣
二、DNS域名解析負載均衡
DNS
域名解析負載均衡就是在用戶請求DNS服務器,獲取域名對應的IP地址時,DNS服務器直接給出負載均衡後的服務器IP。
DNS
,不用咱們去維護負載均衡服務器;DNS
,並且DNS
負載均衡的控制權在域名服務商那裏,網站沒法作更多的改善和更強大的管理。三、反向代理服務器
在用戶的請求到達反向代理服務器時(已經到達網站機房),由反向代理服務器根據算法轉發到具體的服務器。經常使用的
Apache
,Nginx
均可以充當反向代理服務器。
四、IP層負載均衡
在請求到達負載均衡器後,負載均衡器經過修改請求的目的
IP
地址,從而實現請求的轉發,作到負載均衡。
五、數據鏈路層負載均衡
在請求到達負載均衡器後,負載均衡器經過修改請求的
MAC
地址,從而作到負載均衡,與IP
負載均衡不同的是,當請求訪問完服務器以後,直接返回客戶。而無需再通過負載均衡器。
一、rr輪詢調度算法
顧名思義,輪詢分發請求。
二、wrr加權調度算法
咱們給每一個服務器設置權值
Weight
,負載均衡調度器根據權值調度服務器,服務器被調用的次數跟權值成正比。
三、sh原地址散列算法
提取用戶
IP
,根據散列函數得出一個key
,再根據靜態映射表,查處對應的value
,即目標服務器IP
。過目標機器超負荷,則返回空。
四、dh目標地址散列算法
原理同上,只是如今提取的是目標地址的
IP
來作哈希。
五、lc最少鏈接算法
優先把請求轉發給鏈接數少的服務器。
六、wlc加權最少鏈接算法
在
lc
的基礎上,爲每臺服務器加上權值。算法爲:(活動鏈接數 * 256 + 非活動鏈接數) ÷ 權重
,計算出來的值小的服務器優先被選擇。
七、sed最短時間望延遲算法
其實sed跟wlc相似,區別是不考慮非活動鏈接數。算法爲:
(活動鏈接數 +1 ) * 256 ÷ 權重
,一樣計算出來的值小的服務器優先被選擇。
八、nq永不排隊算法
改進的
sed
算法。咱們想一下什麼狀況下才能「永不排隊」,那就是服務器的鏈接數爲0的時候,那麼假若有服務器鏈接數爲0,均衡器直接把請求轉發給它,無需通過sed的計算。
九、LBLC基於局部性最少鏈接算法
負載均衡器根據請求的目的
IP
地址,找出該IP
地址最近被使用的服務器,把請求轉發之。若該服務器超載,最採用最少鏈接數算法。
十、LBLCR帶複製的基於局部性最少鏈接算法
負載均衡器根據請求的目的IP地址,找出該IP地址最近使用的「服務器組」,注意,並非具體某個服務器,而後採用最少鏈接數從該組中挑出具體的某臺服務器出來,把請求轉發之。若該服務器超載,那麼根據最少鏈接數算法,在集羣的非本服務器組的服務器中,找出一臺服務器出來,加入本服務器組,而後把請求轉發。
一、NAT
負載均衡器接收用戶的請求,轉發給具體服務器,服務器處理完請求返回給均衡器,均衡器再從新返回給用戶。
二、DR
負載均衡器接收用戶的請求,轉發給具體服務器,服務器出來玩請求後直接返回給用戶。須要系統支持
IP Tunneling
協議,難以跨平臺。
三、TUN
同上,但無需
IP Tunneling
協議,跨平臺性好,大部分系統均可以支持。
一、Session Sticky
Session sticky
就是把同一個用戶在某一個會話中的請求,都分配到固定的某一臺服務器中,這樣咱們就不須要解決跨服務器的session
問題了,常見的算法有ip_hash
算法,即上面提到的兩種散列算法。
二、Session Replication
Session replication
就是在集羣中複製session
,使得每一個服務器都保存有所有用戶的session
數據。
Session
佔用內存大且浪費。三、Session數據集中存儲
Session
數據集中存儲就是利用數據庫來存儲session
數據,實現了session
和應用服務器的解耦。
Session replication
的方案,集羣間對於寬帶和內存的壓力大幅減小;Session
的數據庫。四、Cookie Base
Cookie base
就是把Session
存在Cookie
中,由瀏覽器來告訴應用服務器個人session
是什麼,一樣實現了session
和應用服務器的解耦。
值得一提的是:
Nginx
目前支持的負載均衡算法有wrr
、sh
(支持一致性哈希)、fair
(lc)。但Nginx
做爲均衡器的話,還能夠一同做爲靜態資源服務器。Keepalived + ipvsadm
比較強大,目前支持的算法有:rr
、wrr
、lc
、wlc
、lblc
、sh
、dh
Keepalived
支持集羣模式有:NAT
、DR
、TUN
Nginx
自己並無提供session
同步的解決方案,而Apache
則提供了session
共享的支持。解決了以上的問題以後,系統的結構以下:
上面咱們老是假設數據庫負載正常,但隨着訪問量的的提升,數據庫的負載也在慢慢增大。那麼可能有人立刻就想到跟應用服務器同樣,把數據庫一份爲二再負載均衡便可。
但對於數據庫來講,並無那麼簡單。假如咱們簡單的把數據庫一分爲二,而後對於數據庫的請求,分別負載到A機器和B機器,那麼顯而易見會形成兩臺數據庫數據不統一的問題。那麼對於這種狀況,咱們能夠先考慮使用讀寫分離和主從複製的方式。
讀寫分離後的系統結構以下:
這個結構變化後也會帶來兩個問題:
解決方案:
MySQL
自帶的Master + Slave
的方式實現主從複製。MyCat
。MyCat
是從Cobar
發展而來的,而Cobar
是阿里開源的數據庫中間件,後來中止開發。MyCat
是國內比較好的MySql
開源數據庫分庫分表中間件。數據庫作讀庫的話,經常對模糊查找力不從心,即便作了讀寫分離,這個問題還未能解決。以咱們所舉的交易網站爲例,發佈的商品存儲在數據庫中,用戶最常使用的功能就是查找商品,尤爲是根據商品的標題來查找對應的商品。對於這種需求,通常咱們都是經過like
功能來實現的,可是這種方式的代價很是大,並且結果很是不許確。此時咱們可使用搜索引擎的倒排索引來完成。
搜索引擎具備的優勢:它可以大大提升查詢速度和搜索準確性。
搜索引擎並不能替代數據庫,它解決了某些場景下的精準、快速、高效的「讀」操做,是否引入搜索引擎,須要綜合考慮整個系統的需求。
引入搜索引擎後的系統結構以下:
經常使用的緩存機制包括頁面級緩存、應用數據緩存和數據庫緩存。
隨着訪問量的增長,逐漸出現了許多用戶訪問同一部分熱門內容的狀況,對於這些比較熱門的內容,不必每次都從數據庫讀取。咱們可使用緩存技術,例如可使用Google的開源緩存技術Guava
或者使用Memecahed
做爲應用層的緩存,也可使用Redis
做爲數據庫層的緩存。
另外,在某些場景下,關係型數據庫並非很適合,例如我想作一個「每日輸入密碼錯誤次數限制」的功能,思路大概是在用戶登陸時,若是登陸錯誤,則記錄下該用戶的
IP
和錯誤次數,那麼這個數據要放在哪裏呢?假如放在內存中,那麼顯然會佔用太大的內容;假如放在關係型數據庫中,那麼既要創建數據庫表,還要簡歷對應的Java bean
,還要寫SQL
等等。而分析一下咱們要存儲的數據,無非就是相似{ip:errorNumber}
這樣的key:value
數據。對於這種數據,咱們能夠用NOSQL
數據庫來代替傳統的關係型數據庫。
除了數據緩存,還有頁面緩存。好比使用HTML5
的localstroage
或者Cookie
。除了頁面緩存帶來的性能提高外,對於併發訪問且頁面置換頻率小的頁面,應儘可能使用頁面靜態化技術。
值得一提的是:
緩存集羣的調度算法不一樣與上面提到的應用服務器和數據庫。最好採用一致性哈希算,這樣才能提升命中率。
加入緩存後的系統結構以下:
咱們的網站演進到如今,交易、商品、用戶的數據都還在同一個數據庫中。儘管採起了增長緩存和讀寫分離的方式,但隨着數據庫的壓力繼續增長,數據庫數據量的瓶頸愈來愈突出,此時,咱們能夠有數據垂直拆分和水平拆分兩種選擇。
垂直拆分的意思是把數據庫中不一樣的業務數據拆分到不一樣的數據庫中,結合如今的例子,就是把交易、商品、用戶的數據分開。
優勢:
缺點:
問題:
Join
。解決問題方案:
MyCat
,MyCat
提供了豐富的跨庫Join
方案,詳情可參考MyCat
官方文檔。數據垂直拆分後的結構以下:
數據水平拆分就是把同一個表中的數據拆分到兩個甚至多個數據庫中。產生數據水平拆分的緣由是某個業務的數據量或者更新量到達了單個數據庫的瓶頸,這時就能夠把這個表拆分到兩個或更多個數據庫中。
優勢:
問題:
SQL路由
的問題,由於如今用戶信息分在了兩個數據庫中,須要在進行數據操做時瞭解須要操做的數據在哪裏。解決問題方案:
MyCat
。MyCat
能夠經過SQL
解析模塊對咱們的SQL
進行解析,再根據咱們的配置,把請求轉發到具體的某個數據庫。 咱們能夠經過UUID
保證惟一或自定義ID方案來解決。MyCat
也提供了豐富的分頁查詢方案,好比先從每一個數據庫作分頁查詢,再合併數據作一次分頁查詢等等。數據水平拆分後的結構以下:
隨着業務的發展,業務愈來愈多,應用愈來愈大。咱們須要考慮如何避免讓應用愈來愈臃腫。這就須要把應用拆開,從一個應用變爲倆個甚至更多。仍是以咱們上面的例子,咱們能夠把用戶、商品、交易拆分開。變成「用戶、商品」和「用戶,交易」兩個子系統。
拆分後的結構:
問題:
這樣拆分後,可能會有一些相同的代碼,如用戶相關的代碼,商品和交易都須要用戶信息,因此在兩個系統中都保留差很少的操做用戶信息的代碼。如何保證這些代碼能夠複用是一個須要解決的問題。
解決問題:
經過走服務化SOA的路線來解決頻繁公共的服務。
爲了解決上面拆分應用後所出現的問題,咱們把公共的服務拆分出來,造成一種服務化的模式,簡稱SOA
。
採用服務化以後的系統結構:
優勢:
問題:
如何進行遠程的服務調用?
解決方法:
能夠經過下面的引入消息中間件來解決。
隨着網站的繼續發展,的系統中可能出現不一樣語言開發的子模塊和部署在不一樣平臺的子系統。此時咱們須要一個平臺來傳遞可靠的,與平臺和語言無關的數據,而且可以把負載均衡透明化,能在調用過程當中收集並分析調用數據,推測出網站的訪問增加率等等一系列需求,對於網站應該如何成長作出預測。開源消息中間件有阿里的Dubbo
,能夠搭配Google開源的分佈式程序協調服務Zookeeper
實現服務器的註冊與發現。
引入消息中間件後的結構:
以上的演變過程只是一個例子,並不適合全部的網站,實際中網站演進過程與自身業務和不一樣遇到的問題有密切的關係,沒有固定的模式。只有認真的分析和不斷地探究,才能發現適合本身網站的架構。
歡迎關注技術公衆號: 零壹技術棧
本賬號將持續分享後端技術乾貨,包括虛擬機基礎,多線程編程,高性能框架,異步、緩存和消息中間件,分佈式和微服務,架構學習和進階等學習資料和文章。