原文网址:http://www.bzfshop.net/article/180.html
对一个电子商务网站而言,最宝贵的资源就是数据。服务器是很廉价的东西,即使烧了好几个也问题不大,但是用户数据如果丢失了,那整个业务就会陷入停顿,一天由于业务停顿而带来的损失可能是好几个服务器几年的成本。随着棒主妇商城(http://www.bangzhufu.com)业务的增长,我们开始考虑异地容灾的问题,假如某天服务器突然报废了,或者机房整个挂掉了,或者机房所在城市发生地震了,我们需要保证公司业务的正常运行,尽量做到业务不中断,或者中断时间非常短(10分钟内切换到另外一个城市的备份上,重新启动业务)。
这篇文章记录了我们搭建“垮IDC异地备份”的实现,对于一个中小型电商而言,它足够可靠(数据延迟在1秒以内,最差情况丢失灾难发生前1秒的数据),它足够廉价,它足够安全(数据不会有任何入侵的可能性),它实现足够简单(普通的运维人员就可以实现)。
1. 选择备份服务器
备份服务器,只是用于存储数据,无所谓性能的要求,选择一个配置最低 + 磁盘容量够大的VPS就足够了。
考虑到网络备份的实时性要求,备份VPS服务器到你的主站服务器之间网络性能要好,目前我们选择的备份服务器到主站服务器之间的 ping 延迟在 20ms 以内。
VPS 选择尽量不要和主服务器在同一个城市或者同一个地区。比如四川发生地震,那边的机房都受到影响,如果你的主服务器和备份服务器都在四川,那备份等于没用了。
目前我们的主服务器和备份服务器分别在中国的东部和中西部不同城市,应该不至于发生从东部一直地震到中西部的情况。只要有一个城市服务器没有受到影响,我们就可以在 10 分钟内恢复整个网站的业务,并且网站交易数据几乎没有损失(备份最多损失灾难前 1秒下的订单)
2. 安全性配置
备份数据都是网站最机密的数据,安全就显得非常重要。目前我们采用 iptables 做 IP 认证,确保只有我们自己的服务器才能相互访问,同时数据传输采用 SSL 加密,这样就算有人做网络监听也没法得到我们的数据。
3. 数据库的实时同步备份
数据库备份采用 Master —> Slave 的方式, Slave 纯粹就是一个实时同步的备份,本身没有任何业务访问它。
注意:要做数据库的 Master —> Slave 配置,最好两个数据库是完全一样的版本,否则你可能会遇到很多莫名的错误。
主服务器配置
修改 mysql 的配置文件 my.cnf 在 [mysqld] 下面加入
## 在 [mysqld] 下面加入 server-id = 1 log_bin = /var/log/mysql/mysql-bin.log ## 注意,格式使用 MIXED,千万不要使用缺省的 STATEMENT 方式,数据不安全 binlog_format = MIXED expire_logs_days = 10 max_binlog_size = 100M ## 我们同步除了 cacti 之外其它所有数据库,包括 mysql 系统自身 binlog_ignore_db = cacti
配置的一个重点说明:
binlog_format 千万不要使用 STATEMENT (如果你不写这个配置的话,缺省用的就是 STATEMENT),STATEMENT 好处是同步快,缺点是数据安全没有保障。简单的说,STATEMENT 可以在某些时候会同步失败,并且不能保证数据同步过去一定是正确的。你说你飞快的同步数据库过去,然后发现同步过去的数据有错误,那你丫还同步有啥意义?
重启主服务器 /etc/init.d/mysql stop ; /etc/init.d/mysql start;
查看主服务器的工作状态
## 登陆 mysql mysql -h localhost -u root -p ## 输入密码登陆 ## 执行查询 SQL 语句 mysql> show master status G ## 显示如下结果,说明你的主服务器成功启动了,并且已经开始输出 binlog 了 *************************** 1. row *************************** File: mysql-bin.000004 Position: 8681158 Binlog_Do_DB: Binlog_Ignore_DB: cacti 1 row in set (0.00 sec)
增加同步账号
Slave 要从主服务复制数据,就需要使用一个账号登陆主服务器,然后从主服务器读取 bin log 文件内容。
自己创建一个 slave 账号,并且赋予 replication slave 权限(只需要这一个权限就足够了,其它的没必要)
mysql> GRANT REPLICATION SLAVE ON *.* TO 'slave'@'Slave服务器IP' IDENTIFIED BY 'password';
配置 Slave 服务器
Slave 服务器处于 另外某个城市的机房。平时 Slave 只是作为一个备份,并没有业务的访问,如果主服务器崩溃(比如机房地震了),我们需要手动切换 Slave 为新的主服务器并且提供服务。
由于平时没有任何业务访问,为了保证安全,我配置 Slave 为 ReadOnly 的,防止数据发生任何形式的破坏
在 Slave 服务器的 [mysqld] 里面加入
server-id = 2 log_bin = /var/log/mysql/mysql-bin.log ## 我们同时开启 Slave 的 binlog ,这样如果有别的 Slave,他们可以把这个 Slave 当做主库,从这里做同步 ## 就不用每个 Slave 都从主库同步,这样可以减轻主库的压力 binlog_format = MIXED expire_logs_days = 10 max_binlog_size = 100M log-slave-updates ## 设置数据库为 只读,确保数据不会某种原因而被破坏 read-only=1 replicate-ignore-db=cacti
注意:有些文章会让你把 master 的信息(比如 master-host) 也写到配置文件里面去,我们不建议这么做,你应该在 Slave 启动之后手动设置 master 信息
主库 Dump 数据给 Slave 数据库
网上很多文章都让你用 mysqldump 导出数据,然后在 Slave 再 mysqlimport 导入。这实在是一个傻办法。最简单的办法就是把整个数据库 tar 一下复制过去。
锁住主数据库,确保你 dump 出来的数据是一致的(现在是 read only 了,不能写入库)
## 锁住 Master 数据库 mysql> FLUSH TABLES WITH READ LOCK;
把数据库数据 复制出来(本地复制)
## mysql 的数据文件一般都在 /var/lib/mysql 目录下,我们直接把整个目录复制出来 cp -ar /var/lib/mysql /home/DATA/tmp ## 注意,cp 用了 -a 复制,用于保持文件权限
查看并记录 Master 复制时候的状态
## 执行查询 SQL 语句 mysql> show master status G ## 显示如下结果,说明你的主服务器成功启动了,并且已经开始输出 binlog 了 *************************** 1. row *************************** File: mysql-bin.000004 Position: 8681158 Binlog_Do_DB: Binlog_Ignore_DB: cacti 1 row in set (0.00 sec)
注意:请把这里的 File 和 Position 记录一下,后面配置 Slave 需要使用
提醒: 查看 Master 的状态可以在上面 cp 数据的时候同时进行,反正 cp 也要花一点时间
解锁 Master 恢复数据库的正常访问
我们前面之所以使用本地 cp 来复制数据,目的就是希望这个过程竟可能的快,这样一复制完成就可以把数据库解锁,恢复正常访问了。
## 解锁数据库,数据库可以正常写入了 mysql> UNLOCK TABLES;
到现在为止你的 Master 数据库就释放了,后面不用管它了。
把数据库数据打包发送到 Slave 服务器上
刚才你把数据库 cp 到了哪个目录下? 现在去打包,然后发送到 Slave 服务器上就行了
## 数据库文件打包 ,根据你的数据量大小压缩可能会很慢,耐心等待吧 cd /home/Tmp tar -cjf mysql.tar.bz2 mysql/ ## 把打包好的文件传送到 Slave 数据库上 ## 我们这里采用 rsync,如果你不了解 rsync 的话,你也可以用你熟悉的任何方法 ## 包括 scp, ftp, wget .... rsync -v mysql.tar.bz2 slaveIP地址::/SyncData
Slave 数据库导入数据
怎么导入数据? 最简单的方式就是整个替换 mysql 的数据目录
## 停止 mysql 服务器 /etc/init.d/mysql stop ## 删除旧的数据文件 rm -rf /var/lib/mysql ## 把新的数据文件解压,放到数据目录位置 tar -jxf mysql.tar.bz2 mv mysql /var/lib ## 启动 Slave Mysql /etc/init.d/mysql start ## 设置 Slave 的同步开始状态 mysql -h localhost -u root -p mysql> CHANGE MASTER TO -> MASTER_HOST='master_host_name', -> MASTER_USER='replication_user_name', -> MASTER_PASSWORD='replication_password', -> MASTER_LOG_FILE='前面让你记录下的 master 状态显示的 logfile 名字', -> MASTER_LOG_POS=前面记录下的 postion; ## 启动 Slave 开始复制 mysql> START SLAVE; ## 查看 Slave 的状态 mysql > show slave status G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: IP地址xxxxx Master_User: slave Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000004 Read_Master_Log_Pos: 17767065 Relay_Log_File: mysqld-relay-bin.000018 Relay_Log_Pos: 17766187 Relay_Master_Log_File: mysql-bin.000004 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: Replicate_Ignore_DB: cacti ## 当你看到 Slave_IO_Running: Yes Slave_SQL_Running: Yes 说明 Slave 已经成功启动了,之后它就会自动同步不用管了 ## 如果没有成功,这里应该会报错,你就根据错误自己查错吧
问题: Master —> Slave 运行起来了,假如发生了 网络断网、数据库服务器重启、 … 那数据库同步怎么办?
回答: 放心,Mysql 已经做得足够完善了,你网络断了,重启服务器了,等你的机器恢复之后 myql 会自动从上次断网的地方继续同步,完全不需要人工干预。
进一步的安全考虑
为了让数据库更加安全,我们在 Slave 上对数据库每隔一个小时就做一次全量 dump (使用 mysqldump),然后 bzip2 压缩保存。
一天 24 个小时,保留 30 天的数据量,也就是 30 * 24 * 数据库dump 压缩文件的大小, 需要不小的磁盘空间,不过现在磁盘空间很便宜,这不是什么问题。
4. 网站文件的同步
一个网站,除了有数据库,还有很多别的文件,比如用户上传的图片,你的网站代码之类,光有数据库而没有这些文件你的网站也没法跑起来。
和数据库比起来,网站文件要大得多,而且文件数目也多得多,要做到实时同步必须考虑“增量文件同步”,一般传统的方法是使用 inotify + rsync 写脚本来操作。
我们这里采用 Sersync (https://code.google.com/p/sersync/) 来做网站数据同步 。
配置 Slave 上的 WWW 同步
采用 Rsync 做数据同步
uid=root gid=root use chroot=no max connections=10 pid file=/var/run/rsyncd.pid lock file=/var/run/rsync.lock log file=/var/log/rsyncd.log munge symlinks = no hosts allow=这里填写 Master 的 IP 地址 hosts deny=* list=false read only=no ignore errors [WWW] path=/var/WWW ## 网站数据所在的位置
启动 rsync 服务: rsync –daemon
启动 Master 上的 Sersync2 做数据同步
<?xml version="1.0" encoding="ISO-8859-1"?> <head version="2.5"> <host hostip="localhost" port="8008"></host> <debug start="false"/> <fileSystem xfs="false"/> <filter start="true"> <exclude expression="^BZFSHOP/Asset/*"></exclude> <exclude expression="^BZFSHOP/Runtime/*"></exclude> <exclude expression="^BZFSHOP/Trash/*"></exclude> <exclude expression="^BZFSHOP/Data/cache/*"></exclude> <exclude expression="^BZFSHOP/Data/temp/*"></exclude> </filter> <inotify> <delete start="true"/> <createFolder start="true"/> <createFile start="true"/> <closeWrite start="true"/> <moveFrom start="true"/> <moveTo start="true"/> <attrib start="false"/> <modify start="false"/> </inotify> <sersync> <localpath watch="/var/WWW"> <remote ip="Slave的IP" name="WWW"/> </localpath> <rsync> <commonParams params="-artu"/> <auth start="false" users="root" passwordfile="/etc/rsync.pas"/> <userDefinedPort start="false" port="874"/><!-- port=874 --> <timeout start="false" time="100"/><!-- timeout=100 --> <ssh start="false"/> </rsync> <failLog path="/tmp/www_rsync_fail_log.sh" timeToExecute="3"/><!--default every 3mins execute once--> <crontab start="false" schedule="600"><!--600mins--> <crontabfilter start="false"> <exclude expression="*.php"></exclude> <exclude expression="info/*"></exclude> </crontabfilter> </crontab> <plugin start="false" name="command"/> </sersync> </head>
启动文件监控同步
## 第一次启动加上 -r 参数做首次同步,之后就不要再加 -r 参数了 ./sersync2 -n 2 -d -r -o www.xml
后记
完整的对 数据库 和 所有文件 做了实时同步之后,我们的业务就有了保障。一旦主机房出现严重问题,我们可以使用备份数据立即继续中断的业务,最重要的是,业务数据几乎没有任何的丢失。
不像之前,每天一个备份,如果发生严重故障,只能从“昨天”开始了,中间数据丢失会带来巨大的麻烦。
如果需要更加安全,可以考虑 3 备份或者 N 备份,在 N 个城市同时做实时备份数据,一旦发生多点灾难一样可以迅速恢复业务,当然需要的成本也更高。