數據庫——MySQL讀寫分離后的延遲解決方案
背景:
根據上圖可以看到QPS:10.73k,實際上真實的並發大量數據到達的時候,我這里最高的QPS是將近15k.而目前單個數據庫分片(實例)4CPU8G內存的配置下,最高的性能是7k的QPS。
基於上篇我進行了分庫分表是對於性能有很大的提高,分庫分表實踐和中間件的引申
我這里講解的例子是目前4主8從庫(12個實例),以下每個實例都會稱為分片。單個分片配置mysql版本5.7.19(一會說明不同版本是讀寫分離的不同策略),12CPU16G內存,128G的磁盤,Raid:10。
讀寫分離實踐
讀寫分離可以參考上篇文章的分庫分表實踐中的中間件的用法來實現。主流一般會使用mycat,但是每個中間件都有自己的優點可以擇優和業務特點而用。接下來講讀寫分離后的后遺症。
讀寫分離的延遲和實時insert/update和查詢操作
比如我這里的一個場景:由於數據量大,以人維度的情況下,商品量20w~50w。然后需要分頁查詢未同步下游狀態,進行數據同步后再更新該分頁數據。我當時設定了如下的四種場景,最后選擇了讀寫分離和不分離同時存在,針對於實時要求結果高的依然是master主庫讀寫,變動需求量小的數據,全部轉移slave從庫。
如下是四種場景的方案:
1、 完全分離:全量讀->從庫,全量讀寫->主庫
前提:第一頁查詢邏輯不變
特點:半同步復制,目前是1主2從庫,利用半同步復制原理,1/2的可能性會重復查詢,當然這個幾率需要和延時性進行測試計算可得,也就是最壞的結果可能性是重復查詢50%的可能性。目前反饋主從同步延時1s
方案:
(1)冗余性:去重校驗,對於50%的可能性查詢出的重復數據。
(2)性能:重復數據和校驗會使性能有所降低,但是從庫是2個分攤QPS的壓力,會使性能有所提高,相互抵消一部分。
2、 不完全分離:商品讀寫模塊依然master主庫,其他地方讀->從庫,寫->主庫。
前提:第一頁查詢邏輯不變
特點:由於聯合營銷系統場景單一,主要是圍繞SKU進行。但是會改善一部分壓力。
方案:
(1) 冗余性:代碼冗余地方多,風格不統一。
(2) 性能:會有部分改善,但是從整體看,數據量大的時候,依然是master主庫讀寫壓力大。
3、 完全分離:全量讀->從庫,全量讀寫->主庫。
前提:分頁查詢(不加同步狀態)
特點:分頁查詢隨着頁數和數據量大的情況呈正相關也會時間越來越大。
方案:
(1) 冗余性: 會重復查詢,由於分頁和性能成正相關,數據量越大,耗時越大。
(2) 分頁查詢解決性能損耗來減少性能響應時間的方案
(2.1)可以采用延時關聯策略(彈性數據庫不支持)
(2.2)采用id序列(利用數據庫id索引過濾)和limit組合使用(效果不大)。
4、 完全分離:全量讀->從庫,全量讀寫->主庫
前提:分頁查詢(加同步狀態),最后一次結果集退出的時候進行兜底全量count查詢並重新執行上述邏輯。
特點:分頁查詢隨着頁數和數據量大的情況呈正相關也會時間越來越大。
方案:與上訴3的方案相同。但避免了查詢出重復數據。
讀寫分離和非分離同時存在,改造后的效果圖(我這里的數據量2億):
讀寫分離之前master主庫CPU使用率95%~99%
讀寫分離之后master主庫CPU使用率10%以下。
從上述來看我們的讀寫分離實踐效果還是蠻不錯的,但是這里如下幾個問題:
0、MySQL主從集群主要解決的問題?
1、MySQL主從同步的幾種策略?以及區別?
2、MySQL的主從延遲到底有多大?
3、多少的延遲時間我們能接受?
4、主從延遲的根本原因是什么?
5、當數據量大讀寫分離只要有寫的地方依然會出現延遲導致的數據不一致情況,該如何解決?
0、MySQL主從集群主要解決的問題?
# 多主庫原因:
高並發的情況下,單台MySQL數據庫的連接數多,這樣QPS/OPS就會非常大。就像上述我提到的我這里的壓測結果,MySQL最大7k的QPS。隨着並發數再多,QPS的處理能力也會下降。那么如何解決這個瓶頸。這個時候就會分庫,分攤QPS/OPS的能力,本來單台master庫的QPS/OPS的請求是2w,我這里分片4個master主庫,則相當於每個master主庫分攤5000請求量。(如果不好理解可以比喻成服務器集群,在服務架構演變過程中單台服務器變為多台服務器,如果依然不能理解的話可以參考下這篇文章**大型網站的演進**)
所以這樣我們可以知道降低了單台服務器的連接數請求量。
# 主從庫原因:
那么對於5000單台請求量(基於剛才的假設模型),他的請求構成比例又是如何呢?以及如何防止流量並發的場景導致的系統不可用癱瘓呢?數據丟失呢?
首先我們可以考慮進行數據備份,以及進行流量分析,而一般往往我們就引入了從庫:
一主一從:一個 Master,一個 Slave
一主多從:一個 Master,多個 Slave
請求構成比例可以參考我上面的這個圖(實際生產環境):
可以從圖中看到比例read:write=10.73k:26 近似等於 10000:1,平均比例:298.91:2.4=150:1的比例,明顯是讀取操作大約寫入操作,相當於1次寫入的時候平均承擔了150次請求讀取操作。而當流量並發上來的時候更是誇張到1w:1。那么我們能不能把靜態的數據讀取放到備份數據從庫上呢?答案明顯是可以的。
1、MySQL主從同步的幾種策略?以及區別?
主從同步機制:
那么這里還需要考慮的是一個復制數據的同步機制:
一主一從的情況
一主多從的情況
根據上圖我們來看下他具體是如何實現同步的,我們都知道其實mysql執行的時候是根據binlog日志進行數據執行的。那么我們當然可以根據binlog日志進行最原始的數據二次處理。
2、MySQL的主從延遲到底有多大?
3、多少的延遲時間我們能接受?
4、主從延遲的根本原因是什么?
實現原理:
主從延時時間:Master 執行成功,到 Slave 執行成功,時間差。
由於從庫從主庫拷貝日志以及串行執行SQL的特點,在高並發場景下,從庫的數據一定會比主庫慢一些,是有延時的。所以經常出現,剛寫入主庫的數據可能是讀不到的,要過幾十毫秒,甚至幾百毫秒才能讀取到。
而且這里還有另外一個問題,就是如果主庫突然宕機,然后恰好數據還沒同步到從庫,那么有些數據可能在從庫上是沒有的,有些數據可能就丟失了
mysql的兩個機制:
# 一個是半同步復制,用來解決主庫數據丟失問題;
semi-sync復制,指的就是主庫寫入binlog日志之后,就會將強制此時立即將數據同步到從庫,從庫將日志寫入自己本地的relay log之后,接着會返回一個ack給主庫,主庫接收到至少一個從庫的ack之后才會認為寫操作完成了
# 一個是並行復制,用來解決主從同步延時問題。
指的是從庫開啟多個線程,並行讀取relay log中不同庫的日志,然后並行重放不同庫的日志,這是庫級別的並行。
監控主從延遲:
Slave 使用本機當前時間,跟 Master 上 binlog 的時間戳比較
pt-heartbeat、mt-heartbeat
本質:同一條 SQL,Master 上執行結束的時間 vs. Slave 上執行結束的時間。
5、當數據量大讀寫分離只要有寫的地方依然會出現延遲導致的數據不一致情況,該如何解決?
1、分析mysql日志 看是否慢查詢太多
2、統計高峰時期的寫入語句數量以及平均值
3、檢查同步時主庫和從庫的網絡數據傳輸量
4、統計服務器運行狀態信息
5、從探針的角度來考慮問題,方法是在Master上增加一個自增表,這個表僅含有1個的字段。當Master接收到任何數據更新的請求時,均會觸發這個觸發器,該觸發器更新自增表中的記錄。如下圖所示:
由於Count_table也參與Mysq的主從同步,因此在Master上作的 Update更新也會同步到Slave上。當Client通過Proxy進行數據讀取時,Proxy可以先向Master和Slave的 Count_table表發送查詢請求,當二者的數據相同時,Proxy可以認定 Master和Slave的數據狀態是一致的,然后把select請求發送到Slave服務器上,否則就發送到Master上。如下圖所示:
瓶頸思考的角度:sql語句包含大量慢查詢,高並發,網絡傳輸問題以及服務器配置
Note:
讀寫分離不適用的場景不能強行使用:
否則讀寫分離的主從延遲導致的影響會不止如下幾條:
異常情況下, HA 無法切換: HA 軟件需要檢查數據的一致性,延遲時,主備不一致
備庫 Hang 會引發備份失敗:flush tables with read lock 會 900s 超時
以 Slave 為基准進行的備份,數據不是最新的,而是延遲的。
這樣就會導致的結果讀寫分離沒有意義,主備容災失效。
那么又回歸到了原始開始的場景,如果要使用那么區分自己的業務場景,並細化事務,提升SQL執行速度,優化索引,減少不必要的DML操作,
以及定位2/8原則到底是哪些表的數據影響主從延遲大。然后最重要的一點就是有時候往往業務邏輯是引發問題的根本原因,優化業務邏輯是
最根本的問題。動態數據變更頻繁的必須走實時的讀寫master主庫。否則高並發流量場景下,讀寫分離帶來的損失會更大。
# 1.mysql數據庫從庫同步的延遲問題
首先在服務器上執行show slave satus;可以看到很多同步的參數:
Master_Log_File:SLAVE中的I/O線程當前正在讀取的主服務器二進制日志文件的名稱
Read_Master_Log_Pos:在當前的主服務器二進制日志中,SLAVE中的I/O線程已經讀取的位置
Relay_Log_File:SQL線程當前正在讀取和執行的中繼日志文件的名稱
Relay_Log_Pos:在當前的中繼日志中,SQL線程已讀取和執行的位置
Relay_Master_Log_File:由SQL線程執行的包含多數近期事件的主服務器二進制日志文件的名稱
Slave_IO_Running:I/O線程是否被啟動並成功地連接到主服務器上
Slave_SQL_Running:SQL線程是否被啟動
Seconds_Behind_Master:從屬服務器SQL線程和從屬服務器I/O線程之間的時間差距,單位以秒計。
#從庫同步延遲情況的出現
1、show slave status顯示參數Seconds_Behind_Master不為0,這個數值可能會很大
2、show slave status顯示參數Relay_Master_Log_File和Master_Log_File顯示bin-log的編號
相差很大,說明bin-log在從庫上沒有及時同步,所以近期執行的bin-log和當前IO線程所讀的bin-log相差很大
3、mysql的從庫數據目錄下存在大量mysql-relay-log日志,該日志同步完成之后就會被系統自動刪除,存在大量日志,說明主從同步延遲很厲害
# a、MySQL數據庫主從同步延遲原理
mysql主從同步原理:
主庫針對寫操作,順序寫binlog,從庫單線程去主庫順序讀”寫操作的binlog”,從庫取到binlog在本地原樣執行(隨機寫),來保證主從數據邏
輯上一致。mysql的主從復制都是單線程的操作,主庫對所有DDL和DML產生binlog,binlog是順序寫,所以效率很高,slave的Slave_IO_Running
線程到主庫取日志,效率比較高,下一步,問題來了,slave的Slave_SQL_Running線程將主庫的DDL和DML操作在slave實施。DML和DDL的IO操作是
隨即的,不是順序的,成本高很多,還可能可slave上的其他查詢產生lock爭用,由於Slave_SQL_Running也是單線程的,所以一個DDL卡主了,需要
執行10分鍾,那么所有之后的DDL會等待這個DDL執行完才會繼續執行,這就導致了延時。
有朋友會問:“主庫上那個相同的DDL也需要執行10分,為什么slave會延時?”,答案是master可以並發,Slave_SQL_Running線程卻不可以。
# b、 MySQL數據庫主從同步延遲是怎么產生的?
當主庫的TPS並發較高時,產生的DDL數量超過slave一個sql線程所能承受的范圍,那么延時就產生了,當然還有就是可能與slave的大型query語句
產生了鎖等待。
首要原因:數據庫在業務上讀寫壓力太大,CPU計算負荷大,網卡負荷大,硬盤隨機IO太高
次要原因:讀寫binlog帶來的性能影響,網絡傳輸延遲。
#c、 MySQL數據庫主從同步延遲解決方案。
架構方面:
1.業務的持久化層的實現采用分庫架構,mysql服務可平行擴展,分散壓力。
2.單個庫讀寫分離,一主多從,主寫從讀,分散壓力。這樣從庫壓力比主庫高,保護主庫。
3.服務的基礎架構在業務和mysql之間加入memcache或者redis的cache層。降低mysql的讀壓力。
4.不同業務的mysql物理上放在不同機器,分散壓力。
5.使用比主庫更好的硬件設備作為slave
總結,mysql壓力小,延遲自然會變小。
硬件方面:
1.采用好服務器,比如4u比2u性能明顯好,2u比1u性能明顯好。
2.存儲用ssd或者盤陣或者san,提升隨機寫的性能。
3.主從間保證處在同一個交換機下面,並且是萬兆環境。
總結,硬件強勁,延遲自然會變小。一句話,縮小延遲的解決方案就是花錢和花時間。
# mysql主從同步加速
1、sync_binlog在slave端設置為0
2、–logs-slave-updates 從服務器從主服務器接收到的更新不記入它的二進制日志。
3、直接禁用slave端的binlog
4、slave端,如果使用的存儲引擎是innodb,innodb_flush_log_at_trx_commit =2
# 從文件系統本身屬性角度優化
master端
修改linux、Unix文件系統中文件的etime屬性, 由於每當讀文件時OS都會將讀取操作發生的時間回寫到磁盤上,對於讀操作頻繁的數據庫文件
來說這是沒必要的,只會增加磁盤系統的負擔影響I/O性能。可以通過設置文件系統的mount屬性,組織操作系統寫atime信息,在linux上的操作為:
打開/etc/fstab,加上noatime參數
/dev/sdb1 /data reiserfs noatime 1 2
然后重新mount文件系統
#mount -oremount /data
PS:
主庫是寫,對數據安全性較高,比如sync_binlog=1,innodb_flush_log_at_trx_commit = 1 之類的設置是需要的
而slave則不需要這么高的數據安全,完全可以講sync_binlog設置為0或者關閉binlog,innodb_flushlog也可以設置為0來提高sql的執行效率
1、sync_binlog=1 o
MySQL提供一個sync_binlog參數來控制數據庫的binlog刷到磁盤上去。
默認,sync_binlog=0,表示MySQL不控制binlog的刷新,由文件系統自己控制它的緩存的刷新。這時候的性能是最好的,但是風險也是最大的。
一旦系統Crash,在binlog_cache中的所有binlog信息都會被丟失。
如果sync_binlog>0,表示每sync_binlog次事務提交,MySQL調用文件系統的刷新操作將緩存刷下去。最安全的就是sync_binlog=1了,表示
每次事務提交,MySQL都會把binlog刷下去,是最安全但是性能損耗最大的設置。這樣的話,在數據庫所在的主機操作系統損壞或者突然掉電的
情況下,系統才有可能丟失1個事務的數據。
但是binlog雖然是順序IO,但是設置sync_binlog=1,多個事務同時提交,同樣很大的影響MySQL和IO性能。
雖然可以通過group commit的補丁緩解,但是刷新的頻率過高對IO的影響也非常大。對於高並發事務的系統來說,
“sync_binlog”設置為0和設置為1的系統寫入性能差距可能高達5倍甚至更多。
所以很多MySQL DBA設置的sync_binlog並不是最安全的1,而是2或者是0。這樣犧牲一定的一致性,可以獲得更高的並發和性能。
默認情況下,並不是每次寫入時都將binlog與硬盤同步。因此如果操作系統或機器(不僅僅是MySQL服務器)崩潰,有可能binlog中最后的語句丟失了。
要想防止這種情況,你可以使用sync_binlog全局變量(1是最安全的值,但也是最慢的),使binlog在每N次binlog寫入后與硬盤同步。即使sync_binlog
設置為1,出現崩潰時,也有可能表內容和binlog內容之間存在不一致性。
2、innodb_flush_log_at_trx_commit (這個很管用)
抱怨Innodb比MyISAM慢 100倍?那么你大概是忘了調整這個值。默認值1的意思是每一次事務提交或事務外的指令都需要把日志寫入(flush)硬盤,
這是很費時的。特別是使用電池供電緩存(Battery backed up cache)時。設成2對於很多運用,特別是從MyISAM表轉過來的是可以的,它的意思
是不寫入硬盤而是寫入系統緩存。
日志仍然會每秒flush到硬 盤,所以你一般不會丟失超過1-2秒的更新。設成0會更快一點,但安全方面比較差,即使MySQL掛了也可能會丟失事務的
數據。而值2只會在整個操作系統 掛了時才可能丟數據。
**數據庫——MySQL分庫分表的演進和實踐以及中間件的比較:**https://blog.csdn.net/wolf_love666/article/details/82773300
**大型網站架構演化:**https://blog.csdn.net/wolf_love666/article/details/77162031
**主從同步和主從延遲:**http://ningg.top/inside-mysql-master-slave-delay/
**解決Mysql讀寫分離數據延遲:**https://blog.51cto.com/ww123/2108302