一次惨痛的数据存储丢失事件

0

问题排查定位

上周某天正上着班发现博客挂了,接着发现摄影站也挂了,当时想着应该是服务器资源不足服务都挂了,虽然这类问题已经很久很久没出现过了,于是按照惯常的处理办法,登录腾讯云控制台对服务器进行重启。

服务器完成重启后,发现两个网站仍然无法访问,服务没启动成功,由于两个站点是多地区部署,于是通过在本机手工绑定 HOSTS 的办法来测试另一个地区的服务是否仍存活,结果发现都挂了。

既然两地的服务都挂了,那么可以大致判定是存储服务异常了,两地服务使用的存储服务是部署在另一个单独的服务器上,这个服务器上有通过 Docker 部署的 MySQL、Mongo、Redis 等服务,而用于提供 Web 服务的服务器上用 Docker 部署了 PHP 和 Node.js。

原想着登录存储服务器看看情况,结果发现 ssh 连接不上,ping 也无响应,这下看来有点麻烦了,可能是存储服务器到期被回收了,这个存储服务器是朋友提供的腾讯云,所以到期前我是收不到任何提醒的,问题开始变得棘手,需要重新部署存储服务,然后使用备份的数据进行恢复。

在一开始使用朋友提供的服务器来做独立的存储服务时,就已经预想过假如服务器被回收了要如何处理,这一天终于还是来了。

数据存储的备份方案

由于是个人网站,资源非常有限,服务器配置都是“小水管”,所以数据库也不会考虑专属集群,没有搭建主从服务,没有购买数据盘,同时考虑到数据库是写少读多,对数据实时性要求并不高。

数据存储采用的数据备份方式是最简单粗暴的办法,每天凌晨备份完整的数据库快照到云服务器上,同时为了防止出现单点服务器存储的故障,或者服务器到期被回收,定期同步最新的备份到自家内网的存储服务上。云服务器上的备份是日常的备份,而自家内网的存储服务是冗余备份,为了避免频繁读写内网存储服务的硬盘(之前已经坏过一次硬盘了),设置了每 3 天备份一次。

MySQL/Mongo 存储服务 -> 云服务器硬盘(常规) -> 内网存储服务(冗余)

服务器被毫无征兆的回收后,唯一的希望就寄托在冗余备份上了,回家一看,顿时傻眼了,冗余备份全是空文件!一阵绝望涌上心头,没想到平时都好好的,一出问题还连锁反应,备份全挂了。

empty bakfile

数据恢复方案

冷静下来之后,开始分析补救方案: 1. 冗余备份为了减少新文件的写入,使用的是覆盖写入的方式,可能连硬盘数据恢复都无力回天。 2. 使用测试环境中之前同步过的正式环境的数据做备份恢复,一查数据发现版本太老了。 3. 求助腾讯云,看腾讯云能否恢复被回收的服务。

经过与腾讯云客服的沟通,得知到期的服务会在 7 天后才完全释放,也就是说只要到期不到 7 天,数据都还有救。于是联系上朋友(该服务器的主人)去腾讯云控制台中进行确认,所幸,到期时间还不满 7 天。在腾讯云客服的指导下,对到期的服务器创建镜像,然后使用该镜像来创建实例,这样原来到期的服务器又复活了。当然也可以直接对原服务器进行续费,但是按月续费的费用稍高,新创建的实例可以选择按量计费,只需把数据导出后就行了。

问题归因

至此,数据存储就可以正常恢复了。但是原来的冗余备份方案为什么关键时刻掉链子了?进一步分析发现,原来下载冗余备份文件时使用了 wget,而该工具的 -O 参数会先创建好空文件,然后把下载的数据写入到空文件中。

wget -O http://xxx.com/download-back

当服务器被回收后,备份下载服务就不可用了,而 wget 依然会创建一个新的空文件,导致原来正常的备份文件被空文件覆盖了,真是一个大坑啊,还是异常处理做得不够。

解决方案

由于原来使用 shell 语句编写的备份下载和写入脚本,新的方案改用 Node.js 来开发,功能上更强大,果断抛弃了 wget 工具。在新的方案中,会先基于文件流对比文件内容是否一致,如果文件不一致,且文件内容合法再写入新文件。整个方案并不复杂,就没有贴代码的必要了。

反思

如果数据比较重要,那么一定要做好备份方案,有条件就购买专业的带备份恢复的存储服务,或者使用主从模式,但是主从模式也需要有离线备份。在做离线备份时最好有冗余备份,防止离线备份的硬盘出问题,最后冗余备份一定要假设极端情况的处理是否到位,避免出现重要数据丢失的悲剧。