如何在不使用Celery的情况下在Django中运行周期性任务?
在Django中用线程实现周期性更新Prometheus内存指标
首先明确:完全可以用线程来做这件事,但要注意线程的启动时机——绝对不能放在settings.py里,因为Django在启动过程中会多次加载settings(比如runserver命令会启动两个进程,导致线程重复创建),而且settings是配置文件,不该放业务逻辑代码。
核心思路
Prometheus的内存指标(比如Gauge、Counter)属于当前进程的内存对象,所以必须在Django的web进程内更新。Celery的worker是独立进程,内存不共享,所以确实用不了。而线程和主进程共享内存,刚好满足需求。
推荐的启动位置
1. 利用AppConfig.ready()方法
每个Django app的AppConfig.ready()方法会在Django启动完成后执行一次(生产环境下),但runserver调试模式下会启动两个进程(主进程+worker进程),所以需要加判断避免重复启动线程。
示例代码:
在你的app目录下创建/修改apps.py:
from django.apps import AppConfig import threading import time from prometheus_client import Gauge # 定义你的Prometheus指标(可以单独放在metrics.py里,这里为了示例写在一起) MY_CUSTOM_METRIC = Gauge('my_custom_metric', '自定义周期性更新指标') def update_metric_task(): """周期性更新指标的任务函数""" while True: # 替换成你的实际更新逻辑:比如从数据库查询、调用API等 current_value = 42 # 示例值 MY_CUSTOM_METRIC.set(current_value) time.sleep(60) # 每分钟执行一次,按需调整 class YourAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'your_app_name' def ready(self): # 避免runserver调试模式下重复启动线程 import sys is_runserver = 'runserver' in sys.argv is_reload_process = '--noreload' not in sys.argv and is_runserver # 只在主进程/非重载进程中启动线程 if not is_reload_process or (is_runserver and '--noreload' in sys.argv): # 设为daemon线程:主进程退出时自动终止,防止僵尸线程 metric_thread = threading.Thread(target=update_metric_task, daemon=True) metric_thread.start()
然后在settings.py的INSTALLED_APPS里替换成这个配置类:
INSTALLED_APPS = [ # ... 其他app 'your_app_name.apps.YourAppConfig', ]
2. 在WSGI/ASGI入口文件启动
生产环境下,WSGI(比如wsgi.py)或ASGI(asgi.py)是web服务器的入口,只会执行一次,这里启动线程更稳妥,不会有调试模式的重复问题。
示例(wsgi.py):
import os import threading import time from prometheus_client import Gauge from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'your_project.settings') # 定义指标 MY_CUSTOM_METRIC = Gauge('my_custom_metric', '自定义周期性更新指标') def update_metric_task(): while True: current_value = 42 # 替换实际逻辑 MY_CUSTOM_METRIC.set(current_value) time.sleep(60) # 启动线程(在WSGI应用初始化前或后都可以) metric_thread = threading.Thread(target=update_metric_task, daemon=True) metric_thread.start() application = get_wsgi_application()
注意事项
- 必须将线程设为
daemon=True:确保主进程退出时,线程会自动终止,避免遗留僵尸线程。 - 如果用多进程的web服务器(比如Gunicorn启动多个worker进程),每个进程都会有自己的线程和内存指标,此时Prometheus会采集到多个进程的指标。如果需要统一指标,建议改用Prometheus的多进程模式(通过文件共享指标数据),或者只启动单个worker进程。
- 替代方案:可以用APScheduler库,它支持在Django中以线程池/进程池方式运行周期性任务,线程池模式下同样能更新内存指标,配置更灵活,适合复杂的定时需求。
内容的提问来源于stack exchange,提问作者TieDad
相关产品推荐
相关产品推荐

