بلاگ/راه‌اندازی Celery برای پردازش وظایف پس‌زمینه در Django روی سرور مجازی و اختصاصی: Redis، Beat و مانیتورینگ با Flower
به‌روز شده

راه‌اندازی Celery برای پردازش وظایف پس‌زمینه در Django روی سرور مجازی و اختصاصی: Redis، Beat و مانیتورینگ با Flower

1405/08/230 بازدید
راه‌اندازی Celery برای پردازش وظایف پس‌زمینه در Django روی سرور مجازی و اختصاصی: Redis، Beat و مانیتورینگ با Flower

راه‌اندازی Celery برای پردازش وظایف پس‌زمینه در Django روی سرور مجازی و اختصاصی: Redis، Beat و مانیتورینگ با Flower

راه‌اندازی Celery در جنگو با Redis و Flower روی سرور مجازی

اگر در Django منتظر ارسال یک ایمیل یا پردازش یک فایل تا تمام‌شدن درخواست کاربر بمانید، کاربر چند ثانیه به صفحهٔ سفید نگاه می‌کند. Celery همین کار را از مسیر درخواست بیرون می‌برد و در پس‌زمینه اجرا می‌کند. این مطلب راه اندازی Celery در جنگو را با Redis به‌عنوان broker، Beat برای وظایف زمان‌بندی‌شده و Flower برای مانیتورینگ، قدم‌به‌قدم روی یک سرور لینوکسی نشان می‌دهد.

Celery چه کاری انجام می‌دهد و چه زمانی لازمش دارید؟

Celery یک صف وظیفه است: جنگو به‌جای اجرای مستقیم یک کار سنگین، آن را در صف می‌گذارد و یک یا چند Worker جدا از پردازش وب آن را اجرا می‌کنند. چند نمونهٔ رایج:

  • ارسال ایمیل یا پیامک بعد از ثبت‌نام، بدون این‌که کاربر منتظر پاسخ SMTP بماند.
  • تولید گزارش یا فایل اکسل بزرگ که چند ثانیه تا چند دقیقه طول می‌کشد.
  • پردازش تصویر یا ویدیوی آپلودشده (تغییر سایز، فشرده‌سازی، تبدیل فرمت).
  • اجرای کارهای زمان‌بندی‌شده مثل پاک‌سازی شبانهٔ رکوردهای منقضی یا همگام‌سازی دوره‌ای با یک API خارجی.

اگر همهٔ کارهای برنامه زیر یک ثانیه طول می‌کشند، احتمالا هنوز به Celery نیاز ندارید. وقتی یک درخواست HTTP معمولی شروع به کند شدن به‌خاطر یک کار جانبی می‌کند، وقت اضافه کردن صف است.

چرا این کار روی هاست اشتراکی جنگو جا نمی‌افتد

خیلی از سؤال‌های «هاست جنگو» در واقع دنبال جایی هستند که بشود Python و Django را اجرا کرد. Worker و Beat هر دو فرآیندهای دائمی هستند که باید همیشه در پس‌زمینه زنده بمانند و معمولا به systemd یا دسترسی ریشه برای تعریف سرویس نیاز دارند؛ چیزی که هاست اشتراکی جنگو معمولا در اختیار نمی‌گذارد. برای همین Celery جدی معمولا روی یک سرور مجازی یا اختصاصی اجرا می‌شود که کنترل کامل روی systemd و پورت‌های شبکه را دارید، نه روی یک پنل هاست اشتراکی.

پیش‌نیازها

pip install celery django-celery-beat flower

تنظیم Celery در پروژه Django

کنار فایل settings.py، یک فایل جدید به نام celery.py در پوشهٔ اصلی پروژه بسازید (همان پوشه‌ای که wsgi.py در آن است):

# myproject/celery.py
import os
from celery import Celery

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "myproject.settings")

app = Celery("myproject")
app.config_from_object("django.conf:settings", namespace="CELERY")
app.autodiscover_tasks()

در __init__.py همان پوشه، Celery را در زمان بالا آمدن جنگو بار کنید:

# myproject/__init__.py
from .celery import app as celery_app

__all__ = ("celery_app",)

در settings.py، Redis را به‌عنوان broker و result backend معرفی کنید:

CELERY_BROKER_URL = "redis://127.0.0.1:6379/0"
CELERY_RESULT_BACKEND = "redis://127.0.0.1:6379/0"
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_TIMEZONE = "Asia/Tehran"

عدد /0 و /1 بعد از آدرس Redis، شماره دیتابیس داخل Redis است؛ جدا نگه‌داشتن broker از نتایج روی دو عدد مختلف (مثلا /0 و /1) باعث می‌شود پاک کردن یکی، دادهٔ دیگری را خراب نکند.

حالا یک وظیفه بنویسید، مثلا در orders/tasks.py:

from celery import shared_task

@shared_task
def send_order_confirmation(order_id):
    # ارسال ایمیل یا پیامک تأیید سفارش
    ...

و در ویو یا سیگنال، به‌جای صدا زدن مستقیم تابع، آن را در صف می‌گذارید:

send_order_confirmation.delay(order.id)

نمودار جریان کار از Django به صف Redis و Worker Celery

اجرای Worker با systemd

اجرای celery -A myproject worker مستقیم در ترمینال فقط برای تست خوب است؛ با بسته شدن SSH، Worker هم می‌میرد. برای اجرای دائمی یک unit file بسازید:

# /etc/systemd/system/celery-worker.service
[Unit]
Description=Celery Worker for myproject
After=network.target redis.service

[Service]
Type=simple
User=django
Group=django
WorkingDirectory=/home/django/myproject
EnvironmentFile=/home/django/myproject/.env
ExecStart=/home/django/myproject/venv/bin/celery -A myproject worker --loglevel=info --concurrency=4
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now celery-worker
sudo systemctl status celery-worker

عدد --concurrency تعداد پردازش موازی Worker است؛ روی یک VPS با ۲ هسته، عدد ۴ معمولا نقطهٔ شروع خوبی است. اگر وظایف بیشتر I/O-bound هستند (مثلا منتظر پاسخ یک API خارجی)، می‌شود این عدد را بالاتر برد و مصرف RAM را با systemctl status و دستور free -h چک کرد. برای محدود کردن سخت‌گیرانهٔ CPU و رم این سرویس در سطح سیستم، محدود کردن مصرف CPU و RAM با systemd و cgroups روش را نشان می‌دهد.

Celery Beat برای وظایف زمان‌بندی‌شده

Beat همان کاری را می‌کند که Cron در لینوکس می‌کند، با این تفاوت که داخل خود Celery تعریف می‌شود و وظیفه را مستقیم در صف همان Workerها می‌گذارد. برای تعریف وظایف دوره‌ای از پنل ادمین (با django-celery-beat) یا در settings.py استفاده کنید:

from celery.schedules import crontab

CELERY_BEAT_SCHEDULE = {
    "cleanup-expired-sessions": {
        "task": "accounts.tasks.cleanup_expired_sessions",
        "schedule": crontab(hour=3, minute=0),
    },
}

Beat باید به‌عنوان یک سرویس جدا از Worker اجرا شود، وگرنه با هر ری‌استارت ممکن است یک وظیفه دو بار زمان‌بندی شود:

# /etc/systemd/system/celery-beat.service
[Unit]
Description=Celery Beat for myproject
After=network.target redis.service

[Service]
Type=simple
User=django
Group=django
WorkingDirectory=/home/django/myproject
EnvironmentFile=/home/django/myproject/.env
ExecStart=/home/django/myproject/venv/bin/celery -A myproject beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now celery-beat

اگر فقط چند کار ساعتی ساده دارید و نمی‌خواهید وابستگی تازه‌ای به پروژه اضافه کنید، جایگزینی Cron با Systemd Timer یک گزینهٔ سبک‌تر است. تفاوت اصلی این است که Beat وظیفه را در همان صف Celery می‌گذارد و از منطق retry و مانیتورینگ Celery بهره می‌برد، در حالی که Systemd Timer یک اسکریپت جدا را مستقیم اجرا می‌کند.

مانیتورینگ با Flower

Flower یک داشبورد وب است که وضعیت Worker، صف فعلی، وظایف در حال اجرا و تاریخچهٔ خطاها را نشان می‌دهد. اجرای مستقیم:

celery -A myproject flower --port=5555 --basic-auth=admin:yourpassword

برای اجرای دائمی، مثل دو مورد قبل یک unit file بسازید:

# /etc/systemd/system/celery-flower.service
[Unit]
Description=Flower monitoring for Celery
After=network.target redis.service

[Service]
Type=simple
User=django
WorkingDirectory=/home/django/myproject
EnvironmentFile=/home/django/myproject/.env
ExecStart=/home/django/myproject/venv/bin/celery -A myproject flower --port=5555 --basic-auth=admin:yourpassword
Restart=on-failure

[Install]
WantedBy=multi-user.target

پورت ۵۵۵۵ را مستقیم روی اینترنت باز نگذارید؛ آن را فقط روی 127.0.0.1 گوش بدهید و پشت یک ریورس پروکسی با HTTPS و همان basic auth قرار دهید، همان الگویی که برای هر پنل مدیریتی دیگر روی سرور به کار می‌رود.

انتخاب بین سرور مجازی و اختصاصی برای Celery

هم VPS و هم سرور اختصاصی برای این سه سرویس (Worker، Beat، Flower) کافی هستند؛ تفاوت اصلی در حجم صف و تعداد Workerهای موازی است.

معیارسرور مجازی (VPS)سرور اختصاصی
مناسب برایصف با حجم متوسط، یک یا چند Worker با concurrency معمولیصف با حجم بالا، چند Worker موازی روی چند هسته اختصاصی
مقیاس‌پذیریارتقای پلن سریع و بدون جابه‌جایی سرورظرفیت ثابت سخت‌افزار، مناسب بار پیش‌بینی‌شده
هزینهٔ شروعپایین‌تر، مناسب تست و پروژه‌های تازهبالاتر، مناسب زمانی که بار واقعی شناخته‌شده است
اجرای Redis روی همان سرورممکن، برای صف‌های کوچک تا متوسط کافی استپیشنهاد می‌شود Redis را روی منابع جدا نگه دارید تا Worker و Redis رقیب منابع هم نباشند

برای شروع یا تست اولیهٔ همین معماری، سرور مجازی آریانت کافی است؛ وقتی صف وظایف و تعداد Worker بالا رفت، سرور اختصاصی منابع ثابت و جدا از هر پروژهٔ دیگر می‌دهد.

جمع‌بندی

راه اندازی Celery در جنگو سه بخش دارد: یک broker (اینجا Redis)، Workerهایی که وظیفه را اجرا می‌کنند و در صورت نیاز Beat برای زمان‌بندی. هر سه را با یک سرویس systemd جدا نگه دارید تا با هر ری‌استارت سرور خودشان بالا بیایند، و وضعیت صف را از همان ابتدا با Flower زیر نظر داشته باشید، نه وقتی که صف وظایف منتظر رشد کرده باشد.

سؤالات متداول

آیا به‌جای Redis می‌شود RabbitMQ را broker کرد؟ بله، Celery از هر دو پشتیبانی می‌کند. Redis برای شروع ساده‌تر است چون معمولا از قبل روی پروژه نصب است؛ اگر صف پیچیده‌تر و نیاز به garantee تحویل پیام دارید، راه‌اندازی RabbitMQ گزینهٔ دیگری است.

اگر Worker در حین اجرای یک وظیفه کرش کند چه اتفاقی می‌افتد؟ با تنظیم acks_late=True روی وظیفه، Redis تا وقتی Worker تأیید نکرده که کار تمام شده، آن را از صف حذف نمی‌کند و وظیفه به یک Worker دیگر می‌رسد. بدون این تنظیم، وظیفه در لحظهٔ دریافت تأیید می‌شود و با کرش Worker از دست می‌رود.

چند تا Worker باید اجرا کنم؟ به تعداد هسته و نوع وظیفه بستگی دارد. برای شروع، --concurrency را برابر تعداد هسته‌های CPU بگذارید و با celery -A myproject inspect active ببینید صف خالی می‌ماند یا تجمع می‌کند؛ بر اساس همین، عدد را بالا یا پایین ببرید.

چرا Beat روی یک سرور دو نمونه نباید اجرا شود؟ اگر دو نمونهٔ Beat هم‌زمان اجرا شوند، هر وظیفهٔ زمان‌بندی‌شده دو بار در صف قرار می‌گیرد. همیشه فقط یک سرویس celery-beat فعال نگه دارید؛ تعداد Worker می‌تواند چند نمونه باشد، Beat نه.