专栏首页FundebugFundebug是这样备份数据的

Fundebug是这样备份数据的

摘要: 数据还是要备份的,万一删库了呢?

本文代码仓库: fundebug-mongod

引言

今年 8 月,腾讯云竟然把客户前沿数据的数据弄没了,Fundebug在第一时间进行了一些简单的技术分析

一方面,腾讯云对这件事负有不可推卸的责任,他们刚开始说是什么硬盘固件版本 bug(该声明已删),后来承认是人为操作失误导致的。 另一方面,前沿数据没有备份业务数据,也是一种非常不专业的行为,导致业务无法恢复,必须完全重新开始。

因此,所有的开发者都应该从这个事件吸取教训,不要偷懒,严格备份业务数据,否则数据一旦出问题,损失将无法挽回。

Fundebug 数据备份方案

我们还分享了 Fundebug 的数据备份方案,供大家参考:

备份方案

时间粒度

细节

MongoDB 复制集

实时

搭建 3 个节点(1 个 Primary 和 2 个 Secondary)的 MongoDB 复制集,实时同步数据。

阿里云磁盘快照

每天

每天凌晨自动快照所有磁盘,包括系统盘和备份数据盘。

mongodump 导出核心数据

每天

每天凌晨将 MongoDB 核心数据导出到复制集之外的服务器磁盘(该磁盘会每天进行快照)。

阿里云对象存储

每天

每天凌晨将 mongodump 导出的数据使用 gpg 非对称加密之后,上传到阿里云深圳数据中心的对象存储,设置跨区域复制,自动同步到杭州数据中心,每份数据保留 1 个月。

本地硬盘备份

每周

每周六中午从阿里云对象存储下载加密的备份数据,存储到本地磁盘。

大概是因为我们没有公布备份方案的技术细节,我们受到了质疑

要么多重备份是假的

对于这种指责,我的原则是必须怼回去。那么,这篇博客我来详细介绍一下我们数据备份方案吧~所有源代码都在 GitHub 仓库Fundebug/fundebug-mongodb-backup,欢迎 star。

MongoDB 复制集

生产环境使用单节点的 MongoDB 数据库,除非访问量非常低或者不在乎服务可用性,否则基本上是不可能的,这辈子都不可能。单节点 MongoDB 存在单点故障(single point of failure),一旦挂了,整个应用就挂了。更糟糕的是,如果数据损坏,恢复将非常麻烦。

MongoDB 有多种可能性会挂掉,最常见的就是高峰期内存使用量飙升,导致 Linux 的 Out of Memory (OOM) killer 将 mongod 进程杀死,这种情况Fundebug遇见过不少次,那我们是如何安全渡过的呢?答案是复制集(replica set)

复制集由多个 MongoDB 节点构成,它们的数据是实时同步的,因此数据几乎完全相同。当某个节点挂掉时,应用可以自动切换到其他节点,这样保证了服务的可用性。

Fundebug 的 MongoDB 都运行在 Docker 容器中,其 Docker Compose 配置文件如下:

version: "2.2"

services:
    mongo:
        image: mongo:3.2
        network_mode: "host"
        restart: always
        cpus: 7
        mem_limit: 30g
        command: --replSet rs0  --oplogSize 25600
        volumes:
            - /mongodb/data:/data/db
        logging:
            driver: "json-file"
            options:
                max-size: "5g"

oplog

复制集一个非常重要的参数是oplog的大小,使用–oplogSize选项可以指定。我们设定的值是 25600MB,即 25GB。oplog(operation log)是复制集节点同步数据的关键,Primary 节点将数据库写操作记录到 oplog 中,Secondary 节点从 Primary 节点处复制 oplog 并应用到本地数据库中。因此,oplog 大小决定了 Primary 和 Secondary 节点可以接受的数据最大”时间差”。使用rs.printReplicationInfo()可以查看 oplog 信息:

rs.printReplicationInfo()
configured oplog size:   25600MB
log length start to end: 11409secs (3.17hrs)
oplog first event time:  Sat Sep 22 2018 12:02:04 GMT+0800 (CST)
oplog last event time:   Sat Sep 22 2018 15:12:13 GMT+0800 (CST)
now:                     Sat Sep 22 2018 15:12:13 GMT+0800 (CST)

可知 oplog 中记录了最近 3.17 小时的数据库写操作,假设复制集中某个节点由于宕机有 4 个小时没有同步数据,则重启该节点也无法与其他节点同步了!这时会出现“too stale to catch up – entering maintenance mode”的错误,只能手动同步数据。

因此,我们建议 oplog 的值应该尽量设大一些,否则以后修改 oplog的步骤挺麻烦的。事实上,25GB 的 oplog 大小对于Fundebug的 MongoDB 复制集来说已经不够了,我们需要修改。

Fundebug 的 MongoDB 复制集由 1 个 Primary 节点和 2 个 Secondary 节点构成,为保证我们服务可用性发挥了非常关键的作用!我之后所介绍的备份方案都是冗余措施,我们从来没有真正使用过那些备份数据,而复制集“拯救”了我们不少次,强烈建议大家都配置一下。

关于 MongoDB 复制集的更多技术细节,以后我再单独详述,欢迎关注。

阿里云磁盘快照

快照能够保留某一时间点的磁盘数据状态,因此可以作为一种数据备份方式。很简单,配置一下自动快照策略就好了:

我备份了系统盘,万一数据丢失比如被删库,至少还能回滚磁盘。每周快照 1 次,保存 7 天。因为服务全部运行在 Docker 里面,服务器本身基本上没有什么配置,备份的需求不大,实际上我们也从来没有回滚过磁盘。

另外,我没有对 MongoDB 数据盘直接进行快照,因为发现快照后的数据无法恢复(这一点有待进一步确认)。

我只是将 mongodump 导出的核心数据所在磁盘进行了快照。每天快照 1 次,保存两天。这样做可以确保核心数据的安全性。

mongodump 导出核心数据

使用mongodump命令,可以全量导出 MongoDB 数据。对应的,之后可以使用mongorestore命令将备份数据导入 MongoDB。

导出数据的脚本dump-data.sh如下:

#!/bin/sh

# 删除前一天导出的数据
rm -rf /data/mongodb_backup

DIR=`date +%Y%m%d%H%M`
OUT=/data/mongodb_backup/$DIR
mkdir -p $DEST

# 全量导出MongoDB数据(排除部分集合)
mongodump --host "rs0/192.168.59.11:27017,192.168.59.12:27017,192.168.59.13:27017" \
          --db fundebug-production \
          --excludeCollection events \
          --out $OUT

使用–excludeCollection选项,可以排除部分不需要备份的集合。例如,Fundebug累计处理了6 亿+的错误事件,存在 event 集合中,因为我们已经聚合过了,所以没有必要备份,而且数据量太大,备份也不现实。

使用 crontab 脚本定期执行dump-data.sh脚本:

# 每天凌晨4点导出数据
0 4 * * * /root/fundebug-mongodb-backup/dump-data.sh

阿里云对象存储

使用 mongodump 导出的数据保存在测试服务器的数据磁盘上,从地域层面上来说都在同一个地方,即阿里云深圳数据中心。如果要做到异地备份,可以借助阿里云的对象存储服务的跨区域复制功能,将备份数据自动同步到阿里云杭州数据中心。

在上传备份数据之前,使用 gpg 命令进行非对称加密,可以保证数据安全性。加密导出数据的脚本encrypt-data.sh脚本如下:

#!/bin/bash

DIR=`find /data/mongodb_backup/ -maxdepth 1 -type d ! -path /data/mongodb_backup/`
source=$DIR/fundebug-production
cd $source

# 将导出数据加密
for file in * ; do
    gpg --batch --yes -v -e -r fundebug --output $source/$file.gpg --always-trust $file
done ;

除了加密,gpg 还有一定的压缩效果,这样可以减少备份数据量,一举两得。关于 gpg 命令的细节,可以查看参考博客。

使用阿里云提供的 Node.js 客户端ali-oss,可以将加密之后的.gpg 文件上传到阿里云的对象存储服务中。使用multipartUpload方法即可,upload.js部分代码如下:

// 上传单个文件
async function uploadFile(fileName, filePath) {
    try {
        const result = await store.multipartUpload(fileName, filePath, {
            parallel: 4,
            partSize: 1024 * 1024,
            progress: function(p) {
                logger.info("Progress: " + p);
            }
        });
        if (result.res.statusCode === 200) {
            logger.info(`upload file success! ${fileName}`);
        } else {
            const message = `upload file fail! ${fileName}`;
            logger.error(message);
            logger.error(result);
            fundebug.notifyError(new Error(message), {
                metaData: {
                    message: message,
                    result: result
                }
            });
        }
    } catch (error) {
        const message = `upload file fail! ${fileName}`;
        logger.error(message);
        logger.error(error);
        fundebug.notifyError(error, {
            metaData: {
                message: message,
                error: error
            }
        });
    }
}

代码运行在 Docker 容器中,使用 curl 命令访问 HTTP 接口/upload 即可触发执行上传操作,使用 crontab 定期执行:

# 每天凌晨4点备份数据
0 4 * * * /root/mongodb-backup/dump-data.sh && /root/mongodb-backup/encrypt-data.sh && docker restart mongodb-backup && sleep 1m && curl http://127.0.0.1:9160/upload

备份数据通过数据卷(volume)映射到容器中,每天需要重启容器,才能访问每天导出的新数据。

在阿里云上为备份数据的存储空间配置跨区域复制,即可实现自动异地备份,非常方便。其他对象存储云服务应该也支持这种功能吧。

本地磁盘备份

前文提到的备份方式,其实都是在阿里云内部 COPY 数据。那么问题来了,阿里云挂了怎么办?这种事情当然基本上不可能发生,毕竟我们有多处备份,甚至实现了异地备份。

既然备份数据都上传到阿里云对象存储了,下载到本地也不是什么难事。使用ali-osslistget方法即可实现,download.js部分代码如下:

// 获取当天上传到阿里OSS的文件列表
async function listFilesToDownload(day) {
    const result = await store.list({ prefix: day });
    return result.objects;
}

// 将阿里云OSS中的文件下载到本地
async function downloadFile(fileName, path) {
    try {
        const file = fileName.split("/")[1];
        const filepath = `${path}/${file}`;
        await store.get(fileName, filepath);
    } catch (error) {
        const message = `download file fail! ${fileName}`;
        logger.error(message);
        logger.error(error);
        fundebug.notifyError(error, {
            metaData: {
                error: error,
                message: message
            }
        });
    }
}

代码运行在 Docker 容器中,部署在本地机器,使用 curl 命令访问 HTTP 接口/download 即可触发执行下载操作,使用 crontab 定期执行:

# 每周六中午从阿里云下载备份数据
0 12 * * 6 curl http://127.0.0.1:9160/download

结论

本文提到的所有的数据备份方式完全自动化执行,没有什么技术难度,成本也不高,可以极大提高数据安全性。

参考

版权声明

转载时请注明作者 Fundebug以及本文地址: https://blog.fundebug.com/2018/09/27/how-does-fundebug-backup-data/

您的用户遇到BUG了吗?

体验Demo 免费使用

.copyright *{box-sizing:border-box}

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • JavaScript中Map和ForEach的区别

    Fundebug
  • Array构造的数组使用map为何失效?

    如果你和我一样,看到这种使用传统的 for 循环的方式会有点不大习惯了。事实上,我已经好多年不使用 for 循环写代码了。毕竟,各种高阶函数,像 forEach...

    Fundebug
  • JavaScript 的 4 种数组遍历方法: for VS forEach() VS for/in VS for/of

    我们有多种方法来遍历 JavaScript 的数组或者对象,而它们之间的区别非常让人疑惑。Airbnb 编码风格禁止使用 for/in 与 for/of,你知道...

    Fundebug
  • 【另类可视化】芝加哥的城市大数据

    大数据文摘
  • 以大数据之名,变身!——In big data we trust

    先关注一则旧闻11月20日,德国联邦网络局禁止在该国销售儿童智能手表,穿戴设备的麦克风,可让家长听到孩子的环境,涉嫌侵犯他人隐私。另10月,挪威消费者理事会在报...

    企鹅号小编
  • 如何使用Python基线预测进行时间序列预测

    建立基线对于任何时间序列预测问题都是至关重要的。

    人工智能资讯小编
  • 《Pokemon Go》:历经三年起伏,从一款AR游戏成为了一类AR游戏

    这段时间以来,AR游戏的势头一直高涨不下。许多类型各异的AR游戏频出,但还是以效仿《Pokemon Go》为多。《Pokemon Go》作为最成功的AR游戏之一...

    VRPinea
  • excel数据分析工具库系列三|趋势平滑

    今天要跟大家分享的内容是数据分析工具库系列三——趋势平滑! 在时间序列数据中,往往存在很多周期性趋势以及随机干扰因素,给我们的分析工作工作带来很多不便。 当然有...

    数据小磨坊
  • 配置 RAC 负载均衡与故障转移

        Oracle负载均衡主要是指新会话连接到RAC数据库时,如何判定这个新的连接要连到哪个节点进行工作?通常情况下,负载均...

    Leshami
  • SwiftUI:自定义动画

    当我们将修饰符.animation(.default)附加到视图时,SwiftUI将使用默认的系统动画自动为该视图发生的任何变化设置动画。实际上,这是一种“缓入...

    韦弦zhy

扫码关注云+社区

领取腾讯云代金券