一、知识点
这边源码阅读分析,可以获得如下知识点
1、django-crontab的原理
2、django-crontab任务ID如何生产
3、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_jobs
和 remove_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实现页面管理任务》
如果你觉得有所收获,欢迎关注"菩提老鹰
"进行点赞和喜欢哦~
一起交流,分享知识,快乐生活,我是老鹰,我们下一期见~
网友评论