django-crontab项目源码解读

一、知识点

这边源码阅读分析,可以获得如下知识点

1、django-crontab的原理

2、django-crontab任务ID如何生产

3、django-crontab使用限制


很早之前写过关于django-crontab的文章,比如

django 任务管理之crontab

Django实现crontab远程任务管理系统

今天在和一个朋友聊运维平台任务管理中心的问题时,突然想起来django-crontab在进行任务的管理时:

1、先在Django项目中开发相关的功能函数

2、然后在 项目settings.py 中配置 CRONJOBS 列表变量

3、最后通过 python manage.py crontab add 添加任务


这里有几个问题思考

二、crontab add的时候,遇到的问题

其实是先删除所有的旧的,然后再创建所有的任务

源码参考

# django_crontab/management/commands/crontab.py
def handle(self, *args, **options):
    """
    Dispatches by given subcommand
    """
    if options['subcommand'] == 'add':
        with Crontab(**options) as crontab:
            crontab.remove_jobs()
            crontab.add_jobs()
    ... ...

问题1: 就是如果针对某个任务,修改了任务部分配置(这里的配置指的是 CRONJOBS 中配置的) 执行 add or remove时会报错

比如这里有两个任务

CRONJOBS = [
    ('*/1 * * * *', 'jobs.cron.say_hello', [], {}, '>> /tmp/say_hello_v2.txt'),
    ('*/2 * * * *', 'jobs.cron.say_hello', ['james'], {}, '> /tmp/say_hello.txt'),
]

前面已经通过 python manage.py crontab add 添加创建了任务,现在 修改第二个任务的日志输出 为 /tmp/say_hello-modify.txt

然后不管是先执行 crontab add 还是 crontab remove 都会先报个错,然后提示你再次执行 crontab add 就可以了

(temp) [devops@test-xxxx-01-vm /tmp/django_jobs_crontab Mon Mar 11 17:11:02]$ vim django_jobs_crontab/settings.py
(temp) [devops@test-xxxx-01-vm /tmp/django_jobs_crontab Mon Mar 11 17:11:16]$ python manage.py crontab add
removing cronjob: (5e89171a1a9f87fd618b87d1e913d550) -> ('*/1 * * * *', 'jobs.cron.say_hello', [], {}, '>> /tmp/say_hello_v2.txt')
removing cronjob: (1e10640e1aafd33e3d4c47d5ecf0c888) -> ('*/2 * * * *', 'jobs.cron.say_hello', ['james'], {}, '> /tmp/say_hello.txt')
Traceback (most recent call last):
  File "/tmp/django_jobs_crontab/manage.py", line 22, in <module>
    main()
  ... ...
  File "/data/colinspace/.pyenv/versions/temp/lib/python3.9/site-packages/django_crontab/crontab.py", line 171, in __get_job_by_hash
    raise RuntimeError(
RuntimeError: No job with hash 420fc9eb9dac411dd0c17fd6d6b42f84 found. It seems the crontab is out of sync with your settings.CRONJOBS. Run "python manage.py crontab add" again to resolve this issue!

重点在 No job with hash 420fc9eb9dac411dd0c17fd6d6b42f84 found

这个字符串是django-crontab模板给每个人任务生产的唯一ID。

要想知道ID是怎么来的,ID是否其他作用,我们先来看看django-crontab的核心原理


三、django-crontab的实现原理

1、先从django_crontab/management/commands/crontab.py 代码入口

因为目前django-crontab管理任务,都是通过命令行的形式,所以操作入口肯定在 commands下

看到在 Command类 的 handle 方法中通过获取的 subcommand 参数来实现任务的增(add)、查(show)、删(remove)和执行(run) 四个操作

另外有个细节大家需要关注下

实际执行 remove_jobs/add_jobs/show_jobs/run_job 等Crontab类的方法是都是通过 with 实现的。

with使用调用,因为with语句提供了一种简洁的方式来使用上下文管理器,来确保资源的正确获取和释放

关于Python的上下文管理器可以参考文章 《聊聊Python中上下文管理》

关于上下文管理,这里我们知道它是一种用于管理资源的 Python 对象,通过定义 enter() 和 exit() 方法,它可以在进入和退出代码块时执行特定的操作。

2、看到 django_crontab/crontab.py 中对于Crontab类的定义

# django_crontab/crontab.py
class Crontab(object):
    def __init__(self, **option):
        self.verbosity = int(options.get('verbosity', 1))
        self.readonly = options.get('readonly', False)
        self.crontab_lines = []
        self.settings = Settings(settings)

    def __enter__(self):
        self.read()
        return self

    def __exit__(self, type, value, traceback):
        if not self.readonly:
            self.write()
    
    def read(self):
        # 实际是调用 linux 系统 crontab -l 展示结果
        ... ...
    
    def write(self):
        # 实际是读取crontab_lines内容,然后写入到临时文件,然后通过 Linux系统的 crontab tmp_path_file 来把任务写入到当前用户的crontab文件中
        # 一般是 /var/spool/cron/username-xxx
        ... ...

    def add_jobs(self):
        ... ...

    def show_jobs(self):
        ... ...

    def remove_jobs(self):
        ... ...
    
    def run_job(self, job_hash):
        ... ...
    
    def __hash_job(self, job):
        ... ...

    def __get_job_by_hash(self, job_hash):
        ... ...

让我们来分析如上代码

2.1、实现了 enter() 和 exit() 方法,分别是在进入Crontab类和退出的时候执行一些操作

2.2、进入的时候调用了read方法,核心是把当前Linux系统当前用户的任务查出来赋值给self.crontab_lines 变量

2.3、退出的时候,如果readonly 为False的时候,调用write方法 把 self.crontab_lines的值回写到 Linux系统当前用户的任务文件中去

2.4、然后 show_jobsremove_jobs 其实都是操作self.crontab_lines 变量,结合 上下文 来更新 Linux系统当前用户的任务文件

2.5、add_jobs 核心是把 Django settings中配置的CRONJOBS变量的内容以特定的格式(self.settings.CRONTAB_LINE_PATTERN)写入到 self.crontab_lines 变量

所以django-crontab的核心原理就是: 通过读取 Django settings中配置的CRONJOBS变量的内容 以特定的格式写入到self.crontab_lines,然后通过上下文管理来更新 Linux系统当前用户的任务文件


四、django-crontab 的任务ID

在上面分析django-crontab的实现原理中说道 add_jobs 方法按照特定格式写入self.crontab_lines,其中调用了如下代码

self.__hash_job(job)

核心是给每个Job任务,根据job的实际配置来生产一个唯一的ID

def __hash_job(self, job):
    """
    Builds an md5 hash representing the job
    """
    j = json.JSONEncoder(sort_keys=True).encode(job)
    h = hashlib.md5(j.encode('utf-8')).hexdigest()
    return h

为何这里要单独介绍下 任务ID的生成呢,两个点

第一、是前面第二趴介绍 先修改了任务本身,然后执行crontab add or remove遇到问题

是因为任务的ID是根据任务的配置本身来做的md5串,所以修改任务本身任何地方,就会导致任务会有新的ID产生。

因为 crontab add 是先调用 remove_jobs然后调用add_jobs, 在remove_jobs 删除的任务是从 self.crontab_lines来的,然后先从 self.crontab_lines中删除旧的(注意旧的任务ID是旧的)

然后在self.verbosity>=1 时,进行print输出是调用了 self.__get_job_by_hash

def __get_job_by_hash(self, job_hash):
    for job in self.settings.CRONJOBS:
        if self.__hash_job(job) == job_hash:
            return job
    raise RuntimeError(
        'No job with hash %s found. It seems the crontab is out of sync with your settings.CRONJOBS. '
        'Run "python manage.py crontab add" again to resolve this issue!' % job_hash
    )

这是拿新的任务的ID( self.__hash_job(job) )和旧的ID做比较,如果相等怎返回job,否则报错, 新的因为任务本身修改而发生变化,所以ID值肯定发生了变化

第二、这里点留个悬念,后续文章中会涉及到, 是关于运维平台开发统一的任务管理中心时会有介绍


然后回到第二趴说使用django-crontab时的问题

问题2、就是无法单独查看或者删除某个指定ID的任务

虽然每个任务都有了ID,通过 show 可以看到所有任务,但是想只查看/删除 指定ID的任务是无法实现的,只会全部展示或者删除所有任务(从源码执行这两个操作都是遍历循环self.crontab_lines)

当然 run_job 是通过ID获取到job配置,然后获取到django视图函数之后执行该函数。


今天的源码阅读就介绍到这里,相信大家对django-crontab有了更深入的了解。

如果有疑问,随后沟通交流哈。

下篇文章预告 《优化django-crontab实现页面管理任务》


如果你觉得有所收获,欢迎关注"菩提老鹰"进行点赞和喜欢哦~
一起交流,分享知识,快乐生活,我是老鹰,我们下一期见~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,108评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,699评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,812评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,236评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,583评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,739评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,957评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,704评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,447评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,643评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,133评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,486评论 3 256
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,151评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,889评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,782评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,681评论 2 272

推荐阅读更多精彩内容