V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Phishion
V2EX  ›  Django

请问 Django 在启动的时,如何执行一次性业务代码?

  •  
  •   Phishion · 2021-11-10 17:44:11 +08:00 · 3305 次点击
    这是一个创建于 870 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我尝试过各大网站转载的“放在 urls 文件里执行”的办法,但是很不幸,我发现会执行多次。

    具体业务就是设计了一个 Redis Set 队列控制并发,任务正常运行的时候是可以加入、弹出的,但是如果任务在运行过程中发生死机,或者其它未知的 Django 整体崩溃,虽然概率很小,但是一旦发生这个队列就会产生脏数据,我现在是考虑启动 Django 的时候,自动清空这个队列,但是就要求这个函数仅运行一次,如果实现不了,我就只能考虑其他办法。

    请问有什么其他方法能让我的业务代码在启动的时候就运行一次么?

    第 1 条附言  ·  2021-11-11 16:35:23 +08:00

    通过各个方案对比最终结论如下:

    • 在 apps 中定义 def ready() 因为多线程的问题,不能保证执行一次,且各个线程执行的时间十分不固定
    • 在 urls 中定义与上述问题一致,且实现不优雅
    • 在中间件中定义看起来就很复杂,而且我觉得会影响性能,也没必要
    • 自定义启动命令,绝对保证只执行一次,可维护性较好(胜出)

    操作如下:

    1 . 新建一个文件,例如:yourapp/management/commands/startup.py

    2 . 在文件中定义启动程序

    from django.core.management.base import BaseCommand, CommandError
    
    
    class Command(BaseCommand):
    
        def handle(self, *args, **kwargs):
            try:
                 pass  # your code
            except Exception as e:
                raise CommandError('Initialization failed: {}'.format(e))
    
    

    3 . 在外部通过 python3 manage.py startup 执行

    参见: https://pythonin1minute.com/where-to-put-django-startup-code/

    希望能帮助到检索到这个帖子的伙伴。

    关键词 Django 首次启动 运行 代码 一次

    27 条回复    2022-05-29 22:03:55 +08:00
    rationa1cuzz
        1
    rationa1cuzz  
       2021-11-10 17:48:54 +08:00   ❤️ 1
    Django 启动执行任务吗?中间件了解一下
    SmiteChow
        2
    SmiteChow  
       2021-11-10 17:52:54 +08:00   ❤️ 1
    1. 判断是否“产生脏数据”
    2. 再重置数据

    逻辑应该是这样的,而不是次数问题
    676529483
        3
    676529483  
       2021-11-10 17:54:47 +08:00   ❤️ 1
    放在 url 里确实执行一次,但是是一个进程执行一次。用 uwsgi 或者 gunicorn 启动多个进程会发生这个问题。
    从只执行一次的角度,需要别的进程来做,比如 celery 、job 平台等。
    但我感觉你说的业务场景,可以适当修改下更好,比如执行队列数据时,发现历史脏数据就清除掉
    NaVient
        4
    NaVient  
       2021-11-10 18:00:49 +08:00   ❤️ 1
    manage.py 不是有 main 函数吗?
    huazhaozhe
        5
    huazhaozhe  
       2021-11-10 18:02:19 +08:00 via Android   ❤️ 1
    redis 本身不就可以做嘛
    另外这个很奇怪啊,如果要清空队列那不是任务就没了,那我崩溃了几次就清除几次和一次启动清除了几次有啥区别,脏数据嘛用其他方式解决,保证重复任务也没问题这样子?
    Phishion
        6
    Phishion  
    OP
       2021-11-10 18:04:15 +08:00
    @SmiteChow 判断是否有脏数据就要查表,我可以接受启动的时候查询一次是否有脏数据,但是我不能隔段时间就轮询一次数据库,所以还是要解决如何让它只启动一次的问题。
    Phishion
        7
    Phishion  
    OP
       2021-11-10 18:07:43 +08:00
    @huazhaozhe 崩溃其实是极端条件,我外面全部 try 包来了,可以保证任务是否成功都能安全退出队列,另外请问 redis 本身怎么做?我任务队列要持久化的,对应的 Key 肯定不能设置超时
    freakxx
        8
    freakxx  
       2021-11-10 18:08:09 +08:00   ❤️ 1
    我尝试过各大网站转载的“放在 urls 文件里执行”的办法,但是很不幸,我发现会执行多次。

    。。。这种做法完全就是傻逼的做法,把业务代码扔到 router ,无论代码能不能跑都是不对的

    -----

    django 是以 app 为基本单位划分业务逻辑,
    你需要执行的代码,放在 apps.py 里面做执行
    具体搜下 django app run code once 之类的关键词

    找到的结果大概这样
    https://pythonin1minute.com/where-to-put-django-startup-code/
    Phishion
        9
    Phishion  
    OP
       2021-11-10 18:14:46 +08:00
    @676529483 感谢回复,你这个方法是最稳的,但是这块儿代码我想让它 15 秒轮询一次,因为除非是极端条件,否则根本不会出现这样的事情,所以想尽量不查表完成,当然也不是必须,如果我没找到好的方法就按常规的解决。
    Phishion
        10
    Phishion  
    OP
       2021-11-10 18:15:23 +08:00
    @freakxx 感谢回复,我找找
    vicalloy
        11
    vicalloy  
       2021-11-10 18:20:44 +08:00   ❤️ 1
    写个 django 从 command ,在 crontab 里定时调用。
    chuanqirenwu
        12
    chuanqirenwu  
       2021-11-10 18:50:24 +08:00   ❤️ 1
    重写 get_asig_application 或者 get_wsgi_application 方法,在 django.setup 后执行校验逻辑,另外可以看下 django 的 system check 系统,应该有相应的 hook 。
    tomczhen
        13
    tomczhen  
       2021-11-10 19:40:28 +08:00 via Android   ❤️ 1
    kidblg
        14
    kidblg  
       2021-11-10 19:41:36 +08:00   ❤️ 1
    面向可维护的开发吧,得考虑后来接手人的感受。
    wuwukai007
        15
    wuwukai007  
       2021-11-10 19:56:13 +08:00   ❤️ 1
    放在 wsgi 然后 gunicorn 启动的时候指定 --preload
    zengxs
        16
    zengxs  
       2021-11-10 20:31:11 +08:00   ❤️ 1
    通过 Django 的中间件,用 APScheduler 启动一个定时任务

    https://gist.github.com/zengxs/f3d0d1a894587af929fed835d4d78bbd
    GTim
        17
    GTim  
       2021-11-10 21:10:31 +08:00   ❤️ 1
    adoal
        18
    adoal  
       2021-11-10 21:18:13 +08:00 via iPhone   ❤️ 1
    没有运维知识的纯程序员往往会把什么乱七八糟的需求都用项目的主编程语言写到主程序里面去。其实呢,你的程序在生产环境运行时,可以外面包一个 sh 脚本来启动,把清理数据的操作单独写一个几行的 py 在 django 之前运行不就可以了嘛。
    naijoag
        19
    naijoag  
       2021-11-10 21:44:26 +08:00   ❤️ 1
    def ready
    Phishion
        20
    Phishion  
    OP
       2021-11-10 22:45:09 +08:00
    感谢各位回复,我来想一个优雅的方法解决这个问题
    ericls
        21
    ericls  
       2021-11-11 08:08:08 +08:00 via iPhone
    ready
    littlezzll
        22
    littlezzll  
       2021-11-11 09:10:24 +08:00 via Android
    celery ?
    asmoker
        23
    asmoker  
       2021-11-11 09:24:49 +08:00 via Android
    可以参考下 migrate 的逻辑
    yufpga
        24
    yufpga  
       2021-11-11 09:43:42 +08:00
    方法挺多的, 简单点在 settings.py 中做, 标准点就是楼上说的 ready, 可以翻一下你自己项目下面的 wsgi.py ,django.setup(), apps.populate(settings.INSTALLED_APPS)的源码. 楼上说多进程启动的问题, 在做这部分操作的时候,用 redis 做个分布式锁就好了
    Rebely
        25
    Rebely  
       2021-11-11 10:51:52 +08:00
    写 command, 用 cli 调用
    steptodream
        26
    steptodream  
       2021-11-11 12:47:57 +08:00
    直接写在 wsgi.py 文件里就行啊
    def xxx():
    pass

    xxx()
    gaogang
        27
    gaogang  
       2022-05-29 22:03:55 +08:00
    @steptodream
    这种方式下,如果用的 gunicorn 多进程模式启动 而且没有开启 preload_app 的话 还是会执行多次的
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2786 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 548ms · UTC 15:05 · PVG 23:05 · LAX 08:05 · JFK 11:05
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.