之前比较关注如何使用Cassandra,但是真正想大规模使用前提还是需要搞清楚备份机制,确保数据安全。
本文主要内容来自文档 "Cassandra2.2"的翻译。最后部分为真实操作案例。
这里假设你已经了解了Cassandra的压缩、墓碑、数据一致性。
原始文档链接:http://docs.datastax.com/en/cassandra/2.2/cassandra/operations/opsBackupRestore.html
备份和数据恢复
关于镜像
Cassandra 通过直接保存所有在data目录中的磁盘数据文件(SSTable file)的镜像来备份数据。当系统还在线的时候,你可以保存所有的keyspace数据或者单个keyspace数据,或者某一张表的数据。
使用并行的ssh工具,比如pssh,你可以给整个集群做镜像。这提供一种最终一致性备份。虽然没有一个节点可以在镜像制作过程中保证他和备份节点数据的一致性,Cassandra内置一致性机制会使用一个恢复镜像恢复一致性。
当整个系统范围内的镜像都已经完成,你可以开启每个节点的增量备份,它将备份那些最后一次镜像后有改变的数据:每次SSTable 刷新,一个硬链接被复制到 data目录的/backups 子目录中(provided JNA is enabled)
如果允许JNA,镜像将只是建立一个硬链接。否则io将由于文件被从一个地方拷贝到另一处而增长,将明显降低效率。
如何得到一份镜像
通过 nodetool snapshot指令来获取每个节点的镜像。如果要获取全局的镜像,使用并行ssh工具运行这个指令,比如pssh。
镜像首先将所有内存数据刷新到磁盘中,然后给keyspace中的每个SSTable创建硬链接。你需要在每个节点上有足够的空间来容纳生成的镜像文件。单一镜像需要很小的空间。然而镜像会导致你的磁盘使用量随时间快速增长,因为镜像需要留着那些被删除的过期数据。当镜像制作完成。如果你需要的话,你可以将备份文件移动到其他位置,或者就放在那。
注意:Cassandra只能在table schema存在的情况下恢复数据,所以建议你同样备份一下schema(也就是system.schema_*系列的表).
操作过程
运行nodetool snapshot指令,需要指定hostname,JMX端口,keyspace名称。比如:
$ nodetool -h localhost -p 7199 snapshot mykeyspace
结果
snapshot被创建在(原文)目录中,每个s镜像都包含多个包含镜像当时数据的.db文件。
镜像的路径可能是:
使用安装包:/var/lib/cassandra/data/mykeyspace/users-081a1500136111e482d09318a3b15cc2/snapshots/1406227071618/mykeyspace-users-ka-1-Data.db
使用tar包: install_location/data/data/mykeyspace/users-081a1500136111e482d09318a3b15cc2/snapshots/1406227071618/mykeyspace-users-ka-1-Data.db
增量备份
当增量备份被开启(默认关闭),Cassandra 将硬链接每个刷新过的(flushed)的在keyspace数据目录中的SStable到备份目录中。这允许保存备份的偏移量而不是整个镜像。同时,增量备份将和镜像一起提供可靠的最新的备份机制。
和镜像一样,Cassandra 并不会自动清理增量备份文件。DataStax建议配置一个程序在新的镜像生成后,清理增量备份文件。
操作过程
编辑cassandra.yaml 配置文件,将incremental_backups 设置为true,你需要在每个节点都进行此操作
从镜像中恢复
从镜像中还原keyspace需要所有的table的镜像文件,如果使用增量备份,任何在镜像后创建的增量备份文件都是必须的。
正常来说,在从镜像还原之前,你需要清空(truncate)table. 如果备份发生在删除之前,而你在删除后还原数据没有先清空,你无法得到原始的数据(行)。在压缩后,墓碑存档在和原始数据不同的SSTable中,所以当还原包含着原始数据的SSTable但是没有移除墓碑,数据依旧表现出被删除状态(虽然它还在那)。
Cassandra 仅能再table schema存在的情况下备份镜像。如果你没有备份schema,你可以做下面的任何一种工作。
方法1:
- 还原镜像,按照下面的方法
- 重新创建schema
方法2 :
- 重建schema
- 还原镜像
- 执行nodetool refresh
你有很多方法可以还原数据:
- 使用sstableloader 工具
- 拷贝镜像的SSTable文件到/data/keyspace/table_name-uuid目录中,然后用JConsole 在各个column family 中调用JMX方法loadNewSSTables()在column family MBean,你可以使用nodetool refresh 代替调用loadNewSSTables()
data目录位置依赖于你的安装或者配置方式,默认是下面两种。
- Package installations: /var/lib/cassandra/data
- Tarball installations: install_location/data/data
- 使用Node Restart Method 下面将会介绍。
如果还原一个节点,你需要首先关闭节点。如果还原整个集群,你需要关闭所有节点,还原镜像数据,然后再次启动所有节点。
注意:还原镜像数据将导致正在还原的节点,CPU和IO临时增加。
译者注:这边可能文档没有更新完整,前面有提到需要truncate table,在关闭节点前先执行一次。
- 关闭节点
- 清理所有commitlog中的文件
这是为了防止commitlog重放导致数据更新,这可能导致还原到某个固定时间点的目标失败。
- 清理有*.db文件在data_directory/keyspace_name/keyspace_name-keyspace_name目录中,但是不要删除镜像和备份子目录。
- 确定最近的镜像数据目录
- 将数据拷贝内容到这个目录
data_directory/keyspace_name/talbe_name-UUID
- 如果你采用了增量备份,复制这里的所有数据
data_driectory/keyspace_name/table_name-UUID/backups
- 粘贴到
data_directory/keyspace_name/table_name-UUID中
- 重启节点
这会导致暂时性的io增长和占有大量CPU资源。
- 执行nodetool repair
实际操作记录 (Node Restart Method)
集群简介
4个节点,分别为
192.168.1.2
192.168.1.3
192.168.1.4
192.168.1.5
之后就不写完整ip 用节点2 节点3 节点4 节点5
Cassandra 版本2.1.7
创建备份测试用的数据
登录负载最低的节点2,创建一个keyspace
/* 创建tshop的keyspace */
CREATE KEYSPACE tshop WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '2'} ;
use tshop;
/* 创建一张user表 */
CREATE TABLE user (
uid int PRIMARY KEY,
group_id int,
nick varchar
) WITH compaction = { 'class': 'org.apache.cassandra.db.compaction.LeveledCompactionStrategy'};
/* 写入一些数据 */
insert into user (uid,group_id,nick) values(1,1,'kevin');
insert into user (uid,group_id,nick) values(2,1,'luxi');
insert into user (uid,group_id,nick) values(3,1,'happy');
insert into user (uid,group_id,nick) values(4,1,'豆沙柚子');
insert into user (uid,group_id,nick) values(5,1,'五仁月饼');
备份所有节点数据:
$ bin/nodetool -h 192.168.1.2 snapshot tshop
$ bin/nodetool -h 192.168.1.3 snapshot tshop
$ bin/nodetool -h 192.168.1.4 snapshot tshop
$ bin/nodetool -h 192.168.1.5 snapshot tshop
备份完成后会显示一个时间邮戳,这就是备份目录的名字,也方便还原时判定备份的时间。
/* 删除数据,假设是一次意外事故 */
delete from user where uid in (1,2,3,4,5);
关闭来自前端的请求,因为数据恢复的过程中,集群不得不关闭。
清空user表,前面有提到原因。
>truncate user;
关闭集群的所有节点
清理commitlog 避免有未提交数据的破坏
把数据目录下的*.db文件删除
把备份目录中的*.db文件放到数据目录中
重启所有节点
在每个节点上执行nodetool repair
$ bin/nodetool -h 192.168.1.2 repair tshop
$ bin/nodetool -h 192.168.1.3 repair tshop
$ bin/nodetool -h 192.168.1.4 repair tshop
$ bin/nodetool -h 192.168.1.5 repair tshop
结果
之前丢失的user数据全部还原成功
实际操作案例(增量备份)
增量备份的数据体积更小,但是备份时间似乎不好控制,先试一下。
首先基于上面一个案例,开启增量备份。
如果有安装OpsCenter 就直接改,否则需要到每个节点上去开启这个配置。
incremental_backups: true
继续往数据库写入数据
insert into user (uid,group_id,nick) values(6,1,'昵称1');
insert into user (uid,group_id,nick) values(7,1,'昵称2');
insert into user (uid,group_id,nick) values(8,1,'昵称3');
insert into user (uid,group_id,nick) values(9,1,'昵称4');
insert into user (uid,group_id,nick) values(10,1,'昵称5');
insert into user (uid,group_id,nick) values(11,1,'昵称6');
insert into user (uid,group_id,nick) values(12,1,'昵称7');
/* 如果有新数据写入,执行flush操作,可以让增量备份作一次备份,但是这并不是总是有效果,有可能当时没有写入数据,不过也意味着距离上次备份不需要再备份新的数据了 */
$ bin/nodetool -h 192.168.1.2 flush tshop
$ bin/nodetool -h 192.168.1.3 flush tshop
$ bin/nodetool -h 192.168.1.4 flush tshop
$ bin/nodetool -h 192.168.1.5 flush tshop
这时候,目录中多了一个backup的目录。
/* 再一次 删除数据,假设是一次意外事故 */
delete from user where uid in (3,4,5,6,7);
我观察了一下backup目录文件,会有多组文件,所谓多组就是这些文件名可能非常相似,只有中间一个数字差别.
$ ls backup
-rw-rw-r-- 1 cassandra cassandra 43 Sep 3 18:39 tshop-user-ka-2-CompressionInfo.db
-rw-rw-r-- 1 cassandra cassandra 138 Sep 3 18:39 tshop-user-ka-2-Data.db
-rw-rw-r-- 1 cassandra cassandra 9 Sep 3 18:39 tshop-user-ka-2-Digest.sha1
-rw-rw-r-- 1 cassandra cassandra 16 Sep 3 18:39 tshop-user-ka-2-Filter.db
-rw-rw-r-- 1 cassandra cassandra 54 Sep 3 18:39 tshop-user-ka-2-Index.db
-rw-rw-r-- 1 cassandra cassandra 4442 Sep 3 18:39 tshop-user-ka-2-Statistics.db
-rw-rw-r-- 1 cassandra cassandra 80 Sep 3 18:39 tshop-user-ka-2-Summary.db
-rw-rw-r-- 1 cassandra cassandra 91 Sep 3 18:39 tshop-user-ka-2-TOC.txt
-rw-rw-r-- 1 cassandra cassandra 43 Sep 3 18:44 tshop-user-ka-4-CompressionInfo.db
-rw-rw-r-- 1 cassandra cassandra 138 Sep 3 18:44 tshop-user-ka-4-Data.db
-rw-rw-r-- 1 cassandra cassandra 9 Sep 3 18:44 tshop-user-ka-4-Digest.sha1
-rw-rw-r-- 1 cassandra cassandra 16 Sep 3 18:44 tshop-user-ka-4-Filter.db
-rw-rw-r-- 1 cassandra cassandra 54 Sep 3 18:44 tshop-user-ka-4-Index.db
-rw-rw-r-- 1 cassandra cassandra 4442 Sep 3 18:44 tshop-user-ka-4-Statistics.db
-rw-rw-r-- 1 cassandra cassandra 80 Sep 3 18:44 tshop-user-ka-4-Summary.db
-rw-rw-r-- 1 cassandra cassandra 91 Sep 3 18:44 tshop-user-ka-4-TOC.txt
显然有 “2” 组 和 “4” 组,时间一前一后,如果想恢复到18:39分,那么,就别吧“4”组的数据放到数据目录中。这个数字的变化我暂时没有找到一个解释,是不是随着flush操作次数增加而增大?(测试结果是这样的)但是还原操作后,这个数字似乎会回到一个比较小的值。所以用数字判断数据前后应该不靠谱,文件生成时间会靠谱一些,但是反复还原操作后,备份也会混乱。
周期性镜像备份还是不可替代,增量备份可以作为一种粒度更小的时间控制的补充。例如每天一个镜像,最后一个镜像后,开始保留增量。保留最新增量的方法就是在镜像制作完成后,删除所有当前增量备份,这样就是最新了,但是这两个操作间的数据又怎么办?
其他操作和之前镜像还原完全相同。
结果
数据全部还原