Fundebug是這樣備份數據的

  • 2019 年 12 月 30 日
  • 筆記

摘要: 數據還是要備份的,萬一刪庫了呢?

本文程式碼倉庫: 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-oss的list和get方法即可實現,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}

Tags: