nova 中定时任务 periodic_task 原理分析 - 文章教程

nova 中定时任务 periodic_task 原理分析

发布于 2021-03-11 字数 15110 浏览 1008 评论 0

在 nova 源代码中, 可以在很多函数上看到 @periodic_task 这样的修饰符, 我们知道这是nova的定时任务, 可以让这个函数周期性执行, 但是可能不太了解这个修饰符产生作用的原理和用法, 这里将详细说明一下.

decorator nova.manager.periodic_task

    # in nova.manager
    def periodic_task(*args, **kwargs):
        def decorator(f):
            f._periodic_task = True
            f._ticks_between_runs = kwargs.pop('ticks_between_runs', 0)
            return f

        if kwargs:
            return decorator
        else:
            return decorator(args[0])

可以看到@periodic_task其实只是给被修饰的函数加上了_periodic_task_ticks_between_runs 2个attr, 并没有做其他的操作. 周期执行函数的action实际上是nova.manager.Managernova.utils.LoopingCall 配合实现的, 后面将详细说明.

black magic of nova.manager.ManagerMeta

    # in nova.manager
    class ManagerMeta(type):
        def __init__(cls, names, bases, dict_):
            super(ManagerMeta, cls).__init__(names, bases, dict_)

            try:
                cls._periodic_tasks = cls._periodic_tasks[:]
            except AttributeError:
                cls._periodic_tasks = []

            try:
                cls._ticks_to_skip = cls._ticks_to_skip.copy()
            except AttributeError:
                cls._ticks_to_skip = {}

            for value in cls.__dict__.values():
                if getattr(value, '_periodic_task', False):
                    task = value
                    name = task.__name__
                    cls._periodic_tasks.append((name, task))
                    cls._ticks_to_skip[name] = task._ticks_between_runs

nova.manager中的ManagerMeta类是nova.manager.Manager的metaclass(在后面可以看到), 这里可以 简单的认为ManagerMetaManager的父类. 在nova.manager.Manager初始化时, 会调用ManagerMeta__init__()方法.

(metaclass属于python里的黑魔法内容, 这里不做详细说明, 大法师们感兴趣可以去看看官方手册 http://docs.python.org/reference/datamodel.html#customizing-class-creation)

for value in cls.__dict__.values():
    if getattr(value, '_periodic_task', False):

ManagerMeta.__init__()中的这两行会将cls(也就是Manager对象自己, 注意是 Manager对象 不是Manager的实例) 中所有使用过@periodic_task修饰符修饰的 函数对象 过滤出来. 过滤后会将这些函数对象放入Manager._periodic_tasks中, 后面定时任务的实现都是从这个变量里取出函数对象并执行.

nova.manager.Manager.periodic_tasks

(注意和nova.manager.periodic_task的区别)

    class Manager(base.Base):
        __metaclass__ = ManagerMeta

        def periodic_tasks(self, context, raise_on_error=False):
            for task_name, task in self._periodic_tasks:
                full_task_name = '.'.join([self.__class__.__name__, task_name])

                ticks_to_skip = self._ticks_to_skip[task_name]
                if ticks_to_skip > 0:
                    LOG.debug(_("Skipping %(full_task_name)s, %(ticks_to_skip)s"
                                " ticks left until next run"), locals())
                    self._ticks_to_skip[task_name] -= 1
                    continue

                self._ticks_to_skip[task_name] = task._ticks_between_runs
                LOG.debug(_("Running periodic task %(full_task_name)s"), locals())

                try:
                    task(self, context)
                except Exception as e:
                    if raise_on_error:
                        raise
                    LOG.exception(_("Error during %(full_task_name)s: %(e)s"),
                                  locals())

从前面ManagerMeta的说明我们已经知道Manager对象建立时, 会将所有attr _periodic_taskTrue 的函数对象 放入self._periodic_tasks中.

Manager中, 我们可以发现一个和periodic_task十分相似的函数periodic_tasks, 通过阅读函数代码可以发现 这个函数实际上的作用就是把所有在self._periodic_tasks中的函数对象(也就是所有用@periodic_task修饰符修饰过的函数) 全部遍历并调用一遍. 如果能定期调用这个函数的话, 就能实现类似linux中crontab的定时任务功能.

下面将说明nova如何定时调用nova.manager.Manager.periodic_tasks实现定时任务.

nova.utils.LoopingCall

    class LoopingCall(object):
        def __init__(self, f=None, *args, **kw):
            self.args = args
            self.kw = kw
            self.f = f
            self._running = False

        def start(self, interval, now=True):
            self._running = True
            done = event.Event()

            def _inner():
                if not now:
                    greenthread.sleep(interval)
                try:
                    while self._running:
                        self.f(*self.args, **self.kw)
                        if not self._running:
                            break
                        greenthread.sleep(interval)
                except LoopingCallDone, e:
                    self.stop()
                    done.send(e.retvalue)
                except Exception:
                    LOG.exception(_('in looping call'))
                    done.send_exception(*sys.exc_info())
                    return
                else:
                    done.send(True)

            self.done = done

            greenthread.spawn(_inner)
            return self.done

        def stop(self):
            self._running = False

        def wait(self):
            return self.done.wait()

nova.utils.LoopingCall的作用就是实现前面提到的定时调用函数的功能.

将函数对象作为LoopingCall的第一个构造参数传入构造一个LoopingCall对象, 然后调用其start()方法后 调用其wait()方法, 就可以实现定时执行函数的功能.

start方法的interval参数为函数两次执行期间的时间间隔, 单位为秒. 前面提到的@periodic_task修饰符 可以设置一个参数ticks_between_runs, 是与其配合使用的, 指经过几次ticks才执行函数. 比如, 上下文为 @periodic_task(ticks_between_runs=2)并且interval=5的话, 被修饰的函数将每 (2+1)*5=15 seconds 执行一次

如果被修饰的函数里raise nova.utils.LoopingCallDone, 可以让LoopingCall的定时任务close gracefully.

Examples

下面两个例子演示了如何使用nova.manager中的@periodic_task修饰符, 或直接使用nova.utils.LoopingCall 在nova中实现定时任务.

periodic_task.py

    #!/usr/bin/env python2

    from nova import manager
    from nova import utils
    import time
    import inspect
    import eventlet

    class TestManager(manager.Manager):
        def __init__(self, *args, **kwargs):
            self.count = 0
            super(TestManager, self).__init__(*args, **kwargs)

        @manager.periodic_task
        def foooooo(self, context=None):
            self.count += 1
            print "Just you know why"
            #print time.ctime()

        @manager.periodic_task
        def baaaaar(self, context=None):
            self.count += 1
            print "Panda"
            #print time.ctime()

        @manager.periodic_task(ticks_between_runs=3)
        def panda(self, context=None):
            print "Panda. Mae teleheshka."


    test_manager = TestManager()
    periodic = utils.LoopingCall(test_manager.periodic_tasks, context=None)
    periodic.start(interval=0.8753)
    periodic.wait()

Output:

stanzgy % python periodic_task.py
Just you know why
Panda
Just you know why
Panda
Just you know why
Panda
Just you know why
Panda
Panda. Mae teleheshka.
Just you know why
Panda
Just you know why
Panda
Just you know why
Panda
Just you know why
Panda
Panda. Mae teleheshka.
...

periodic_func.py

    #!/usr/bin/env python2

    from nova import utils
    import inspect
    import eventlet

    global count
    count=0

    def panda(tagline):
        global count
        count += 1
        print "#", count, "Panda.", tagline
        if count >= 9:
            raise utils.LoopingCallDone

    periodic = utils.LoopingCall(panda, "Mae teleheshka.")
    periodic.start(interval=0.8753)
    periodic.wait()

Output:

stanzgy % python periodic_func.py
# 1 Panda. Mae teleheshka.
# 2 Panda. Mae teleheshka.
# 3 Panda. Mae teleheshka.
# 4 Panda. Mae teleheshka.
# 5 Panda. Mae teleheshka.
# 6 Panda. Mae teleheshka.
# 7 Panda. Mae teleheshka.
# 8 Panda. Mae teleheshka.
# 9 Panda. Mae teleheshka.

如果你对这篇文章有疑问,欢迎到本站 社区 发帖提问或使用手Q扫描下方二维码加群参与讨论,获取更多帮助。

扫码加入群聊

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

目前还没有任何评论,快来抢沙发吧!

关于作者

JSmiles

生命进入颠沛而奔忙的本质状态,并将以不断告别和相遇的陈旧方式继续下去。

2583 文章
29 评论
84935 人气
更多

推荐作者

猫性小仙女

文章 1 评论 0

qq_VO6LhT

文章 0 评论 0

猿舌电影

文章 0 评论 0

7556275422

文章 0 评论 0

YYQ_139

文章 0 评论 0