MassiveApp 数据维护与备份策略
1. 日志轮转优化
logrotate 内置的最短时间间隔是每天一次,这意味着日志轮转脚本每天只能自动运行一次。但为了应对高流量情况,我们可以使用 cron 作业来让日志轮转脚本每天运行两次。具体做法是使用
-f
选项,该选项可以强制 logrotate 工具按需运行脚本。
以下是新的 Puppet 资源,将其放入
massiveapp
模块的
init.pp
中以配置这个 cron 作业:
class massiveapp {
$current_path = "/var/massiveapp/current"
file {
"/etc/logrotate.d/massiveapp.conf":
owner => root,
group => root,
mode => 755,
content => template("massiveapp/massiveapp.logrotate.conf.erb")
}
cron {
"massiveapp_logrotate":
command => "/usr/bin/logrotate -f /etc/logrotate.d/massiveapp.conf",
user => "vagrant",
hour => [0,12],
minute => "0"
}
}
通过上述配置,我们可以根据指标图确定 MassiveApp 最不繁忙的时间,并安排日志轮转。运行 Puppet 后,这个 cron 作业将确保应用程序日志保持合理的大小。
2. 应用数据归档
MassiveApp 数据库中的一些表随着时间推移可能会包含大量行,例如
versions
表。由于
Account
模型启用了审计功能,每次创建、更新或删除
Account
时,
versions
表都会新增一行。
为了控制
versions
表的行数,我们将实现归档策略。具体做法是使用 nightly cron 作业和 Rake 任务,将六个月前的记录转储到备份 SQL 文件中,然后从
versions
表中删除这些记录。
以下是 Rake 任务的代码:
namespace :archive do
task :dump_old_records_to_sql => :environment do
threshold = 6.months.ago.beginning_of_day
next if Version.exists("item_type = ? and created_at < ?", "Account", threshold)
outdir = ENV['OUTDIR'] || Rails.root
outfile = File.join(outdir, "#{threshold.strftime('%Y%m%d')}.sql")
where = "created_at < '#{threshold.strftime("%Y-%m-%d")}'"
config = ActiveRecord::Base.configurations[RAILS_ENV]
command = "mysqldump "
command << " -u#{config['username']} "
command << " -p#{config['password']} " if config['password'].present?
command << " --single-transaction "
command << " --quick "
command << " --tables "
command << " --where \"#{where}\" "
command << " #{config['database']} "
command << " versions "
command << " > #{outfile} "
# Dump the records
`#{command}`
# Delete the records
Version.delete_all(["created_at < ?", threshold])
end
end
为了每天运行这个任务,我们需要在
massiveapp
模块中添加另一个 cron 资源:
cron {
"massiveapp_archive":
command => "RAILS_ENV=production \
/usr/local/bin/rake archive:dump_old_records_to_sql",
user => "vagrant",
hour => "0",
minute => "10"
}
部署
massiveapp
并运行 Puppet 后,每天晚上都会创建新的 SQL 文件,用于存储六个月前的记录。随着活动的增加,我们可能需要将这个简单的归档策略改为更具扩展性的策略,例如对
versions
表进行分区或迁移到不同的数据库服务器。
3. 数据备份与 MySQL 故障转移
3.1 确定备份数据
为了确保 MassiveApp 存储的数据不会因硬件故障或编程错误而丢失,我们需要确定需要备份的数据。以下是不需要备份的数据:
- MassiveApp 部署在服务器上的源代码,因为服务器故障时可以重新部署。
- 配置文件,如
my.cnf
、
httpd.conf
等,这些文件由 Puppet 生成,服务器丢失时可以使用 Puppet 重建。
- 系统或应用程序日志文件,有用的数据会在 Ganglia 图表中捕获和汇总。
- Memcached 中的数据,MySQL 数据库是其权威来源,Memcached 进程崩溃时可以重启并重新填充。
因此,真正需要备份的数据是 MySQL 数据库中的数据,这些数据是 MassiveApp 用户创建的,无法通过 Puppet 或其他自动化工具重新生成。
3.2 从硬件故障中恢复
MySQL 数据可能因硬件故障而丢失,如主板短路、硬盘同时损坏或服务器被盗。恢复 MySQL 主服务器故障的方法有多种,从没有备份服务器到使用网络监控工具实现全自动无缝故障转移。
为了演示,我们选择中间方案,即设置一个 MySQL 从服务器进行持续复制,但需要手动干预使 MassiveApp 进行故障转移。如果从服务器没有落后于主服务器,主服务器损坏时,我们可以将 MassiveApp 指向从服务器,从而在不丢失数据的情况下恢复服务。
以下是设置 MySQL 复制主从服务器的步骤:
1.
创建虚拟机
:
- 创建一个新目录
mysql_replication_test
,并在该目录中创建
Vagrantfile
:
Vagrant::Config.run do |config|
config.vm.define :mysql_master do |m|
m.vm.box = "lucid32"
m.vm.host_name = 'mysql_master'
m.vm.forward_port 3306, 3406
m.vm.network :hostonly, "33.33.13.37"
end
config.vm.define :mysql_slave do |s|
s.vm.box = "lucid32"
s.vm.host_name = 'mysql_slave'
s.vm.forward_port 3306, 3407
s.vm.network :hostonly, "33.33.13.38"
end
config.vm.define :app do |a|
a.vm.box = "lucid32"
a.vm.host_name = 'app'
a.vm.forward_port 80, 4568
a.vm.network :hostonly, "33.33.13.39"
end
end
- 运行 `vagrant up` 启动虚拟机。
-
安装 MySQL
:
- 分别连接到主服务器和从服务器:
$ vagrant ssh mysql_master
$ vagrant ssh mysql_slave
- 更新操作系统软件包列表:
master $ sudo apt-get update -y
slave $ sudo apt-get update -y
- 安装 MySQL 服务器:
master $ sudo apt-get install mysql-server -y
slave $ sudo apt-get install mysql-server -y
-
配置主服务器
:
-
编辑
/etc/mysql/my.cnf文件,取消以下两行的注释:
-
编辑
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
- 修改 `bind-address` 设置,使 MySQL 绑定到所有网络接口:
bind-address = 0.0.0.0
- 重启 MySQL 服务:
master $ sudo service mysql restart
- 开放权限,允许从服务器连接:
master $ mysql -uroot -proot -e "grant all on *.* to root@'%' identified by 'root'"
- 获取主服务器日志位置:
master $ mysql -uroot -proot -e "flush tables with read lock; show master status;"
-
配置从服务器
:
-
编辑
/etc/mysql/my.cnf文件,设置唯一的服务器 ID 和二进制日志:
-
编辑
server-id = 2
log_bin = /var/log/mysql/mysql-bin.log
bind-address = 0.0.0.0
- 重启 MySQL 服务:
slave $ sudo service mysql restart
- 配置从服务器连接主服务器:
slave $ mysql -uroot -proot -e "change master to \
master_host='33.33.13.37',\
master_port=3306,\
master_user='root',\
master_password ='root',\
master_log_file='mysql-bin.000001',\
master_log_pos=230"
- 启动复制:
slave $ mysql -uroot -proot -e "start slave"
-
验证复制
:
- 在主服务器上创建数据库和表:
master $ mysql -uroot -proot -e "create database repltest;\
use repltest;\
create table tt(id int);\
insert into tt values (42);"
- 在从服务器上验证数据是否复制:
slave $ mysql -uroot -proot repltest -e "select * from tt\G"
3.3 故障转移
为了使 MassiveApp 从 MySQL 主服务器故障转移到从服务器,我们需要修改
config/database.yml
文件并重启应用程序。为了避免直接修改
database.yml
,我们将添加一个新的
standby.database.yml
文件,用于覆盖标准的
database.yml
。
以下是
standby.database.yml
文件的内容:
production:
adapter: mysql2
encoding: utf8
database: massiveapp_production
username: root
password: root
host: 33.33.13.38
port: 3306
我们还需要一个 Capistrano 任务来在主服务器故障时将这个文件复制到正确的位置:
namespace :deploy do
desc "Switch application to slave server"
task :switch_to_slave do
standby_file = "#{current_path}/config/standby.database.yml"
run "cp #{standby_file} #{current_path}/config/database.yml"
deploy.restart
end
end
当主服务器故障时,我们可以通过以下步骤进行故障转移:
1. 关闭主服务器:
$ vagrant halt mysql_master
- 运行 Capistrano 任务:
$ cap deploy:switch_to_slave
3.4 恢复删除的数据
除了硬件故障,MySQL 数据还可能因编程或管理错误而丢失。数据删除会迅速传播到复制从服务器,导致所有数据库都被损坏,此时复制无法提供帮助,我们需要离线备份来恢复数据。
以下是使用
mysqldump
工具安全转储 MySQL 数据库的命令:
$ mysqldump -uroot -proot --master-data \
--quick --single-transaction \
--triggers massiveapp | gzip > massiveapp.sql.gz
该命令的选项说明如下:
-
--master-data
:确保转储文件中包含二进制日志位置信息。
-
--quick
:一次获取一行数据,避免处理大表时出现问题。
-
--single-transaction
:允许在不锁定 InnoDB 表的情况下进行一致的转储。
-
--triggers
:包含数据库触发器,避免恢复数据库后因触发器缺失而导致问题。
-
| gzip
:将输出通过管道传输到 gzip 进行压缩,减少磁盘使用。
每小时运行一次该命令可以获得相对最新的离线备份。我们可以编写 Nagios 监控器,当数据库转储时间超过几分钟时发出警报。当转储时间接近半小时时,我们可以重新评估备份策略,如升级复制从服务器的硬件、降低数据库转储频率或使用文件系统快照避免数据库转储。
3.5 恢复数据的场景
恢复意外丢失的数据是一项复杂的任务,需要考虑以下几种场景:
-
丢失不常更改的表
:如果丢失的是不常更改的表(如
countries
表),可以直接恢复该表。由于主键会恢复为之前的值,其他表的引用会重新建立。
-
数据损坏
:如果表中的部分列被置为 null 或错误值,可以手动重建该表。将备份表恢复到临时表名,然后编写 SQL 脚本或 Rake 任务重置原始表中的值。在编写任务时需要小心,避免覆盖用户自错误更新后更新的值。此时网站可能会停机,需要做出业务决策,是确保所有数据完美恢复还是尽快让网站上线。
-
丢失大量关联记录
:如果从一个与其他表有许多关系的表中删除了大量记录,并且所有相关记录也被删除,恢复整个对象图是一项艰巨的任务。根据损坏的程度,我们可能需要将主数据库离线进行后续分析,将最新备份恢复到另一台服务器,然后重新启动网站。之后可以投入资源重建丢失的数据。
综上所述,通过合理的日志轮转、数据归档、备份和故障转移策略,我们可以确保 MassiveApp 的数据安全和系统的高可用性。在实际应用中,我们可以根据具体情况对这些策略进行调整和优化,以应对不同的挑战。
4. 总结与建议
4.1 策略总结
为了保障 MassiveApp 的数据安全和系统稳定运行,我们采取了一系列策略,下面通过表格进行总结:
| 策略类型 | 具体措施 | 作用 |
| ---- | ---- | ---- |
| 日志轮转 | 使用 cron 作业和 logrotate 的
-f
选项,让日志轮转脚本每天运行两次 | 控制应用程序日志大小,避免日志文件过大影响系统性能 |
| 数据归档 | 利用 nightly cron 作业和 Rake 任务,将
versions
表中六个月前的记录转储到备份 SQL 文件并删除原记录 | 控制
versions
表行数,减轻数据库负担 |
| 数据备份 | 在复制从服务器上使用
mysqldump
工具每小时进行一次数据库备份 | 防止因硬件故障或编程错误导致数据丢失 |
| MySQL 故障转移 | 设置 MySQL 主从复制,在主服务器故障时手动将 MassiveApp 切换到从服务器 | 减少硬件故障对业务的影响,保障系统可用性 |
4.2 流程图展示
下面是 MySQL 主从复制及故障转移的 mermaid 流程图:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(创建虚拟机):::process
B --> C(安装 MySQL):::process
C --> D(配置主服务器):::process
D --> E(获取主服务器日志位置):::process
E --> F(配置从服务器):::process
F --> G(启动复制):::process
G --> H(验证复制):::process
H --> I{主服务器故障?}:::decision
I -- 是 --> J(关闭主服务器):::process
J --> K(运行 Capistrano 任务):::process
K --> L([结束]):::startend
I -- 否 --> M([继续运行]):::startend
4.3 建议与展望
- 策略优化 :随着业务的发展,当前的归档和备份策略可能需要进一步优化。例如,对于归档策略,可以考虑根据数据的重要性和访问频率进行更细致的划分,将不同类型的数据归档到不同的存储介质中。对于备份策略,可以采用增量备份的方式,减少备份时间和存储空间的占用。
- 自动化程度提升 :目前的故障转移需要手动干预,在未来可以考虑引入自动化工具,如 Pacemaker 等,实现更快速、无缝的故障转移,减少系统停机时间。同时,对于数据恢复过程,也可以编写自动化脚本,提高恢复效率。
- 监控与预警 :建立完善的监控体系,对日志轮转、数据归档、备份和故障转移等过程进行实时监控。当出现异常情况时,及时发出预警,以便管理员能够及时处理。例如,监控数据库备份的时间和大小,当备份时间过长或备份文件大小异常时,及时通知管理员。
- 测试与演练 :定期进行故障转移和数据恢复的测试演练,确保在实际发生故障时,相关人员能够熟练操作,减少因操作不熟练而导致的故障恢复时间延长。同时,通过测试演练,也可以发现策略中存在的问题,及时进行调整和优化。
通过以上的总结和建议,我们可以更好地理解和应用这些策略,确保 MassiveApp 在面对各种挑战时能够保持稳定运行,为用户提供可靠的服务。在实际操作中,需要根据具体情况不断调整和完善这些策略,以适应业务的发展和变化。
超级会员免费看
14

被折叠的 条评论
为什么被折叠?



