在现代Web开发中,经常会遇到需要处理耗时任务的情况,比如发送邮件、处理图片、数据统计分析等。如果直接在请求响应流程中处理这些任务,会导致用户体验极差。这时候就需要引入异步任务队列系统,而Celery+RabbitMQ正是这个领域的黄金组合。
我最早接触这套方案是在处理一个电商平台的订单导出功能时。当用户请求导出半年内的订单数据时,后端需要查询数据库、生成Excel文件,这个过程可能需要几分钟。通过Celery+RabbitMQ,我们实现了"请求-响应"与"任务处理"的解耦,用户点击导出后立即返回,后台任务完成后通过邮件发送结果文件。
RabbitMQ作为消息代理(Broker),负责接收和分发任务消息。它是一个实现了AMQP协议的消息队列服务器,具有消息持久化、灵活的路由机制等特性。而Celery则是Python生态中最流行的分布式任务队列框架,它负责定义任务、发送任务到队列,并从队列中获取任务执行。
首先需要安装RabbitMQ和Celery。RabbitMQ的安装根据操作系统不同有所差异:
bash复制# Ubuntu/Debian
sudo apt-get install rabbitmq-server
# MacOS (使用Homebrew)
brew install rabbitmq
安装完成后启动RabbitMQ服务:
bash复制# Linux
sudo service rabbitmq-server start
# MacOS
brew services start rabbitmq
然后安装Python相关的包:
bash复制pip install celery pika
创建一个最简单的Celery应用只需要几行代码。新建一个tasks.py文件:
python复制from celery import Celery
app = Celery('tasks', broker='amqp://guest:guest@localhost:5672//')
@app.task
def add(x, y):
return x + y
这个例子中:
注意:生产环境中不应该使用默认的guest账号,应该创建专用用户并设置密码
Celery支持多种任务定义方式。除了上面看到的最简单的函数任务,还可以定义:
python复制# 带有重试机制的任务
@app.task(bind=True, max_retries=3)
def process_data(self, data):
try:
return expensive_computation(data)
except Exception as exc:
self.retry(exc=exc)
# 定时任务
@app.task
def periodic_task():
print("This runs every 10 seconds")
# 任务链
from celery import chain
chain(add.s(2, 2), add.s(4)).apply_async()
任务可以通过多种方式调用:
python复制# 同步调用(不推荐,会阻塞)
result = add(4, 4)
# 异步调用
async_result = add.delay(4, 4)
# 带ETA/倒计时的调用
from datetime import datetime, timedelta
add.apply_async((4, 4), eta=datetime.now() + timedelta(seconds=30))
# 带优先级的调用
add.apply_async((4, 4), priority=0) # 0最高,9最低
Celery任务有几种状态:
获取任务结果:
python复制result = add.delay(4, 4)
print(result.status) # 查看状态
print(result.get()) # 获取返回值(会阻塞)
print(result.get(timeout=1)) # 带超时的获取
RabbitMQ自带一个Web管理界面,默认监听15672端口。启用方式:
bash复制rabbitmq-plugins enable rabbitmq_management
然后访问http://localhost:15672,使用guest/guest登录。在这里可以:
为了防止RabbitMQ重启后消息丢失,需要配置持久化:
python复制app.conf.update(
task_serializer='json',
accept_content=['json'], # 忽略其他内容类型
result_serializer='json',
timezone='Asia/Shanghai',
enable_utc=True,
task_default_queue='default',
task_default_exchange='default',
task_default_routing_key='default',
task_acks_late=True,
task_reject_on_worker_lost=True,
task_track_started=True,
broker_transport_options={
'max_retries': 3,
'interval_start': 0,
'interval_step': 0.2,
'interval_max': 0.5,
}
)
在Celery中可以通过定义多个队列来实现任务优先级和隔离:
python复制from kombu import Queue
app.conf.task_queues = (
Queue('default', routing_key='task.default'),
Queue('high_priority', routing_key='task.high'),
Queue('low_priority', routing_key='task.low'),
)
app.conf.task_routes = {
'tasks.add': {'queue': 'high_priority'},
}
在生产环境中,通常需要启动多个Worker进程:
bash复制celery -A tasks worker --loglevel=info --concurrency=4
这里的--concurrency=4表示启动4个工作进程。一般建议设置为CPU核心数的1-2倍。
Celery提供了多种监控方式:
Flower - Celery的实时监控工具
bash复制pip install flower
flower -A tasks --port=5555
日志配置
在tasks.py中添加:
python复制from celery.signals import after_setup_logger
@after_setup_logger.connect
def setup_loggers(logger, *args, **kwargs):
import logging
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# 文件日志
fh = logging.FileHandler('celery.log')
fh.setFormatter(formatter)
logger.addHandler(fh)
# 控制台日志
ch = logging.StreamHandler()
ch.setFormatter(formatter)
logger.addHandler(ch)
预取限制(Prefetch limit)
bash复制celery -A tasks worker --loglevel=info --concurrency=4 --prefetch-multiplier=1
任务超时设置
python复制@app.task(soft_time_limit=60, time_limit=120)
def long_running_task():
pass
结果过期设置
python复制app.conf.result_expires = 3600 # 1小时后过期
症状:无法连接到RabbitMQ服务器
解决方案:
bash复制sudo rabbitmqctl status
python复制app = Celery('tasks', broker='amqp://username:password@hostname:port/vhost')
症状:队列中有大量未处理任务
解决方案:
症状:Worker内存使用持续增长
解决方案:
--max-tasks-per-child参数bash复制celery -A tasks worker --max-tasks-per-child=100
muppy检查内存使用假设我们需要处理用户上传的图片:
python复制@app.task
def process_uploaded_image(image_path):
# 1. 生成缩略图
thumb_path = generate_thumbnail(image_path)
# 2. 上传到云存储
upload_to_s3.delay(thumb_path)
# 3. 更新数据库记录
update_db_record.delay(image_path, thumb_path)
return thumb_path
@app.task
def generate_thumbnail(image_path):
from PIL import Image
img = Image.open(image_path)
img.thumbnail((200, 200))
thumb_path = f"thumb_{image_path}"
img.save(thumb_path)
return thumb_path
@app.task
def upload_to_s3(file_path):
import boto3
s3 = boto3.client('s3')
s3.upload_file(file_path, 'my-bucket', file_path)
@app.task
def update_db_record(original, thumbnail):
from models import Image
Image.objects.filter(path=original).update(thumbnail=thumbnail)
Celery支持定时任务(周期性任务),通过beat调度器实现:
python复制from celery.schedules import crontab
app.conf.beat_schedule = {
'every-10-seconds': {
'task': 'tasks.periodic_task',
'schedule': 10.0, # 每10秒执行一次
},
'daily-report': {
'task': 'tasks.send_daily_report',
'schedule': crontab(hour=8, minute=0), # 每天8点执行
},
}
启动beat服务:
bash复制celery -A tasks beat --loglevel=info
Celery支持复杂的工作流定义:
python复制from celery import chain, chord, group
# 链式调用
chain(add.s(2, 2), add.s(4)).apply_async()
# 并行任务组
group(add.s(i, i) for i in range(10))().get()
# 带回调的并行任务
chord((add.s(i, i) for i in range(10)), xsum.s())().get()
默认使用JSON序列化,也可以自定义:
python复制from kombu.serialization import register
def my_serializer(obj):
return str(obj).encode('utf-8')
def my_deserializer(obj):
return obj.decode('utf-8')
register('my_serializer', my_serializer, my_deserializer,
content_type='application/x-my-serializer',
content_encoding='utf-8')
app.conf.task_serializer = 'my_serializer'
Celery提供了丰富的信号系统,可以在任务生命周期的各个阶段插入自定义逻辑:
python复制from celery.signals import task_prerun, task_postrun
@task_prerun.connect
def task_prerun_handler(sender=None, task_id=None, task=None, args=None, kwargs=None, **extras):
print(f"Task {task.name} is about to run")
@task_postrun.connect
def task_postrun_handler(sender=None, task_id=None, task=None, args=None, kwargs=None, retval=None, state=None, **extras):
print(f"Task {task.name} finished with state {state}")
集成Prometheus监控Celery:
python复制from celery import Celery
from prometheus_client import start_http_server, Counter
app = Celery('tasks', broker='amqp://localhost')
TASKS_STARTED = Counter('celery_tasks_started', 'Number of tasks started')
TASKS_COMPLETED = Counter('celery_tasks_completed', 'Number of tasks completed')
@app.task(bind=True)
def monitored_task(self):
TASKS_STARTED.inc()
try:
result = do_something()
TASKS_COMPLETED.inc()
return result
except Exception:
pass
if __name__ == '__main__':
start_http_server(8000)
除了使用Prometheus,还可以自定义指标收集:
python复制from celery import current_task
from statsd import StatsClient
statsd = StatsClient()
@app.task(bind=True)
def instrumented_task(self):
statsd.incr('tasks.started')
try:
result = do_work()
statsd.incr('tasks.completed')
statsd.timing('tasks.duration', current_task.request.times()['total'])
return result
except Exception as e:
statsd.incr('tasks.failed')
raise
RabbitMQ支持SSL/TLS加密:
python复制app.conf.broker_use_ssl = {
'keyfile': '/path/to/key.pem',
'certfile': '/path/to/cert.pem',
'ca_certs': '/path/to/ca.pem',
'cert_reqs': ssl.CERT_REQUIRED
}
在RabbitMQ中创建专用用户:
bash复制rabbitmqctl add_user myuser mypassword
rabbitmqctl set_permissions -p / myuser ".*" ".*" ".*"
rabbitmqctl set_user_tags myuser administrator
然后在Celery中使用:
python复制app = Celery('tasks', broker='amqp://myuser:mypassword@localhost:5672/myvhost')
对所有任务输入进行验证:
python复制@app.task
def process_data(data):
if not is_valid(data):
raise ValueError("Invalid input data")
return _process_data(data)
典型的Celery+RabbitMQ的Docker Compose配置:
yaml复制version: '3'
services:
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
volumes:
- rabbitmq_data:/var/lib/rabbitmq
worker:
build: .
command: celery -A tasks worker --loglevel=info
depends_on:
- rabbitmq
environment:
- CELERY_BROKER_URL=amqp://rabbitmq:5672
beat:
build: .
command: celery -A tasks beat --loglevel=info
depends_on:
- rabbitmq
environment:
- CELERY_BROKER_URL=amqp://rabbitmq:5672
volumes:
rabbitmq_data:
在Kubernetes中部署Celery Worker的Deployment示例:
yaml复制apiVersion: apps/v1
kind: Deployment
metadata:
name: celery-worker
spec:
replicas: 3
selector:
matchLabels:
app: celery-worker
template:
metadata:
labels:
app: celery-worker
spec:
containers:
- name: worker
image: my-celery-image
command: ["celery", "-A", "tasks", "worker", "--loglevel=info"]
env:
- name: CELERY_BROKER_URL
value: "amqp://rabbitmq:5672"
虽然Celery+RabbitMQ是Python生态中最流行的异步任务解决方案,但也有其他选择:
Redis作为Broker
Apache Kafka
RQ(Redis Queue)
Dramatiq
在实际项目中,Celery+RabbitMQ仍然是大多数Python开发者的首选,特别是在需要可靠消息传递、复杂工作流和良好监控支持的场景中。