五分鐘帶你掌握 MySQL 讀寫分離
讀寫分離是為了將對數據庫的讀、寫分散到不同的數據庫實例上。這樣的設計并不一定是完美的。讀寫分離主要針對的是讀多寫少的場景,對于寫多讀少的場景就不合適了。比如,持久化落庫就是一個寫多讀少的場景,多數情況是在不斷的將數據記錄到數據庫,偶爾才需要去查詢一下,相當于歸檔,大多數數據可能都不會被訪問到。當然,大多數應用都有讀多寫少的特性,這也使得讀寫分離具有廣泛的應用場景。
圖片
多數情況下,我們的讀寫分離都是采用一主多從的架構,也就是一臺主數據庫負責寫入操作,其他數據庫負責讀取操作。主數據庫和其他數據庫之間會進行數據同步,這種同步是準實時的。既然是準實時,說明還是有一個時間差,這個時間差導致了主庫和從庫的數據不一致。
那么如何處理這種不一致情況呢?通常有 3 個思路:
- 將讀請求交給主庫
顯然,從庫沒有我想要的數據,我們就可以從主庫讀取,不同的框架都提供強制走主庫的 API 支持。這是比較主流的一種做法,但是這種方法增加了主庫的壓力,相當于讀寫分離一定程度上失效了。
- 延遲讀取
有一個不用動腦子的方法,就是讓程序 sleep 一段時間之后再去讀數據,通常主從之間的時間差不會差太大,都是毫秒級別的。
- 業務規避
上面的兩個技術方案并不是很完美,最合適的方案是在業務層面繞開這種可能依賴主從數據同步的場景,比如,我們可以在完成寫入請求之后,避免立即進行請求操作。幸運的是,這種場景并不多見,多數情況下,我們可以天然的規避這些操作。
如何實現讀寫分離?
那么說了這么多,如何實現讀寫分離呢?其實很多現成的框架組件都幫我們做好了,無論使用哪個框架,它們的原理都是一樣的,基本都是以下 3 步:
- 首先部署多個 MySQL 實例,選擇其中一臺作為主庫;
- 保證主從數據庫是準實時同步;
- 將寫請求分給主庫,讀請求分給從庫。
其中比較著名的是官方的 MySQL Router 以及阿里的 MyCat。它們都是獨立部署的比較重要的中間件。實際上是在程序和 MySQL 中間加了一個代理層。應用程序將所有的請求都交給代理層處理,由代理層負責分發讀寫請求,將它們路由到對應的數據庫中。
圖片
更常見的做法是直接引入一個 Jar 包,這個包允許你配置多個表,主從數據庫地址等等,這樣就不需要部署獨立的中間件了,簡化了開發和運維的成本,其中比較著名的是 Sharding-JDBC,這個比較推薦,很多大廠都在使用。
下面我們聊聊讀寫分離的核心——主從同步。主從同步主要是通過 MySQL binlog 日志來進行的,binlog 主要記錄了 MySQL 數據庫中數據的所有 DDL 和 DML 操作。因此,我們根據主庫的 binlog 日志就可以把數據同步到其他數據庫實例中。咱們貼一個來自國外 toptal 平臺的文章《MySQL Master-Slave Replication on the Same Machine》的圖:
圖片
上面的過程是這樣的:
- 從庫創建一個 I/O 線程向主庫請求更新的 binlog;
- 主庫創建一個 binlog dump 線程來發送 binlog;
- 從庫的 I/O 線程將接收的 binlog 寫入到 relay log 中(這里的 relay log 和 binlog 格式類似,下面我們會講到,在這里大家當成是另一個 binlog 就好);
- 從庫讀取 relay log 同步到本地,通過 SQL 線程在本地執行里面的內容。
通過上面的分析,咱們可以很明顯地看出來,由于 binlog 包含了所有 DDL 和 DML 操作,因此 binlog 除了可以用于主從復制,還可以恢復數據,是一個多功能的文件。上面提到的 I/O 線程和 SQL 線程可以看作是在從庫上工作的 2 個流水線工人,I/O 線程負責原材料的運輸,寫入本地的 relay log 中,SQL 線程負責從 relay log 獲取原材料并加工。
上面我們提到了 relay log 的格式和 binlog 非常相似,那么為什么需要 relay log 呢?relay log 中文翻譯為“中繼日志”,中繼的意思是橋梁紐帶,除了數據本身以外 relay log 內部還包含了一些子文件,這些文件記錄了上一次讀取到主庫同步過來的 binlog 的位置,復制的進度以及連接主庫的配置信息等等。
很多三方工具可以幫助我們實現 MySQL 和其他數據庫或者另外一臺 MySQL 數據庫之間的數據同步。大多數情況下,這些三方工具的底層原理都是基于 binlog。它們的原理就是在模擬 MySQL 主從復制的過程,解析 binlog 將數據同步到其他的數據源。
除了 MySQL,比如咱們常用的分布式 NoSQL、緩存 Redis 等,也通過主從復制實現了讀寫分離。
總結
今天我們聊了 MySQL 的讀寫分離,讀寫分離幾乎在所有大并發的場景得到了運用。主寫從讀已經成為一個很普遍的技術場景。讀寫分離給我們帶來方便的同時,我們也要注意主從同步的延時。通常可以通過 API 強制走主庫來避免這個問題,但是這就相當于沒有做讀寫分離,更好的方案是在業務上避免這種操作,比如不要在插入之后立刻讀取。
我們討論了讀寫分離的原理和市面上常用的工具,雖然有很多中間件很有名,但是大廠還是用 Sharding-JDBC 最多,因為 Sharding-JDBC 輕量、幾乎無侵入。最后我們了解了一下主從復制的流程,這個流程只有 4 步,非常簡單。