Ubuntu下systemd托管Python Telegram Bot定时通知重复触发问题
这不是systemd缓存配置导致的问题,核心原因是multiprocessing启动的定时任务子进程,在执行systemctl restart bot时没有随主进程一同终止,残留的旧进程持续运行旧版本定时逻辑,最终出现新旧时间点同时触发通知的现象。
systemd服务默认的进程清理逻辑不会主动追踪你通过Python multiprocessing模块fork出的子进程,主进程被终止后,残留的子进程会被init进程收养继续在后台常驻。每修改一次代码重启一次服务,就会多残留一个旧的schedule进程,通知触发次数会随重启次数不断增加。
先清理当前已经存在的后台残留进程,避免旧任务继续触发:
# 停止当前运行的bot服务 systemctl stop bot # 杀掉所有bot用户下运行的bot.py相关进程 pkill -u bot -f bot.py # 执行以下命令无输出即代表清理完成 ps aux | grep bot.py | grep -v grep
两个方案二选一即可,优先选择方案1,改动成本最低。
方案1:修改systemd服务配置,确保重启时清理所有子进程
编辑bot.service文件,在[Service]段落下新增两行配置:
[Service] Type=simple User=bot WorkingDirectory=/home/bot/tgbot ExecStart=/usr/bin/python3 /home/bot/tgbot/bot.py Restart=always # 新增以下两行配置 KillMode=mixed TimeoutStopSec=3
配置说明:
KillMode=mixed:让systemd在停止服务时,先给主进程发送终止信号,超时后给cgroup内所有剩余子进程发送强制终止信号,彻底避免孤儿进程残留TimeoutStopSec=3:给进程预留3秒的正常退出时间,避免强制终止导致的逻辑异常
配置修改完成后重新加载服务并启动:
systemctl daemon-reload systemctl start bot
后续执行systemctl restart bot时,所有旧的子进程都会被一并清理,不会再有残留。
方案2:优化代码逻辑,移除不必要的多进程
你的场景不需要单独开多进程运行schedule调度,直接在主进程运行调度逻辑即可从根源上避免子进程残留问题。另外你贴出的代码存在缩进错误、类方法缺少cls/self参数、调度循环位置错误的问题,修正后的参考逻辑如下:
import time import schedule # 此处保留你原有bot初始化等业务代码 def send_message(): bot.send_message(user_ID, 'Message Text') if __name__ == "__main__": # 注册定时任务 schedule.every().day.at("19:00").do(send_message) # 主循环运行调度 while True: schedule.run_pending() time.sleep(1)
注意:所有定时任务注册、进程启动类逻辑必须放在if __name__ == "__main__":代码块内,避免multiprocessing fork进程时重复加载执行任务注册逻辑,导致单进程内也出现任务重复触发的问题。
修复完成后,可以把定时时间修改为距离当前1-2分钟的时间点,重启服务后等待触发,确认只收到1条通知即代表修复成功,后续改回你需要的通知时间即可。
内容的提问来源于stack exchange,提问作者Friken

