以前分享了一个监控到代码更新后程序自动reload的机制,当时用的是监控启动文件md5的机制。 现在换了一种方法, 用watchdog来监控文件的更新状态。 watchdog相比以前的方法,方便的实现了关于多个文件的监控,或是针对文件夹的监控… 借助于系统的inotify机制,不用像python那种费劲的一个个的对于文件时间或者是md5 。 当然他也有缺点,可能更多的是针对开发方面的, 他总是莫名的跟其他的逻辑有些少许的冲突,比如我下面说的daemon化….
文章总是 转来转去,标注下原文链接 http://xiaorui.cc/?p=1334
Done:
1、 忽视监控的文件
2、 接收外部传来的SIGTERM信号
3、 增加了daemon服务
遇到了一个坑爹的东西,就是把reload程序做成daemon化,对于后端daemon,我一般用python-daemon,别人都封装好了,你直接引用就好了。
with daemon.DaemonContext():
gan_ni_mei()
结果运行后,和我的reload代码有不少的问题,简单看了下是我这边的信号接收有冲突 ! 因为没女人,太饥渴了,心情不好,直接放弃!也就没有具体研究下代码,干脆自己实现daemon,用的也是最简单的fork chdir setid 那种方式…. 前面没有考虑周全,也是遇到了问题,比如是工作的环境, 当我用chdir把环境提到根的时候,我这边的watchdog会提示监控的数目太多…
看到网上有人说,需要调节内核的参数,但是我突然想到 我这边就监控当前的那几个破文件不至于超出内核inotify的默认阀值吧.. 结果strace发现,他居然从根上开始检索监控,这不是扯淡么? 这就知道问题所在了,需要我们在使用watchdog的时候制定的文件夹,或者是把环境提取到当前的python环境。
sudo sysctl fs.inotify.max_user_watches=<some random high number>
另外再说下,虽然这监控reload程序是支持各种信号的,但是需要你启动的那个程序也支持信号的。 比如它可能正在处理任务, 那么可以自己增加一个signal ,这样不至于任务被强制的干掉,让他自己干完了后,下一个循环的时候判断信号的状态。 废话不多说了,下面是reload的代码
#!/usr/bin/env python
import sys, time, re, os, subprocess, signal
from watchdog.observers import Observer
#
from watchdog.events import FileSystemEventHandler
class Reloader:
def __init__(self, command, delay=0, sig=signal.SIGTERM):
self.command = command
self.delay = delay
self.sig = sig
self.pid = 0
def start_command(self):
self.pid = subprocess.Popen(self.command, preexec_fn=os.setsid).pid
#如果有需要,可以把程序扔到后台,标准的输出放到 dev/null里面
def stop_command(self):
if self.pid > 0:
try:
os.killpg(self.pid, self.sig)
except OSError:
pass
self.pid = 0
def restart_command(self):
print "Restarting command"
self.stop_command()
self.start_command()
class ReloadEventHandler(FileSystemEventHandler):
def __init__(self, ignore_patterns=[]):
self._modified = False
self.ignore_patterns = [re.compile(r) for r in ignore_patterns]
def dispatch(self, event):
if event.is_directory:
return
path = os.path.basename(event.src_path)
if any(r.match(path) for r in self.ignore_patterns):
return
super(ReloadEventHandler, self).dispatch(event)
def on_modified(self, event):
print "Detected change in %s" % event.src_path
self._modified = True
@property
def modified(self):
if self._modified:
self._modified = False
return True
else:
return False
def load_patterns(name):
patterns = []
if os.path.exists(name):
with open(name, "r") as f:
pass
for line in f:
p = line.strip()
if p:
patterns.append(p)
return patterns
def main():
if len(sys.argv) < 2:
print "Usage: reload <command>"
exit(1)
path = "."
sig = signal.SIGTERM
delay = 0.25
command = sys.argv[1:]
ignorefile = ".reloadignore"
ignore_patterns = load_patterns(ignorefile)
event_handler = ReloadEventHandler(ignore_patterns)
reloader = Reloader(command, signal)
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
reloader.start_command()
try:
while True:
time.sleep(delay)
if event_handler.modified:
reloader.restart_command()
except KeyboardInterrupt:
observer.stop()
observer.join()
reloader.stop_command()
if __name__ == "__main__":
main()
用法也很是简单,直接后面跟你要进行的命令就可以了。
[ root@bj-buzz-docker01:/data/buzzMaster ]$ python reload.py python b.py 1430149966.98 1430149967.98 1430149968.98 1430149969.98 1430149970.98
至于daemon,大家可以加入下面的代码实现reload的守候进程状态。
path = 这里需要写入当前的文件目录环境位置
delay = 0.25 监控时间的周期
command = sys.argv[1:]
ignorefile = ".reloadignore"
try:
pid = os.fork()
if pid > 0:
sys.exit(0)
except OSError, e:
print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
sys.exit(1)
os.chdir("/")
os.setsid()
os.umask(0)
try:
pid = os.fork()
if pid > 0:
print "Daemon PID %d" % pid
sys.exit(0)
except OSError, e:
print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
sys.exit(1)
#os.chdir("/") 也可以用这个来配置环境
sig = signal.SIGTERM
ignore_patterns = load_patterns(ignorefile)
event_handler = ReloadEventHandler(ignore_patterns)
reloader = Reloader(command, signal)
observer = Observer()
observer.schedule(event_handler, path, recursive=True)
observer.start()
先这么招吧,这个代码写得不是很满意,讲究这用吧
