在日常开发及运维工作中,可能会遇到各种各样的程序故障。
大部分故障都是由代码bug或操作不当引起,下面就9月2日发生的一次脚本升级故障说起。
最近对进程监控相关脚本进行优化,周五在几台机器上测试OK。考虑到这次修改并不涉及主要逻辑变更,还是选择了下班后更新脚本。
晚上十点多钟的时候接到运维同事打来的电话,反映有好几台机器上的log_monitor.pl进程占用大量系统资源,严重影响业务程序运行。
考虑到晚上刚刚对脚本进行升级,第一反应当然是更新后的脚本问题,于是马上中断正在进行的升级操作,对代码进行紧急回退,并kill所有正在运行的脚本。
脚本全部回退并结束所有正在运行的进程之后,终于不再出现系统资源占用过高的机器,这才开始彻查产生故障的原因。
这里监控脚本消耗系统资源主要表现在下面两个方面:
1、监控脚本本身占用大量内存,最高多达7.1G。
2、部分机器上拉起大量logclient进程(上报远程日志的客户端程序),消耗大量系统资源。
经反复核查测试,发现问题并非由新脚本引起,而是出在脚本升级过程中。详述如下:
一、更新前的监控脚本包含以下处理逻辑:
1、脚本每次执行完,都会上传本次执行的监控日志到远程logserver(新脚本已取消该逻辑)。
2、上传远程日志时会调用get_main_ip.sh获取本地IP(脚本执行前会检查该是否存在)。
二、新监控脚本升级过程如下:
1、清理conf、bin、tmp目录下所有脚本及配置文件,保留log目录下所有日志。
2、用新脚本及配置文件覆盖对应目录。
三、产生问题的场景及原因分析:
当新脚本升级的时候如果老脚本已被调起,则:
1、老脚本在上传监控日志时找不到配置文件,从而尝试上传整个监控日志内容,导致大量logclient进程被拉起。
2、上传远程日志的时候,如果get_main_ip.sh文件被删除,会触发一个隐含的递归调用消耗大量内存。
注:上面两种情况在更新后的脚本中实际都已消除,新脚本不再上传监控日志,也消除了隐含的递归调用。
四、规避此类问题的方法:
1、脚本升级时,采用先清理原有文件、再杀原有进程、最后覆盖新文件的方式。
2、避免多个处理过程之间隐含的递归调用。
3、增强代码健壮性,加强对运行过程中类似文件被意外删除等异常情况的处理,避免出现不可预料的后果。
五、从规范上保证程序健壮性:
1、对于公共脚本(在所有机器部署的),代码至少由两个人进行二次验证。
2、代码升级前必须灰度。
3、对循环中的敏感操作进行确认(如fork,发送告警,拉起其它脚本等)。
4、对需要频繁执行的程序,增加不可重入的逻辑,避免程序同时运行多个实例。