使用Celery处理Python Web应用中的异步任务

目录

  • [使用Celery处理Python Web应用中的异步任务](#使用Celery处理Python Web应用中的异步任务)
    • [1. 引言](#1. 引言)
      • [1.1 异步任务的重要性](#1.1 异步任务的重要性)
      • [1.2 Celery简介](#1.2 Celery简介)
    • [2. Celery基础概念](#2. Celery基础概念)
      • [2.1 核心组件架构](#2.1 核心组件架构)
      • [2.2 任务执行流程](#2.2 任务执行流程)
    • [3. 环境配置与安装](#3. 环境配置与安装)
      • [3.1 安装依赖](#3.1 安装依赖)
      • [3.2 消息代理选择与配置](#3.2 消息代理选择与配置)
    • [4. 基础用法与实践](#4. 基础用法与实践)
      • [4.1 最小化Celery应用](#4.1 最小化Celery应用)
      • [4.2 基础任务示例](#4.2 基础任务示例)
    • [5. 高级特性与最佳实践](#5. 高级特性与最佳实践)
      • [5.1 任务重试与错误处理](#5.1 任务重试与错误处理)
      • [5.2 任务工作流与链式操作](#5.2 任务工作流与链式操作)
    • [6. Web框架集成](#6. Web框架集成)
      • [6.1 Flask集成示例](#6.1 Flask集成示例)
      • [6.2 Django集成示例](#6.2 Django集成示例)
    • [7. 监控与管理](#7. 监控与管理)
      • [7.1 使用Flower监控Celery](#7.1 使用Flower监控Celery)
      • [7.2 自定义监控指标](#7.2 自定义监控指标)
    • [8. 性能优化与最佳实践](#8. 性能优化与最佳实践)
      • [8.1 配置优化](#8.1 配置优化)
      • [8.2 部署配置](#8.2 部署配置)
    • [9. 完整代码示例](#9. 完整代码示例)
      • [9.1 项目结构](#9.1 项目结构)
      • [9.2 完整的Celery应用配置](#9.2 完整的Celery应用配置)
      • [9.3 依赖文件](#9.3 依赖文件)
    • [10. 总结](#10. 总结)
      • [10.1 核心优势](#10.1 核心优势)
      • [10.2 性能指标对比](#10.2 性能指标对比)
      • [10.3 最佳实践总结](#10.3 最佳实践总结)

『宝藏代码胶囊开张啦!』------ 我的 CodeCapsule 来咯!✨

写代码不再头疼!我的新站点 CodeCapsule 主打一个 "白菜价"+"量身定制 "!无论是卡脖子的毕设/课设/文献复现 ,需要灵光一现的算法改进 ,还是想给项目加个"外挂",这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网

使用Celery处理Python Web应用中的异步任务

1. 引言

1.1 异步任务的重要性

在现代Web应用开发中,用户体验和系统性能是至关重要的考量因素。传统的同步请求-响应模式在处理耗时操作时会显著降低用户体验,比如:

  • 用户注册时发送验证邮件
  • 处理大型文件上传和解析
  • 生成复杂的报表和数据导出
  • 调用第三方API服务
  • 执行数据分析和机器学习推理

这些操作如果采用同步方式处理,会导致用户长时间等待,甚至出现请求超时。异步任务处理通过将耗时操作转移到后台执行,让Web应用能够立即响应用户请求,大大提升了用户体验和系统吞吐量。

1.2 Celery简介

Celery是一个基于分布式消息传递的异步任务队列/作业队列,专注于实时处理,同时支持任务调度。它的核心特性包括:

  • 分布式架构:支持多工作进程和服务器
  • 灵活的消息传输:支持Redis、RabbitMQ、Amazon SQS等
  • 任务调度:支持定时任务和周期性任务
  • 结果存储:支持多种后端存储任务结果
  • 监控和管理:提供丰富的监控工具

Celery的典型应用场景包括:

  • 后台任务处理
  • 定时任务调度
  • 分布式计算
  • 工作流管理

2. Celery基础概念

2.1 核心组件架构

Celery架构包含以下几个核心组件:
Celery生态系统 发送任务 任务队列 执行任务 存储结果 查询结果 监控工具 Flower 定时任务 Beat Client/Web应用 消息代理 Broker Celery Worker 任务结果后端

各个组件的功能说明:

  • Client:应用程序代码,负责发送任务
  • Broker:消息中间件,负责接收和分发任务
  • Worker:工作进程,负责执行任务
  • Backend:存储任务状态和结果
  • Beat:定时任务调度器
  • Flower:实时监控工具

2.2 任务执行流程

任务在Celery中的完整执行流程可以用以下公式表示:

Task Lifecycle = Submit → Queue → Execute → Result \text{Task Lifecycle} = \text{Submit} \rightarrow \text{Queue} \rightarrow \text{Execute} \rightarrow \text{Result} Task Lifecycle=Submit→Queue→Execute→Result

具体步骤:

  1. 任务提交 :Client调用task.delay()task.apply_async()
  2. 消息序列化:任务参数被序列化并发送到Broker
  3. 任务排队:Broker将任务放入相应的队列
  4. 任务获取:Worker从Broker获取任务
  5. 任务执行:Worker执行任务函数
  6. 结果存储:执行结果存储到Backend
  7. 结果查询 :Client可以通过AsyncResult查询任务状态和结果

3. 环境配置与安装

3.1 安装依赖

首先安装必要的依赖包:

bash 复制代码
pip install celery redis flower

对于生产环境,建议使用固定版本:

bash 复制代码
pip install celery==5.3.4 redis==4.6.0 flower==1.2.0

3.2 消息代理选择与配置

Celery支持多种消息代理,以下是主要选项的对比:

代理 优点 缺点 适用场景
Redis 安装简单、性能高 持久化需要配置 开发、测试、中小项目
RabbitMQ 功能丰富、稳定可靠 配置复杂、资源消耗大 生产环境、企业级应用
Amazon SQS 完全托管、可扩展 成本较高、延迟较大 AWS环境、云原生应用

这里我们选择Redis作为消息代理,安装和配置:

bash 复制代码
# 安装Redis服务器
sudo apt-get install redis-server

# 启动Redis
redis-server

# 或者使用Docker
docker run -d -p 6379:6379 redis:alpine

4. 基础用法与实践

4.1 最小化Celery应用

创建一个基础的Celery应用:

python 复制代码
# celery_app.py
import os
from celery import Celery
from datetime import timedelta

# 设置环境变量
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

# 创建Celery应用实例
app = Celery(
    'myproject',
    broker='redis://localhost:6379/0',  # Redis作为消息代理
    backend='redis://localhost:6379/1',  # Redis作为结果后端
    include=[  # 包含任务模块
        'tasks.email_tasks',
        'tasks.file_tasks',
        'tasks.analytics_tasks'
    ]
)

# 配置Celery
app.conf.update(
    # 基础配置
    task_serializer='json',
    accept_content=['json'],
    result_serializer='json',
    timezone='Asia/Shanghai',
    enable_utc=True,
    
    # 任务路由配置
    task_routes={
        'tasks.email_tasks.*': {'queue': 'email'},
        'tasks.file_tasks.*': {'queue': 'files'},
        'tasks.analytics_tasks.*': {'queue': 'analytics'},
    },
    
    # 任务结果配置
    result_expires=3600,  # 结果过期时间1小时
    
    # Worker配置
    worker_prefetch_multiplier=4,  # 预取任务数量
    worker_max_tasks_per_child=1000,  # 每个子进程最大任务数
)

if __name__ == '__main__':
    app.start()

4.2 基础任务示例

创建各种类型的任务:

python 复制代码
# tasks/email_tasks.py
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from celery import Celery
from celery_app import app
import time
import logging

logger = logging.getLogger(__name__)

@app.task(bind=True, max_retries=3, default_retry_delay=60)
def send_email(self, to_email, subject, body, html_body=None):
    """
    发送邮件任务
    Args:
        to_email: 收件人邮箱
        subject: 邮件主题
        body: 邮件正文(纯文本)
        html_body: 邮件正文(HTML格式)
    """
    try:
        # 模拟邮件配置(实际使用时替换为真实配置)
        smtp_server = "smtp.example.com"
        port = 587
        sender_email = "noreply@example.com"
        password = "your_password"
        
        # 创建邮件
        message = MIMEMultipart("alternative")
        message["Subject"] = subject
        message["From"] = sender_email
        message["To"] = to_email
        
        # 添加纯文本版本
        part1 = MIMEText(body, "plain")
        message.attach(part1)
        
        # 添加HTML版本(如果提供)
        if html_body:
            part2 = MIMEText(html_body, "html")
            message.attach(part2)
        
        # 发送邮件
        with smtplib.SMTP(smtp_server, port) as server:
            server.starttls()
            server.login(sender_email, password)
            server.sendmail(sender_email, to_email, message.as_string())
        
        logger.info(f"邮件发送成功: {to_email}")
        return {"status": "success", "message": f"邮件已发送至 {to_email}"}
        
    except smtplib.SMTPException as e:
        logger.error(f"邮件发送失败: {str(e)}")
        # 重试逻辑
        raise self.retry(exc=e, countdown=60)

@app.task
def send_bulk_emails(emails_data):
    """
    批量发送邮件任务
    Args:
        emails_data: 邮件数据列表,每个元素包含to_email, subject, body
    """
    results = []
    for email_data in emails_data:
        try:
            # 为每个邮件创建单独的任务
            result = send_email.delay(
                email_data['to_email'],
                email_data['subject'],
                email_data['body']
            )
            results.append(result.id)
        except Exception as e:
            logger.error(f"创建邮件任务失败: {str(e)}")
            results.append(None)
    
    return {
        "total_emails": len(emails_data),
        "task_ids": results,
        "status": "processing"
    }

文件处理任务:

python 复制代码
# tasks/file_tasks.py
import os
import pandas as pd
from celery import Celery
from celery_app import app
import time
import logging
from pathlib import Path

logger = logging.getLogger(__name__)

@app.task(bind=True)
def process_csv_file(self, file_path, output_path=None):
    """
    处理CSV文件任务
    Args:
        file_path: 输入文件路径
        output_path: 输出文件路径
    """
    try:
        # 更新任务状态
        self.update_state(
            state='PROGRESS',
            meta={'current': 0, 'total': 100, 'status': '开始读取文件'}
        )
        
        # 读取CSV文件
        df = pd.read_csv(file_path)
        
        self.update_state(
            state='PROGRESS',
            meta={'current': 30, 'total': 100, 'status': '文件读取完成,开始处理数据'}
        )
        
        # 模拟数据处理
        time.sleep(2)
        
        # 数据清洗和处理
        # 1. 处理缺失值
        df.fillna(method='ffill', inplace=True)
        
        # 2. 数据类型转换
        numeric_columns = df.select_dtypes(include=['object']).columns
        for col in numeric_columns:
            try:
                df[col] = pd.to_numeric(df[col], errors='ignore')
            except:
                pass
        
        self.update_state(
            state='PROGRESS',
            meta={'current': 70, 'total': 100, 'status': '数据清洗完成,开始生成报告'}
        )
        
        # 生成统计报告
        report = {
            'total_rows': len(df),
            'total_columns': len(df.columns),
            'column_names': list(df.columns),
            'data_types': dict(df.dtypes),
            'missing_values': dict(df.isnull().sum()),
            'basic_stats': df.describe().to_dict() if len(df.select_dtypes(include=['number']).columns) > 0 else {}
        }
        
        # 保存处理后的文件
        if output_path:
            df.to_csv(output_path, index=False)
            report['output_file'] = output_path
        
        self.update_state(
            state='PROGRESS',
            meta={'current': 100, 'total': 100, 'status': '任务完成'}
        )
        
        return report
        
    except Exception as e:
        logger.error(f"CSV文件处理失败: {str(e)}")
        return {'status': 'error', 'message': str(e)}

@app.task
def generate_excel_report(data, output_path):
    """
    生成Excel报表任务
    Args:
        data: 报表数据
        output_path: 输出文件路径
    """
    try:
        # 确保输出目录存在
        os.makedirs(os.path.dirname(output_path), exist_ok=True)
        
        # 创建Excel writer
        with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
            # 写入多个sheet
            if isinstance(data, dict):
                for sheet_name, sheet_data in data.items():
                    if isinstance(sheet_data, list):
                        df = pd.DataFrame(sheet_data)
                        df.to_excel(writer, sheet_name=sheet_name, index=False)
                    elif isinstance(sheet_data, pd.DataFrame):
                        sheet_data.to_excel(writer, sheet_name=sheet_name, index=False)
        
        return {
            'status': 'success',
            'output_path': output_path,
            'file_size': os.path.getsize(output_path)
        }
        
    except Exception as e:
        logger.error(f"生成Excel报表失败: {str(e)}")
        return {'status': 'error', 'message': str(e)}

5. 高级特性与最佳实践

5.1 任务重试与错误处理

实现健壮的任务重试机制:

python 复制代码
# tasks/advanced_tasks.py
import requests
from celery import Celery
from celery_app import app
import logging
from time import sleep
from requests.exceptions import RequestException

logger = logging.getLogger(__name__)

class CircuitBreaker:
    """简单的熔断器实现"""
    def __init__(self, failure_threshold=5, recovery_timeout=60):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.failure_count = 0
        self.last_failure_time = None
        self.state = "CLOSED"  # CLOSED, OPEN, HALF_OPEN
    
    def can_execute(self):
        if self.state == "OPEN":
            if self.last_failure_time and \
               (time.time() - self.last_failure_time) > self.recovery_timeout:
                self.state = "HALF_OPEN"
                return True
            return False
        return True
    
    def record_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        
        if self.failure_count >= self.failure_threshold:
            self.state = "OPEN"
    
    def record_success(self):
        self.failure_count = 0
        self.state = "CLOSED"

# 创建熔断器实例
api_circuit_breaker = CircuitBreaker()

@app.task(bind=True, max_retries=5, default_retry_delay=30)
def call_external_api(self, url, method='GET', data=None, headers=None):
    """
    调用外部API任务,包含重试和熔断机制
    """
    # 检查熔断器状态
    if not api_circuit_breaker.can_execute():
        logger.warning(f"熔断器开启,跳过API调用: {url}")
        raise self.retry(countdown=60)
    
    try:
        # 设置请求参数
        request_kwargs = {
            'timeout': 30,
            'headers': headers or {}
        }
        
        if method.upper() == 'GET':
            response = requests.get(url, **request_kwargs)
        elif method.upper() == 'POST':
            request_kwargs['json'] = data
            response = requests.post(url, **request_kwargs)
        else:
            raise ValueError(f"不支持的HTTP方法: {method}")
        
        # 检查响应状态
        response.raise_for_status()
        
        # 记录成功
        api_circuit_breaker.record_success()
        
        return {
            'status': 'success',
            'status_code': response.status_code,
            'data': response.json() if response.content else None
        }
        
    except RequestException as e:
        # 记录失败
        api_circuit_breaker.record_failure()
        
        logger.error(f"API调用失败: {str(e)}")
        
        # 根据错误类型决定重试策略
        if isinstance(e, requests.exceptions.Timeout):
            retry_delay = 10  # 超时快速重试
        elif isinstance(e, requests.exceptions.ConnectionError):
            retry_delay = 30  # 连接错误中等重试
        else:
            retry_delay = 60  # 其他错误慢速重试
        
        # 计算重试次数和延迟
        retry_count = self.request.retries
        exponential_backoff = retry_delay * (2 ** retry_count)
        
        raise self.retry(exc=e, countdown=min(exponential_backoff, 300))

@app.task(bind=True)
def task_with_fallback(self, main_task_args, fallback_task_args):
    """
    带有降级策略的任务
    """
    try:
        # 尝试执行主要任务
        result = call_external_api.delay(**main_task_args)
        
        # 设置超时
        try:
            return result.get(timeout=30)
        except TimeoutError:
            logger.warning("主任务超时,执行降级任务")
            # 主任务超时,执行降级任务
            return execute_fallback_task.delay(**fallback_task_args).get()
            
    except Exception as e:
        logger.error(f"任务执行失败: {str(e)}")
        # 执行降级任务
        return execute_fallback_task.delay(**fallback_task_args).get()

@app.task
def execute_fallback_task(**kwargs):
    """降级任务"""
    logger.info("执行降级任务")
    return {'status': 'fallback', 'message': '使用降级方案处理'}

5.2 任务工作流与链式操作

创建复杂的任务工作流:

python 复制代码
# tasks/workflow_tasks.py
from celery import chain, group, chord
from celery_app import app
import logging

logger = logging.getLogger(__name__)

@app.task
def data_processing_workflow(data_source, output_format='excel'):
    """
    数据处理的完整工作流
    """
    try:
        # 创建工作流链
        workflow = chain(
            validate_data_source.s(data_source),
            extract_data.s(),
            transform_data.s(),
            load_data.s(output_format)
        )
        
        # 执行工作流
        result = workflow()
        
        return {
            'workflow_id': result.id,
            'status': 'started',
            'message': '数据处理工作流已启动'
        }
        
    except Exception as e:
        logger.error(f"工作流创建失败: {str(e)}")
        return {'status': 'error', 'message': str(e)}

@app.task
def validate_data_source(data_source):
    """验证数据源"""
    logger.info(f"验证数据源: {data_source}")
    # 模拟验证逻辑
    if not data_source or not isinstance(data_source, str):
        raise ValueError("无效的数据源")
    
    return {'data_source': data_source, 'valid': True}

@app.task
def extract_data(validation_result):
    """提取数据"""
    logger.info("提取数据")
    data_source = validation_result['data_source']
    
    # 模拟数据提取
    extracted_data = [
        {'id': i, 'value': f"data_{i}", 'timestamp': f"2024-01-{i:02d}"}
        for i in range(1, 6)
    ]
    
    return {
        'source': data_source,
        'data': extracted_data,
        'record_count': len(extracted_data)
    }

@app.task
def transform_data(extraction_result):
    """转换数据"""
    logger.info("转换数据")
    data = extraction_result['data']
    
    # 模拟数据转换
    transformed_data = []
    for record in data:
        transformed_record = record.copy()
        transformed_record['processed'] = True
        transformed_record['transformed_value'] = record['value'].upper()
        transformed_data.append(transformed_record)
    
    return {
        'original_count': len(data),
        'transformed_data': transformed_data,
        'transformation_applied': ['uppercase', 'timestamp_processing']
    }

@app.task
def load_data(transformation_result, output_format):
    """加载数据"""
    logger.info(f"加载数据,输出格式: {output_format}")
    
    data = transformation_result['transformed_data']
    
    # 根据输出格式处理数据
    if output_format == 'excel':
        result = generate_excel_report.delay(
            {'processed_data': data},
            f'/tmp/report_{len(data)}_records.xlsx'
        )
    else:
        result = {'status': 'completed', 'format': output_format, 'record_count': len(data)}
    
    return result

@app.task
def parallel_processing_workflow(data_chunks):
    """
    并行处理工作流
    """
    # 创建并行任务组
    parallel_tasks = group(
        process_data_chunk.s(chunk)
        for chunk in data_chunks
    )
    
    # 创建聚合任务(在所有并行任务完成后执行)
    workflow = chord(parallel_tasks)(aggregate_results.s())
    
    return {
        'workflow_id': workflow.id,
        'chunk_count': len(data_chunks),
        'status': 'started'
    }

@app.task
def process_data_chunk(data_chunk):
    """处理数据块"""
    logger.info(f"处理数据块,大小: {len(data_chunk) if data_chunk else 0}")
    
    # 模拟数据处理
    processed_chunk = [
        {**item, 'processed': True, 'chunk_id': id(item)}
        for item in (data_chunk or [])
    ]
    
    return {
        'input_size': len(data_chunk) if data_chunk else 0,
        'output_size': len(processed_chunk),
        'data': processed_chunk
    }

@app.task
def aggregate_results(individual_results):
    """聚合结果"""
    logger.info("聚合并行处理结果")
    
    total_records = sum(result['output_size'] for result in individual_results)
    successful_chunks = len([r for r in individual_results if r['output_size'] > 0])
    
    return {
        'total_processed_records': total_records,
        'successful_chunks': successful_chunks,
        'total_chunks': len(individual_results),
        'success_rate': successful_chunks / len(individual_results) if individual_results else 0,
        'aggregation_time': '2024-01-01 12:00:00'  # 实际应该使用当前时间
    }

6. Web框架集成

6.1 Flask集成示例

在Flask应用中集成Celery:

python 复制代码
# app.py (Flask应用)
from flask import Flask, request, jsonify, render_template
from celery_app import app as celery_app
from tasks.email_tasks import send_email, send_bulk_emails
from tasks.file_tasks import process_csv_file, generate_excel_report
from tasks.workflow_tasks import data_processing_workflow, parallel_processing_workflow
from celery.result import AsyncResult
import os

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/api/send-email', methods=['POST'])
def api_send_email():
    """发送邮件API接口"""
    try:
        data = request.get_json()
        
        # 验证必要参数
        required_fields = ['to_email', 'subject', 'body']
        for field in required_fields:
            if field not in data:
                return jsonify({
                    'status': 'error',
                    'message': f'缺少必要字段: {field}'
                }), 400
        
        # 异步发送邮件
        task = send_email.delay(
            to_email=data['to_email'],
            subject=data['subject'],
            body=data['body'],
            html_body=data.get('html_body')
        )
        
        return jsonify({
            'status': 'success',
            'message': '邮件发送任务已提交',
            'task_id': task.id
        }), 202
        
    except Exception as e:
        return jsonify({
            'status': 'error',
            'message': f'请求处理失败: {str(e)}'
        }), 500

@app.route('/api/upload-csv', methods=['POST'])
def api_upload_csv():
    """上传并处理CSV文件"""
    try:
        if 'file' not in request.files:
            return jsonify({
                'status': 'error',
                'message': '没有上传文件'
            }), 400
        
        file = request.files['file']
        if file.filename == '':
            return jsonify({
                'status': 'error',
                'message': '没有选择文件'
            }), 400
        
        if not file.filename.endswith('.csv'):
            return jsonify({
                'status': 'error',
                'message': '只支持CSV文件'
            }), 400
        
        # 保存上传的文件
        upload_dir = 'uploads'
        os.makedirs(upload_dir, exist_ok=True)
        file_path = os.path.join(upload_dir, file.filename)
        file.save(file_path)
        
        # 异步处理文件
        output_path = os.path.join(upload_dir, f'processed_{file.filename}')
        task = process_csv_file.delay(file_path, output_path)
        
        return jsonify({
            'status': 'success',
            'message': '文件处理任务已提交',
            'task_id': task.id,
            'original_file': file.filename
        }), 202
        
    except Exception as e:
        return jsonify({
            'status': 'error',
            'message': f'文件处理失败: {str(e)}'
        }), 500

@app.route('/api/task/<task_id>', methods=['GET'])
def get_task_status(task_id):
    """获取任务状态"""
    try:
        task_result = AsyncResult(task_id, app=celery_app)
        
        response_data = {
            'task_id': task_id,
            'status': task_result.status,
        }
        
        if task_result.status == 'SUCCESS':
            response_data['result'] = task_result.result
        elif task_result.status == 'FAILURE':
            response_data['error'] = str(task_result.result)
        elif task_result.status == 'PROGRESS':
            response_data['progress'] = task_result.result
        
        return jsonify(response_data)
        
    except Exception as e:
        return jsonify({
            'status': 'error',
            'message': f'获取任务状态失败: {str(e)}'
        }), 500

@app.route('/api/batch-process', methods=['POST'])
def api_batch_process():
    """批量数据处理"""
    try:
        data = request.get_json()
        
        if 'data_chunks' not in data or not isinstance(data['data_chunks'], list):
            return jsonify({
                'status': 'error',
                'message': '缺少data_chunks字段或格式不正确'
            }), 400
        
        # 启动并行处理工作流
        task = parallel_processing_workflow.delay(data['data_chunks'])
        
        return jsonify({
            'status': 'success',
            'message': '批量处理工作流已启动',
            'task_id': task.id,
            'chunk_count': len(data['data_chunks'])
        }), 202
        
    except Exception as e:
        return jsonify({
            'status': 'error',
            'message': f'批量处理失败: {str(e)}'
        }), 500

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=5000)

6.2 Django集成示例

在Django项目中集成Celery:

python 复制代码
# myproject/celery.py
import os
from celery import Celery

# 设置Django默认设置模块
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')

app = Celery('myproject')

# 从Django设置中读取Celery配置
app.config_from_object('django.conf:settings', namespace='CELERY')

# 自动从所有已注册的Django app中发现任务
app.autodiscover_tasks()

@app.task(bind=True, ignore_result=True)
def debug_task(self):
    print(f'Request: {self.request!r}')
python 复制代码
# myproject/__init__.py
from .celery import app as celery_app

__all__ = ('celery_app',)

Django视图示例:

python 复制代码
# myapp/views.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from celery.result import AsyncResult
from .tasks import send_email, process_csv_file, data_processing_workflow
import json
import os

@csrf_exempt
@require_http_methods(["POST"])
def send_email_view(request):
    """发送邮件视图"""
    try:
        data = json.loads(request.body)
        
        # 验证参数
        required_fields = ['to_email', 'subject', 'body']
        for field in required_fields:
            if field not in data:
                return JsonResponse({
                    'status': 'error',
                    'message': f'Missing required field: {field}'
                }, status=400)
        
        # 异步发送邮件
        task = send_email.delay(
            to_email=data['to_email'],
            subject=data['subject'],
            body=data['body'],
            html_body=data.get('html_body')
        )
        
        return JsonResponse({
            'status': 'success',
            'message': 'Email task submitted',
            'task_id': task.id
        }, status=202)
        
    except Exception as e:
        return JsonResponse({
            'status': 'error',
            'message': f'Request processing failed: {str(e)}'
        }, status=500)

@csrf_exempt
@require_http_methods(["GET"])
def task_status_view(request, task_id):
    """任务状态查询视图"""
    try:
        task_result = AsyncResult(task_id)
        
        response_data = {
            'task_id': task_id,
            'status': task_result.status,
        }
        
        if task_result.status == 'SUCCESS':
            response_data['result'] = task_result.result
        elif task_result.status == 'FAILURE':
            response_data['error'] = str(task_result.result)
        elif task_result.status == 'PROGRESS':
            response_data['progress'] = task_result.result
        
        return JsonResponse(response_data)
        
    except Exception as e:
        return JsonResponse({
            'status': 'error',
            'message': f'Failed to get task status: {str(e)}'
        }, status=500)

7. 监控与管理

7.1 使用Flower监控Celery

Flower是Celery的实时监控工具,提供Web界面:

python 复制代码
# 启动Flower监控
celery -A celery_app flower --port=5555

# 带认证的启动
celery -A celery_app flower --port=5555 --basic_auth=admin:password

Flower提供的功能:

  • 实时任务监控
  • Worker状态查看
  • 任务历史记录
  • 任务速率统计
  • API接口

7.2 自定义监控指标

实现自定义监控和指标收集:

python 复制代码
# tasks/monitoring_tasks.py
from celery import Celery
from celery_app import app
import time
import logging
from datetime import datetime, timedelta
from prometheus_client import Counter, Histogram, Gauge, generate_latest
import redis

logger = logging.getLogger(__name__)

# 定义监控指标
TASK_STARTED_COUNTER = Counter('celery_task_started_total', 'Total started tasks', ['task_name'])
TASK_COMPLETED_COUNTER = Counter('celery_task_completed_total', 'Total completed tasks', ['task_name', 'status'])
TASK_DURATION_HISTOGRAM = Histogram('celery_task_duration_seconds', 'Task duration in seconds', ['task_name'])
ACTIVE_TASKS_GAUGE = Gauge('celery_active_tasks', 'Currently active tasks', ['task_name'])

# Redis连接用于存储统计信息
redis_client = redis.Redis(host='localhost', port=6379, db=2)

@app.task(bind=True)
def monitored_task(self, *args, **kwargs):
    """
    带有监控的任务装饰器
    """
    task_name = self.name
    start_time = time.time()
    
    # 更新指标
    TASK_STARTED_COUNTER.labels(task_name=task_name).inc()
    ACTIVE_TASKS_GAUGE.labels(task_name=task_name).inc()
    
    try:
        # 记录任务开始时间
        task_key = f"task:{self.request.id}"
        redis_client.hset(task_key, mapping={
            'name': task_name,
            'start_time': start_time,
            'status': 'started',
            'args': str(args),
            'kwargs': str(kwargs)
        })
        
        # 执行实际任务逻辑
        result = self.run(*args, **kwargs)
        
        # 记录成功
        end_time = time.time()
        duration = end_time - start_time
        
        TASK_COMPLETED_COUNTER.labels(task_name=task_name, status='success').inc()
        TASK_DURATION_HISTOGRAM.labels(task_name=task_name).observe(duration)
        ACTIVE_TASKS_GAUGE.labels(task_name=task_name).dec()
        
        # 更新Redis记录
        redis_client.hset(task_key, mapping={
            'end_time': end_time,
            'duration': duration,
            'status': 'success',
            'result': str(result)
        })
        
        # 设置过期时间(24小时)
        redis_client.expire(task_key, 24 * 3600)
        
        return result
        
    except Exception as e:
        # 记录失败
        end_time = time.time()
        duration = end_time - start_time
        
        TASK_COMPLETED_COUNTER.labels(task_name=task_name, status='failure').inc()
        TASK_DURATION_HISTOGRAM.labels(task_name=task_name).observe(duration)
        ACTIVE_TASKS_GAUGE.labels(task_name=task_name).dec()
        
        # 更新Redis记录
        redis_client.hset(task_key, mapping={
            'end_time': end_time,
            'duration': duration,
            'status': 'failure',
            'error': str(e)
        })
        redis_client.expire(task_key, 24 * 3600)
        
        raise

@app.task
def collect_celery_metrics():
    """
    收集Celery指标任务
    """
    try:
        # 获取任务统计
        inspect = app.control.inspect()
        
        # 活跃任务
        active_tasks = inspect.active()
        active_count = sum(len(tasks) for tasks in (active_tasks or {}).values())
        
        # 预定任务
        scheduled_tasks = inspect.scheduled()
        scheduled_count = sum(len(tasks) for tasks in (scheduled_tasks or {}).values())
        
        # 注册任务
        registered_tasks = inspect.registered()
        task_count = sum(len(tasks) for tasks in (registered_tasks or {}).values())
        
        # 存储到Redis
        metrics_key = "celery:metrics"
        metrics_data = {
            'active_tasks': active_count,
            'scheduled_tasks': scheduled_count,
            'registered_tasks': task_count,
            'timestamp': datetime.now().isoformat()
        }
        
        redis_client.hset(metrics_key, mapping=metrics_data)
        
        logger.info(f"指标收集完成: {metrics_data}")
        
        return metrics_data
        
    except Exception as e:
        logger.error(f"指标收集失败: {str(e)}")
        return {'status': 'error', 'message': str(e)}

@app.task
def cleanup_old_tasks(days=7):
    """
    清理旧任务记录
    """
    try:
        # 查找过期的任务键
        pattern = "task:*"
        keys = redis_client.keys(pattern)
        
        deleted_count = 0
        for key in keys:
            task_data = redis_client.hgetall(key)
            if b'start_time' in task_data:
                start_time = float(task_data[b'start_time'])
                task_age = time.time() - start_time
                
                # 删除超过指定天数的记录
                if task_age > days * 24 * 3600:
                    redis_client.delete(key)
                    deleted_count += 1
        
        logger.info(f"清理了 {deleted_count} 个旧任务记录")
        return {'deleted_count': deleted_count, 'days': days}
        
    except Exception as e:
        logger.error(f"清理任务失败: {str(e)}")
        return {'status': 'error', 'message': str(e)}

8. 性能优化与最佳实践

8.1 配置优化

生产环境下的Celery配置优化:

python 复制代码
# celery_production.py
import os
from celery import Celery
from celery.schedules import crontab

# 生产环境配置
class ProductionConfig:
    # Broker设置
    broker_url = 'redis://localhost:6379/0'
    result_backend = 'redis://localhost:6379/1'
    
    # 任务序列化
    task_serializer = 'json'
    result_serializer = 'json'
    accept_content = ['json']
    
    # 时区设置
    timezone = 'Asia/Shanghai'
    enable_utc = True
    
    # Worker设置
    worker_prefetch_multiplier = 1  # 生产环境建议设为1
    worker_max_tasks_per_child = 100  # 防止内存泄漏
    worker_disable_rate_limits = False
    
    # 任务执行设置
    task_acks_late = True  # 任务完成后确认
    task_reject_on_worker_lost = True  # Worker丢失时拒绝任务
    task_always_eager = False  # 生产环境必须为False
    
    # 结果设置
    result_expires = 3600  # 1小时
    result_compression = 'gzip'
    
    # 队列路由
    task_routes = {
        'tasks.email_tasks.*': {'queue': 'email'},
        'tasks.file_tasks.*': {'queue': 'files'},
        'tasks.analytics_tasks.*': {'queue': 'analytics'},
        'tasks.monitoring_tasks.*': {'queue': 'monitoring'},
    }
    
    # 定时任务
    beat_schedule = {
        'collect-metrics-every-5-minutes': {
            'task': 'tasks.monitoring_tasks.collect_celery_metrics',
            'schedule': 300.0,  # 每5分钟
        },
        'cleanup-old-tasks-daily': {
            'task': 'tasks.monitoring_tasks.cleanup_old_tasks',
            'schedule': crontab(hour=2, minute=0),  # 每天凌晨2点
        },
        'send-daily-report': {
            'task': 'tasks.email_tasks.send_daily_report',
            'schedule': crontab(hour=9, minute=0),  # 每天上午9点
        },
    }
    
    # 安全设置
    worker_hijack_root_logger = False
    worker_redirect_stdouts = False

# 创建应用
app = Celery('myproject')
app.config_from_object(ProductionConfig)

# 自动发现任务
app.autodiscover_tasks(['tasks'])

8.2 部署配置

使用Supervisor管理Celery进程:

ini 复制代码
; /etc/supervisor/conf.d/celery.conf
; Celery Worker进程
[program:celery_worker]
command=/path/to/venv/bin/celery -A celery_production worker --loglevel=info --concurrency=4
directory=/path/to/your/project
user=www-data
numprocs=1
stdout_logfile=/var/log/celery/worker.log
stderr_logfile=/var/log/celery/worker.log
autostart=true
autorestart=true
startsecs=10
stopwaitsecs=600

; Celery Beat进程
[program:celery_beat]
command=/path/to/venv/bin/celery -A celery_production beat --loglevel=info
directory=/path/to/your/project
user=www-data
numprocs=1
stdout_logfile=/var/log/celery/beat.log
stderr_logfile=/var/log/celery/beat.log
autostart=true
autorestart=true
startsecs=10

9. 完整代码示例

9.1 项目结构

复制代码
myproject/
├── app.py                 # Flask应用
├── celery_app.py          # Celery应用配置
├── celery_production.py   # 生产环境配置
├── requirements.txt       # 依赖包
├── tasks/                 # 任务模块
│   ├── __init__.py
│   ├── email_tasks.py
│   ├── file_tasks.py
│   ├── advanced_tasks.py
│   ├── workflow_tasks.py
│   └── monitoring_tasks.py
├── templates/             # Flask模板
│   └── index.html
└── uploads/               # 文件上传目录

9.2 完整的Celery应用配置

python 复制代码
# celery_app.py (完整版本)
import os
import logging
from celery import Celery
from celery.signals import after_setup_logger, after_setup_task_logger

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)

# 创建Celery应用
app = Celery(
    'myproject',
    broker='redis://localhost:6379/0',
    backend='redis://localhost:6379/1',
    include=[
        'tasks.email_tasks',
        'tasks.file_tasks',
        'tasks.advanced_tasks',
        'tasks.workflow_tasks',
        'tasks.monitoring_tasks'
    ]
)

# 基础配置
app.conf.update(
    # 序列化配置
    task_serializer='json',
    result_serializer='json',
    accept_content=['json'],
    timezone='Asia/Shanghai',
    enable_utc=True,
    
    # 任务路由
    task_routes={
        'tasks.email_tasks.*': {'queue': 'email'},
        'tasks.file_tasks.*': {'queue': 'files'},
        'tasks.advanced_tasks.*': {'queue': 'api'},
        'tasks.workflow_tasks.*': {'queue': 'workflow'},
        'tasks.monitoring_tasks.*': {'queue': 'monitoring'},
    },
    
    # 任务结果
    result_expires=3600,
    
    # Worker配置
    worker_prefetch_multiplier=4,
    worker_max_tasks_per_child=1000,
    
    # 任务确认
    task_acks_late=True,
    task_reject_on_worker_lost=True,
)

# 日志配置信号处理器
@after_setup_logger.connect
def setup_logger(logger, *args, **kwargs):
    """配置Celery日志"""
    logger.setLevel(logging.INFO)

@after_setup_task_logger.connect
def setup_task_logger(logger, *args, **kwargs):
    """配置任务日志"""
    logger.setLevel(logging.INFO)

if __name__ == '__main__':
    app.start()

9.3 依赖文件

txt 复制代码
# requirements.txt
celery==5.3.4
redis==4.6.0
flower==1.2.0
flask==2.3.3
pandas==2.0.3
requests==2.31.0
prometheus-client==0.17.1
openpyxl==3.1.2

10. 总结

10.1 核心优势

通过本文的全面介绍,我们可以看到Celery在Python Web应用异步任务处理中的核心优势:

  1. 提升用户体验:将耗时操作异步化,避免用户长时间等待
  2. 提高系统吞吐量:通过任务队列平衡负载,提高系统处理能力
  3. 增强系统可靠性:任务重试、错误处理和降级机制
  4. 灵活的扩展性:支持分布式部署和水平扩展
  5. 完善的生态系统:丰富的监控工具和第三方集成

10.2 性能指标对比

使用Celery前后系统性能的对比可以用以下公式表示:

性能提升 = T sync − T async T sync × 100 % \text{性能提升} = \frac{T_{\text{sync}} - T_{\text{async}}}{T_{\text{sync}}} \times 100\% 性能提升=TsyncTsync−Tasync×100%

其中:

  • T sync T_{\text{sync}} Tsync 是同步处理时间
  • T async T_{\text{async}} Tasync 是异步处理时间

在实际应用中,对于耗时操作,性能提升通常可以达到80%以上。

10.3 最佳实践总结

  1. 任务设计原则

    • 任务应该是幂等的
    • 合理设置任务超时和重试策略
    • 使用任务状态跟踪和进度报告
  2. 部署运维

    • 使用进程管理工具(如Supervisor)
    • 配置完善的监控和告警
    • 定期清理任务结果和日志
  3. 性能优化

    • 根据任务特性配置合适的并发数
    • 使用任务路由实现负载均衡
    • 合理配置消息代理和结果后端
  4. 错误处理

    • 实现完善的异常处理机制
    • 使用熔断器和降级策略
    • 记录详细的错误日志

通过合理使用Celery,我们可以构建出高性能、高可用的Web应用系统,为用户提供更好的服务体验。

相关推荐
excel2 小时前
前端读取文件夹并通过 SSH 上传:完整实现方案 ✅
前端
双向333 小时前
【征文计划】基于Rokid CXR-M SDK 打造AI 实时会议助手:从连接到自定义界面的完整实践
前端
Lei活在当下3 小时前
【业务场景架构实战】6. 从业务痛点到通用能力:Android 优先级分页加载器设计
前端·后端·架构
千里马-horse3 小时前
Async++ 源码分析3---cancel.h
开发语言·c++·async++·cancel
你的人类朋友3 小时前
什么是基础设施中间件
前端·后端
知识分享小能手3 小时前
微信小程序入门学习教程,从入门到精通,WXML(WeiXin Markup Language)语法基础(8)
前端·学习·react.js·微信小程序·小程序·vue·个人开发
MYX_3094 小时前
第四章 神经网络的学习
python·神经网络·学习
海绵宝龙4 小时前
将若依(RuoYi)项目创建为私有Gitee仓库的完整步骤
前端·gitee
K_i1345 小时前
指针步长:C/C++内存操控的核心法则
java·开发语言