前言
一个简单的需求,即定时启动 Python 脚本,这种需求很常见,比如定时启动一段程序对服务器状态进行收集,写到文件中,方便运维后期审计,查看服务器占用高峰时间段,从而判断出公司产品在该时间段较多人使用,或定时清除其他程序的日志,释放线上服务器的空间。这块常见的架构是有个转存程序,将日志通过 nginx 文件服务挂起,然后该程序请求这种文件,将其存储在数据服务器中,而线上服务器的日志就不需要了(游戏日志通常比较大,所以转存程序也需要设计一下)。
Python 定时任务常见需求包括服务器状态收集或日志清理。文中对比了 threading.Timer、APScheduler 及 Linux crontab 三种方案。threading 模块适合简单场景但稳定性较差,生产环境推荐使用 crontab。通过 Shell 脚本包装 Python 启动命令可解决相对路径问题,确保多文件项目正常执行。此外需注意 CentOS 系统中 yum 依赖系统自带 Python 版本,避免误删导致包管理器失效。

一个简单的需求,即定时启动 Python 脚本,这种需求很常见,比如定时启动一段程序对服务器状态进行收集,写到文件中,方便运维后期审计,查看服务器占用高峰时间段,从而判断出公司产品在该时间段较多人使用,或定时清除其他程序的日志,释放线上服务器的空间。这块常见的架构是有个转存程序,将日志通过 nginx 文件服务挂起,然后该程序请求这种文件,将其存储在数据服务器中,而线上服务器的日志就不需要了(游戏日志通常比较大,所以转存程序也需要设计一下)。
我们来实现一下定时启动 Python 的需求,当然,定时启动其他任何程序也都一样。
一开始,为了省事,直接使用 Python 的 threading 模块。threading 模块下有个 Timer 模块,它可以实现定时启动 Python 程序的需求,用法如下:
from threading import Timer
import datetime
def timedTask():
print(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
main() # 主题程序逻辑
global timer
timer = Timer(300, timedTask)
timer.start()
if __name__ == '__main__':
timedTask()
值得一提的是,timer 需要使用 global timer,据说尝试运行时,会释放无需使用的占用资源。
实现方法很简单,即创建 Timer() 实例,传入两个参数,分别是时间间隔(单位为秒)与定时任务本身,构成一个死递归(因为没有逃出条件),然后就是调用 Timer 实例的 start() 方法。
不推荐,虽然网上博客说使用 global timer 会释放无用资源,但实际没有考证,这种写法在服务器上跑起来的程序通常一天就断。我周日启动该程序,周一来公司看,对应的 Python 程序挂了。
APScheduler 是 Python 用于执行定时操作的第三方框架,作为一个框架,它就有它对应的各种概念,没必要搞那么复杂,学习成本有点高,放弃。
最后还是转到了 Linux 的 crontab 服务,该服务主要就是用于实现定时任务的,其语法如下:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * command to be executed
minute: 代表一小时内的第几分,范围 0-59。
hour: 代表一天中的第几小时,范围 0-23。
mday: 代表一个月中的第几天,范围 1-31。
month: 代表一年中第几个月,范围 1-12。
wday: 代表星期几,范围 0-7 (0 及 7 都是星期天)。
who: 要使用什么身份执行该指令,当您使用 crontab -e 时,不必加此字段。
command: 所要执行的指令。
sudo service crond start # 启动服务
sudo service crond stop # 关闭服务
sudo service crond restart # 重启服务
sudo service crond reload # 重新载入配置
sudo service crond status # 查看服务状态
到这里,关于 crontab 常见的文件就是叫你使用 crontab -e 来编写对应 crontab 配置文件,配置内容的语法如上,例子如下:
# 每天早上 6 点
0 6 * * * echo "Good morning." >> /tmp/test.txt
# 每两个小时
0 */2 * * * echo "Have a break now." >> /tmp/test.txt
# 晚上 11 点到早上 8 点之间每两个小时和早上八点
0 23-7/2,8 * * * echo "Have a good dream" >> /tmp/test.txt
但这边不会这样操作,这种写法并不适合于真正的工作中,就是一个 Toy,我希望的是全自动化,这里通过 shell 脚本来实现自动添加 crontab 任务。
Shell 脚本代码如下:
#!/bin/bash
cd Logsys/
work_path=/x1_game/agent_flask/LogSys/
if [ -n "$1" ]; then
echo "Usages: sh startLog.sh [start stop]"
exit 0
fi
if [ "$1" = "start" ]; then
if [ ! -d logs ]; then
mkdir logs
fi
if [ -f logs/logsys.ini ]; then
echo "logsys task is in crontab, can not add the task to crontab agent!"
exit 0
else
echo "$(date +%Y%m%d)" >> logs/logsys.ini
chmod 777 start.sh
echo "*****${work_path}start.sh start>${work_path}logs/cron.log 2>&1" >> /var/spool/cron/root
echo "add LogSys task to crontab"
fi
elif [ "$1" = "stop" ]; then
if [ -f logs/logsys.ini ]; then
rm -rf logs/logsys.ini
rm -rf /var/spool/cron/root
echo "Stop success and remove the logsys.ini"
else
echo "logsys is not running"
fi
fi
这是我使用的完整 shell 脚本,这里自动添加 crontab 任务的命令只有一行,就是 echo "* * * * * ${work_path}start.sh start >> ${work_path}logs/cron.log 2>&1" >> /var/spool/cron/root,这个命令会每分钟都会调用 start.sh 脚本,而 start.sh 脚本中启动了 python,几个坑需要注意,crontab 中请使用绝对路径,因为 crontab 启动程序时,相对路径所对应的坐标系其实与你手动启动该脚本时是不同的,使用绝对路径省事,这里还将 start.sh 脚本的输出内容都重定向到对应的日志文件中。
为什么不直接通过 crontab 启动 python 程序呢?而是要再绕一层,通过 shell 脚本来启动,这其实也是一个坑,除非你是单 python 文件,不然通常都使用 shell 脚本的形式启动 python,而不在直接使用 crontab 来启动,这同样是因为 crontab 启动的任务相对路径的坐标系改变了,多文件的 python 项目相互引入文件时,使用的坐标系与 crontab 启动时不同,导致 crontab 直接启动 python 项目会失败,所以技巧就在于,通过 shell 脚本来启动 python 程序,在启动前,通过 cd 命令进入 python 项目对应的目录,这样就将启动时的相对路径的坐标系改成与 python 的一致了。
具体可以看一下我的 start.sh 脚本,代码如下:
#!/bin/bash
currTime=$(date "+%Y %m %d")
logfile=${currTime}_logsys.log
if [ -n "$1" ]; then
echo "Usages: sh start.sh [start stop]"
exit 0
fi
if [ ! -d logs ]; then
mkdir logs
fi
pid=$(ps -ef | grep log2mysql | grep -v grep | awk '{print $2}')
if [ "$1" = "start" ]; then
if [ -n "$pid" ]; then
echo "log2mysql is running, can not start"
exit 0
fi
cd /x1_game/agent_flask/LogSys/
nohup /usr/local/bin/python -u log2mysql.py > logs/$logfile 2>&1 &
pid=$(ps -ef | grep log2mysql | grep -v grep | awk '{print $2}')
echo "start log2mysql, pid is:"$pid
elif [ "$1" = "stop" ]; then
if [ -n "$pid" ]; then
echo "kill log2mysql, pid is:"$pid
kill -9 $pid
else
echo "log2mysql is not running"
fi
fi
通过 python 启动任务的关键命令在于
cd /x1_game/agent_flask/LogSys/
nohup /usr/local/bin/python -u log2mysql.py > logs/$logfile 2>&1 &
首先会进入要启动 python 项目的所在目录,然后再通过 python 启动对应的 py 文件,这里使用 python 解释器同样要使用全路径,因为线上系统中存在多个 python,因为该 python 程序是耗时程序,所以我希望它在后台运行,所以使用了 nohup 与 & 关键字,将其放在后台去运行。
CentOS 系统中的 yum 是依赖 python 的,具体到 centos6,其 yum 依赖系统本身就存在的 python2.6,但开发环境通常要使用 python2.7,此时最好不要删除系统中自带的 python2.6,如果你直接删除,会导致 yum 使用不了,此时就需要修改一下 yum 对应文件中的 python 指向,最好的方法就是直接安装 python2.7,然后在/usr/bin 下创建对应的软连接来使用。
Python 程序员在工作中其实不能只会 python,因为 python 虽然强大,但也会有其缺陷,所以什么好用,用什么才是对的,还有 python 是一种语言,不要被语言局限。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online