django+celery+django-celery-beat+redis实现定时任务且动态配置和异步任务

一、在项目同级目录下新增celery.py、celeryconfig.py文件

二、项目下代码如下

__init__.py文件中新增如下代码

from __future__ import absolute_import, unicode_literals
from .celery import app as celery_app
#
__all__ = ['celery_app']

broker和backend配置

# celery任务与结果
broker = "redis://localhost:6379/1"   # 任务队列
backend = "redis://localhost:6379/2"  # 结果存储

celeryconfig.py文件中设置celery的配置

from djangoAutoTest import settings
from djangoAutoTest.env.common import broker
from djangoAutoTest.env.common import backend

# 设置结果存储
CELERY_RESULT_BACKEND = backend
# 设置代理人broker
BROKER_URL = broker
# celery 的启动工作数量设置
CELERY_WORKER_CONCURRENCY = 20
# 任务预取功能,就是每个工作的进程/线程在获取任务的时候,会尽量多拿 n 个,以保证获取的通讯成本可以压缩。
CELERYD_PREFETCH_MULTIPLIER = 20
# 非常重要,有些情况下可以防止死锁
CELERYD_FORCE_EXECV = True
# celery 的 worker 执行多少个任务后进行重启操作
CELERY_WORKER_MAX_TASKS_PER_CHILD = 100
# 禁用所有速度限制,如果网络资源有限,不建议开足马力。
CELERY_DISABLE_RATE_LIMITS = True
CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler'
DJANGO_CELERY_BEAT_TZ_AWARE = False
CELERY_TIMEZONE = settings.TIME_ZONE
CELERY_ENABLE_UTC = True

celery.py文件中初始化celery实例

from __future__ import absolute_import, unicode_literals

import os
from celery import Celery
from celery.schedules import crontab
from djangoAutoTest import settings, celeryconfig


os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'djangoAutoTest.settings')

app = Celery('djangoAutoTest')

# 从settings中获取配置,所有配置需要带CELERY_前缀
app.config_from_object(celeryconfig)


# # 定时任务
# app.conf.beat_schedule = {
#     # 进度汇报提醒
#     'notice_progress_report': {
#         'task': 'haohan.tasks.notice_progress',  # 设置是要将哪个任务进行定时
#         'schedule': crontab(hour=14, minute=16),
#     },
# }


# 自动收集任务
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)

三、在django的应用下创建tasks.py文件

tasks.py中写任务

from __future__ import absolute_import, unicode_literals
from celery import shared_task
from haohan.utils.jenkinsBuild import *
from common.framework.log import Logger
import datetime
from common.framework.database import Mysql_DB
from haohan.utils.sendFeishuMsg import SendFeiShuMsg
from djangoAutoTest.env import common

logger = Logger("submit_platform").get_logger()
base_url = common.auto_platform_url


# jenkins服务分支同步
@shared_task
def jenkins_services_sync():
    try:
        logger.info("开始jenkins服务同步")
        update_service_branch_data()
        logger.info("jenkins服务同步完成")
    except Exception as e:
        logger.error(f"jenkins服务同步失败:{e}")
        raise e

四、在settings中注册django_celery_beat

settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_crontab',
    'django_comment_migrate',
    'haohan',
   ....
'django_celery_beat', ]

五、生成django-celery-beat的数据表

python3 manage.py makemigrations
python3 manage.py migrate

六、管理定时任务(两种方式)

(1)、进入django的admin后台配置任务

python3 manage.py createsuperuser

(2)、自己开发界面去管理定时任务

 

 新增任务:

 

 

附自定义任务管理代码:

后端:django

# -*- coding:utf-8 -*-


import django
import json
import os
import pytz

from django_celery_beat import models as celery_models
from django.http import JsonResponse, HttpResponse
from celery import current_app
from common.framework.log import Logger

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "djangoAutoTest.settings")
django.setup()

logger = Logger().get_logger()


# 定时任务列表
def task_index(request):
    if request.method == "GET":
        all_crontab = list(celery_models.PeriodicTask.objects.values('id', 'name', 'task', 'args', 'kwargs', 'enabled', 'crontab'))
        for task in all_crontab:
            crontab_obj = celery_models.CrontabSchedule.objects.filter(id=task["crontab"]).first()
            task["run_time"] = '{0} {1} {2} {3} {4}'.format(
                crontab_obj.minute, crontab_obj.hour,
                crontab_obj.day_of_month, crontab_obj.month_of_year,
                crontab_obj.day_of_week)
        all_tasks_list = list(current_app.tasks.keys())
        tasks_list = []
        for i in all_tasks_list:
            if not str(i).startswith('celery'):
                tasks_list.append(i)
        return JsonResponse(
            {"code": 200, "data": {"all_crontab": list(all_crontab), "tasks_list": tasks_list}, "msg": "获取定时任务成功"})


# 获取任务信息
def get_task_info(request):
    if request.method == "POST":
        task_id = request.body["taskId"]
        task_obj = list(
            celery_models.PeriodicTask.objects.filter(id=task_id).values('id', 'name', 'task', 'args', 'kwargs', 'enabled', 'crontab'))
        crontab_obj = celery_models.CrontabSchedule.objects.filter(id=task_obj[0]["crontab"]).first()
        task_obj[0]["runTime"] = '{0} {1} {2} {3} {4}'.format(
            crontab_obj.minute, crontab_obj.hour,
            crontab_obj.day_of_month, crontab_obj.month_of_year,
            crontab_obj.day_of_week)
        return JsonResponse({"code": 200, "data": {"taskInfo": task_obj}, "msg": "获取定时任务成功"})


# 新增定时任务
def add_task(request):
    if request.method == "POST":
        try:
            task_name = request.body["name"]
            task_class = request.body["task"]
            enabled = request.body["enabled"]
            args = request.body["args"]
            kwargs = request.body["kwargs"]
            crontab_time = request.body["runTime"]
            run_time = str(crontab_time).strip().split(" ")
            if len(run_time) != 5:
                return JsonResponse({"code": 201, "msg": "定时任务时间格式错误"})
            schedule, _ = celery_models.CrontabSchedule.objects.get_or_create(minute=run_time[0], hour=run_time[1],
                                                                              day_of_week=run_time[4],
                                                                              day_of_month=run_time[2],
                                                                              month_of_year=run_time[3],
                                                                              timezone=pytz.timezone('Asia/Shanghai'))
            task_obj = celery_models.PeriodicTask.objects.create(crontab=schedule, name=task_name, args=args,
                                                                 kwargs=kwargs, task=task_class,
                                                                 enabled=enabled)
            task_obj.save()
            celery_models.PeriodicTasks.changed(task_obj)
            return JsonResponse({"code": 200, "msg": "添加定时任务成功"})
        except Exception as e:
            logger.error(f"添加定时任务失败:{e}")
            return JsonResponse({"code": 201, "msg": "添加定时任务失败"})


# 编辑任务
def edit_task(request):
    if request.method == "POST":
        try:
            crontab_time = request.body["runTime"]
            run_time = str(crontab_time).strip().split(" ")
            if len(run_time) != 5:
                return JsonResponse({"code": 201, "msg": "定时任务时间格式错误"})
            else:
                celery_models.PeriodicTask.objects.filter(id=request.body["id"]).update(name=request.body["name"],
                                                                                        task=request.body["task"],
                                                                                        args=request.body["args"],
                                                                                        kwargs=request.body["kwargs"],
                                                                                        enabled=request.body["enabled"])
                celery_models.CrontabSchedule.objects.filter(id=request.body["crontab"]).update(
                    minute=run_time[0], hour=run_time[1],
                    day_of_week=run_time[4],
                    day_of_month=run_time[2],
                    month_of_year=run_time[3],
                )
                task_obj = celery_models.PeriodicTask.objects.get(id=request.body["id"])
                celery_models.PeriodicTasks.changed(task_obj)
            return JsonResponse({"code": 200, "msg": "编辑任务成功"})
        except Exception as e:
            logger.error(f"编辑任务失败:{e}")
            return JsonResponse({"code": 201, "msg": "编辑任务失败"})


# 删除任务
def delete_task(request):
    if request.method == "POST":
        try:
            task_id = request.body["id"]
            task_obj = celery_models.PeriodicTask.objects.get(id=task_id)
            task_obj.crontab.delete()
            task_obj.delete()
            return JsonResponse({"code": 200, "msg": "任务删除成功"})
        except Exception as e:
            logger.error(f"删除任务失败:{e}")
            return JsonResponse({"code": 201, "msg": "任务删除失败"})

 

前端代码:vue

<template>
  <div class="fillcontain">
    <div class="col"></div>
    <div class="homeTitle">定时任务</div>
    <div class="hr_bottom"></div>
    <div style="height: 45px; position: relative">
      <el-button type="primary" @click="addCrontab" class="priBtnSty">新增任务
      </el-button>
    </div>
    <el-container>
      <el-table :data="tableData">
        <el-table-column prop="id"
                         header-align="left"
                         label="序号"
                         min-width="15%"
                         align="left">

        </el-table-column>
        <el-table-column prop="name"
                         header-align="left"
                         label="任务名称"
                         show-overflow-tooltip
                         min-width="50%"
                         align="left">

        </el-table-column>
        <el-table-column prop="task"
                         header-align="left"
                         min-width="50%"
                         show-overflow-tooltip
                         label="方法名称"
                         align="left">

        </el-table-column>
        <el-table-column prop="args"
                         header-align="left"
                         min-width="50%"
                         show-overflow-tooltip
                         label="位置参数"
                         align="left">

        </el-table-column>
        <el-table-column prop="kwargs"
                         header-align="left"
                         min-width="50%"
                         show-overflow-tooltip
                         label="关键字参数"
                         align="left">

        </el-table-column>
        <el-table-column prop="run_time"
                         header-align="left"
                         min-width="30%"
                         align="left">
          <template slot-scope="scope" slot="header">
            <el-tooltip effect="dark" :content="'格式含义:' + meaning" placement="top">
              <span>执行时间 <i class="el-icon-info"></i></span>
            </el-tooltip>
          </template>
        </el-table-column>
        <el-table-column prop="enabled"
                         header-align="left"
                         label="启用"
                         min-width="15%"
                         align="left">
          <template slot-scope="scope">
            <span v-if="scope.row.enabled" style="color: green; font-weight: bolder"></span>
            <span v-if="!scope.row.enabled" style="color: red; font-weight: bolder"></span>
          </template>

        </el-table-column>
        <el-table-column
                header-align="right"
                align="right"
                label="操作"
                width="150"
        >
          <template slot-scope="scope">
            <el-button type="primary" @click="editCrontab(scope.row)" class="btnSty">编辑
            </el-button>
            <el-button type="primary" @click="deleteCrontab(scope.row)" class="btnSty">删除
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </el-container>
    <pagiationCpn
            :pageTotal="pagiations.total"
            :cur-page="processData.page"
            @handleCurrentChange="handleCurrentChange"
            @handleSizeChange="handleSizeChange"
    >
    </pagiationCpn>
    <tasksDialogCpn :tasksDialog="tasksDialog" v-if="tasksDialog" :taskId="taskId"
                    @closeDialog="closeDialog" :title="title" :tasksList="tasksList"></tasksDialogCpn>

  </div>
</template>

<script>
  import pagiationCpn from "cps/pagination/index";
  import tasksDialogCpn from "@/page/tasksManage/components/tasksDialog";
  import {axiosRequest} from "@/utils/common";

  export default {
    name: "index",
    data() {
      return {
        tableData: [],
        pagiations: {
          total: 0
        },
        processData: {
          page: 1,
          limit: 10,
        },
        meaning: "分/时/日/月/周几",
        tasksDialog: false,
        taskId: "",
        title: "",
        tasksList: [],
      }
    },
    components: {
      pagiationCpn,
      tasksDialogCpn,
    },
    mounted() {
      this.getCrontabList()
    },

    methods: {
      getCrontabList() {
        axiosRequest('/haohan/task_index/', "get").then(res => {
          if (res.code === 200) {
            this.tableData = res.data.all_crontab
            this.tasksList = res.data.tasks_list
          }
        })
      },

      addCrontab() {
        this.tasksDialog = true
        this.title = "新增任务"

      },

      editCrontab(row) {
        this.tasksDialog = true
        this.title = "编辑任务"
        this.taskId = row.id
      },

      deleteCrontab(row) {

        this.$confirm('确认删除此任务,是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          this.taskId = row.id
          const params = Object.assign({}, {"id": this.taskId})
          axiosRequest('/haohan/delete_task/', 'post', params).then(res => {
            if (res.code === 200){
              this.$message.success(res.msg)
              this.getCrontabList()
            }else {
              this.$message.error(res.msg)
            }
          }).catch(() => {
            this.$message.info("取消删除")
          })


        })



      },

      // 上下分页
      handleCurrentChange(val) {
        this.processData.page = val;
        this.getCrontabList()
      },
      // 每页显示多少条
      handleSizeChange(val) {
        this.processData.limit = val;
        this.getCrontabList()
      },

      closeDialog() {
        this.tasksDialog = false
      },
    }
  }
</script>

<style lang="less">
  @import "../../style/base.less";

</style>

新增任务的dialog

<template>
  <div>
    <!-- 添加或修改定时任务对话框 -->
    <el-dialog :title="title" :visible.sync="isVisible"
               :close-on-click-modal='false'
               :close-on-press-escape='false'
               :modal-append-to-body="false"
               @close="closeDialog"
               >
      <el-form ref="taskForm" :model="taskForm" :rules="rules" label-width="150px">
        <el-form-item label="任务名称" prop="name">
          <el-col :span="14">
            <el-input v-model="taskForm.name" placeholder="请输入任务名称" style="width: 100%"/>
          </el-col>
        </el-form-item>
        <el-form-item label="执行方法" prop="task">
          <el-col :span="14">
            <el-select v-model="taskForm.task" placeholder="请选择执行的方法" filterable clearable style="width: 100%">
              <el-option v-for="item in tasksList" :key="item.index" :value="item" :label="item"></el-option>
            </el-select>
          </el-col>
        </el-form-item>
        <el-form-item label="位置参数">
          <el-col :span="14">
            <el-input v-model="taskForm.args" placeholder="请输入位置参数(Example: ['arg1', 'arg2'])" style="width: 100%"/>
            <span style="font-size: 12px; color: #9a9999">Example: ["arg1", "arg2"]</span>
          </el-col>
        </el-form-item>
        <el-form-item label="关键字参数">
          <el-col :span="14">
            <el-input v-model="taskForm.kwargs" placeholder="请输入关键字参数(Example: {'argument': 'value'})" style="width: 100%"/>
            <span style="font-size: 12px; color: #9a9999">Example: {"argument": "value"}</span>
          </el-col>
        </el-form-item>
        <el-form-item label="执行时间" prop="runTime">
          <el-col :span="14">
            <el-input v-model="taskForm.runTime" placeholder="请输入定时任务执行时间" style="width: 100%"/>
            <img src="../components/c2.png" style="width: 100%;">
          </el-col>
        </el-form-item>
        <el-form-item label="是否启用" prop="enabled">
          <el-col :span="14">
            <el-checkbox v-model="taskForm.enabled"></el-checkbox>
          </el-col>
        </el-form-item>

      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button @click="isVisible = false">取 消</el-button>
        <el-button type="primary" @click="submitTaskForm('taskForm')">确 定</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
  import {axiosRequest} from "@/utils/common";

  export default {
    name: "tasksDialog",
    data(){
      return {
        isVisible: '',
        taskForm: {
          name: "",
          task: "",
          runTime: "",
          enabled: true,
          args: '[]',
          kwargs: '{}',
        },
        rules: {
          name: [{required: true, message: '任务名称不能为空', trigger: 'blur'}],
          task: [{required: true, message: '执行方法不能为空', trigger: 'blur'}],
          runTime: [{required: true, message: '执行时间不能为空', trigger: 'blur'}],
        },
      }
    },
    props: ['taskId', 'title', 'tasksList'],

    mounted(){
      if (this.title === "编辑任务") {
        this.getTaskInfo()
      }

    },

    methods: {
      submitTaskForm(form){
        this.$refs[form].validate((valid) => {
          if (valid) {
            if (this.taskForm.id){
              axiosRequest('/haohan/edit_task/', "post", this.taskForm).then(res => {
                if (res.code === 200){
                  this.$message.success(res.msg)
                  this.isVisible = false
                  this.$parent.getCrontabList()
                }else {
                  this.$message.error(res.msg)
                }
              })
            }else {
              axiosRequest("/haohan/add_task/", "post", this.taskForm).then(res => {
                if (res.code === 200) {
                  this.$message.success(res.msg)
                  this.isVisible = false
                  this.$parent.getCrontabList()
                }else {
                  this.$message.error(res.msg)
                }
              })
            }


          }
        })
      },

      getTaskInfo(){
        axiosRequest('/haohan/get_task_info/', "post", {"taskId": this.taskId}).then(res => {
          if (res.code === 200) {
            this.taskForm = res.data.taskInfo[0]
          }
        })
      },

      closeDialog(){
        this.$emit('closeDialog');
      },
    }
  }
</script>

<style scoped>

</style>

七、启动celery和beat

# 启动任务 worker 并指定日志输出文件
celery -A djangoAutoTest worker -l INFO --logfile=./common/logs/submit_platform.log

# 启动定时器触发 beat 并指定日志输出文件
celery -A djangoAutoTest beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler --logfile=./common/logs/submit_platform.log

建议:

服务器上可以让运维将启动命令加在jenkins构建的脚本中

RUN echo "nohup celery -A djangoAutoTest worker -l INFO --logfile=./common/logs/submit_platform.log &" >> /opt/${ServiceName}/start.sh
RUN echo "nohup celery -A djangoAutoTest beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler --logfile=./common/logs/submit_platform.log &" >> /opt/${ServiceName}/start.sh
RUN echo "python3 manage.py runserver 0.0.0.0:sport" >> /opt/${ServiceName}/start.sh

也可以自己去服务器上执行后台运行

nohup celery -A djangoAutoTest worker -l INFO --logfile=./common/logs/submit_platform.log &
nohup celery -A djangoAutoTest beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler --logfile=./common/logs/submit_platform.log &

 

posted on 2022-06-01 09:40  阿虾  阅读(1474)  评论(0编辑  收藏  举报

导航