6.9 同步 FAQ
問: master還在運行中,如何在不停止它的情況下配置slave?
答: 需要設計幾個選項參數。如果已經有了master的備份並且記錄了數據快照二進制日志文件名以及偏移位置(運行 SHOW MASTER STATUS 查看結果),執行以下步驟:
確定slave指定了一個唯一的服務器編號。
在slave上執行如下語句,把一些選項值改成實際值:
MySQL> CHANGE MASTER TO
-> MASTER_HOST='master_host_name',
-> MASTER_USER='master_user_name',
-> MASTER_PASSWord='master_pass',
-> MASTER_LOG_FILE='recorded_log_file_name',
-> MASTER_LOG_POS=recorded_log_position;
在slave上執行 START SLAVE 語句。
如果事先沒有備份master的數據,可以用以下方法快速創建一個備份。以下所有的操作都是在master上。
提交語句:
MySQL> FLUSH TABLES WITH READ LOCK;
確保這個鎖一直存在,執行以下命令(或者其他類似的):
shell> tar zcf /tmp/backup.tar.gz /var/lib/MySQL
執行以下語句,記錄下輸出的結果,後面要用到:
MySQL> SHOW MASTER STATUS;
釋放鎖:
MySQL> UNLOCK TABLES;
上述步驟的另一個辦法是創建master的SQL轉儲文件。只需在master上執行 MySQLdump --master-data 命令,然後將導出來的SQL轉儲文件載入slave。不過,這麼做會制作二進制數據快照的方式慢一點。
無論使用上述兩種方法的哪種,最後都能創建master的數據快照然後記錄二進制日志文件名以及偏移位置。可以在好幾的其他的slave上使用同一個備份的二進制數據快照。得到master的快照後,只要master的二進制日志完好無損,接著就能開始設置slave了。兩個決定是否需要等待較長時間的限制是:在master上磁盤空間保存二進制日志,以及slave從master抓取更新事件。
也可以使用 LOAD DATA FROM MASTER。這個語句可以很方便地在slave上取得數據快照並且能立刻調整二進制日志文件名以及偏移位置。在將來,我們推薦用 LOAD DATA FROM MASTER 來設置slave。警告,它只能用於 MyISAM 表,並且可能會保持一個較長時間的讀鎖。由於它還沒達到所期望的高效率,因此如果數據表很大,最好還是在執行完 FLUSH TABLES WITH READ LOCK 後直接制作二進制數據快照。
問:是否slave總是需要連接到master?
答:不,非必需。slave可以好幾小時甚至幾天關閉或者不連接master,然後重連再取得更新操作日志。例如,可以在撥號鏈接上設置一個mater/slave關系,撥號可能只是零星的不定期的連接。這種做法隱含的是,在任何指定的時間裡,除非使用特殊的度量標准,否則slave不能保證總是能和master保持同步。在未來,有個選項可以阻止master,除非至少有一個slave在同步中。
問:怎麼知道比master晚了多少?也就是說,怎麼知道slave最後同步的時間?
答:如果slave是4.1.1或者更高,只需查看 SHOW SLAVE STATUS 結果中的 Seconds_Behind_Master 字段。對於老版本,可以用以下辦法。如果在slave上執行 SHOW PROCESSLIST 語句結果顯示SQL線程(對MySQL 3.23則是slave線程)正在運行,這就意味著該線程至少從master讀取一個更新操作事件。詳情請看"6.3 Replication Implementation Details"。
當SQL線程執行一個master上讀取的更新操作事件時,它把自己的時間改成事件的時間(這也就是 TIMESTAMP 也要同步的原因)。在
SHOW PROCESSLIST 結果中的 Time 字段中,slave的SQL線程顯示的秒數就是最後一次同步的時間戳和slave本機的實際時間相差秒數。可以根據這個值來判斷最後同步的時間。注意,如果slave已經從master斷開好幾個小時了,然後重新連接,就能看到slave的
SHOW PROCESSLIST 結果中的SQL線程的Time 字段的值類似3600。這是因為slave正在執行一個小時前的語句。
問:如何強制master在slave趕上全部更新之前阻止更新操作?
答:執行以下步驟:
在master上,執行以下語句:
MySQL> FLUSH TABLES WITH READ LOCK;
MySQL> SHOW MASTER STATUS; 記錄下結果中的日志文件名以及偏移位置,它們是同步的坐標值。
在slave上,提交以下語句,MASTER_POS_WAIT() 函數的參數的值就是前面取得的同步坐標值:
MySQL> SELECT MASTER_POS_WAIT('log_name', log_offset);
SELECT 語句會阻止更新,直到slave同步到了上述日志文件及位置。在這個時候,slave就和master保持同步了,並且這個語句就會返回。
在master上,執行以下語句允許master重新處理更新操作:
MySQL> UNLOCK TABLES;
問:設置一個雙向復制時要注意什麼問題?
答:MySQL同步目前還不支持任何在master和slave上的分布式(跨服務器)更新鎖協議以保證操作的原子性。也就是說,存在這樣的可能性:客戶端A在並存的master 1上做了一個更新,同時,在它同步到並存master 2上之前,客戶端B在master 2上可能也做了一個和客戶端A在master 1上不同的更新操作。因此,當客戶端A所做的更新同步到master 2時,它將產生和master 1上不同的數據表,盡管master 2上的更新操作也全都同步到master 1上去。這意味著除非能確保所有的更新都能以任何順序安全地執行,否則不要使用雙向同步,或者除非注意在客戶端程序中的不知原因的無序更新操作。
同時也要意識到在所關心的更新問題上,雙向同步實際上並不能很大地改善性能(甚至沒有)。兩個服務器都需要執行同樣數量的更新操作,在一台服務器上也是。唯一區別的是,可能這樣做會減少一些鎖爭奪,因為來自其他服務器的更新操作都會被串行地放到slave線程中。甚至這種好處還可以作為網絡延遲的補償。
問:我如何利用同步來提高系統性能?
答:需要安裝一個服務器作為master並且把所有的寫操作直接放在這上面。然後配置多個廉價的使用機架磁盤的slave,把讀操作分配給master和slave。還可以在啟動slave時使用 --skip-innodb, --skip-bdb, --low-priority-updates,和 --delay-key-write=ALL 選項來提高slave端的性能。這種情況下,slave會使用非事務的 MyISAM 表來代替 InnoDB 和 BDB 表,已取得更快速度。
問:如何准備客戶端應用程序的代碼來適應同步應用?
答:如果代碼中負責存取數據庫的部分已經被合理地抽象化/模塊化了,將它們轉化成適用運行於同步環境中將會很平滑和簡單。只需要修改數據庫存取實現部分,把所有的寫操作放到master上,把所有的讀操作放到master或者slave上。如果你的代碼還沒達到這個層次的抽象化,那麼這將成為整理代碼的機會和動機。可以使用類似以下函數創建封裝類庫或者模塊:
safe_writer_connect()
safe_reader_connect()
safe_reader_statement()
safe_writer_statement()
每個函數名的 safe_ 表示它們會處理所有的錯誤情況。可以使用其他函數名。重要的是,要為讀連接、寫連接、讀、寫定義好統一的接口。
然後將客戶端代碼轉換成使用封裝的類庫。已開始可能是很痛苦且麻煩的,不過在將來長期運行中就能得到回報了。所有使用上述方法的應用程序都會在master/slave配置中有優勢,即使包含多個slave。這些代碼將很容易維護,一些額外的麻煩也會很少。自豪需要修改一個或者兩個函數;例如,想要記錄每個語句執行了多長時間,或者在上千個語句中哪個出現錯誤了。
如果已經寫了很多代碼,你可能想要自動轉換它們,那麼可以使用MySQL發布的 replace 工具,或者自己寫轉換腳本。理想地,你的代碼已經使用了統一的編程風格。如果不是,最好重寫它們,或者可以遍歷檢查一下,手工規范化一下代碼風格。
問:MySQL同步何時且有多少能提高系統性能?
答:MySQL同步對於頻繁讀但不頻繁寫的系統很有好處。理論上來講,使用單一master/多slave的配置,就可以通過這個方法來衡量系統:增加更多的slave直到用完所有的網絡帶寬或者master的更新操作增長到了不能再處理的點了。
想要知道增加多少個slave之後得到的性能才能平穩,以及能提高多少性能,就需要知道查詢模式,並且根據經驗對典型的master和slave做讀(每秒讀或 max_reads)和寫(max_write)基准測試得到它們之間的關系。下例展示了一個理想系統取得的性能的簡單計算方法。
設定系統負載由10%寫和90%讀組成,我們已經通過基准測試確定 max_reads 是1200 - 2 * max_writes。換句話說,系統可以達到每秒做沒有寫的1200次讀操作,寫操作平均是讀操作的2倍慢,它們之間的關系是線性的。讓我們假設master和每個slave都有同樣的容量,有一個master和N個slave。每個服務器(master或slave):
reads = 1200 - 2 * writes
reads = 9 * writes / (N + 1) (讀是分開的,但是所有寫是在所有的服務器上的)
9 * writes / (N + 1) + 2 * writes = 1200
writes = 1200 / (2 + 9/(N+1))
最後的等式說明了N個slave的最大寫數量,給它每分鐘的最高讀頻率1200和1次寫9次讀的機率。
分析結論比率如下:
如果 N = 0(意味著沒有同步),系統大致可以處理每秒 1200/11 = 109 次寫。
如果 N = 1,增加到每秒 184 次寫。
如果 N = 8,增加到每秒 400 次寫。
如果 N = 17,增加到每秒 480 次寫。
最終,隨著N接近無窮大(我們的預算為負無窮大),則可以達到幾乎每秒 600 次寫,大約提高系統吞吐量 5.5 倍。盡管如此,當有8台服務器時,已經提高了4倍了。
注意,上面的計算是假設了網絡帶寬無窮大,並且忽略了一些系統中比較大的因素。在很多情況下,當系統增加 N 個同步slave之後,是無法精確計算出上述預計結果的。不過,先看看下列問題將有助於你知道是否有和有多少系統性能上的改善:
系統讀/寫得比率是多少?
減少讀操作後一個服務器能增加處理多少寫操作?
你的網絡帶寬足夠給多少slave使用?
問:如何利用同步提供冗余/高可用性?
答:使用當前已經可用的特性,可以配置一個master和一個(或多個)slave,並且寫一個腳本監控master是否運行著。然後通知應用程序和slave在發現錯誤時修改master。一些建議如下:
使用 CHANGE MASTER TO 語句告訴slave修改master。
一個讓應用程序定位master所在主機的辦法就是給master使用動態DNS。例如bind就可以用 `nsupdate` 來動態更新DNS。
使用 --log-bin 選項,不使用
--log-slave-updates 選項來啟動slave。這樣就能讓slave運行 STOP SLAVE; RESET MASTER 語句後隨時准備變成master,並且在其他slave上運行
CHANGE MASTER TO。例如,有以下配置方案:
WC v WC----> M / | / | v v v S1 S2 S3
M 表示masetr,S 表示slave,WC表示提交讀寫操作的客戶端;只提交讀操作的客戶端沒有表示出來,因為它們無需切換。S1,S2,S3都是使用
--log-bin 選項,不用 --log-slave-updates 選項運行的slave。由於除非指定 --log-slave-updates 參數,否則從master讀到的更新操作都不會記錄到二進制日志中,因此每個slave上的二進制日志都是空的。如果因為某些原因 M 不能用了,可以指定一個slave作為master。例如,如果指定S1,則所有的WC都要重定向到S1上,S2和S3都需要從S1上同步。
確定所有的slave都已經處理完各自的中繼日志了。在每個slave上,提交 STOP SLAVE IO_THREAD 語句,然後檢查 SHOW PROCESSLIST 的結果直到看到 Has read all relay log 了。當所有的slave都這樣子之後,就可以按照新的方案設置了。在slave S1上提交
STOP SLAVE 和 RESET MASTER 語句將其提升為master。
在其他slave S2和S3上,提交 STOP SLAVE 和 CHANGE MASTER
TO MASTER_HOST='S1' ( 'S1' 代表S1的真實主機名) 語句修改master。把S2,S3如何連接到S1的參數(用戶,密碼,端口等)都附加到 CHANGE MASTER 後面。在
CHANGE MASTER 中無需指定S1的二進制日志文件名和偏移位置:因為 CHANGE MASTER 默認就是第一個二進制日志和偏移位置4。最後,在S2和S3上提交 START SLAVE 語句。
然後讓所有的WC都把他們的語句重定向到S1上。從這個時候開始,從所有的WC發送到S1上的更新語句都會寫到S1的二進制日志中,它們包含了從M死掉之後發送到S1的全部更新語句。
配置結果如下:
WC / | WC | M(unavailable) \ | \ | v v S1<--S2 S3 ^ | +-------+
當M又起來了之後,只需在M上提交和在S2和S3上的一樣的 CHANGE MASTER 語句,將它變成一個slave並且讀取自從它死掉之後的全部WC提交的更新操作。想要把M重新變成master(例如因為它的性能更好),就執行類似上面的操作,把S1當作失效了,把M提升為新的master。在這個步驟中,別忘了在把S2和S3修改成為M的slave之前在M上運行 RESET MASTER 語句。否則的話,它們會從M開始失效的那個時刻開始讀取WC提交的更新操作日志。
現在我們就運行著一個完整的自動選擇master的MySQL同步系統,不過在它准備好之前,需要創建自己的監控工具。
6.10 同步疑難解答
如果按照上述步驟設定好同步之後,它不能正常工作的話,首先檢查以下內容:
查看一下錯入日志信息。不少用戶都在這方面做得不夠好以至於浪費時間。
master是否在記錄二進制日志?用 SHOW MASTER STATUS 檢查一下狀態。如果是,Position 的值不為零;否則,確定master上使用了 log-bin 和 server-id 選項。
slave是否運行著?運行 SHOW SLAVE STATUS 語句檢查
Slave_IO_Running 和 Slave_SQL_Running 的值是否都是。如果不是,確定是否用同步參數啟動slave服務器了。
如果slave正在運行,它是否建立了到master的連接?運行
SHOW PROCESSLIST 語句檢查I/O和SQL線程的 State 字段值。詳情請看"6.3 Replication Implementation Details"。如果I/O線程狀態為 Connecting to master,就檢查一下master上同步用戶的權限是否正確,master的主機名,DNS設置,master是否確實正在運行著,以及slave是否可連接到master,等等。
如果slave以前運行著,但是現在停止了,原因通常是一些語句在master上能成功但在slave上卻失敗了。如果salve已經取得了master的全部快照,並且除了slave線程之外不會修改他的數據,那麼應該不會發生這樣的情形。如果確實發生了,那麼可能是一個bug或者你碰到了"6.7 Replication Features and Known Problems"中提到的同步限制之一。如果是一個bug,那麼請按照"6.11 Reporting Replication Bugs"的說明報告它。
如果一個語句在master上成功了,但是在slave上卻失敗了,並且這時不能做一次完整的數據庫再同步(也就是刪除slave上的數據,重新拷貝master的快照),那麼試一下:
判斷slave的數據表是否和master的不一樣。試著找到怎麼會發生這種情況,然後將slave的表同步成和master一樣之後運行 START SLAVE。
如果上述步驟不生效或者沒有執行,試著這個語句是否能被手工安全地運行(如果有必要),然後忽略master的下一個語句。
如果決定要忽略master的下一個語句,只需在slave上提交以下語句:
MySQL> SET GLOBAL SQL_SLAVE_SKIP_COUNTER = n;
MySQL> START SLAVE;如果下一個語句沒有使用 AUTO_INCREMENT 或 LAST_INSERT_ID(),那麼 n 的值應為為 1。否則,它的值為 2。設定為 2 是因為 AUTO_INCREMENT 或 LAST_INSERT_ID() 在master的二進制日志中占用了2條日志。
如果確定slave精確地同步master了,並且沒有除了slave線程之外的對數據表的更新操作,則推斷這是因為bug產生的差異。如果是使用最近的版本,請報告這個問題,如果使用的是舊版本,試著升級一下。
6.11 報告同步Bugs
當確定沒有包含用戶的錯誤,並且同步還是不能正常工作或者不穩定,就可以報告bug了。我們需要你盡量多的跟蹤bug的信息。請花點時間和努力准備一個好的bug報告。
如果有一個演示bug的可重現測試案例的話,請進入我們的bug數據庫http://bugs.MySQL.com/。如果碰到一個幽靈般的問題(不可重現),請按照如下步驟:
確定沒有包含用戶錯誤。例如,在slave線程以外更新slave的數據,那麼數據就會不能保持同步,也可能會導致違反更新時的唯一鍵問題。這是外部干涉導致同步失敗的問題。
使用 --log-slave-updates 和 --log-bin 選項啟動slave。這會導致slave將從master讀取的更新操作寫到自己的二進制日志中。
在重設同步狀態之前保存所有的證據。如果我們沒有任何信息或者只有粗略的信息,這將很難或者不可能追查到這個問題。需要收集以下證據:
master上的所有二進制日志
slave上的所有二進制日志
發現問題時,在master上執行 SHOW MASTER STATUS 的結果
發現問題時,在master上執行 SHOW SLAVE STATUS 的結果
記錄master和slave的錯誤日志
用 MySQLbinlog 來檢查二進制日志。例如,用以下方法有助於找到有問題的查詢:
shell> MySQLbinlog -j pos_from_slave_status
/path/to/log_from_slave_status | head
一旦收集好了問題的證據,首先將它隔離到一個獨立的測試系統上。然後在我們的bug數據庫http://bugs.MySQL.com/上進可能詳細地報告問題。