2012年06月4日

使用MongoDB的GridFS保存用户文件的折腾日记

作者 非鱼

装上MongoDB以后,写好了读写文件的类,开始折腾。

第一天,把一个小系统的用户照片迁移进去,总数据量500多M,很快就完成了,MongoDB进程占用内存500多M,然后读写文件速度都很快,貌似一切正常。存储的地方生成了几个独立的文件,大小分别是64M,128M,256M,512M。翻倍增长。

运行一天后发现没什么问题,第二天,把主系统的用户头像部分迁移进去,文件总量近2万个,大小1.8G左右。迁移还算顺利,然后各个调用也算基本正常,不出所料,生成了第5个文件,1G。进程占用内存1.8G。服务器总共16G内存,还空余3G。

又跑了一天,决定把全部的用户文件都迁移进去,于是花了一天把所有上传下载的代码都改掉,然后晚上先更新了网站,让用户停止往文件目录里写东西,然后开一个命令做迁移。总文件数大概5万,数据量6G左右。结果发现MongoDB进程内存使用量噌噌的往上长,很快可用内存就没了。这时候发现了一个悲剧的事情,迁移的这个命令写错了,这些文件都写进了用户头像那个库。这个库又生成了两个2G的文件。于是明白了第一件事:文件容量翻倍到2G为止。

然后修改代码,按照写进去的数据,又Delete了一遍,但是,可以想像,存储文件的体积是不会自动缩小的,性能第一嘛。用db.stats()命令查看了一下,数据空间1.9G左右,存储空间4G多,文件大小6G多。然后找了一下,有个repair命令,执行了一下,(用下载的bin目录下的mongo.exe连接服务器,use DBName,然后执行db.repairDatabase(),)有所收获,给删了一个2G的文件。再看看db.stats(),存储空间和数据容量很接近了,都是1.9G左右,文件大小4G,没办法了,它不会再缩小了。浪费就浪费一点空间吧,后面新写入的数据会使用这些空间的。

然后重新修改了代码迁移正式数据,结果内存很快就增长到了让我崩溃的地步,眼看着系统可用内存减少到没有,SQLServer占用的内存从3G逐步减少,最后只剩下了可怜的60M内存,要不是现在网站访问量小,估计用户肯定打不开网站了,因为内存变小以后,CPU的使用就开始突增,可以想像,没有缓存了嘛。终于忍耐着迁移完一个目录,大概2/3的数据,MongoDB占用了6G内存,其它程序基本都被挤没了。

这跟我看到的文档说法可不太一样,文档里说,MongoDB占用的内存虽然看上去量很大,但是实际它会告诉操作系统,这些系统是可用的,当有别的程序请求的时候,可以缩减我的内存使用量。

于是,冒着生命危险做了一件事:重启MongoDB的服务。到服务管理器里,点重启,结果也是不出所料,停止服务的时候弹了个错误对话框。再点启动,启动成功了,只占用18M内存。sql server开始恢复。结果,发现网站上的图片都打不开了,读取程序报错,说本地的mongodb端口拒绝连接。一身冷汗就出来了。打开它的日志文件看看,倒是说启动成功了,但是在修复journal,然后也没个进度。只能从CPU占用上猜测它是不是干完了。过了一会儿,再刷新,图片又可以出来了。上天保佑我的没有丢失数据吧。

然后继续迁移剩下的部分,完成之后,生成了2个2G的文件,加上前面那些一半容量的,总共6G的6个文件,内存占用又升到4G多。然后这次尝试用命令行net stop MongoDB来停止,居然很快就停了,查看日志,一切正常,显示所有文件都正常关闭并清理了。再启动,很好,提示没有文件需要修复。打开图片链接,一切正常。

刷新了几个页面,估计打开的图片有几十个了,再去看进程,还是18M内存占用。见鬼了,这玩意儿只在写入的时候才会把文件放到内存里,读取的时候只取索引放内存里?如果像写入时那样的话,现在至少应该占用200M内存以上了。

限制一下SQLServer的最低内存占用,以防意外。下一步,就是好好研究一下这玩意儿到底要怎么备份比较好了。只要启用了journal功能,任何突然断电关机都不会造成数据丢失,启动时会自行修复,只是修复可能会有点慢。在目前这种数据量下,dump出来备份也是可以接受的,不过最理想的应该还是建一个slave服务器,自动复制数据。但是如果是异地服务器,速度慢的话,就怕oplog超出容量造成数据丢失,最好还是在同一个网内做备份,不过目前,不太现实。