Người dùng bấm “Đăng ký” và phải chờ 8 giây vì hệ thống đang gửi email. Giải pháp: trả lời ngay, còn việc gửi email đẩy sang hàng đợi cho worker xử lý. Đó chính là Celery.
Cài đặt
pip install "celery[redis]"
# Cần một message broker, thường là Redis
docker run -d -p 6379:6379 redis
Định nghĩa tác vụ
# tasks.py
from celery import Celery
app = Celery("ung_dung", broker="redis://localhost:6379/0",
backend="redis://localhost:6379/1")
@app.task
def gui_email(dia_chi, tieu_de):
# ... gửi email thật ở đây
return f"Đã gửi tới {dia_chi}"
@app.task
def tao_bao_cao(thang):
# công việc nặng, chạy vài phút
return f"Báo cáo tháng {thang}"
Chạy worker
celery -A tasks worker --loglevel=info --concurrency=4
Gọi tác vụ từ ứng dụng
from tasks import gui_email
# Không chờ kết quả — trả về ngay lập tức
gui_email.delay("[email protected]", "Chào mừng")
# Cần theo dõi kết quả
ket_qua = gui_email.delay("[email protected]", "Hóa đơn")
print(ket_qua.id)
print(ket_qua.ready()) # xong chưa?
print(ket_qua.get(timeout=30)) # chờ và lấy kết quả
Tự thử lại khi lỗi
@app.task(bind=True, max_retries=3, default_retry_delay=60)
def goi_api(self, url):
try:
return requests.get(url, timeout=10).json()
except requests.RequestException as e:
raise self.retry(exc=e) # thử lại sau 60 giây
Lên lịch định kỳ với Celery Beat
from celery.schedules import crontab
app.conf.beat_schedule = {
"bao-cao-hang-ngay": {
"task": "tasks.tao_bao_cao",
"schedule": crontab(hour=7, minute=0),
"args": ("09",),
},
}
celery -A tasks beat --loglevel=info
Ba nguyên tắc thực chiến
- Chỉ truyền dữ liệu nhỏ. Gửi ID bản ghi, không gửi cả đối tượng — tham số phải chuyển được sang JSON.
- Viết tác vụ idempotent. Chạy hai lần vẫn cho kết quả đúng, vì worker có thể xử lý lại sau sự cố.
- Đặt thời gian giới hạn.
@app.task(time_limit=300)để một tác vụ treo không chiếm worker mãi mãi.
Nếu nhu cầu của bạn chỉ là vài tác vụ nền đơn giản, arq hay RQ nhẹ hơn nhiều. Celery đáng dùng khi bạn cần lịch chạy, thử lại, định tuyến hàng đợi và giám sát ở quy mô thật.
