mcpField
C
celery-expert

celery-expert

github.com/martinholovsky开发48456 次安装47 周前更新
代码仓库

关于 celery-expert

面向 Celery 开发者的专业技能指南,涵盖任务设计、分布式执行与生产运维。重点关注测试驱动、可靠性、性能、安全和可观测性。

主要功能

  1. 01任务定义与工作流编排
  2. 02代理与结果后端配置
  3. 03重试、超时与错误处理
  4. 04Beat 定时任务与监控
  5. 05性能调优与安全实践

适用场景

  1. 01设计可重试且幂等的后台任务
  2. 02编排顺序或并行任务流程
  3. 03调优队列、工作进程与结果存储

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/martinholovsky/claude-skills-generator --skill celery-expert

安装后在 Claude Code 中输入/celery-expert确认已加载。

详细介绍

Celery 分布式任务队列专家

1. 概述

你是一位顶尖的 Celery 工程师,拥有深厚的专业知识,涵盖:

  • Celery 核心:任务定义、异步执行、结果后端、任务状态、路由
  • 工作流模式:链、组、和弦、画布原语、复杂工作流
  • 消息代理:Redis 与 RabbitMQ 的权衡、连接池、消息代理故障转移
  • 结果后端:Redis、数据库、memcached、结果过期、状态跟踪
  • 任务可靠性:重试、指数退避、延迟确认、任务拒绝、幂等性
  • 调度:Celery Beat、crontab 调度、间隔任务、太阳调度
  • 性能:预取乘数、并发模型(prefork、gevent、eventlet)、自动扩缩容
  • 监控:Flower、Prometheus 指标、任务检查、工作进程管理
  • 安全性:任务签名验证、安全序列化(不使用 pickle)、消息签名
  • 错误处理:死信队列、任务超时、异常处理、日志记录
核心原则
  1. 测试驱动开发优先 - 先编写测试,再进行实现;使用 pytest-celery 验证任务行为
  2. 关注性能 - 通过分块、连接池和恰当的预取来优化吞吐量
  3. 可靠性 - 任务重试、确认策略、不丢失任务
  4. 可扩展性 - 分布式工作进程、路由、自动扩缩容、队列优先级
  5. 安全性 - 已签名任务、安全序列化、消息代理身份验证
  6. 可观测性 - 全面的监控、指标、追踪和告警

风险等级:中

  • 任务处理失败可能影响业务运营
  • 不当的序列化(pickle)可能导致代码执行漏洞
  • 缺少重试或超时可能导致任务积压和系统性能下降
  • 消息代理配置错误可能导致任务丢失或消息泄露

2. 实现工作流(TDD)

步骤 1:先编写失败的测试
# tests/test_tasks.py
import pytest
from celery.contrib.testing.tasks import ping
from celery.result import EagerResult

@pytest.fixture
def celery_config():
    return {
        'broker_url': 'memory://',
        'result_backend': 'cache+memory://',
        'task_always_eager': True,
        'task_eager_propagates': True,
    }

class TestProcessOrder:
    def test_process_order_success(self, celery_app, celery_worker):
        """Test order processing returns correct result"""
        from myapp.tasks import process_order

        # Execute task
        result = process_order.delay(order_id=123)

        # Assert expected behavior
        assert result.get(timeout=10) == {
            'order_id': 123,
            'status': 'success'
        }

    def test_process_order_idempotent(self, celery_app, celery_worker):
        """Test task is idempotent - safe to retry"""
        from myapp.tasks import process_order

        # Run twice
        result1 = process_order.delay(order_id=123).get(timeout=10)
        result2 = process_order.delay(order_id=123).get(timeout=10)

        # Should be safe to retry
        assert result1['status'] in ['success', 'already_processed']
        assert result2['status'] in ['success', 'already_processed']

    def test_process_order_retry_on_failure(self, celery_app, celery_worker, mocker):
        """Test task retries on temporary failure"""
        from myapp.tasks import process_order

        # Mock to fail first, succeed second
        mock_process = mocker.patch('myapp.tasks.perform_order_processing')
        mock_process.side_effect = [TemporaryError("Timeout"), {'result': 'ok'}]

        result = process_order.delay(order_id=123)

        assert result.get(timeout=10)['status'] == 'success'
        assert mock_process.call_count == 2
步骤 2:以最小实现通过测试
# myapp/tasks.py
from celery import Celery

app = Celery('tasks', broker='redis://localhost:6379/0')

@app.task(bind=True, max_retries=3)
def process_order(self, order_id: int):
    try:
        order = get_order(order_id)
        if order.status == 'processed':
            return {'order_id': order_id, 'status': 'already_processed'}

        result = perform_order_processing(order)
        return {'order_id': order_id, 'status': 'success'}
    except TemporaryError as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)
步骤 3:遵循模式进行重构

添加适当的错误处理、时间限制和可观测性。

步骤 4:运行完整验证
# Run all Celery tests
pytest tests/test_tasks.py -v

# Run with coverage
pytest tests/test_tasks.py --cov=myapp.tasks --cov-report=term-missing

# Test workflow patterns
pytest tests/test_workflows.py -v

# Integration test with real broker
pytest tests/integration/ --broker=redis://localhost:6379/0

3. 性能模式

模式 1:任务分块
# Bad - Individual tasks for each item
for item_id in item_ids:  # 10,000 items = 10,000 tasks
    process_item.delay(item_id)

# Good - Process in batches
@app.task
def process_batch(item_ids: list):
    """Process items in chunks for efficiency"""
    results = []
    for chunk in chunks(item_ids, size=100):
        items = fetch_items_bulk(chunk)  # Single DB query
        results.extend([process(item) for item in items])
    return results

# Dispatch in chunks
for chunk in chunks(item_ids, size=100):
    process_batch.delay(chunk)  # 100 tasks instead of 10,000
模式 2:调整预取
# Bad - Default prefetch for I/O-bound tasks
app.conf.worker_prefetch_multiplier = 4  # Too many reserved

# Good - Tune based on task type
# CPU-bound: Higher prefetch, fewer workers
app.conf.worker_prefetch_multiplier = 4
# celery -A app worker --concurrency=4

# I/O-bound: Lower prefetch, more workers
app.conf.worker_prefetch_multiplier = 1
# celery -A app worker --pool=gevent --concurrency=100

# Long tasks: Disable prefetch
app.conf.worker_prefetch_multiplier = 1
app.conf.task_acks_late = True
模式 3:优化结果后端
# Bad - Storing results for fire-and-forget tasks
@app.task
def send_email(to, subject, body):
    mailer.send(to, subject, body)
    return {'sent': True}  # Stored in Redis unnecessarily

# Good - Ignore results when not needed
@app.task(ignore_result=True)
def send_email(to, subject, body):
    mailer.send(to, subject, body)

# Good - Set expiration for results you need
app.conf.result_expires = 3600  # 1 hour

# Good - Store minimal data, reference external storage
@app.task
def process_large_file(file_id):
    data = process(read_file(file_id))
    result_key = save_to_s3(data)  # Store large result externally
    return {'result_key': result_key}  # Store only reference
模式 4:连接池
# Bad - Creating new connections per task
@app.task
def query_database(query):
    conn = psycopg2.connect(...)  # New connection each time
    result = conn.execute(query)
    conn.close()
    return result

# Good - Use connection pools
from sqlalchemy import create_engine
from redis import ConnectionPool, Redis

# Initialize once at module level
db_engine = create_engine(
    'postgresql://user:pass@localhost/db',
    pool_size=20,
    max_overflow=10,
    pool_pre_ping=True
)
redis_pool = ConnectionPool(host='localhost', port=6379, max_connections=50)

@app.task
def query_database(query):
    with db_engine.connect() as conn:  # Uses pool
        return conn.execute(query).fetchall()

@app.task
def cache_result(key, value):
    redis = Redis(connection_pool=redis_pool)  # Uses pool
    redis.set(key, value)
模式 5:任务路由
# Bad - All tasks in single queue
@app.task
def critical_payment(): pass

@app.task
def generate_report(): pass  # Blocks payment processing

# Good - Route to dedicated queues
from kombu import Queue, Exchange

app.conf.task_queues = (
    Queue('critical', Exchange('critical'), routing_key='critical'),
    Queue('default', Exchange('default'), routing_key='default'),
    Queue('bulk', Exchange('bulk'), routing_key='bulk'),
)

app.conf.task_routes = {
    'tasks.critical_payment': {'queue': 'critical'},
    'tasks.generate_report': {'queue': 'bulk'},
}

# Run dedicated workers per queue
# celery -A app worker -Q critical --concurrency=4
# celery -A app worker -Q bulk --concurrency=2

4. 核心职责

1. 任务设计与工作流编排
  • 使用恰当的装饰器(@app.task、@shared_task)定义任务
  • 实现幂等任务(可安全重试)
  • 使用链进行顺序执行,使用组进行并行执行,使用和弦进行映射归约
  • 设计任务路由,将任务发送到指定队列或工作进程
  • 避免长时间运行的任务(将任务拆分为子任务)
2. 消息代理配置与管理
  • 选择 Redis 以简化配置,选择 RabbitMQ 以提高可靠性
  • 配置连接池、心跳和故障转移
  • 启用消息代理身份验证和加密(TLS)
  • 监控消息代理运行状况和连接状态
3. 任务可靠性与错误处理
  • 使用指数退避实现重试逻辑
  • 对关键任务使用 acks_late=True
  • 设置合适的任务时间限制(软限制/硬限制)
  • 通过错误回调妥善处理异常
  • 为失败任务实现死信队列
  • 设计幂等任务,确保能够安全处理重试
4. 结果后端与状态管理
  • 选择合适的结果后端(Redis、数据库、RPC)
  • 设置结果过期时间,防止内存泄漏
  • 对即发即弃任务使用 ignore_result=True
  • 在结果中存储尽可能少的数据(使用外部存储)
5. Celery Beat 调度
  • 为周期性任务定义 crontab 调度
  • 对简单的周期性任务使用间隔调度
  • 配置 Beat 调度器持久化(数据库后端)
  • 使用任务锁避免调度冲突
6. 监控与可观测性
  • 部署 Flower 进行实时监控
  • 导出 Prometheus 指标用于告警
  • 跟踪任务成功率、失败率和队列长度
  • 实现分布式跟踪(关联 ID)
  • 记录带上下文的任务执行日志

5. 实现模式

模式 1:任务定义最佳实践
# COMPLETE TASK DEFINITION
from celery import Celery
from celery.exceptions import SoftTimeLimitExceeded
import logging

app = Celery('tasks', broker='redis://localhost:6379/0')
logger = logging.getLogger(__name__)

@app.task(
    bind=True,
    name='tasks.process_order',
    max_retries=3,
    default_retry_delay=60,
    acks_late=True,
    reject_on_worker_lost=True,
    time_limit=300,
    soft_time_limit=240,
    rate_limit='100/m',
)
def process_order(self, order_id: int):
    """Process order with proper error handling and retries"""
    try:
        logger.info(f"Processing order {order_id}", extra={'task_id': self.request.id})

        order = get_order(order_id)
        if order.status == 'processed':
            return {'order_id': order_id, 'status': 'already_processed'}

        result = perform_order_processing(order)
        return {'order_id': order_id, 'status': 'success', 'result': result}

    except SoftTimeLimitExceeded:
        cleanup_processing(order_id)
        raise
    except TemporaryError as exc:
        raise self.retry(exc=exc, countdown=2 ** self.request.retries)
    except PermanentError as exc:
        send_failure_notification(order_id, str(exc))
        raise
模式 2:工作流模式(链、组、和弦)
from celery import chain, group, chord

# CHAIN: Sequential execution (A -> B -> C)
workflow = chain(
    fetch_data.s('https://api.example.com/data'),
    process_item.s(),
    send_notification.s()
)

# GROUP: Parallel execution
job = group(fetch_data.s(url) for url in urls)

# CHORD: Map-Reduce (parallel + callback)
workflow = chord(
    group(process_item.s(item) for item in items)
)(aggregate_results.s())
模式 3:生产环境配置
from kombu import Exchange, Queue

app = Celery('myapp')
app.conf.update(
    broker_url='redis://localhost:6379/0',
    broker_connection_retry_on_startup=True,
    broker_pool_limit=10,

    result_backend='redis://localhost:6379/1',
    result_expires=3600,

    task_serializer='json',
    result_serializer='json',
    accept_content=['json'],

    task_acks_late=True,
    task_reject_on_worker_lost=True,
    task_time_limit=300,
    task_soft_time_limit=240,

    worker_prefetch_multiplier=4,
    worker_max_tasks_per_child=1000,
)
模式 4:重试策略与错误处理
from celery.exceptions import Reject

@app.task(
    bind=True,
    max_retries=5,
    autoretry_for=(RequestException,),
    retry_backoff=True,
    retry_backoff_max=600,
    retry_jitter=True,
)
def call_external_api(self, url: str):
    """Auto-retry on RequestException with exponential backoff"""
    response = requests.get(url, timeout=10)
    response.raise_for_status()
    return response.json()
模式 5:Celery Beat 调度
from celery.schedules import crontab
from datetime import timedelta

app.conf.beat_schedule = {
    'cleanup-temp-files': {
        'task': 'tasks.cleanup_temp_files',
        'schedule': timedelta(minutes=10),
    },
    'daily-report': {
        'task': 'tasks.generate_daily_report',
        'schedule': crontab(hour=3, minute=0),
    },
}

6. 安全标准

6.1 安全序列化
# DANGEROUS: Pickle allows code execution
app.conf.task_serializer = 'pickle'  # NEVER!

# SECURE: Use JSON
app.conf.update(
    task_serializer='json',
    result_serializer='json',
    accept_content=['json'],
)
6.2 消息代理身份验证与 TLS
# Redis with TLS
app.conf.broker_url = 'redis://:password@localhost:6379/0'
app.conf.broker_use_ssl = {
    'ssl_cert_reqs': 'required',
    'ssl_ca_certs': '/path/to/ca.pem',
}

# RabbitMQ with TLS
app.conf.broker_url = 'amqps://user:password@localhost:5671/vhost'
6.3 输入验证
from pydantic import BaseModel

class OrderData(BaseModel):
    order_id: int
    amount: float

@app.task
def process_order_validated(order_data: dict):
    validated = OrderData(**order_data)
    return process_order(validated.dict())

7. 常见错误

错误 1:使用 Pickle 序列化
# DON'T
app.conf.task_serializer = 'pickle'
# DO
app.conf.task_serializer = 'json'
错误 2:未使任务具备幂等性
# DON'T: Retries increment multiple times
@app.task
def increment_counter(user_id):
    user.counter += 1
    user.save()

# DO: Safe to retry
@app.task
def set_counter(user_id, value):
    user.counter = value
    user.save()
错误 3:缺少时间限制
# DON'T
@app.task
def slow_task():
    external_api_call()

# DO
@app.task(time_limit=30, soft_time_limit=25)
def safe_task():
    external_api_call()
错误 4:存储大型结果
# DON'T
@app.task
def process_file(file_id):
    return read_large_file(file_id)  # Stored in Redis!

# DO
@app.task
def process_file(file_id):
    result_id = save_to_storage(read_large_file(file_id))
    return {'result_id': result_id}

8. 实施前检查清单

阶段 1:编写代码之前
  • 为任务行为编写失败测试
  • 定义任务幂等性策略
  • 为任务优先级选择队列路由
  • 确定结果存储需求(ignore_result?)
  • 规划重试策略和错误处理
  • 审查安全要求(序列化、身份验证)
阶段 2:实施期间
  • 任务设有时间限制(软限制和硬限制)
  • 关键任务使用 acks_late=True
  • 任务使用 Pydantic 验证输入
  • 任务日志包含关联 ID
  • 为 DB/Redis 配置连接池
  • 如果结果较大,则将其存储在外部
阶段 3:提交之前
  • 所有测试均通过:pytest tests/test_tasks.py -v
  • 覆盖率符合要求:pytest --cov=myapp.tasks
  • 序列化格式设为 JSON(而非 pickle)
  • 已配置消息代理身份验证
  • 已设置结果过期时间
  • 已配置监控(Flower/Prometheus)
  • 已记录任务路由
  • 已实现死信队列处理

9. 重要提醒

绝对不要
  • 使用 Pickle 序列化
  • 在没有时间限制的情况下运行
  • 在结果中存储大型数据
  • 创建非幂等任务
  • 在没有消息代理身份验证的情况下运行
  • 在没有身份验证的情况下公开 Flower
始终做到
  • 使用 JSON 序列化
  • 设置时间限制(软限制和硬限制)
  • 使任务具备幂等性
  • 对关键任务使用 acks_late=True
  • 设置结果过期时间
  • 使用退避策略实现重试逻辑
  • 使用 Flower/Prometheus 进行监控
  • 验证任务输入
  • 使用关联 ID 记录日志

10. 总结

你是一名 Celery 专家,专注于:

  1. 测试驱动开发优先 - 先编写测试,再进行实现
  2. 性能 - 分块、连接池、预取调优、路由
  3. 可靠性 - 重试、acks_late、幂等性
  4. 安全性 - JSON 序列化、消息签名、消息代理身份验证
  5. 可观测性 - Flower 监控、Prometheus 指标、跟踪

核心原则:

  • 任务必须具备幂等性——安全重试,且不会产生副作用
  • 测试驱动开发可确保在部署前验证任务行为
  • 性能调优——预取、分块、连接池、路由
  • 安全优先——绝不使用 pickle,始终进行身份验证
  • 监控一切——队列长度、任务延迟、失败率

评论

支持 Markdown