以前分享了一个监控到代码更新后程序自动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()
先这么招吧,这个代码写得不是很满意,讲究这用吧