Bootcamp i backendutvikling med FastAPI · leksjon

Planlagte og periodiske jobber med Celery Beat

Kjør gjentakende jobber med Celery Beat og koordiner cron-lignende tidsplaner trygt på tvers av flere arbeidere.

Leksjon 4 av 413 trinn

Planlagte og periodiske jobber med Celery Beat er en gratis leksjon i Bootcamp i backendutvikling med FastAPI på CoddyKit. Dette er leksjon 4 av 4. Du kan lese valgfritt 3 leksjoner fra denne læringsstien gratis i sin helhet – deretter låser CoddyKit PRO opp alle leksjoner, samt praktisk øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i Bootcamp i backendutvikling med FastAPI, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i Bootcamp i backendutvikling med FastAPI inneholder totalt 4 leksjoner.

Hvorfor Celery Beat?

FastAPI-appen Deres starter engangsoppgaver i bakgrunnen med Celery uten problemer, men noe arbeid må kjøres etter en tidsplan: send en oppsummerings-e-post hver morgen, utløp forlatte handlekurver hvert tiende minutt, og beregn analyser på nytt hver natt.

Celery Beat er en planleggingsprosess. Den kjører ikke oppgaver selv – den våkner ved hvert tidsintervall, avgjør hvilke oppgaver som skal kjøres, og legger dem på meldingsmegleren (Redis/RabbitMQ). De vanlige celery worker-prosessene henter dem og kjører dem.

  • Beat = klokken som publiserer oppgaver som skal kjøres.
  • Worker = musklene som kjører dem.

Denne separasjonen er hele grunnen til at periodiske jobber skalerer: én Beat, mange workere.

Definere tidsplanen

Tidsplaner ligger på Celery-appen under conf.beat_schedule. Hver oppføring knytter et navn til en dict med task-banen, en schedule (sekunder, timedelta eller en crontab) og valgfrie args/kwargs.

Den enkleste tidsplanen er et fast intervall. Nedenfor kjører oppryddingsoppgaven hvert 30. sekund. Strengen for oppgavebanen må samsvare nøyaktig med workerens registrerte navn.

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-lignende tidsplaner med crontab()

Faste intervaller er grovkornede. For ekte kalenderlogikk – «hver ukedag klokken 07:30» – bruker De celery.schedules.crontab. Den gjenspeiler feltene i Unix cron: minute, hour, day_of_week, day_of_month, month_of_year.

  • crontab(minute=0, hour=0) – midnatt hver dag.
  • crontab(minute="*/15") – hvert 15. minutt.
  • crontab(hour=7, minute=30, day_of_week="1-5") – 07:30 mandag–fredag.

Felt som ikke er angitt, får standardverdien * (alle verdier), akkurat som på en crontab-linje.

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"),
    },
}

Sende argumenter og alternativer per oppføring

Hver tidsplanoppføring kan inneholde argumenter og alternativer for det enkelte kallet. Bruk args (posisjonelle argumenter) eller kwargs (nøkkelordargumenter) for å parametrisere den samme oppgaven forskjellig i ulike oppføringer. Med dict-en options kan De rute en periodisk oppgave til en bestemt queue, angi expires eller overstyre prioriteten.

expires er viktig for periodiske jobber: Hvis Beat legger en oppgave i kø mens workerne har mye å gjøre, forkastes en utløpt melding i stedet for å bli kjørt for sent og hope seg opp.

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},
    },
}

Registrere tidsplaner med en dekorator

I stedet for én stor beat_schedule-dict kan De registrere oppføringer nær oppgavene ved hjelp av signalet on_after_configure og app.add_periodic_task. Da ligger tidsplanen sammen med koden den utløser, og De unngår foreldede strengbaner.

add_periodic_task(schedule, signature, name=...) tar intervallet eller crontab-en først, og deretter oppgavesignaturen. De kan sende task.s(arg) for å bygge inn argumenter.

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",
    )

Tidssoner: den vanligste feilen i tidsplaner

Som standard tolker Celery crontab-tider i UTC. Hvis De skriver crontab(hour=7) og forventer lokal tid 07:00, vil jobben kjøres på feil klokkeslett. Angi alltid tidssonen eksplisitt og velg den bevisst.

  • timezone – tidssonen som crontab-tidene evalueres i.
  • enable_utc=True – behold interne tidsstempler i UTC (anbefalt), samtidig som crontab-er evalueres i den valgte timezone.

Fastsett tidssonen i konfigurasjonen slik at alle utviklere og servere er enige, uavhengig av vertens lokale tidssone.

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.

Kjøre Beat og den vedvarende tidsplanfilen

De starter planleggeren som en egen prosess. Standardplanleggeren lagrer tilstanden for «siste kjøring» i en liten fil, slik at den ikke kjører alt på nytt etter en omstart.

  • celery -A app beat -l info – kjør Beat.
  • --schedule /var/run/celerybeat-schedule – hvor shelve-tilstandsfilen ligger.
  • De kan kjøre worker og Beat sammen i utvikling med celery -A app worker -B, men bruk aldri -B i produksjon – det knytter klokken til livssyklusen til én worker.

I produksjon skal De kjøre nøyaktig én Beat-prosess. To Beat-prosesser betyr at alle periodiske oppgaver kjøres to ganger.

Regelen om én planlegger på tvers av workere

De kan skalere workere horisontalt til dusinvis av pods, og Celery håndterer det: meldingsmegleren distribuerer hver melding i køen til nøyaktig én worker. Men Beat er klokken, og De må bare kjøre én klokke.

Hvis to Beat-prosesser kjører, avgjør hver av dem uavhengig at en oppgave skal kjøres, og publiserer den. Dermed ser abonnenter dupliserte kjøringer. Vanlige måter dette skjer ved et uhell på:

  • To replikaer av en pod som begge starter beat.
  • Bruk av worker -B, etterfulgt av skalering av worker-deploymenten til 2 eller flere replikaer.

Løsning: en dedikert Beat-deployment med replicas: 1, separat fra worker-deploymenten som De skalerer fritt.

Idempotens: utform oppgaver slik at de tåler duplikater

Selv med én Beat oppstår duplikater – en Beat-omstart på feil sekund, ny levering fra en meldingsmegler som leverer minst én gang, eller en operatørfeil. Det robuste forsvaret er å gjøre periodiske oppgaver idempotente: å kjøre dem to ganger har samme effekt som å kjøre dem én gang.

Et enkelt mønster er en kortvarig distribuert lås i Redis ved hjelp av SET NX med utløpstid. Den som får låsen, utfører arbeidet; samtidige eller dupliserte kjøringer hopper trygt over 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")

Databasebaserte tidsplaner med django-celery-beat / RedBeat

Standard filbaserte planlegger betyr at endring av en tidsplan krever at De redigerer kode og starter Beat på nytt. For dynamiske tidsplaner trenger De et backend-lager som kan redigeres under kjøring.

  • RedBeat – lagrer tidsplaner i Redis og passer godt for en Redis-basert FastAPI-stack. Angi beat_scheduler = "redbeat.RedBeatScheduler".
  • django-celery-beat – lagrer oppføringer i SQL-tabeller som kan redigeres via et administrasjonsgrensesnitt.

RedBeat tilbyr også en Redis-basert lås. Hvis De ved et uhell starter to Beat-prosesser, er det dermed bare én som blir aktiv planlegger – et sikkerhetsnett for regelen om én klokke.

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

En selvstendig kontroll av forfall i cron

For å bygge en intuitiv forståelse av hva Beat gjør ved hvert tidsintervall, får De her en liten frittstående simulering: Gitt en liste over cron-lignende jobber (intervall i sekunder og tidsstempel for siste kjøring), avgjør den hvilke som skal kjøres nå. Dette er kjernesløyfen Beat utfører – sammenlign «nå» med «neste kjøring» – uten noen meldingsmegler.

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']

Kort kontroll: unngå dupliserte periodiske kjøringer

De distribuerer en FastAPI- og Celery-stack til Kubernetes. For å håndtere belastningen skalerer De worker-deploymenten til fire replikaer, og hver worker startes med celery -A app worker -B. Periodiske oppgaver begynner å kjøre fire ganger hver. Hva er den riktige løsningen?

Oppsummering

De kan nå planlegge gjentakende arbeid med Celery Beat:

  • Beat planlegger, arbeidere utfører — Beat publiserer forfalte oppgaver til meldingsmegleren; én Beat, mange arbeidere.
  • Planer ligger i beat_schedule eller via add_periodic_task; bruk timedelta for intervaller og crontab() for kalenderlogikk.
  • Args/alternativer per oppføring parameteriserer oppgaver og angir queue/expires for å unngå opphopning av foreldede oppgaver.
  • Tidssoner er UTC som standard — angi timezone + enable_utc eksplisitt.
  • Én klokke — bruk aldri worker -B i produksjon; kjør én dedikert Beat-replika.
  • Idempotens (Redis SET NX-låser) beskytter mot dupliserte eller videresendte kjøringer.
  • RedBeat / django-celery-beat gir redigerbare, persistente og låsbeskyttede planer under kjøring.
Gratis å komme i gang

Lær deg Bootcamp i backendutvikling med FastAPI med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
21
Leksjoner
84

Ofte stilte spørsmål

Er leksjonen «Planlagte og periodiske jobber med Celery Beat» gratis?

Ja – du kan lese valgfritt 3 av leksjonene i læringsstien Bootcamp i backendutvikling med FastAPI, inkludert «Planlagte og periodiske jobber med Celery Beat», gratis i sin helhet her på nettet. Deretter låser CoddyKit PRO opp alle leksjoner, samt interaktiv øving med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Kurset i Bootcamp i backendutvikling med FastAPI inneholder totalt 4 leksjoner.

Hva lærer jeg i «Planlagte og periodiske jobber med Celery Beat»?

Kjør gjentakende jobber med Celery Beat og koordiner cron-lignende tidsplaner trygt på tvers av flere arbeidere. Du øver på Bootcamp i backendutvikling med FastAPI med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med Bootcamp i backendutvikling med FastAPI?

Ingen tidligere erfaring er nødvendig. Bootcamp i backendutvikling med FastAPI på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 4 av 4.

Hvor lang tid tar leksjonen «Planlagte og periodiske jobber med Celery Beat»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne Bootcamp i backendutvikling med FastAPI-leksjonen?

Ja. Alle Bootcamp i backendutvikling med FastAPI-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Lett avlasting med BackgroundTasks
  2. Koble Celery-arbeidere til en FastAPI-app
  3. Nye forsøk, idempotens og håndtering av dødbrev
  4. Planlagte og periodiske jobber med Celery Beat
← Tilbake til Bootcamp i backendutvikling med FastAPI