1 回顧下MySQL主從複製
主從複製,是指建立一個和主資料庫完全一樣的資料庫環境(稱為從資料庫),並將主庫的操作行為進行復制的過程:將主資料庫的DDL和DML的操作日誌同步到從資料庫上,
然後在從資料庫上對這些日誌進行重新執行,來保證從資料庫和主資料庫的資料的一致性。
1.1 為什麼要做主從複製
1、在複雜的業務操作中,經常會有操作導致鎖行甚至鎖表的情況,如果讀寫不解耦,會很影響執行中的業務,使用主從複製,讓主庫負責寫,從庫負責讀。
即使主庫出現了鎖表的情景,通過讀從庫也可以保證業務的正常執行。
2、保證資料的熱備份,主庫當機後能夠及時替換主庫,保障業務可用性。
3、架構的演進:業務量擴大,I/O訪問頻率增高,單機無法滿足,主從複製可以做多庫方案,降低磁碟I/O訪問的頻率,提高單機的I/O效能。
4、本質上也是分治理念,主從複製、讀寫分離即是壓力分拆的過程。
5、讀寫比也影響整個拆分方式,讀寫比越高,主從庫比例應越高,才能保證讀寫的均衡,才能保證較好的執行效能。讀寫比下的主從分配方法下:
讀寫比(大約) | 主庫 | 從庫 |
50:50 | 1 | 1 |
66.6:33.3 | 1 | 2 |
80:20 | 1 | 4 |
-- -- | -- -- | -- -- |
1.2 主從複製的原理
當在從庫上啟動複製時,首先建立I/O執行緒連線主庫,主庫隨後建立Binlog Dump執行緒讀取資料庫事件併傳送給I/O執行緒,I/O執行緒獲取到事件資料後更新到從庫的中繼日誌Relay Log中去,之後從庫上的SQL執行緒讀取中繼日誌Relay Log中更新的資料庫事件並應用,
如下圖所示:
細化一下有如下幾個步驟:
1、MySQL主庫在事務提交時把資料變更(insert、delet、update)作為事件日誌記錄在二進位制日誌表(binlog)裡面。
2、主庫上有一個工作執行緒 binlog dump thread,把binlog的內容傳送到從庫的中繼日誌relay log中。
3、從庫根據中繼日誌relay log重做資料變更操作,通過邏輯複製來達到主庫和從庫的資料一致性。
4、MySQL通過三個執行緒來完成主從庫間的資料複製,其中binlog dump執行緒跑在主庫上,I/O執行緒和SQL執行緒跑在從庫上。擁有多個從庫的主庫會為每一個連線到主庫的從庫建立一個binlog dump執行緒。
1.3 主從延遲的原因
MySQL主從複製,讀寫分離是我們常用的資料庫架構,但是在併發量較大、資料變化大的場景下,主從延時會比較嚴重。
延遲的本質原因是:系統TPS併發較高時,主庫產生的DML(也包含一部分DDL)數量超過Slave一個Sql執行緒所能承受的範圍,效率就降低了。
2 幾種解決方案
2.1 最優的系統配置
優化系統配置(系統級、連結層、儲存引擎層),讓資料庫處在最優狀態:最大連線數、允許錯誤數、允許超時時間、pool_size、log_size等,保證記憶體、CPU、儲存空間的擴容(硬體部分)。
1 # TIME_WAIT超時時間,預設是60s 2 net.ipv4.tcp_fin_timeout = 30 3 # 增加tcp支援的佇列數,加大佇列長度可容納更多的等待連線 4 net.ipv4.tcp_max_syn_backlog = 65535 5 # 減少斷開連線時 ,資源回收 6 net.ipv4.tcp_max_tw_buckets = 8000 7 net.ipv4.tcp_tw_reuse = 1 8 net.ipv4.tcp_tw_recycle = 1 9 net.ipv4.tcp_fin_timeout = 10 10 # 開啟檔案的限制 11 *soft nofile 65535 12 *hard nofile 65535
公共引數預設值:
1 max_connections = 151 2 # 同時處理最大連線數,建議設定最大連線數是上限連線數的80%左右,一般預設值為151,可以做適當調整。 3 sort_buffer_size = 2M 4 # 查詢排序時緩衝區大小,只對order by和group by起作用,建議增大為16M 5 open_files_limit = 1024 6 # 開啟檔案數限制,如果show global status like 'open_files'檢視的值等於或者大於open_files_limit值時,程式會無法連線資料庫或卡死
InnoDB引數預設值:
1 innodb_buffer_pool_size = 128M 2 # 索引和資料緩衝區大小,建議設定實體記憶體的70%左右(這個前提是這個伺服器只用做Mysql資料庫伺服器) 3 innodb_buffer_pool_instances = 1 4 # 緩衝池例項個數,推薦設定4個或8個 5 innodb_flush_log_at_trx_commit = 1 6 # 關鍵引數,0代表大約每秒寫入到日誌並同步到磁碟,資料庫故障會丟失1秒左右事務資料。1為每執行一條SQL後寫入到日誌並同步到磁碟,I/O開銷大,執行完SQL要等待日誌讀寫,效率低。2代表只把日誌寫入到系統快取區,再每秒同步到磁碟,效率很高,如果伺服器故障,才會丟失事務資料。對資料安全性要求不是很高的推薦設定2,效能高,修改後效果明顯。 7 sync_binlog=1 8 9 innodb_file_per_table = ON 10 # 是否共享表空間,5.7+版本預設ON,共享表空間idbdata檔案不斷增大,影響一定的I/O效能。建議開啟獨立表空間模式,每個表的索引和資料都存在自己獨立的表空間中,可以實現單表在不同資料庫中移動。 11 innodb_log_buffer_size = 8M 12 # 日誌緩衝區大小,由於日誌最長每秒鐘重新整理一次,所以一般不用超過16M
2.2 資料庫層做合理分治
資料庫分割槽是永恆的話題,主從延遲一定程度上是單臺資料庫主服務操作過於頻繁,使得單執行緒的SQL thread 疲於應付。可以適當的從功能上對資料庫進行拆分,分擔壓力。
資料庫拆分可以參考我的這篇文章《分庫分表》,這邊就不贅述。
2.3 從庫同步完成後響應
假如你的業務時間允許,你可以在寫入主庫的時候,確保資料都同步到從庫了之後才返回這條資料寫入成功,當然如果有多個從庫,你也必須確保每個從庫都寫入成功。當然,這個方案對效能和時間的消耗是極大的,會直接降低你的系統吞吐量,不推薦。
2.4 適當引入快取
可以引入redis或者其他nosql資料庫來儲存我們經常會產生主從延遲的業務資料。當我在寫入資料庫的同時,我們再寫入一份到redis中。
讀取資料的時候,我們可以先去檢視redis中是否有這個資料,如果有我們就可以直接從redis中讀取這個資料。當資料真正同步到資料庫中的時候,再從redis中把資料刪除。如下圖:
這邊還需注意兩點,很重要喲,面試必問:
1、雖然一定程度上緩解延遲的問題,但如果遇到高併發的情況,對Redis的頻繁刪除也不合理,所以需要結合場景綜合考慮,比如定期刪除快取。
2、高併發情況下可能存在slave還沒同步,又有新的值寫進來了,這時候Master --> Slave 還在排隊中,但是Cache已經被更新了。所以如果對Redis進行刪除,可能會誤刪除最新的快取值,導致讀取到的資料是舊的。
如上圖情況,對一個值分別更新 1,2,3,主從同步按照順序進行,剛同步完1,Cache就更新到3了,這時候如果把Cache刪除了,讀請求就會走到從庫去讀,讀到了1,資料就會出現短暫不一致了。
所以這個地方也需要注意,可以同時將唯一鍵(比如主鍵)也做儲存,刪除之前做一個判斷,避免誤刪。或者乾脆不實時刪除快取,低峰值期再來處理。
2.5 多執行緒重放RelayLog
MySQL使用單執行緒重放RelayLog,那能不能在這上面做解法呢,比如使用多執行緒並行重放RelayLog,就可以縮短時間。但是這個對資料一致性是個考驗。
需要考慮如何分割RelayLog,才能夠讓多個資料庫例項,多個執行緒並行重放RelayLog,不會出現不一致。比如RelayLog包含這三條語句給學生授予學分的記錄,你就不知道結果會變成什麼。可能是806甚至是721。
1 update t_score set score = 721 where stu_code=374532; 2 update t_score set score = 806 where stu_code=374532; 3 update t_score set score = 899 where stu_code=374532;
解法就是:
相同庫表上的寫操作,用相同的執行緒來重放RelayLog;不同庫表上的寫操作,可以併發用多個執行緒併發來重放RelayLog。
設計一個雜湊演算法,hash(db-name) % thread-num,表名稱hash之後再模上執行緒數,就能很輕易做到,同一個庫表上的寫操作,被同一個重放執行緒序列執行,做到提效的目的。
這其實也是一種分治的思維,類似上面直接對資料庫進行拆分。
2.6 少量讀業務直連主庫
2.7 適當的限流、降級
3 總結
上面提到了多種方案都是可以討論,每個方法都有弊端和優勢,根據實際情況進行選型。在面試的過程中,也經常遇到候選人跟我討論的。
另外,mysql5.6之後,可以按照庫並行複製。mysql5.7之後,提供了基於GTID並行複製的能力。可以參考學習下。