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