python实现代码更新后实时自动reload机制

以前分享了一个监控到代码更新后程序自动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()

先这么招吧,这个代码写得不是很满意,讲究这用吧



大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

邮箱地址不会被公开。 必填项已用*标注