需求
写了个爬虫脚本放到服务器上定时运行,当脚本碰到耗时的任务阻塞太久的时候可能下一次定时调用会发生,这会导致同时运行的脚本数量大于一个,可能会造成数据库数据重复等问题。现在想解决这个问题,但是并不想更改爬虫脚本相关的代码。
分析需求不难看出同时运行的爬虫脚本不能大于一个,由于脚本是通过定时任务启动(这里假设是 cron),我们可以在启动爬虫的指令上动手脚,在启动爬虫之前先判断是否有爬虫脚本运行,如果有则不启动爬虫。
实现方法1:ps(失败)
最直观的方法是通过 ps 查找指定名称的进程,然后判断进程是否存在,如果存在则不启动脚本:
TASK=`ps aux | grep sh /root/run_crawler.sh | grep -v grep`
if [ "$TASK" = "" ]; then
# 执行一些耗时的任务
python run.py
fi
如示例代码所示,这个脚本的主要问题在 grep 查找进序上,因为通过 cron 启动这个脚本的时候也会启动同名的进程,所以在查找之前该进程就已经被创建,而根据脚本“进程不存在才执行”的逻辑,这段脚本永远不会执行 if 段里面的内容。
实现方法2:判断指定文件是否存在
我们可以在运行脚本之前判断一个指定的文件是否存在,如果存在表示脚本正在运行,否则运行脚本并创建该文件,脚本运行结束后删除该文件,伪代码如下:
filename = 'LOCK'
if filename 不存在:
创建文件(filename)
# 执行耗时操作
python run.py
删除文件(filename)
示例 bash 代码如下:
LOCK="LOCK.temp"
if [ ! -f "$LOCK" ]; then
touch $LOCK
python run_crawlers.py
sleep 2
python push_message.py
rm -rf $LOCK
这种写法有两个主要的问题:
- 文件名可能会和本地文件冲突 - 可以通过生成时间戳文件名等方法解决
- 可能产生 if 内脚本永远无法运行的 bug
第二个问题产生的原因是当我们执行 if 段的代码时,创建了 lock 文件,但是执行过程中脚本的运行被打断,导致 lock 文件无法被自动删除,除非手动删除否则该脚本永远不会执行。
第二个问题的解决方法
增加一个超时机制,即如果 lock 存在的时间超过指定时间,删除 lock,这里用到了 stat
指令用来获取文件创建的时间戳,date +%s
获取当前时间戳,完整代码如下:
LOCK="LOCK.temp"
if [ ! -f "$LOCK" ]; then
touch $LOCK
# 执行一些耗时操作
python main.py
rm -rf $LOCK
else
ts=`stat -c %Y $LOCK`
now=`date +%s`
# 超时时间为 1800s (30min)
if [ $[ $now - $ts ] -gt 1800 ]; then
rm -rf $LOCK
echo "Lock expired, deleted"
fi
fi
总结
以上提供了一种单例运行的思路,实际上还有很多其他的方法能够避免数据重复写的问题,可以根据自己的实际情况选择。