負載均衡的基本思路很簡單:html
在一個服務器集羣中儘量地的平均負載量。前端
基於這個思路,咱們一般的作法是在服務器前端設置一個負載均衡器。負載均衡器的做用是將請求的鏈接路由到最空閒的可用服務器上。如圖 1,顯示了一個大型網站負載均衡設置。其中一個負責 HTTP 流量,另外一個用於 MySQL 訪問。
mysql
負載均衡有五個常見目的:算法
- 可擴展性。負載均衡對某些擴展頗有幫助,好比讀寫分離時從備庫讀數據。
- 高效性。負載均衡由於可以控制請求被路由到何處,所以有助於更有效的使用資源。
- 可用性。靈活的負載均衡方案可以大幅提升服務的可用性。
- 透明性。客戶端無需知道是否存在負載均衡器,也不須要關係在負載均衡器的背後有多少機器。呈現給客戶端看到的就是一個透明的服務器。
- 一致性。若是應用是有狀態的(數據庫事務、網站會話等),那麼負載均衡器就能夠將相關的查詢指向同一個服務器,以防止狀態丟失。
而對於負載均衡的實現,通常有兩種方式:直接鏈接和引入中間件。sql
1 直接鏈接
有些人認爲負載均衡就是配置在應用和 MySQL 服務器直接東西,但實際上這並非惟一的負載均衡方法。接下來咱們就討論一下常見的應用直連的方法,及其相關注意事項。數據庫
1.1 複製的讀寫分離
此種方式下,容易出現一個最大的問題:髒數據。一個典型的例子是,當用戶評論了一篇博文,而後從新加載頁面,卻沒有看到新增的評論。緩存
固然,咱們也不能由於髒數據的問題,就將讀寫分離棄之不用。實際上,對於不少應用,可能對髒數據的容忍度比較高,此時就能夠大膽的引入此種方式。服務器
那麼對於髒數據的容忍度比較低的應用,如何進行讀寫分離呢?接下來,咱們對讀寫分離再進一步區分,相信你總能找到適合本身的一款策略。網絡
1) 基於查詢分離架構
若是應用只有少數數據不能容忍髒數據,咱們能夠將全部不能容忍髒數據的讀和寫都分配到 master 上。其它的讀查詢分配的 slave 上。該策略很容易實現,但若是容忍髒數據的查詢比較少,極可能會出現不能有效使用備庫的狀況。
2) 基於髒數據分離
這是對基於查詢分離策略的小改進。須要作一些額外的工做,好比讓應用檢查複製延遲,以肯定備庫數據是否最新。許多報表類應用均可以使用這個策略:只須要晚上加載的數據複製到備庫接口,並不關心是否是徹底跟上了主庫。
3) 基於會話分離
這個策略比髒數據分離策略更深刻 一些。它是判斷用戶是否修改了數據,用戶不須要看到其餘用戶的最新數據,只須要看到本身的更新。
具體能夠在會話層設置一個標記位,代表用戶是否作了更新,用戶一旦作了更新,就將該用戶的查詢在一段時間內指向主庫。
這種策略在簡單和有效性之間作了很好的妥協,是一種較爲推薦的策略。
固然,若是你的想法夠多,能夠把基於會話的分離策略和複製延遲監控策略結合起來。若是用戶在 10 秒前更新了數據,而全部備庫延遲在 5 秒內,就能夠大膽的從備庫中讀取數據。要注意的是,記得爲整個會話選擇同個備庫,不然一旦多個備庫的延遲不一致,就會給用戶形成困擾。
4) 基於全局版本 / 會話分離
經過記錄主庫日誌座標和備庫已複製的座標對比,確認備庫是否更新數據。當應用指向寫操做時,在提交事務後,執行一次 SHOW MASTER STATUS 操做,而後將主庫日誌座標存儲在緩存中,做爲被修改對象或者會話的版本號。當應用鏈接到備庫時,執行 SHOW SLAVE STATUS,並將備庫上的座標和緩存中的版本號對比。若是備庫比主庫記錄點更新,就代表備庫已更新對應數據,可放心的使用。
實際上,不少讀寫分離策略都須要監控複製延遲來決定讀查詢的分配。不過要注意的是,SHOW SLAVE STATUS 獲得的 Seconds_behind_master 列的值並不能精確的表示延遲。咱們可使用 Percona Toolkit 中的 pt-heartbeat 工具更好的監控延遲。
1.2 修改 DNS 名
對於一些比較簡單的應用,能夠爲不一樣目的建立 DNS。最簡單的方法是隻讀服務器擁有一個 DNS 名(read.mysql-db.com),給負責寫操做的服務器起另一個 DNS 名(write.mysql-db.com)。若是備庫可以跟得上主庫,就把只讀 DNS 名指向到備庫,不然,就指向到主庫。
這種策略很是容易實現,但有個很大的問題是:沒法徹底控制 DNS。
- 修改 DNS 並非馬上生效的,也不是原子性的。將 DNS 的變化傳遞到整個網絡或者網絡間傳播都須要比較長的時間。
- DNS 數據會在各個地方緩存下,它的過時時間是建議性質,而非強制的。
- 可能須要應用或服務器重啓才能使修改後的 DNS 徹底生效。
這種策略較爲危險,即便能夠經過修改 /etc/hosts 文件來避免 DNS 沒法徹底控制的問題,但仍不失理想策略。
1.3 轉移 IP 地址
經過在服務器間轉移虛擬地址,來實現負載均衡。是否是感受和修改 DNS 很像?但實際上徹底是兩碼事。轉移 IP 地址容許 DNS 名保持不變,咱們能夠經過 ARP 命令(不瞭解 ARP,看這裏)強制使 IP 地址的更改快速並且原子性的通知到局域網絡上。
一個比較方便的技術是爲每一個物理服務器分配一個固定的 IP 地址。該 IP 地址固定在服務器上,再也不改變。而後能夠爲每一個邏輯上的 「服務」(能夠理解爲容器)使用一個虛擬 IP 地址。
這樣,IP 就可以很方便的在服務器間轉移,無需從新配置應用,實現也更加容易。
2 引入中間件
上面的策略都是假定應用是和 MySQL 服務器之間鏈接的,可是許多負載均衡都會引入一箇中間件,做爲網絡通訊的代理。它一邊接受全部的通訊,另外一邊將這些請求分發的指定服務器上,並將執行結果發送回請求機器。圖 2 展現了此種架構。
2.1 負載均衡器
如今有許多負載均衡硬件和軟件,但不多有專門爲 MySQL 服務器設計的。Web 服務器一般更須要負載均衡,所以許多多用途的負載均衡設備都會支持 HTTP,而對其餘用途則只有一些不多的基本特性。
MySQL 鏈接只是正常的 TCP/IP 鏈接,因此能夠在 MySQL 上使用多用途負載均衡器。但因爲缺乏 MySQL 專有的特性,所以會多一些限制:
- 分發請求是可能沒法作到很好的負載均衡。
- 對 MySQL 會話支持不足,可能不知道如何把全部從單個 HTTP 會話發送的鏈接請求 「固定」 到一個 MySQL 服務器上。
- 鏈接池和長鏈接可能會阻礙負載均衡器分發鏈接請求。
- 不能很好的對 MySQL 服務器作健康和負載檢查。
2.2 負載均衡算法
有不少算法用來決定哪一個服務器接受下一個鏈接。每一個廠商都有各自不一樣的算法,有如下經常使用方法:
- 隨機分配。從可用的服務器池中隨機選擇一個服務器來處理請求。
- 輪詢。以循環順序發送請求到服務器,例如:A、B、C、A、B、C。
- 哈希。經過鏈接的源 IP 地址進行哈希,將其映射到池中的同一個服務器上。
- 最快響應。將鏈接分配給可以最快處理請求的服務器上。
- 最少鏈接數。將鏈接分配給擁有最少活躍鏈接的服務器上。
- 權重。根據機器的性能等條件,給不一樣機器配置不一樣的權重,以便讓高性能的機器能處理更多的鏈接。
上述各類方法沒有最好,只有最適合的,這取決於具體的工做負載。
另外,咱們只描述了即時處理的算法。但有時候使用排隊算法可能會更有效。例如,一個算法可能只維護給定的數據庫服務器併發數量,同一時刻只容許不超過 N 個活躍事務。若是有太多的活躍事務,就將新的請求放到一個隊列裏,而後讓可用服務器列表來處理。
2.3 一主多備間的負載均衡
最多見的複製結構就是一個主庫加多個備庫。這種架構的擴展性較差,但咱們能夠經過一些方法結合負載均衡來得到更好的效果。
- 功能分區。對於廠家的功能包括報表、分析、數據倉庫以及全文索引,配置一個或一組備庫來擴展單個功能的容量。
- 保證備庫跟上主庫。備庫存在的問題就是髒數據。對於此,咱們可使用函數 MASTER_POS_WAIT() 阻塞主庫的操做,直到備庫遇上了設置的主庫同步點。另外,咱們還可使用複製心跳來檢查延遲狀況。
咱們不能也不該該在應用的開始就就想着把架構作成阿里那樣的架構。最好的方式是實現應用當前所明確須要的,併爲可能的快速增加作好預先規劃。
另外,爲可擴展性制定一個數字目標是頗有意義的,就像咱們爲性能制定了一個精確目標,知足 10K 或 100K 併發同樣。這樣能夠經過相關理論避免諸如序列化或交互操做的開銷問題帶入到咱們的應用中。
在 MySQL 擴展策略方面,典型的的應用在增加到很是龐大時,一般先從單個服務器轉移到向外擴展的擁有備庫的架構,再到數據分片或按功能分區。這裏要注意的是,咱們不提倡諸如 「儘早分片,儘可能分片」 的建議。實際上,分片很複雜,並且成本很高,最主要的是不少應用可能根本不須要。與其花大成本去分片,還不如先去看看新的硬件和新版本的 MySQL 有哪些變化,也許這些新變化會給你帶來驚喜。
總結
- 直接鏈接重 "分離",均衡器和算法有侷限。
- 爲擴展性量化指標。