當網站越做越大的時候,資料庫的 HA (High Available) 就很重要了。業界常用的是 DRBD,上個月我去了北京參加系統架構師大會,中國的網站大部分解決資料庫 HA 都是用 DRBD 。
DRBD 簡單說就是 RAID 1 over TCP 。也就是透過 TCP 讓兩台主機的硬碟內容完全一模一樣,因此不只是 MySQL 可以使用 DRBD ,只要是任何會需要用到硬碟的 Server ,都可以用 DRBD 來做 HA,加上 MySQL InnoDB Engine 本身的 crash safe ,可以做到當一台主機掛點時,另一台主機可以用一模一樣的硬碟內容在最短時間把服務重新跑起來。
DRBD 已經被業界使用了多年,已經算是一個很成熟的架構。2005 年 8 月,LiveJournal 的 Brad Fitzpatrick (也是 memcached, MogileFS, Perlbal, Gearman 的作者) ,寫了一篇 LiveJournal's Backend ,裡面也有提到 DRBD 。那份文件也成為了很多新興網站架構時參考的文件,去北京的系統架構師大會時也發現中國的網站架構大部分與 Livejournal 相似,大概也都受到這份文件影響吧。
2006 年,出現了新的 MMM(Mysql Master-Master Replication Manager) 的架構,到現在來說也算是比較新的技術,業界上使用的也不多,上個月去北京參加系統架構師大會時也沒有任何一家公司有提到。但是相對 DRBD 起來,我比較喜歡 MMM 的架構,所以這篇文章應該也會比較著重於 MMM 的介紹。
MMM 就是在 MySQL 的 Master-Master 加強版。多用一台 Monitor 去監控兩台 MySQL 主機,當主機掛點時,會主動讓另一台主機變成 Master 。
來畫個 DRBD vs MMM 的優缺點比較表格。
DRBD | MMM | |
架構複雜度(1) | (勝) RAID 1 的系統架構可以讓兩台主機視為一台,架構單純。加 SLAVE 也很好加。 |
(敗) 兩台都是 Master ,程式上必需要把 Master/Slave 分開處理。如果需要再加 SLAVE 上來的話,設定也比較複雜,因為 MASTER 是兩台機器會跳來跳去。 |
架構複雜度(2) | (勝) IP 至少需要三個,不需要 Monitor 。 |
(敗) IP 至少需要四個(兩台主機 + Writer IP + Reader IP),還需要一台 Monitor。 |
架構複雜度(3) | (勝) 因為視為一台主機,資料可以隨意 INSERT、UPDATE、DELETE |
(敗) 另外因為兩台 MySQL ,可能會造成一些 Query 使得 Replication Error |
Warn-up | (敗) Stand by 的機器平常 MySQL 是沒跑起來的狀態,當跳機器時可能需要 1 分鐘以上的 slow start 時間。 |
(勝) Stand by 的機器平時就是 online 當 reader 的狀態,因此跳機器時幾乎沒有 slow start 時間,對 user 來講沒有斷線時間。 |
Replication Delay | (勝) 因為架構上可以視為只有一台主機,因此完全不需要 care replication delay 問題。 |
(敗) 平常上線狀態算是 Master/Slave 架構,因此需要注意 Replication Delay 問題。 |
Slow Query (Ex: ALTER TABLE) | (敗) 架構上相當於只有一台主機,因此做 Slow Query 比較困難。 |
(勝) Master/Slave 的架構,可以把 Slave 先 offline ,做完 ALTER TABLE 後,再讓 Slave 跳成 Master ,換成另一台做。 |
機器使用率 | (敗) Stand by 的那台主機真的就是 Stand by ,完全沒有使用到。 |
(勝) Stand by 的主機還可以當作 Reader ,當一個 Master/Slave 的 slave 用。 |
Split-Brain | (平) 發生機率低,但是發生之後很難不捨棄資料,必須要放棄一邊的資料讓兩邊重新同步一次 |
(平) 若 SQL query 設計的不夠好,在跳機器時就很容易發生,但是如果發生的話,要修復資料的難度比較低,不過需要工人智慧來做處理。 |
以上是這一年來 PIXNET 使用 DRBD 和 MMM 上大概遇到的狀況的比較。
接下來下面再針對以上提到的問題再提出解決問題的方法吧。
1. 架構複雜度:(敗的 MMM 的解決方法)
DRBD 不太需要講了,因為架構上可以把他視為只有一台 MySQL 主機,很單純沒什麼問題需要解決,以下來講 MMM 的解決法。
在 PIXNET 這邊,去年八月改版之後,就沒有什麼地方會直接用 PHP 執行 MySQL query 了,我們所有跟資料庫有關的存取,都是透過我們自己寫的一套 library ,而這套 library 就已經解決了寫入會透過 Master , 讀取會透過 Slave 的問題。
如果要使用 MMM 或是任何 Master/Master 架構的話,就儘量不要自己寫 SQL query 用 mysql_query 的寫法,最好是把所有透過 MySQL 的存取的部分用 library 解決,這樣子可以保證寫入會透過 Master ,讀取就全部從 Slave ,如此達到 load balance。但是相對的就會遇到架構比較複雜,也會有 「Replication Delay」 的問題(後面會講解決法。)
另外在 Query 的架構上,因為 MMM 是兩台 Master ,有可能會造成兩台 Master 分別 INSERT 而 Primary key 重覆而 Replication Error。 (Ex: 目前 A[Master],B[Slave] 兩台機器 max increment id 都是 12345 ,這時候我在 A 機器 INSERT 一筆資料(Auto Increment ID = 12346)之後,這個 Query 還來不及 Repliction 到 B 去,A 機器網路就掛了,於是 B 機器就自動成為 Master ,接下來再有人 INSERT 資料進 B 之後,又出現一筆 ID = 12346 的資料。等到 A 機器復活之後,兩台都有了 ID = 12346 的資料,於是就造成 Replication Error 了。
解決方法就是利用 MySQL 的 auto_increment_increment 和 auto_increment_offset 兩個設定
auto_increment_increment = 2 讓兩台機器的 auto incenment 不是每次加一,而是每次加二
然後兩台主機一台的 auto_increment_offset 設 0 ,一台設 1 。這樣子就會讓兩台主機的 auto increment id 一台是奇數、一台是偶數,兩台主機絕對不會遇到撞到的情況。
另外在 CREATE TABLE 或是 DROP TABLE 時的指令,也要記得下 CREATE TABLE IF EXISTS ,讓 Slave 在不存在該 table 時也能正常運作。 UPDATE 的 ON DUPLICATE KEY UPDATE 也要常用。
2. Warm up:
當 DRBD 要跳機器時,因為 stand by 的那台機器等於是重新跑起來一台 MySQL server ,在資料庫 loading 比較大時,因為資料庫的內容都沒有在 memory cache 內,因此可能會需要 1 分鐘以上的 slow start 時間(依照 PIXNET 的經驗一分鐘以內大部分的 Query 都會累積著,此時機器的 IO Usage 也是 100% ,一分鐘開始 Query 就會消化完,機器重開兩分鐘後就可以正常運作了。這邊我不知道 DRBD 有什麼解決方法,MMM 的話則是完全沒這問題,因為 MMM 的 slave 平常就是 online 狀態了。(不過正常來講也不會有這麼多 Warm up 的狀況吧?誰沒事會無聊去重開MySQL server ?)
3. Replication delay:
這點算是 MMM 的致命傷吧,因為 MMM 算是 Master/Slave 的架構,因此就會遇到 Replication delay 。使用者在 Master 寫入了一筆資料,接下來頁面重新整理在 Slave 要抓資料,但是該筆 Master 寫入的資料還來不及寫入 Slave ,因此在新的頁面中使用者就沒看到剛剛所寫入的資料,讓使用者誤以為剛剛寫入失敗。
這邊的解決方法有幾個。
(1) 使用 memcache : 在寫入資料庫時,同時也寫入 memcache ,然後要讀取的時候優先去 memcache 抓,抓不到才會去資料庫問,如此一來架構上因為 memcache 做了,效能提升了,也因為 memcache 同一個 ID 只會對應到一台,因此不用 care replication 的問題。
但是此法在 PHP 似乎無用,原因是可能會有 race condition 。
在寫入 cache 的流程大部分是這樣
a. 取得 cache 資料,存進變數 $cache
b.把要 INSERT 的資料塞進資料庫後,再把這資料同時也塞進 $cache 內
c. 把 $cache 寫進 cache
但是如果有兩個人在幾乎同時要寫入資料,原先的 a => b => c 的流程
變成 a1 => b1 => c1 和 a2 => b2 => c2 。
因為兩者的時間太過接近,可能變成 a1 => a2 => b1 => b2 => c1 => c2 的流程。結果 c2 把 c1 的寫入完全覆蓋掉,c1 的修改就在 cache 中消失的無影無蹤了。
Memcached protocol 在這方面有提供解決方案,叫 cas unique。
單筆 row 只要有修改過,他的 cas unique 一定會修改。
在 get 一筆資料時,他會回傳一個 cas unique 給你。
你在 set 資料時,可以同時把 cas unique 也帶進去。 如果當你 set 時,server 的 cas unique 等於你所給的數字。表示從你 get 到 set 之間,這資料都沒有被任何人改過,那麼你可以放心的 set 進去修改他的值。
如果說這段期間 cas unique 不一樣了,那麼你的 set 會失敗,表示中間已經被人搶先修改過了,那麼你就必須視情況看該怎麼處理。
set, delete 會需要 cas unique ,如果你用 inc, append, prepend 等指令的話,就可以不用在乎 race condition 問題。
不過可惜的是 PHP 預設的 memcache extension 只支援最基本的 set/get ,沒有 cas unique 功能,因此這部分沒辦法這樣解決。
(2) 當資料庫有修改時,接下來的幾筆 query 要直接從 Master 去要,而不是從 Slave
這 點的話光靠程式寫是很困難的。 PIXNET 這邊有透過自己寫的 library ,有做到當這次的 PHP process 有連到過 master 的話,接下來無論是讀寫都會從 master 去讀寫資料。只有在完完全全純粹是讀資料的 process ,才有可能會從 slave 去 query 資料。
不過這種作法並沒辦法完全解決問題,原因是很多網頁的作法是
a. POST 到頁面,去資料庫寫入資料,寫入成功後, 302 Redirect 到成功頁面
b. 在成功頁面 GET 資料。
其中 a 和 b 已經可能是兩個完全不同的 PHP process ,因此 a 在 master 寫入資料後, b 是在 slave 讀資料,還是有可能有因為資料還沒從 master replication 到 slave 去而讀不到資料的情況。
(3) 使用 MySQL 的 MASTER_POS_WAIT('binlog file', 'binlog pos', ['timeout']) function
這 方法並不是一個很乾淨的作法, 在 SLAVE 機器上下 SELECT MASTER_POS_WAIT('xxx', '1234'); 的 MySQL query。他會等到 replication 追到 xxx 這個 replication binlog file 的 1234 的位置他才會 return。如此一來可以作到保證在 slave 的 process 也可以讀到剛剛在 master 寫入的資料。 但是這作法之所以不乾淨是因為寫程式的人必須要顧慮到 database 架構,等於是程式和 db 架構的分野不夠清楚。而且如果兩者的 replication delay 太大,可能會造成 MASTER_POS_WAIT 永無止盡而讓呼叫他的 PHP 程式就卡在那邊。所以這不算是個好方法。
(4) 把寫入和回傳資料的部分都用同一個 process 處理。
在 (2) 中提到大部分網頁的 a, b 兩步驟,寫入成功後就用 302 redirect 導到成功頁面去。這邊改成寫入成功後不導頁面,而是在process 直接回傳成功頁面給你。 由於都是同一個頁面,因此不會有重新連到 slave 抓資料的問題。資料可以全部都是從 master 抓的。就不會有 Replication Delay 的問題了。
缺點時因為不是用 302 重導的,在 Browser 來講,這一頁還是一個以 POST 所產生的頁面,因此 user 可以按 F5 重新整理重送一次 POST 資料,造成 User 有機會可以洗資料。(有時候 User 按 F5 不是惡意的,純粹只是想要重整資料)
不過現在 AJAX 的技術越來越成熟,如果寫入和處理回傳資料都是用 AJAX 的話,這個缺點就不存在了,因為在 AJAX 裡沒有使用者重新整理重送資料的問題。
4. 需要做 Slow Query:
MMM 的架構,是可以在完全不中斷服務的情況下做到 ALTER TABLE 這種 slow query。現在是 Master Master 架構,首先先把 web 上所有 read/write 都導到同一台 Master ,讓另一台 Master 完全沒有量。接下來就在那一台 Master 做出你要的 ALTER TABLE 動作。等到做完 ALTER TABLE 之後,這台 Master 會再自動把另一台這段期間 replication 的資料再抓回來。等到 replication 同步了就大功告成了,再來就是把量都導到這台再重覆同樣的動作,就做到了 ALTER TABLE 而且完全不會中斷服務。
不過這部分要注意就是在做 ALTER TABLE 的時候,必須要 ALTER TABLE 成不影響上線服務的狀態。像是 INT 改成 BIGINT 、增加一組 INDEX KEY 之類的,這些做了之後不會影響到原先的服務。
Split-Brain 和其他雜七雜八的地方就留到第二篇再講吧。
========================================
DRBD 與 MMM 的比較
ronny 去了在北京辦的「2009系統架構師大會」寫的文章:「MySQL 的 DRBD 與 MMM (1)」。
裡面有些地方可以補充一下,在不完全能接受 replication delay 的情況下,可以用 Google 提供的 SemiSyncReplicationDesign patch,所有在 master 的新增、更新、刪除動作時 (也就是有寫入的動作時) 都會等到 master 傳給 slave,並且 slave 說 OK 才結束。
转载:http://clip.artchiu.org/2009/10/19/mysql-%e7%9a%84-drbd-%e8%88%87-mmm-1/