当网站越做越大的时候,资料库的 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 的优缺点比较表格。
DRBDMMM架构复杂度(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 ErrorWarn-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 主机,很单纯没什�N问题需要解决,以下来讲 MMM 的解决法。
在 PIXNET 这边,去年八月改版之后,就没有什�N地方会直接用 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 有什�N解决方法,MMM 的话则是完全没这问题,因为 MMM 的 slave 平常就是 online 状态了。(不过正常来讲也不会有这�N多 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 之间,这资料都没有被任何人改过,那�N你可以放心的 set 进去修改他的值。
如果说这段期间 cas unique 不一样了,那�N你的 set 会失败,表示中间已经被人抢先修改过了,那�N你就必须视情况看该怎�N处理。
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 才结束。