Bootcamp i backendutveckling med FastAPI · Lektion

Schemalagda och periodiska jobb med Celery Beat

Kör återkommande jobb med Celery Beat och samordna cron-liknande scheman säkert mellan flera workers.

Lektion 4 av 413 steg

Schemalagda och periodiska jobb med Celery Beat är en gratis lektion i Bootcamp i backendutveckling med FastAPI på CoddyKit. Detta är lektion 4 av 4. Du kan läsa vilka 3 lektioner som helst i den här lärvägen kostnadsfritt i sin helhet – därefter låser CoddyKit PRO upp alla lektioner, plus praktisk övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Den ingår i lärvägen för Bootcamp i backendutveckling med FastAPI, och Era framsteg synkroniseras mellan webben och CoddyKit-appen. Kursen i Bootcamp i backendutveckling med FastAPI innehåller totalt 4 lektioner.

Varför Celery Beat?

Er FastAPI-app startar utan problem engångstasks i bakgrunden med Celery, men vissa uppgifter måste köras enligt ett schema: skicka ett sammanfattningsmejl varje morgon, förfalla övergivna kundvagnar var tionde minut eller beräkna om analysdata varje natt.

Celery Beat är en schemaläggarprocess. Den kör inte tasks själv — den vaknar vid varje tidsintervall, avgör vilka tasks som ska köras och skickar dem till brokern (Redis/RabbitMQ). Era vanliga celery worker-processer hämtar dem och kör dem.

  • Beat = klockan som publicerar tasks som ska köras.
  • Worker = kraften som kör dem.

Det är denna uppdelning som gör att periodiska jobb kan skalas: en Beat och många workers.

Definiera schemat

Schema definieras på Celery-appen under conf.beat_schedule. Varje post mappar ett namn till en dict med sökvägen till tasken, ett schema (sekunder, timedelta eller en crontab) och valfria args/kwargs.

Det enklaste schemat är ett fast intervall. Nedan körs cleanup-tasken var 30:e sekund. Strängen med task-sökvägen måste exakt motsvara det registrerade namnet hos workern.

from celery import Celery
from datetime import timedelta

app = Celery("jobs", broker="redis://localhost:6379/0")

@app.task(name="tasks.cleanup_sessions")
def cleanup_sessions():
    # delete expired sessions from the DB
    return "cleaned"

app.conf.beat_schedule = {
    "cleanup-every-30s": {
        "task": "tasks.cleanup_sessions",
        "schedule": timedelta(seconds=30),
    },
}

Cron-liknande scheman med crontab()

Fasta intervall är grova. För riktig kalenderlogik — ”varje vardag klockan 07:30” — använder ni celery.schedules.crontab. Den följer Unix crons fält: minute, hour, day_of_week, day_of_month, month_of_year.

  • crontab(minute=0, hour=0) — midnatt varje dag.
  • crontab(minute="*/15") — var 15:e minut.
  • crontab(hour=7, minute=30, day_of_week="1-5") — 07:30 måndag–fredag.

Fält som inte anges får standardvärdet * (alla värden), precis som på en crontab-rad.

from celery.schedules import crontab

app.conf.beat_schedule = {
    "morning-digest": {
        "task": "tasks.send_digest",
        "schedule": crontab(hour=7, minute=30, day_of_week="1-5"),
    },
    "quarter-hour-sync": {
        "task": "tasks.sync_inventory",
        "schedule": crontab(minute="*/15"),
    },
}

Skicka args och alternativ per post

Varje schemapost kan innehålla argument och alternativ för det enskilda anropet. Använd args (positionsargument) eller kwargs (namngivna argument) för att parametrera samma task på olika sätt i olika poster. Med dict-objektet options kan ni dirigera en periodisk task till en specifik queue, ange expires eller åsidosätta prioriteten.

expires är viktigt för periodiska jobb: om Beat lägger en task i kön men workers har mycket att göra kastas ett utgånget meddelande bort i stället för att köras för sent och byggas på i kön.

from celery.schedules import crontab

app.conf.beat_schedule = {
    "warm-cache-eu": {
        "task": "tasks.warm_cache",
        "schedule": crontab(minute="*/5"),
        "kwargs": {"region": "eu-west"},
        "options": {"queue": "cache", "expires": 120},
    },
    "warm-cache-us": {
        "task": "tasks.warm_cache",
        "schedule": crontab(minute="*/5"),
        "kwargs": {"region": "us-east"},
        "options": {"queue": "cache", "expires": 120},
    },
}

Registrera scheman med en decorator

I stället för ett enda stort beat_schedule-dict kan ni registrera poster nära taskerna med signalen on_after_configure och app.add_periodic_task. Då hålls schemat nära koden som det utlöser och ni undviker inaktuella strängsökvägar.

add_periodic_task(schedule, signature, name=...) tar först intervallet eller crontab-uttrycket och därefter task-signaturen. Ni kan skicka task.s(arg) för att bädda in argument.

from celery import Celery
from celery.schedules import crontab

app = Celery("jobs", broker="redis://localhost:6379/0")

@app.task
def rotate_logs(target):
    return f"rotated {target}"

@app.on_after_configure.connect
def setup_periodic_tasks(sender, **kwargs):
    sender.add_periodic_task(
        crontab(hour=3, minute=0),
        rotate_logs.s("app.log"),
        name="nightly-log-rotation",
    )

Tidszoner: det vanligaste schemabugget

Som standard tolkar Celery crontab-tider i UTC. Om ni skriver crontab(hour=7) och förväntar er lokal tid 07:00 körs jobbet vid fel klockslag. Ange alltid tidszonen uttryckligen och fatta detta beslut medvetet.

  • timezone — den zon som crontab-tiderna utvärderas i.
  • enable_utc=True — behåll interna tidsstämplar i UTC (rekommenderas) samtidigt som crontab-uttryck utvärderas i den valda timezone.

Fastställ tidszonen i konfigurationen så att alla utvecklare och servrar använder samma, oavsett värdens lokala TZ.

app.conf.update(
    timezone="Europe/Istanbul",
    enable_utc=True,
)

# crontab(hour=9, minute=0) now means 09:00 Europe/Istanbul,
# stored/transmitted internally as UTC.

Köra Beat och den beständiga schemafilen

Ni startar schemaläggaren som en egen process. Standardschemaläggaren sparar tillståndet för ”senaste körningen” i en liten fil, så att den inte kör om allt efter en omstart.

  • celery -A app beat -l info — kör Beat.
  • --schedule /var/run/celerybeat-schedule — anger var shelve-tillståndsfilen finns.
  • Ni kan köra worker och Beat tillsammans i utvecklingsmiljö med celery -A app worker -B, men använd aldrig -B i produktion — då kopplas klockan till en enda workers livscykel.

I produktion ska ni köra exakt en Beat-process. Två Beats innebär att varje periodisk task körs två gånger.

Regeln om en enda schemaläggare för alla workers

Ni kan skala workers horisontellt till dussintals pods, och Celery hanterar det: brokern distribuerar varje köat meddelande till exakt en worker. Men Beat är klockan, och ni måste bara köra en klocka.

Om två Beat-processer körs avgör var och en självständigt att en task ska köras och publicerar den. Därför ser prenumeranter dubbla körningar. Vanliga sätt detta råkar hända på är:

  • Två repliker av en pod som båda startar beat.
  • Att använda worker -B och sedan skala den worker-distributionen till 2 eller fler repliker.

Åtgärd: använd en dedikerad Beat-Deployment med replicas: 1, separat från den worker-Deployment som ni skalar fritt.

Idempotens: utforma tasks så att de tål dubbletter

Även med en enda Beat uppstår dubbletter — en omstart av Beat i fel sekund, en återleverans från en broker som levererar minst en gång eller ett operatörsfel. Det robusta skyddet är att göra periodiska tasks idempotenta: att köra dem två gånger ska ha samma effekt som att köra dem en gång.

Ett enkelt mönster är ett kortlivat distribuerat lås i Redis med SET NX och en sluttid. Den som får låset utför arbetet; samtidiga eller dubbla körningar hoppar säkert över det.

import redis

r = redis.Redis(host="localhost", port=6379, db=0)

@app.task(name="tasks.charge_subscriptions")
def charge_subscriptions():
    # acquire a lock valid for 300s; only one runner proceeds
    got = r.set("lock:charge_subscriptions", "1", nx=True, ex=300)
    if not got:
        return "skipped: already running"
    try:
        # ... perform the billing run exactly once ...
        return "charged"
    finally:
        r.delete("lock:charge_subscriptions")

Databasbaserade scheman med django-celery-beat / RedBeat

Standardschemaläggaren som använder en fil innebär att ni måste ändra kod och starta om Beat för att ändra ett schema. För dynamiska scheman behöver ni en backendlagring som ni kan redigera under körning.

  • RedBeat — sparar scheman i Redis och passar utmärkt för en FastAPI-stack som endast använder Redis. Ange beat_scheduler = "redbeat.RedBeatScheduler".
  • django-celery-beat — sparar poster i SQL-tabeller som kan redigeras via ett admin-gränssnitt.

RedBeat tillhandahåller också ett Redis-baserat lås, så att endast en schemaläggare är aktiv om ni råkar starta två Beats — ett skydd för regeln om en enda klocka.

app.conf.update(
    redbeat_redis_url="redis://localhost:6379/1",
    beat_scheduler="redbeat.RedBeatScheduler",
    beat_max_loop_interval=5,
)

En fristående kontroll av när cron-jobb ska köras

För att skapa en intuitiv förståelse av vad Beat gör vid varje tidsintervall kommer här en liten fristående simulering: givet en lista med cron-liknande jobb (intervall i sekunder och en tidsstämpel för senaste körningen) avgör den vilka som ska köras nu. Detta är kärnloopen som Beat utför — jämför ”nu” med ”nästa körning” — utan någon broker.

def due_jobs(now, jobs):
    fired = []
    for name, interval, last_run in jobs:
        if now - last_run >= interval:
            fired.append(name)
    return fired

jobs = [
    ("cleanup", 30, 0),
    ("digest", 3600, 3500),
    ("sync", 900, 100),
]

now = 1000
print(due_jobs(now, jobs))  # ['cleanup', 'sync']

Snabb kontroll: undvika dubbla periodiska körningar

Ni distribuerar en FastAPI- och Celery-stack till Kubernetes. För att hantera belastningen skalar ni er worker-Deployment till fyra repliker, och varje worker startas med celery -A app worker -B. Periodiska tasks börjar då köras fyra gånger vardera. Vilken är den korrekta lösningen?

Sammanfattning

Ni kan nu schemalägga återkommande arbete med Celery Beat:

  • Beat schemalägger, workers kör — Beat publicerar förfallna uppgifter till meddelandeförmedlaren; en Beat, många workers.
  • Scheman finns i beat_schedule eller via add_periodic_task; använd timedelta för intervall och crontab() för kalenderlogik.
  • Args/alternativ per post parameteriserar uppgifter och anger queue/expires för att undvika ansamling av inaktuella uppgifter.
  • Tidszoner är UTC som standard — ange uttryckligen timezone + enable_utc.
  • Endast en klocka — använd aldrig worker -B i produktion; kör en enda dedikerad Beat-replik.
  • Idempotens (Redis-lås med SET NX) skyddar mot duplicerade eller återlevererade körningar.
  • RedBeat / django-celery-beat ger redigerbara, beständiga och låsskyddade scheman under körning.
Gratis att börja

Lär dig Bootcamp i backendutveckling med FastAPI med en AI-lärare – gratis

Skriv och kör riktig kod i webbläsaren, få omedelbar hjälp av en AI-lärare dygnet runt och fortsätt där du slutade – på webben eller i appen.

Kurser
21
Lektioner
84

Vanliga frågor

Är lektionen ”Schemalagda och periodiska jobb med Celery Beat” gratis?

Ja – du kan läsa vilka 3 lektioner som helst i lärvägen Bootcamp i backendutveckling med FastAPI, inklusive ”Schemalagda och periodiska jobb med Celery Beat”, kostnadsfritt i sin helhet här på webben. Därefter låser CoddyKit PRO upp alla lektioner, plus interaktiv övning med en inbyggd kodredigerare och en AI-lärare dygnet runt. Kursen i Bootcamp i backendutveckling med FastAPI innehåller totalt 4 lektioner.

Vad lär jag mig i ”Schemalagda och periodiska jobb med Celery Beat”?

Kör återkommande jobb med Celery Beat och samordna cron-liknande scheman säkert mellan flera workers. Ni övar på Bootcamp i backendutveckling med FastAPI med praktisk kod som körs direkt i webbläsaren, medan en AI-handledare som är tillgänglig dygnet runt svarar på Era frågor under lektionen.

Behöver jag någon erfarenhet för att börja lära mig Bootcamp i backendutveckling med FastAPI?

Du behöver inga förkunskaper. Utbildningen i Bootcamp i backendutveckling med FastAPI på CoddyKit är upplagd för allt från nybörjare till avancerade elever, så att du kan börja här eller från början och gå fram i din egen takt. Detta är lektion 4 av 4.

Hur lång tid tar lektionen ”Schemalagda och periodiska jobb med Celery Beat”?

De flesta CoddyKit-lektioner tar cirka 5–10 minuter. Varje lektion är kort och interaktiv, så att du gör stadiga framsteg och kan fortsätta precis där du slutade – på webben eller i appen.

Kan jag skriva och köra kod i den här Bootcamp i backendutveckling med FastAPI-lektionen?

Ja. Varje Bootcamp i backendutveckling med FastAPI-lektion innehåller en inbyggd kodredigerare, så att du kan skriva och köra riktig kod direkt i webbläsaren och få omedelbar AI-feedback – utan lokal installation.

Alla lektioner i den här kursen

  1. Lättviktig avlastning med BackgroundTasks
  2. Koppla Celery-workers till en FastAPI-app
  3. Omförsök, idempotens och dead-letter-hantering
  4. Schemalagda och periodiska jobb med Celery Beat
← Tillbaka till Bootcamp i backendutveckling med FastAPI