數據庫篇 - 如何做好主從分離?

如果你做過稍大型電商項目的話,就知道電商項目在像雙11,雙12這種場景的需要考慮的併發場景與數據庫交互過程有多重要,如果能保證當查詢請求數劇增的情況下,web端能正常運行,依據一些雲廠商的 Benchmark 的結果,在 4 核 8G 的機器上運 MySQL 5.7 時,大概可以支撐 500 的 TPS 和 10000 的 QPS,本篇文章主要說說我的一些看法和實戰經驗。

一、主從讀寫分離

目前其實大部分系統的訪問模型都是讀多寫少,讀寫請求量的差距可能達到幾個數量級,這很好理解,刷朋友圈的請求量肯定比發朋友圈的量大,淘寶上一個商品的瀏覽量也肯定遠大於它的下單量。因此,我們優先考慮數據庫如何抗住更高的查詢請求,那麼首先你需要把讀寫流量區分開,因爲這樣才方便針對讀流量做單獨的擴展,這就是我們所說的主從讀寫分離。

主從讀寫的兩個技術關鍵點

一般來說在主從讀寫分離機制中,我們將一個數據庫的數據拷貝爲一份或者多份,並且寫入到其它的數據庫服務器中,原始的數據庫我們稱爲主庫,主要負責數據的寫入,拷貝的目標數據庫稱爲從庫,主要負責支持數據查詢。可以看到,主從讀寫分離有兩個技術上的關鍵點:

1. 一個是數據的拷貝,我們稱爲主從複製;
2. 在主從分離的情況下,我們如何屏蔽主從分離帶來的訪問數據庫方式的變化,讓開發同學像是在使用單一數據庫一樣。

1. 主從複製

MySQL 的主從複製是依賴於 binlog 的,也就是記錄 MySQL 上的所有變化並以二進制形式保存在磁盤上二進制日誌文件。主從複製就是將 binlog 中的數據從主庫傳輸到從庫上,一般這個過程是異步的,即主庫上的操作不會等待 binlog 同步的完成。

主從複製的過程是這樣的:首先從庫在連接到主節點時會創建一個 IO 線程,用以請求主庫更新的 binlog,並且把接收到的 binlog 信息寫入一個叫做 relay log 的日誌文件中,而主庫也會創建一個 log dump 線程來發送 binlog 給從庫;同時,從庫還會創建一個 SQL 線程讀取 relay log 中的內容,並且在從庫中做回放,最終實現主從的一致性。這是一種比較常見的主從複製方式。

在這個方案中,使用獨立的 log dump 線程是一種異步的方式,可以避免對主庫的主體更新流程產生影響,而從庫在接收到信息後並不是寫入從庫的存儲中,是寫入一個 relay log,是避免寫入從庫實際存儲會比較耗時,最終造成從庫和主庫延遲變長,於性能的考慮,主庫的寫入流程並沒有等待主從同步完成就會返回結果,那麼在極端的情況下,比如說主庫上 binlog 還沒有來得及刷新到磁盤上就出現了磁盤損壞或者機器掉電,就會導致 binlog 的丟失,最終造成主從數據的不一致。不過,這種情況出現的概率很低,對於互聯網的項目來說是可以容忍的。

做了主從複製之後,我們就可以在寫入時只寫主庫,在讀數據時只讀從庫,這樣即使寫請求會鎖表或者鎖記錄,也不會影響到讀請求的執行。同時呢,在讀流量比較大的情況下,我們可以部署多個從庫共同承擔讀流量,這就是所說的“一主多從”部署方式,在你的垂直電商項目中就可以通過這種方式來抵禦較高的併發讀流量。另外,從庫也可以當成一個備庫來使用,以避免主庫故障導致數據丟失。

那麼你可能會說,是不是我無限制地增加從庫的數量就可以抵抗大量的併發呢?實際上並不是的。因爲隨着從庫數量增加,從庫連接上來的 IO 線程比較多,主庫也需要創建同樣多的 log dump 線程來處理複製的請求,對於主庫資源消耗比較高,同時受限於主庫的網絡帶寬,所以在實際使用中,一般一個主庫最多掛 3~5 個從庫。

當然,主從複製也有一些缺陷,除了帶來了部署上的複雜度,還有就是會帶來一定的主從同步的延遲,這種延遲有時候會對業務產生一定的影響,我舉個例子你就明白了,在發微博的過程中會有些同步的操作,像是更新數據庫的操作,也有一些異步的操作,比如說將微博的信息同步給審覈系統,所以我們在更新完主庫之後,會將微博的 ID 寫入消息隊列,再由隊列處理機依據 ID 在從庫中獲取微博信息再發送給審覈系統。此時如果主從數據庫存在延遲,會導致在從庫中獲取不到微博信息,整個流程會出現異常。

這個問題解決的思路有很多,核心思想就是儘量不去從庫中查詢信息,純粹以上面的例子來說,我就有三種解決方案:

第一種方案是數據的冗餘。你可以在發送消息隊列時不僅僅發送微博 ID,而是發送隊列處理機需要的所有微博信息,藉此避免從數據庫中重新查詢數據。

第二種方案是使用緩存。我可以在同步寫數據庫的同時,也把微博的數據寫入到 Memcached 緩存裏面,這樣隊列處理機在獲取微博信息的時候會優先查詢緩存,這樣也可以保證數據的一致性。

最後一種方案是查詢主庫。我可以在隊列處理機中不查詢從庫而改爲查詢主庫。不過,這種方式使用起來要慎重,要明確查詢的量級不會很大,是在主庫的可承受範圍之內,否則會對主庫造成比較大的壓力。

我會優先考慮第一種方案,因爲這種方式足夠簡單,不過可能造成單條消息比較大,從而增加了消息發送的帶寬和時間。

2. 如何訪問數數據庫

我們已經使用主從複製的技術將數據複製到了多個節點,也實現了數據庫讀寫的分離,這時,對於數據庫的使用方式發生了變化。以前只需要使用一個數據庫地址就好了,現在需要使用一個主庫地址和多個從庫地址,並且需要區分寫入操作和查詢操作,如果結合下一節課中要講解的內容“分庫分表”,複雜度會提升更多。爲了降低實現的複雜度,業界湧現了很多數據庫中間件來解決數據庫的訪問問題,這些中間件可以分爲兩類。

 

第一類以淘寶的 TDDL( Taobao Distributed Data Layer)爲代表,以代碼形式內嵌運行在應用程序內部。你可以把它看成是一種數據源的代理,它的配置管理着多個數據源,每個數據源對應一個數據庫,可能是主庫,可能是從庫。當有一個數據庫請求時,中間件將 SQL 語句發給某一個指定的數據源來處理,然後將處理結果返回。

這一類中間件的優點是簡單易用,沒有多餘的部署成本,因爲它是植入到應用程序內部,與應用程序一同運行的,所以比較適合運維能力較弱的小團隊使用;缺點是缺乏多語言的支持,目前業界這一類的主流方案除了 TDDL,還有早期的網易 DDB,它們都是 Java 語言開發的,無法支持其他的語言。另外,版本升級也依賴使用方更新,比較困難。

另一類是單獨部署的代理層方案,這一類方案代表比較多,如早期阿里巴巴開源的 Cobar,基於 Cobar 開發出來的 Mycat,360 開源的 Atlas,美團開源的基於 Atlas 開發的 DBProxy 等等。

這一類中間件部署在獨立的服務器上,業務代碼如同在使用單一數據庫一樣使用它,實際上它內部管理着很多的數據源,當有數據庫請求時,它會對 SQL 語句做必要的改寫,然後發往指定的數據源。

它一般使用標準的 MySQL 通信協議,所以可以很好地支持多語言。由於它是獨立部署的,所以也比較方便進行維護升級,比較適合有一定運維能力的大中型團隊使用。它的缺陷是所有的 SQL 語句都需要跨兩次網絡:從應用到代理層和從代理層到數據源,所以在性能上會有一些損耗。

這些中間件,對你而言,可能並不陌生,但是我想讓你注意到是,在使用任何中間件的時候一定要保證對於中間件有足夠深入的瞭解,否則一旦出了問題沒法快速地解決就悲劇了。

總結

 

  • 主從讀寫分離以及部署一主多從可以解決突發的數據庫讀流量,是一種數據庫橫向擴展的方法;
  • 讀寫分離後,主從的延遲是一個關鍵的監控指標,可能會造成寫入數據之後立刻讀的時候讀取不到的情況;
  • 業界有很多的方案可以屏蔽主從分離之後數據庫訪問的細節,讓開發人員像是訪問單一數據庫一樣,包括有像 TDDL、Sharding-JDBC 這樣的嵌入應用內部的方案,也有像 Mycat 這樣的獨立部署的代理方案。
  • 主從的一致性和寫入性能的權衡,如果你要保證所有從節點都寫入成功,那麼寫入性能一定會受影響;如果你只寫入主節點就返回成功,那麼從節點就有可能出現數據同步失敗的情況,從而造成主從不一致,而在互聯網的項目中,我們一般會優先考慮性能而不是數據的強一致性。
  • 主從的延遲問題,很多詭異的讀取不到數據的問題都可能會和它有關,如果你遇到這類問題不妨先看看主從延遲的數據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章