<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Celery Beat 的周期調度機制及實現原理

科技綠洲 ? 來源:Python實用寶典 ? 作者:Python實用寶典 ? 2023-10-31 15:24 ? 次閱讀

Celery 是一個簡單、靈活且可靠的,處理大量消息的分布式系統,它是一個專注于實時處理的任務隊列,同時也支持任務調度。

為了講解 Celery Beat 的周期調度機制及實現原理,我們會基于Django從制作一個簡單的周期任務開始,然后一步一步拆解 Celery Beat 的源代碼。

相關前置應用知識,可以閱讀以下文章:

  1. 實戰教程!Django Celery 異步與定時任務
  2. Python Celery異步快速下載股票數據

1.Celery 簡單周期任務示例

在 celery_app.tasks.py 中添加如下任務:

@shared_task
def pythondict_task():
    print("pythondict_task")

在 django.celery.py 文件中添加如下配置:

from celery_django import settings
from datetime import timedelta


app.autodiscover_tasks(lambda : settings.INSTALLED_APPS)

CELERYBEAT_SCHEDULE = {
    'pythondict_task': {
        'task': 'celery_app.tasks.pythondict_task',
        'schedule': timedelta(seconds=3),
    },
}

app.conf.update(CELERYBEAT_SCHEDULE=CELERYBEAT_SCHEDULE)

至此,配置完成,此時,先啟動 Celery Beat 定時任務命令:

celery beat -A celery_django -S django

然后打開第二個終端進程啟動消費者:

celery -A celery_django worker

此時在worker的終端上就會輸出類似如下的信息

[2021-07-11 16:34:11,546: WARNING/PoolWorker-3] pythondict_task
[2021-07-11 16:34:11,550: WARNING/PoolWorker-4] pythondict_task
[2021-07-11 16:34:11,551: WARNING/PoolWorker-2] pythondict_task
[2021-07-11 16:34:11,560: WARNING/PoolWorker-1] pythondict_task

看到結果正常輸出,說明任務成功定時執行。

2.源碼剖析

為了明白 Celery Beat 是如何實現周期任務調度的,我們需要從 Celery 源碼入手。

當你執行 Celery Beat 啟動命令的時候,到底發生了什么?

celery beat -A celery_django -S django

當你執行這個命令的時候,Celery/bin/celery.py 中的 CeleryCommand 類接收到命令后,會選擇 beat 對應的類執行如下代碼:

# Python 實用寶典
# https://pythondict.com

from celery.bin.beat import beat

class CeleryCommand(Command):
    commands = {
        # ...
        'beat': beat,
        # ...
    }
    # ...
    def execute(self, command, argv=None):
        try:
            cls = self.commands[command]
        except KeyError:
            cls, argv = self.commands['help'], ['help']
        cls = self.commands.get(command) or self.commands['help']
        try:
            return cls(
                app=self.app, on_error=self.on_error,
                no_color=self.no_color, quiet=self.quiet,
                on_usage_error=partial(self.on_usage_error, command=command),
            ).run_from_argv(self.prog_name, argv[1:], command=argv[0])
        except self.UsageError as exc:
            self.on_usage_error(exc)
            return exc.status
        except self.Error as exc:
            self.on_error(exc)
            return exc.status

此時cls對應的是beat類,通過查看位于bin/beat.py中的 beat 類可知,該類只重寫了run方法和add_arguments方法。

所以此時執行的 run_from_argv 方法是 beat 繼承的 Command 的 run_from_argv 方法:

# Python 實用寶典
# https://pythondict.com

def run_from_argv(self, prog_name, argv=None, command=None):
    return self.handle_argv(prog_name, sys.argv if argv is None else argv, command)

該方法中會調用 Command 的 handle_argv 方法,而該方法在經過相關參數處理后會調用 self(*args, **options) 到 call 函數:

# Python 實用寶典
    # https://pythondict.com
    
    def handle_argv(self, prog_name, argv, command=None):
        """Parse command-line arguments from ``argv`` and dispatch
        to :meth:`run`.

        :param prog_name: The program name (``argv[0]``).
        :param argv: Command arguments.

        Exits with an error message if :attr:`supports_args` is disabled
        and ``argv`` contains positional arguments.

        """
        options, args = self.prepare_args(
            *self.parse_options(prog_name, argv, command))
        return self(*args, **options)

Command 類的 __call__函數:

# Python 實用寶典
    # https://pythondict.com
    
    def __call__(self, *args, **kwargs):
        random.seed() # maybe we were forked.
        self.verify_args(args)
        try:
            ret = self.run(*args, **kwargs)
            return ret if ret is not None else EX_OK
        except self.UsageError as exc:
            self.on_usage_error(exc)
            return exc.status
        except self.Error as exc:
            self.on_error(exc)
            return exc.status

可見,在該函數中會調用到run方法,此時調用的run方法就是beat類中重寫的run方法,查看該方法:

# Python 實用寶典
# https://pythondict.com
    
class beat(Command):
    """Start the beat periodic task scheduler.

    Examples::

        celery beat -l info
        celery beat -s /var/run/celery/beat-schedule --detach
        celery beat -S djcelery.schedulers.DatabaseScheduler

    """
    doc = __doc__
    enable_config_from_cmdline = True
    supports_args = False

    def run(self, detach=False, logfile=None, pidfile=None, uid=None,
            gid=None, umask=None, working_directory=None, **kwargs):
        # 是否開啟后臺運行
        if not detach:
            maybe_drop_privileges(uid=uid, gid=gid)
        workdir = working_directory
        kwargs.pop('app', None)
        # 設定偏函數
        beat = partial(self.app.Beat,
                       logfile=logfile, pidfile=pidfile, **kwargs)

        if detach:
            with detached(logfile, pidfile, uid, gid, umask, workdir):
                return beat().run() # 后臺運行
        else:
            return beat().run() # 立即運行

這里引用了偏函數的知識,偏函數就是從基函數創建一個新的帶默認參數的函數,詳細可見廖雪峰老師的介紹:
https://www.liaoxuefeng.com/wiki/1016959663602400/1017454145929440

可見,此時創建了app的Beat方法的偏函數,并通過 .run 函數執行啟動 beat 進程,首先看看這個 beat 方法:

# Python 實用寶典
    # https://pythondict.com
    @cached_property
    def Beat(self, **kwargs):
        # 導入celery.apps.beat:Beat類
        return self.subclass_with_self('celery.apps.beat:Beat')

可以看到此時就實例化了 celery.apps.beat 中的 Beat 類,并調用了該實例的 run 方法:

# Python 實用寶典
    # https://pythondict.com
    def run(self):
        print(str(self.colored.cyan(
            'celery beat v{0} is starting.'.format(VERSION_BANNER))))
        # 初始化loader
        self.init_loader()
        # 設置進程
        self.set_process_title()
        # 開啟任務調度
        self.start_scheduler()

init_loader 中,會導入默認的modules,此時會引入相關的定時任務,這些不是本文重點。我們重點看 start_scheduler 是如何開啟任務調度的:

# Python 實用寶典
    # https://pythondict.com
    def start_scheduler(self):
        c = self.colored
        if self.pidfile: # 是否設定了pid文件
            platforms.create_pidlock(self.pidfile) # 創建pid文件
        # 初始化service
        beat = self.Service(app=self.app,
                            max_interval=self.max_interval,
                            scheduler_cls=self.scheduler_cls,
                            schedule_filename=self.schedule)
        
        # 打印啟動信息
        print(str(c.blue('__ ', c.magenta('-'),
                  c.blue(' ... __ '), c.magenta('-'),
                  c.blue(' _n'),
                  c.reset(self.startup_info(beat)))))
        # 開啟日志
        self.setup_logging()
        if self.socket_timeout:
            logger.debug('Setting default socket timeout to %r',
                         self.socket_timeout)
            # 設置超時
            socket.setdefaulttimeout(self.socket_timeout)
        try:
            # 注冊handler
            self.install_sync_handler(beat)
            # 開啟beat
            beat.start()
        except Exception as exc:
            logger.critical('beat raised exception %s: %r',
                            exc.__class__, exc,
                            exc_info=True)

我們看下beat是如何開啟的:

# Python 實用寶典
    # https://pythondict.com
    def start(self, embedded_process=False, drift=-0.010):
        info('beat: Starting...')
        # 打印最大間隔時間
        debug('beat: Ticking with max interval- >%s',
              humanize_seconds(self.scheduler.max_interval))
        
        # 通知注冊該signal的函數
        signals.beat_init.send(sender=self)
        if embedded_process:
            signals.beat_embedded_init.send(sender=self)
            platforms.set_process_title('celery beat')

        try:
            while not self._is_shutdown.is_set():
                # 調用scheduler.tick()函數檢查還剩多余時間
                interval = self.scheduler.tick()
                interval = interval + drift if interval else interval
                # 如果大于0
                if interval and interval > 0:
                    debug('beat: Waking up %s.',
                          humanize_seconds(interval, prefix='in '))
                    # 休眠
                    time.sleep(interval)
                    if self.scheduler.should_sync():
                        self.scheduler._do_sync()
        except (KeyboardInterrupt, SystemExit):
            self._is_shutdown.set()
        finally:
            self.sync()

這里重點看 self.scheduler.tick() 方法:

# Python 實用寶典
    # https://pythondict.com
    def tick(self):
        """Run a tick, that is one iteration of the scheduler.

        Executes all due tasks.

        """
        remaining_times = []
        try:
            # 遍歷每個周期任務設定
            for entry in values(self.schedule):
                # 下次運行時間
                next_time_to_run = self.maybe_due(entry, self.publisher)
                if next_time_to_run:
                    remaining_times.append(next_time_to_run)
        except RuntimeError:
            pass

        return min(remaining_times + [self.max_interval])

這里通過 self.schedule 拿到了所有存放在用 shelve 寫入的 celerybeat-schedule 文件的定時任務,遍歷所有定時任務,調用 self.maybe_due 方法:

# Python 實用寶典
    # https://pythondict.com
    def maybe_due(self, entry, publisher=None):
        # 是否到達運行時間
        is_due, next_time_to_run = entry.is_due()

        if is_due:
            # 打印任務發送日志
            info('Scheduler: Sending due task %s (%s)', entry.name, entry.task)
            try:
                # 執行任務
                result = self.apply_async(entry, publisher=publisher)
            except Exception as exc:
                error('Message Error: %sn%s',
                      exc, traceback.format_stack(), exc_info=True)
            else:
                debug('%s sent. id- >%s', entry.task, result.id)
        return next_time_to_run

可以看到,此處會判斷任務是否到達定時時間,如果是的話,會調用 apply_async 調用Worker執行任務。如果不是,則返回下次運行時間,讓 Beat 進程進行 Sleep,減少進程資源消耗。

到此,我們就講解完了 Celery Beat 在周期定時任務的檢測調度機制,怎么樣,小伙伴們有沒有什么疑惑?可以在下方留言區留言一起討論哦。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 終端
    +關注

    關注

    1

    文章

    1018

    瀏覽量

    29643
  • 源代碼
    +關注

    關注

    95

    文章

    2932

    瀏覽量

    66212
  • python
    +關注

    關注

    52

    文章

    4700

    瀏覽量

    83634
收藏 人收藏

    評論

    相關推薦

    uc/os任務調度機制

    uc/os任務調度機制uc/OS 任務調度機制 內核的核心任務是任務調度機制,為了對uC/OS進行分析,我們從任務調度開始。在uC/OS中,一個任務通常是一個無限循環,程序具有如下的結
    發表于 07-07 09:46

    UCOS之任務調度機制

    UCOS之任務調度機制
    發表于 05-30 07:56

    VxWorks系統的任務調度機制

    針對多任務系統而言,調度是指根據一定的算法.將CPU 分配給符合條件的任務使用,不同的系統任務調度機制不同。本文介紹VxWorks系統的任務調度策略和算法.分析優先級倒置產
    發表于 12-16 14:11 ?10次下載

    Linux與VxWorks任務調度機制分析

    Linux與VxWorks任務調度機制分析
    發表于 03-28 09:52 ?19次下載

    嵌入式實時操作系統VxWorks內核調度機制研究

    嵌入式實時操作系統VxWorks內核調度機制研究
    發表于 03-29 12:26 ?13次下載

    μC/OS-II 任務調度機制的改進

    介紹μC/OS-II 任務調度機制,并提出一種改進方法,使μC/OS-II變成一個兼備實時與分時任務調度機制的操作系統; 論述改進后系統的特點和要注意的問題,給出部分源代碼。
    發表于 04-15 11:21 ?14次下載

    高可信賴實時操作系統的防危調度機制

    為增強實時操作系統的防危性,在分析現有調度機制的基礎上,探討了最大關鍵度優先的調度算法,該算法是一種混合型的優先級實時調度算法,由靜態優先級、動態子優先級和
    發表于 05-16 11:52 ?10次下載

    基于輪循機制和RED的語音流調度機制

           本文提出了一種新的VoIP業務流調度機制(RR-RED),通過隨機早期檢測(RED)和輪循機制(Round Robin)控制主動丟包。該機制很好的繼承了R
    發表于 09-03 08:58 ?7次下載

    Li nux與VxWorks任務調度機制分析

    分析了Linux和VxWorks兩種多任務操作系統任務調度機制的異同,從任務控制塊、調度的時機、調度的優先級和調度的策略方面進行了詳細的分析和對比。分析了VxWorks和Linux在P
    發表于 11-13 17:54 ?10次下載

    VxWorks系統的任務調度機制

    針對多任務系統而言,調度是指根據一定的算法.將CPU 分配給符合條件的任務使用,不同的系統任務調度機制不同。本文介紹VxWorks系統的任務調度策略和算法.分析優先級倒置產生
    發表于 11-27 16:26 ?13次下載

    嵌入式實時操作系統VxWorks內核調度機制分析

    本文簡要介紹了多任務內核,重點分析了嵌入式實時操作系統VxWorks的內核調度機制——優先級搶占調度和時間片輪轉調度算法。
    發表于 12-11 16:15 ?14次下載

    Windows CE陷阱調度機制

     一.什么是陷阱調度機制?        一般來說,嵌入式操作系統主要由兩部分組成:運行在核心態的內核系統和運行在用戶態的環境子系統組成。因
    發表于 08-27 14:38 ?579次閱讀

    基于動態概率休眠調度機制的WSNs拓撲控制算法_韓瑞艷

    基于動態概率休眠調度機制的WSNs拓撲控制算法_韓瑞艷
    發表于 03-19 19:19 ?0次下載

    虛擬計算資源調度機制研究

    針對基于Xen的vCPU調度機制對虛擬機網絡性能的影響進行了深入研究和分析。提出一種高效、準確、輕量級的網絡排隊敏感類型虛擬機(NSVM)識別方法,可根據當前虛擬機I/O傳輸特征將容易受到影響
    發表于 02-08 17:08 ?0次下載
    虛擬計算資源<b class='flag-5'>調度機制</b>研究

    NB―IoT物理控制信道NB―PDCCH及資源調度機制

    NB―IoT物理控制信道NB―PDCCH及資源調度機制(現代電源技術試題及答案)-NB―IoT物理控制信道NB―PDCCH及資源調度機制 ? ? ? ? ?
    發表于 08-31 19:56 ?13次下載
    NB―IoT物理控制信道NB―PDCCH及資源<b class='flag-5'>調度機制</b>
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>