使用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应用系统,为用户提供更好的服务体验。

相关推荐
橙子家8 小时前
浏览器缓存之【基础键值存储】:Local storage 和 Session storage
前端
程序员龙叔10 小时前
编写高质量 Skill 系列 -- 如何设计需求分析与用例生成的 SKILL
自动化测试·软件测试·python·软件测试工程师·接口测试·性能测试·skill·ai测试
星星在线10 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒11 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x11 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者12 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重13 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
用户83562907805113 小时前
使用 Python 操作 Word 内容控件
后端·python
LDR00613 小时前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术13 小时前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript