前言
我们的服务中,有一个数据库定时备份功能,这个功能基于quartz调度框架来定时执行bat脚本,脚本中通过执行mysqldump命令来实现mysql数据库备份。这样一个mysql数据库备份功能,在部分客户的生产环境中稳定复现备份出的文件大小为0kb问题。
备注:服务部署在Windows Server中。
梳理
备份脚本
@echo off
set date0=%date:~0,10%
set time0=%time:~0,2%
if %time0% lss 10 (
set time0=0%time:~1,7%
)else set time0=%time:~0,8%
set dttm=%date0:/=%%time0::=%
set MYSQLPATH=_mysqlpath_
set BAKPATH=_backpath_
set USERNAME=_user_
set PASSWORD=_password_
cd %MYSQLPATH%\bin
mysqldump --databases business-data -u %USERNAME% -p%PASSWORD% --default-character-set=utf8 --single-transaction --hex-blob --quick --routines --triggers --flush-logs > %BAKPATH%\business-data_%dttm%.sql
pause
业务逻辑
在原始的bat脚本中含有一些路径变量和账号密码信息,因此并不能直接执行,执行前需要在定时任务的逻辑中动态查找mysqldump命令的路径和配置的备份路径以及账号密码信息,等找到并替换原始bat脚本中的变量后,会生成一个新的bat脚本,这个有真实信息的bat脚本通过java中的Runtime运行时的exec(String command)执行。
排查分析
排查过程如下:
1. 取生产环境业务系统运行日志进行分析,并未发现任何异常;
2. 在定时任务的逻辑中一些关键节点增加日志分析,也并未发现异常;
经过上面两个排查后,可以确定问题并不在于定时任务的Java业务代码层中,那剩下的方向有以下两点:
3. bat脚本语法不正确导致不能正常备份;
4. bat脚本语法没有问题,可能跟系统环境有关系。
验证第3项猜测。我们通过cmd命令窗口直接执行bat脚本,这个时候发现脚本能正常执行,而且也备份出来了不是0kb的文件,说明脚本本身没有问题。
验证第4项猜测。一般我们自己打开cmd命令窗口执行某个命令或某个脚本时,如果发生异常,窗口上是可以看到异常信息的。那么Java程序通过Runtime运行bat脚本时,是不是也打开了一个cmd命令窗口,这个窗口对于应用程序可见,但对我们不可见,当它发生异常的时候,是不是也输出了一些对应的异常信息呢?为此,我在业务代码中尝试捕捉bat脚本执行过程中由cmd命令窗口输出的异常信息。
业务代码中捕捉到的cmd命令窗口输出的异常信息如下:
图 1
做一下对比,下面这个是我手动打开的cmd命令窗口执行bat脚本结果截图:
图 2
可以看出两者的确是有差别的,图1明显看到了执行过程中发生了异常。
在图1和图2中,Directory后面都跟着一个路径信息,这个是我在bat脚本中加了参数%cd%,用于输出当前cmd命令窗口所处的路径。图1输出的路径告诉我们当前cmd命令行窗口位于C:\Windows\system32中,图2的cmd命令行窗口位于E:\xxxx\tomcat\webapps\ROOT\temp\中。我们的服务部署在E盘上,这个temp文件夹也恰好位于E盘,所以在执行bat脚本的第16行代码(cd %MYSQLPATH%\bin)切换路径到mysql的bin文件夹时,图2能正常切换并找到了mysqldump命令,然后进行数据库的备份;而图1的cmd命令窗口行位于C盘,用第16行代码无法正常切换,导致它报找不到mysqldump命令的异常。
解决方案
当服务没有部署在C盘时,bat脚本无法通过第16行代码(cd %MYSQLPATH%\bin)切换到其它盘符,问题根源就在于此。针对这个问题,只需要让bat脚本能从一个盘符切到另一个盘符就好了,而 /d 参数恰巧能满足这个需求,修改后的第16行代码验证通过,如下:
cd /d %MYSQLPATH%\bin