此係列文章爲極客時間上從0開始學架構學習後感悟總結,雖然隔了一段時間了,那麼就再看一遍而且進行感悟昇華,排版格式上有問題,後期再複習時也會進行更新web
一. 高性能數據庫集羣:讀寫分離算法
讀寫分離的基本原理是將數據庫讀寫操做分散到不一樣的節點上。數據庫
數據庫服務器搭建主從集羣,一主一從、一主多從均可以編程
數據庫主機負責讀寫操做,從機只負責讀操做後端
數據庫主機經過複製將數據同步到從機,每臺數據庫服務器都存儲了全部的業務數據瀏覽器
業務服務器將寫操做發給數據庫主機,將讀操做發給數據庫從機緩存
從代碼層面與運維層面實現讀寫分離很簡單並不複雜,複雜來源於讀寫分離實現後引出的兩個問題。複製延遲、分配機制。安全
複製延遲體現於,從主機寫入數據後因爲複製須要時間,此時馬上查詢從機沒法獲取剛纔新寫加的數據,對應的解決方式有3種:服務器
分配機制通常則爲兩種選型,程序代碼封裝與中間件封裝。網絡
二. 高性能數據庫集羣:分庫分表
業務分庫指的是按照業務模塊將數據分散到不一樣的數據庫服務器。例如,一個簡單的電商網站,包括用戶、商品、訂單三個業務模塊,咱們能夠將用戶數據、商品數據、訂單數據分開放到三臺不一樣的數據庫服務器上,而不是將全部數據都放在一臺數據庫服務器上。相應的也會帶來一些問題
既然有問題,那麼咱們對應的解決方式呢,咱們回想架構設計三原則:合適、簡單、演化,對應的問題一會兒就豁然開朗了
將不一樣業務數據分散存儲到不一樣的數據庫服務器,可以支撐百萬甚至千萬用戶規模的業務,但若是業務繼續發展,同一業務的單表數據也會達到單臺數據庫服務器的處理瓶頸,此時就須要對單表數據進行拆分。
垂直分表適合將表中某些不經常使用且佔了大量空間的列拆分出去。垂直分表引入的複雜性主要體如今表操做的數量要增長。所以要區分好經常使用與不經常使用字段
水平分表適合錶行數特別大的表,有的公司要求單錶行數超過 5000 萬就必須進行分表,這個數字能夠做爲參考,但並非絕對標準,關鍵仍是要看錶的訪問性能。對於一些比較複雜的表,可能超過 1000 萬就要分表了;而對於一些簡單的表,即便存儲數據超過 1 億行,也能夠不分表。但無論怎樣,當看到表的數據量達到千萬級別時,做爲架構師就要警覺起來,由於這極可能是架構的性能瓶頸或者隱患。引出的問題有:
a) 範圍路由:範圍路由的優勢是能夠隨着數據的增長平滑地擴充新的表,一個比較隱含的缺點是分佈不均勻
b) Hash 路由:Hash 路由的優缺點和範圍路由基本相反,Hash 路由的優勢是表分佈比較均勻,缺點是擴充新的表很麻煩,全部數據都要重分佈
c) 配置路由:配置路由設計簡單,使用起來很是靈活,尤爲是在擴充表的時候,只須要遷移指定的數據,而後修改路由表就能夠了,缺點就是必須多查詢一次,會影響總體性能;並且路由表自己若是太大(例如,幾億條數據),性能一樣可能成爲瓶頸,若是咱們再次將路由表分庫分表,則又面臨一個死循環式的路由算法選擇問題。
2.Join操做。水平分表後,數據分散在多個表中,若是須要與其餘表進行 join 查詢,須要在業務代碼或者數據庫中間件中進行屢次 join 查詢,而後將結果合併。
3.Count操做。水平分表後,雖然物理上數據分散到多個表中,但某些業務邏輯上仍是會將這些表看成一個表來處理
a) count相加:具體作法是在業務代碼或者數據庫中間件中對每一個表進行 count() 操做,而後將結果相加。這種方式實現簡單,缺點就是性能比較低。
b) 記錄數表:具體作法是新建一張表,假如表名爲「記錄數表」,包含 table_name、row_count 兩個字段,每次插入或者刪除子表數據成功後,都更新「記錄數表」。
4.order by操做。水平分表後,數據分散到多個子表中,排序操做沒法在數據庫中完成,只能由業務代碼或者數據庫中間件分別查詢每一個子表中的數據,而後彙總進行排序
三. NoSql
關係數據庫通過幾十年的發展後已經很是成熟,強大的 SQL 功能和 ACID 的屬性,使得關係數據庫普遍應用於各式各樣的系統中,但這並不意味着關係數據庫是完美的,關係數據庫存在以下缺點:
針對上述問題,分別誕生了不一樣的 NoSQL 解決方案,這些方案與關係數據庫相比,在某些應用場景下表現更好。但世上沒有免費的午飯,NoSQL 方案帶來的優點,本質上是犧牲 ACID 中的某個或者某幾個特性,所以咱們不能盲目地迷信 NoSQL 是銀彈,而應該將 NoSQL 做爲 SQL 的一個有力補充,NoSQL != No SQL,而是 NoSQL = Not Only SQL
K-V 存儲
K-V 存儲的全稱是 Key-Value 存儲,其中 Key 是數據的標識,和關係數據庫中的主鍵含義同樣,Value 就是具體的數據。
Redis 是 K-V 存儲的典型表明,它是一款開源(基於 BSD 許可)的高性能 K-V 緩存和存儲系統。Redis 的 Value 是具體的數據結構,包括 string、hash、list、set、sorted set、bitmap 和 hyperloglog,因此經常被稱爲數據結構服務器。
Redis支持的操做在關係型數據庫中實現很麻煩,並且須要進行屢次 SQL 操做,性能很低。例如:刪除第一個值並更新全部的id
Redis 的缺點主要體如今並不支持完整的 ACID 事務,Redis 雖然提供事務功能,但 Redis 的事務和關係數據庫的事務不可同日而語,Redis 的事務只能保證隔離性和一致性(I 和 C),沒法保證原子性和持久性(A 和 D)。雖然 Redis 並無嚴格遵循 ACID 原則,但實際上大部分業務也不須要嚴格遵循 ACID 原則。
文檔數據庫
爲了解決關係數據庫 schema 帶來的問題,文檔數據庫應運而生。文檔數據庫最大的特色就是 no-schema,能夠存儲和讀取任意的數據。目前絕大部分文檔數據庫存儲的數據格式是 JSON(或者 BSON),由於 JSON 數據是自描述的,無須在使用前定義字段,讀取一個 JSON 中不存在的字段也不會致使 SQL 那樣的語法錯誤。
優勢有
缺點是
列式數據庫
顧名思義,列式數據庫就是按照列來存儲數據的數據庫,與之對應的傳統關係數據庫被稱爲「行式數據庫」,由於關係數據庫是按照行來存儲數據的。
優勢是
缺點在於必須在特定場景下才能發揮它的優點
全文搜索引擎
全文搜索引擎的技術原理被稱爲「倒排索引」(Inverted index),也常被稱爲反向索引、置入檔案或反向檔案,是一種索引方法,其基本原理是創建單詞到文檔的索引。之因此被稱爲「倒排」索引,是和「正排「索引相對的,「正排索引」的基本原理是創建文檔到單詞的索引。
正排索引示例:
倒排索引示例:
四. 高性能緩存架構
雖然咱們能夠經過各類手段來提高存儲系統的性能,但在某些複雜的業務場景下,單純依靠存儲系統的性能提高不夠的,典型的場景有:
緩存就是爲了彌補存儲系統在這些複雜業務場景下的不足,其基本原理是將可能重複使用的數據放到內存中,一次生成、屢次使用,避免每次使用都去訪問存儲系統。緩存可以帶來性能的大幅提高,以 Memcache 爲例,單臺 Memcache 服務器簡單的 key-value 查詢可以達到 TPS 50000 以上,其基本的架構是:
緩存雖然可以大大減輕存儲系統的壓力,但同時也給架構引入了更多複雜性。
緩存穿透
緩存穿透是指緩存沒有發揮做用,業務系統雖然去緩存查詢數據,但緩存中沒有數據,業務系統須要再次去存儲系統查詢數據。一般狀況下有兩種狀況:
第一種狀況是被訪問的數據確實不存在。通常狀況下,若是存儲系統中沒有某個數據,則不會在緩存中存儲相應的數據,這樣就致使用戶查詢的時候,在緩存中找不到對應的數據,每次都要去存儲系統中再查詢一遍,而後返回數據不存在。緩存在這個場景中並無起到分擔存儲系統訪問壓力的做用。一般狀況下,業務上讀取不存在的數據的請求量並不會太大,但若是出現一些異常狀況,例如被黑客攻擊,故意大量訪問某些讀取不存在數據的業務,有可能會將存儲系統拖垮。這種狀況的解決辦法比較簡單,若是查詢存儲系統的數據沒有找到,則直接設置一個默認值(能夠是空值,也能夠是具體的值)存到緩存中,這樣第二次讀取緩存時就會獲取到默認值,而不會繼續訪問存儲系統。
第二種狀況是存儲系統中存在數據,但生成緩存數據須要耗費較長時間或者耗費大量資源。若是恰好在業務訪問的時候緩存失效了,那麼也會出現緩存沒有發揮做用,訪問壓力所有集中在存儲系統上的狀況。典型的就是電商的商品分頁,假設咱們在某個電商平臺上選擇「手機」這個類別查看,因爲數據巨大,不能把全部數據都緩存起來,只能按照分頁來進行緩存,因爲難以預測用戶到底會訪問哪些分頁,所以業務上最簡單的就是每次點擊分頁的時候按分頁計算和生成緩存。一般狀況下這樣實現是基本知足要求的,可是若是被競爭對手用爬蟲來遍歷的時候,系統性能就可能出現問題。這種狀況並無太好的解決方案,由於爬蟲會遍歷全部的數據,並且何時來爬取也是不肯定的,多是天天都來,也多是每週,也多是一個月來一次,咱們也不可能爲了應對爬蟲而將全部數據永久緩存。一般的應對方案要麼就是識別爬蟲而後禁止訪問,但這可能會影響 SEO 和推廣;要麼就是作好監控,發現問題後及時處理,由於爬蟲不是攻擊,不會進行暴力破壞,對系統的影響是逐步的,監控發現問題後有時間進行處理。
緩存雪崩
緩存雪崩是指當緩存失效(過時)後引發系統性能急劇降低的狀況。當緩存過時被清除後,業務系統須要從新生成緩存,所以須要再次訪問存儲系統,再次進行運算,這個處理步驟耗時幾十毫秒甚至上百毫秒。而對於一個高併發的業務系統來講,幾百毫秒內可能會接到幾百上千個請求。因爲舊的緩存已經被清除,新的緩存還未生成,而且處理這些請求的線程都不知道另外有一個線程正在生成緩存,所以全部的請求都會去從新生成緩存,都會去訪問存儲系統,從而對存儲系統形成巨大的性能壓力。這些壓力又會拖慢整個系統,嚴重的會形成數據庫宕機,從而造成一系列連鎖反應,形成整個系統崩潰。
緩存熱點
緩存熱點的解決方案就是複製多份緩存副本,將請求分散到多個緩存服務器上,減輕緩存熱點致使的單臺緩存服務器壓力。
雖然緩存系統自己的性能比較高,但對於一些特別熱點的數據,若是大部分甚至全部的業務請求都命中同一份緩存數據,則這份數據所在的緩存服務器的壓力也很大。例如,某明星微博發佈「咱們」來宣告戀愛了,短期內上千萬的用戶都會來圍觀。對於粉絲數超過 100 萬的明星,每條微博均可以生成 100 份緩存,緩存的數據是同樣的,經過在緩存的 key 裏面加上編號進行區分,每次讀緩存時都隨機讀取其中某份緩存。緩存副本設計有一個細節須要注意,就是不一樣的緩存副本不要設置統一的過時時間,不然就會出現全部緩存副本同時生成同時失效的狀況,從而引起緩存雪崩效應。正確的作法是設定一個過時時間範圍,不一樣的緩存副本的過時時間是指定範圍內的隨機值。
五. 單服務器高性能模式:PPC與TPC
單服務器高性能的關鍵之一就是服務器採起的併發模型,併發模型有以下兩個關鍵設計點:
以上兩個設計點最終都和操做系統的 I/O 模型及進程模型相關。
PPC模式
PPC 是 Process Per Connection 的縮寫,其含義是指每次有新的鏈接就新建一個進程去專門處理這個鏈接的請求,這是傳統的 UNIX 網絡服務器所採用的模型。基本的流程圖是:
注意,圖中有一個小細節,父進程「fork」子進程後,直接調用了 close,看起來好像是關閉了鏈接,其實只是將鏈接的文件描述符引用計數減一,真正的關閉鏈接是等子進程也調用 close 後,鏈接對應的文件描述符引用計數變爲 0 後,操做系統纔會真正關閉鏈接。
PPC 模式實現簡單,比較適合服務器的鏈接數沒那麼多的狀況,例如數據庫服務器。對於普通的業務服務器,在互聯網興起以前,因爲服務器的訪問量和併發量並無那麼大,這種模式其實運做得也挺好,世界上第一個 web 服務器 CERN httpd 就採用了這種模式。互聯網興起後,服務器的併發和訪問量從幾十劇增到成千上萬,這種模式的弊端就凸顯出來了,主要體如今這幾個方面:
Prefork模式
PPC 模式中,當鏈接進來時才 fork 新進程來處理鏈接請求,因爲 fork 進程代價高,用戶訪問時可能感受比較慢,prefork 模式的出現就是爲了解決這個問題。顧名思義,prefork 就是提早建立進程(pre-fork)。系統在啓動的時候就預先建立好進程,而後纔開始接受用戶的請求,當有新的鏈接進來的時候,就能夠省去 fork 進程的操做,讓用戶訪問更快、體驗更好。prefork 的基本示意圖是:
prefork 的實現關鍵就是多個子進程都 accept 同一個 socket,當有新的鏈接進入時,操做系統保證只有一個進程能最後 accept 成功。但這裏也存在一個小小的問題:「驚羣」現象,就是指雖然只有一個子進程能 accept 成功,但全部阻塞在 accept 上的子進程都會被喚醒,這樣就致使了沒必要要的進程調度和上下文切換了。幸運的是,操做系統能夠解決這個問題,例如 Linux 2.6 版本後內核已經解決了 accept 驚羣問題。prefork 模式和 PPC 同樣,仍是存在父子進程通訊複雜、支持的併發鏈接數量有限的問題,所以目前實際應用也很少。Apache 服務器提供了 MPM prefork 模式,推薦在須要可靠性或者與舊軟件兼容的站點時採用這種模式,默認狀況下最大支持 256 個併發鏈接。
TPC模式
TPC 是 Thread Per Connection 的縮寫,其含義是指每次有新的鏈接就新建一個線程去專門處理這個鏈接的請求。與進程相比,線程更輕量級,建立線程的消耗比進程要少得多;同時多線程是共享進程內存空間的,線程通訊相比進程通訊更簡單。所以,TPC 其實是解決或者弱化了 PPC fork 代價高的問題和父子進程通訊複雜的問題。
注意,和 PPC 相比,主進程不用「close」鏈接了。緣由是在於子線程是共享主進程的進程空間的,鏈接的文件描述符並無被複制,所以只須要一次 close 便可。TPC 雖然解決了 fork 代價高和進程通訊複雜的問題,可是也引入了新的問題,具體表如今:
除了引入了新的問題,TPC 仍是存在 CPU 線程調度和切換代價的問題。所以,TPC 方案本質上和 PPC 方案基本相似,在併發幾百鏈接的場景下,反而更多地是採用 PPC 的方案,由於 PPC 方案不會有死鎖的風險,也不會多進程互相影響,穩定性更高。
Prethread模式
好熟悉的名詞,與上面那個貌似有殊途同歸之妙。和 prefork 相似,prethread 模式會預先建立線程,而後纔開始接受用戶的請求,當有新的鏈接進來的時候,就能夠省去建立線程的操做,讓用戶感受更快、體驗更好。因爲多線程之間數據共享和通訊比較方便,所以實際上 prethread 的實現方式相比 prefork 要靈活一些,常見的實現方式有下面幾種:
Apache 服務器的 MPM worker 模式本質上就是一種 prethread 方案,但稍微作了改進。Apache 服務器會首先建立多個進程,每一個進程裏面再建立多個線程,這樣作主要是爲了考慮穩定性,即:即便某個子進程裏面的某個線程異常致使整個子進程退出,還會有其餘子進程繼續提供服務,不會致使整個服務器所有掛掉。prethread 理論上能夠比 prefork 支持更多的併發鏈接,Apache 服務器 MPM worker 模式默認支持 16 × 25 = 400 個併發處理線程。
六. 單服務器高性能模式:Reactor與Proactor
上一節爲單服務器高性能的 PPC 和 TPC 模式,它們的優勢是實現簡單,缺點是都沒法支撐高併發的場景,尤爲是互聯網發展到如今,各類海量用戶業務的出現,PPC 和 TPC 徹底無能爲力。所以咱們引出應對高併發場景的單服務器高性能架構模式:Reactor 和 Proactor。
Reactor
PPC 模式最主要的問題就是每一個鏈接都要建立進程(,鏈接結束後進程就銷燬了,這樣作實際上是很大的浪費。爲了解決這個問題,一個天然而然的想法就是資源複用,即再也不單獨爲每一個鏈接建立進程,而是建立一個進程池,將鏈接分配給進程,一個進程能夠處理多個鏈接的業務。引入資源池的處理方式後,會引出一個新的問題:進程如何才能高效地處理多個鏈接的業務?當一個鏈接一個進程時,進程能夠採用「read -> 業務處理 -> write」的處理流程,若是當前鏈接沒有數據能夠讀,則進程就阻塞在 read 操做上。這種阻塞的方式在一個鏈接一個進程的場景下沒有問題,但若是一個進程處理多個鏈接,進程阻塞在某個鏈接的 read 操做上,此時即便其餘鏈接有數據可讀,進程也沒法去處理,很顯然這樣是沒法作到高性能的。解決這個問題的最簡單的方式是將 read 操做改成非阻塞,而後進程不斷地輪詢多個鏈接。這種方式可以解決阻塞的問題,但解決的方式並不優雅。首先,輪詢是要消耗 CPU 的;其次,若是一個進程處理幾千上萬的鏈接,則輪詢的效率是很低的。爲了可以更好地解決上述問題,很容易能夠想到,只有當鏈接上有數據的時候進程纔去處理,這就是 I/O 多路複用技術的來源。I/O 多路複用技術概括起來有兩個關鍵實現點:
事件反應的意思,能夠通俗地理解爲「來了一個事件我就有相應的反應」,這裏的「我」就是 Reactor,具體的反應就是咱們寫的代碼,Reactor 會根據事件類型來調用相應的代碼進行處理。Reactor 模式也叫 Dispatcher 模式(在不少開源的系統裏面會看到這個名稱的類,其實就是實現 Reactor 模式的),更加貼近模式自己的含義,即 I/O 多路複用統一監聽事件,收到事件後分配(Dispatch)給某個進程。
Reactor 模式的核心組成部分包括 Reactor 和處理資源池(進程池或線程池),其中 Reactor 負責監聽和分配事件,處理資源池負責處理事件。初看 Reactor 的實現是比較簡單的,但實際上結合不一樣的業務場景,Reactor 模式的具體實現方案靈活多變,主要體如今:
將上面兩個因素排列組合一下,理論上能夠有 4 種選擇,但因爲「多 Reactor 單進程」實現方案相比「單 Reactor 單進程」方案,既複雜又沒有性能優點,所以「多 Reactor 單進程」方案僅僅是一個理論上的方案,實際沒有應用。最終 Reactor 模式有這三種典型的實現方案:
以上方案具體選擇進程仍是線程,更多地是和編程語言及平臺相關。例如,Java 語言通常使用線程(例如,Netty),C 語言使用進程和線程均可以。例如,Nginx 使用進程,Memcache 使用線程。
單 Reactor 單進程 / 線程
單 Reactor 單進程的模式優勢就是很簡單,沒有進程間通訊,沒有進程競爭,所有都在同一個進程內完成。但其缺點也是很是明顯,具體表現有:
所以,單 Reactor 單進程的方案在實踐中應用場景很少,只適用於業務處理很是快速的場景,目前比較著名的開源軟件中使用單 Reactor 單進程的是 Redis。
單 Reactor 多線程
單 Reator 多線程方案可以充分利用多核多 CPU 的處理能力,但同時也存在下面的問題:
你可能會發現,我只列出了「單 Reactor 多線程」方案,沒有列出「單 Reactor 多進程」方案,這是什麼緣由呢?主要緣由在於若是採用多進程,子進程完成業務處理後,將結果返回給父進程,並通知父進程發送給哪一個 client,這是很麻煩的事情。由於父進程只是經過 Reactor 監聽各個鏈接上的事件而後進行分配,子進程與父進程通訊時並非一個鏈接。若是要將父進程和子進程之間的通訊模擬爲一個鏈接,並加入 Reactor 進行監聽,則是比較複雜的。而採用多線程時,由於多線程是共享數據的,所以線程間通訊是很是方便的。雖然要額外考慮線程間共享數據時的同步問題,但這個複雜度比進程間通訊的複雜度要低不少。
多 Reactor 多進程 / 線程
多 Reactor 多進程 / 線程的方案看起來比單 Reactor 多線程要複雜,但實際實現時反而更加簡單,主要緣由是:
目前著名的開源系統 Nginx 採用的是多 Reactor 多進程,採用多 Reactor 多線程的實現有 Memcache 和 Netty。
Proactor
Reactor 是非阻塞同步網絡模型,由於真正的 read 和 send 操做都須要用戶進程同步操做。這裏的「同步」指用戶進程在執行 read 和 send 這類 I/O 操做的時候是同步的,若是把 I/O 操做改成異步就可以進一步提高性能,這就是異步網絡模型 Proactor。Proactor 中文翻譯爲「前攝器」比較難理解,與其相似的單詞是 proactive,含義爲「主動的」,所以咱們照貓畫虎翻譯爲「主動器」反而更好理解。Reactor 能夠理解爲「來了事件我通知你,你來處理」,而 Proactor 能夠理解爲「來了事件我來處理,處理完了我通知你」。這裏的「我」就是操做系統內核,「事件」就是有新鏈接、有數據可讀、有數據可寫的這些 I/O 事件,「你」就是咱們的程序代碼。
理論上 Proactor 比 Reactor 效率要高一些,異步 I/O 可以充分利用 DMA 特性,讓 I/O 操做與計算重疊,但要實現真正的異步 I/O,操做系統須要作大量的工做。目前 Windows 下經過 IOCP 實現了真正的異步 I/O,而在 Linux 系統下的 AIO 並不完善,所以在 Linux 下實現高併發網絡編程時都是以 Reactor 模式爲主。因此即便 Boost.Asio 號稱實現了 Proactor 模型,其實它在 Windows 下采用 IOCP,而在 Linux 下是用 Reactor 模式(採用 epoll)模擬出來的異步模型。
七. 高性能負載均衡:分類及架構
單服務器不管如何優化,不管採用多好的硬件,總會有一個性能天花板,當單服務器的性能沒法知足業務需求時,就須要設計高性能集羣來提高系統總體的處理性能。高性能集羣的本質很簡單,經過增長更多的服務器來提高系統總體的計算能力。因爲計算自己存在一個特色:一樣的輸入數據和邏輯,不管在哪臺服務器上執行,都應該獲得相同的輸出。所以高性能集羣設計的複雜度主要體如今任務分配這部分,須要設計合理的任務分配策略,將計算任務分配到多臺服務器上執行。高性能集羣的複雜性主要體如今須要增長一個任務分配器,以及爲任務選擇一個合適的任務分配算法。
DNS 負載均衡
DNS 是最簡單也是最多見的負載均衡方式,通常用來實現地理級別的均衡。例如,北方的用戶訪問北京的機房,南方的用戶訪問深圳的機房。DNS 負載均衡的本質是 DNS 解析同一個域名能夠返回不一樣的 IP 地址。例如,一樣是 www.baidu.com,北方用戶解析後獲取的地址是 61.135.165.224(這是北京機房的 IP),南方用戶解析後獲取的地址是 14.215.177.38(這是深圳機房的 IP)。
DNS 負載均衡實現簡單、成本低,但也存在粒度太粗、負載均衡算法少等缺點。仔細分析一下優缺點,其優勢有:
缺點有:
針對 DNS 負載均衡的一些缺點,對於時延和故障敏感的業務,有一些公司本身實現了 HTTP-DNS 的功能,即便用 HTTP 協議實現一個私有的 DNS 系統。這樣的方案和通用的 DNS 優缺點正好相反。
硬件負載均衡
硬件負載均衡是經過單獨的硬件設備來實現負載均衡功能,這類設備和路由器、交換機相似,能夠理解爲一個用於負載均衡的基礎網絡設備。目前業界典型的硬件負載均衡設備有兩款:F5 和 A10。這類設備性能強勁、功能強大,但價格都不便宜,通常只有「土豪」公司纔會考慮使用此類設備。普通業務量級的公司一是負擔不起,二是業務量沒那麼大,用這些設備也是浪費。
硬件負載均衡的優勢是:
硬件負載均衡的缺點是:
軟件負載均衡
軟件負載均衡經過負載均衡軟件來實現負載均衡功能,常見的有 Nginx 和 LVS,其中 Nginx 是軟件的 7 層負載均衡,LVS 是 Linux 內核的 4 層負載均衡。4 層和 7 層的區別就在於協議和靈活性,Nginx 支持 HTTP、E-mail 協議;而 LVS 是 4 層負載均衡,和協議無關,幾乎全部應用均可以作,例如,聊天、數據庫等。
軟件和硬件的最主要區別就在於性能,硬件負載均衡性能遠遠高於軟件負載均衡性能。Ngxin 的性能是萬級,通常的 Linux 服務器上裝一個 Nginx 大概能到 5 萬 / 秒;LVS 的性能是十萬級,聽說可達到 80 萬 / 秒;而 F5 性能是百萬級,從 200 萬 / 秒到 800 萬 / 秒都有(數據來源網絡,僅供參考,如需採用請根據實際業務場景進行性能測試)。固然,軟件負載均衡的最大優點是便宜,一臺普通的 Linux 服務器批發價大概就是 1 萬元左右,相比 F5 的價格,那就是自行車和寶馬的區別了。
除了使用開源的系統進行負載均衡,若是業務比較特殊,也可能基於開源系統進行定製(例如,Nginx 插件),甚至進行自研。
下面是 Nginx 的負載均衡架構示意圖:
軟件負載均衡的優勢:
缺點:
負載均衡典型架構
前面咱們介紹了 3 種常見的負載均衡機制:DNS 負載均衡、硬件負載均衡、軟件負載均衡,每種方式都有一些優缺點,但並不意味着在實際應用中只能基於它們的優缺點進行非此即彼的選擇,反而是基於它們的優缺點進行組合使用。具體來講,組合的基本原則爲:DNS 負載均衡用於實現地理級別的負載均衡;硬件負載均衡用於實現集羣級別的負載均衡;軟件負載均衡用於實現機器級別的負載均衡。
須要注意的是,上圖只是一個示例,通常在大型業務場景下才會這樣用,若是業務量沒這麼大,則沒有必要嚴格照搬這套架構。例如,一個大學的論壇,徹底能夠不須要 DNS 負載均衡,也不須要 F5 設備,只須要用 Nginx 做爲一個簡單的負載均衡就足夠了。
八. 高性能負載均衡:算法
負載均衡算法數量較多,並且能夠根據一些業務特性進行定製開發,拋開細節上的差別,根據算法指望達到的目的,大致上能夠分爲下面幾類。
輪詢
負載均衡系統收到請求後,按照順序輪流分配到服務器上。輪詢是最簡單的一個策略,無須關注服務器自己的狀態,例如:
須要注意的是負載均衡系統無須關注「服務器自己狀態」,這裏的關鍵詞是「自己」。也就是說,只要服務器在運行,運行狀態是不關注的。但若是服務器直接宕機了,或者服務器和負載均衡系統斷連了,這時負載均衡系統是可以感知的,也須要作出相應的處理。例如,將服務器從可分配服務器列表中刪除,不然就會出現服務器都宕機了,任務還不斷地分配給它,這明顯是不合理的。
總而言之,「簡單」是輪詢算法的優勢,也是它的缺點。
加權輪詢
負載均衡系統根據服務器權重進行任務分配,這裏的權重通常是根據硬件配置進行靜態配置的,採用動態的方式計算會更加契合業務,但複雜度也會更高。
加權輪詢是輪詢的一種特殊形式,其主要目的就是爲了解決不一樣服務器處理能力有差別的問題。例如,集羣中有新的機器是 32 核的,老的機器是 16 核的,那麼理論上咱們能夠假設新機器的處理能力是老機器的 2 倍,負載均衡系統就能夠按照 2:1 的比例分配更多的任務給新機器,從而充分利用新機器的性能。
加權輪詢解決了輪詢算法中沒法根據服務器的配置差別進行任務分配的問題,但一樣存在沒法根據服務器的狀態差別進行任務分配的問題。
負載最低優先
負載均衡系統將任務分配給當前負載最低的服務器,這裏的負載根據不一樣的任務類型和業務場景,能夠用不一樣的指標來衡量。例如:
負載最低優先的算法解決了輪詢算法中沒法感知服務器狀態的問題,由此帶來的代價是複雜度要增長不少。例如:
負載最低優先算法基本上可以比較完美地解決輪詢算法的缺點,由於採用這種算法後,負載均衡系統須要感知服務器當前的運行狀態。固然,其代價是複雜度大幅上升。通俗來說,輪詢多是 5 行代碼就能實現的算法,而負載最低優先算法可能要 1000 行才能實現,甚至須要負載均衡系統和服務器都要開發代碼。負載最低優先算法若是自己沒有設計好,或者不適合業務的運行特色,算法自己就可能成爲性能的瓶頸,或者引起不少莫名其妙的問題。因此負載最低優先算法雖然效果看起來很美好,但實際上真正應用的場景反而沒有輪詢(包括加權輪詢)那麼多。
性能最優類
負載最低優先類算法是站在服務器的角度來進行分配的,而性能最優優先類算法則是站在客戶端的角度來進行分配的,優先將任務分配給處理速度最快的服務器,經過這種方式達到最快響應客戶端的目的。
和負載最低優先類算法相似,性能最優優先類算法本質上也是感知了服務器的狀態,只是經過響應時間這個外部標準來衡量服務器狀態而已。所以性能最優優先類算法存在的問題和負載最低優先類算法相似,複雜度都很高,主要體如今:
Hash類
負載均衡系統根據任務中的某些關鍵信息進行 Hash 運算,將相同 Hash 值的請求分配到同一臺服務器上,這樣作的目的主要是爲了知足特定的業務需求。例如: