当前位置:首页 > 问答 > 正文

怎么优雅点把MongoDB关了,别直接硬停数据库啥的

要优雅地关闭MongoDB,核心思想是“打个招呼再走”,而不是直接拔电源,直接杀进程或者断电,就像正在写字的你被人猛地撞了一下胳膊,轻则字写歪了,重则本子划破,甚至之前写好的内容都可能因为墨水没干而糊掉,对数据库来说,这就可能导致数据损坏,下次启动都成问题。

最直接、最推荐的方法就是使用MongoDB自带的关闭命令,你需要连接到数据库实例,然后告诉它:“请开始准备关闭吧。” 具体做法是,打开一个命令行终端(比如Linux的shell或者Windows的cmd),使用MongoDB的客户端工具mongosh(新版本)或mongo(旧版本)连接到你的数据库实例,连接上去之后,你会看到一个交互式的提示符,你有几种方式可以下达关机指令。

第一种方式,也是最常见的方式,是切换到admin数据库再执行命令,因为关机这种管理权限很高的操作,通常需要在管理员数据库里执行,你可以输入use admin,然后输入db.shutdownServer(),就像你对一个管家说“请把整栋大楼的电源安全地关掉”一样,数据库接到这个命令后,并不会立刻断电,而是会做一系列准备工作:它会停止接受新的连接请求,让当前正在进行的读写操作安安稳稳地做完,然后把内存里所有已经修改但还没写到硬盘上的数据(这叫“脏页”)认认真真地、完整地同步到硬盘的文件中,这个过程被称为“刷盘”,只有当所有数据都确保安全落盘了,它才会真正地停止进程,体面地“睡去”,根据MongoDB官方手册的说明,这是关闭独立实例(Standalone)或副本集(Replica Set)成员的标准方法。

第二种方式,如果你有系统权限,也可以在操作系统层面发送一个信号,在Linux系统里,你可以找到MongoDB数据库进程的PID(进程号),然后向它发送一个SIGINT信号(就是键盘按Ctrl+C时会产生的信号)或者SIGTERM信号,这个信号相当于轻轻拍一下数据库的肩膀,说“该收工了”,MongoDB进程捕获到这个信号后,会明白你的意思,然后启动和上面db.shutdownServer()一样的优雅关闭流程,这种方法适合写在一些自动化脚本里,但要注意,千万不要使用SIGKILL信号(也就是kill -9),这个信号是“必杀技”,操作系统会强行、立即结束进程,不给数据库任何收拾现场的机会,这就是我们极力要避免的“硬停”。

上面说的是关闭一个单独的MongoDB实例,如果你的环境更复杂一些,用的是MongoDB副本集(简单理解就是一个带有多台机器做备份的高可用架构),关闭的步骤就需要多一点考虑,要讲究一下顺序,目的是为了不影响业务的正常运行,优雅关闭副本集的核心原则是“先摘流量,再关备机,最后关主机”。

想象一下副本集里有一个主节点(Primary),它负责处理所有的写请求和主要的读请求;还有若干个从节点(Secondary),它们同步主节点的数据作为备份,如果你二话不说直接把主节点关了,整个集群会瞬间乱套,剩下的从节点会因为联系不上老大而陷入恐慌,需要花费几秒到几十秒的时间来投票选举出一个新的主节点,在这段选举期间,数据库是无法提供写服务的,这就会导致你的应用程序报错,正确的做法是:

你应该先关闭那些不承担主要读写任务的从节点,在关闭每一个从节点之前,最好先通过命令让它进入维护状态,比如先执行一下rs.stepDown()让它主动退出可能参与的选择,然后再用上面提到的db.shutdownServer()把它关掉,这样关闭对集群完全没有影响,因为流量本来就不在它身上。

当你把所有的从节点都安全关闭之后,最后再来处理主节点,这时,整个集群里就只剩下主节点自己了,即使你把它关掉,也不会触发选举(因为没其他节点可选了),你连接到这个最后的孤零零的主节点,再次使用db.shutdownServer()命令,它就会安然关闭,通过这样的顺序,整个关闭过程对前端应用来说是完全无感的,实现了平滑下线,MongoDB在副本集管理的相关文档中阐述了这种滚动重启和关闭的最佳实践。

除了使用命令,在一些特定的部署环境下,比如你用的是Docker容器,优雅关闭也有需要注意的地方,当你用docker stop命令停止一个运行着MongoDB的容器时,Docker会先向容器内的主进程发送一个SIGTERM信号,等待一段时间(默认10秒)让进程自己退出,如果超时了还不退出,才会强行发送SIGKILL信号,关键在于要确保MongoDB进程能够正确地收到并响应这个SIGTERM信号,官方提供的MongoDB Docker镜像都已经处理好了这一点,它会执行优雅关闭,但为了保险起见,你可以确保你的Dockerfile里MongoDB进程是作为PID为1的启动进程,这样信号才能被正确传递,你也可以在docker stop命令中通过-t参数适当延长等待时间,给数据库足够的刷盘时间,比如设置为60秒:docker stop -t 60 my-mongo-container

无论用什么方法,养成好习惯总是有益的,在计划关闭数据库之前,如果条件允许,最好还是对重要数据做一次完整的备份,这叫有备无患,关闭完成后,也别忘了一眼系统日志(通常是MongoDB的log文件),看看有没有什么错误或警告信息,确认一下关闭过程确实是干净利落的,日志里如果能看到类似“shutdown: going to close listening sockets...”和“dbexit: really exiting”这样的正常关机信息,你就可以放心了,这些细致的步骤,虽然看起来有点繁琐,但比起数据损坏后漫长的恢复过程,甚至是无法恢复带来的巨大损失,这点时间投入是非常值得的,它体现了对数据的尊重和对系统稳定性的负责态度。

怎么优雅点把MongoDB关了,别直接硬停数据库啥的