Come Pianificare Attività in Python senza Celery e Alternative
Ogni sviluppatore backend probabilmente si è trovato in questa situazione. Stai costruendo un servizio web con FastAPI o Flask, il progetto è ancora piccolo, in esecuzione su un singolo server. Poi il product manager chiede: "Ehi, inviamo ai clienti un riepilogo ogni mattina alle 9, e azzeriamo i carrelli scaduti ogni ora?"
Il primo istinto è aggiungere Celery con Celery Beat e Redis. Ma avviare un broker, configurare i worker e monitorare i daemon solo per due attività in background sembra eccessivo. La seconda opzione è il cron di sistema, ma questo distribuisce la logica tra il codice dell'applicazione e la configurazione del server. Vuoi lo scheduler integrato nel codice, capace di gestire asyncio, e scalare su più nodi quando necessario.
È qui che entra in gioco la libreria APScheduler (Advanced Python Scheduler).
Cosa può fare la libreria
APScheduler esiste da un po', e il progetto sta attualmente passando alla versione 4.0. Il quarto branch è ancora in stato di pre-release, ma ha significativamente ristrutturato l'architettura della libreria.
Precedentemente era principalmente uno scheduler locale per un singolo processo, ma ora lo strumento si è evoluto in un sistema completo di code distribuite e pianificazione. Il caso d'uso più semplice funziona ancora con solo tre righe di codice.
Il concetto fondamentale: dichiari normali funzioni Python o coroutine e alleghi un trigger con le condizioni di esecuzione necessarie.
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()
async def send_digest():
print("Отправляем утренний дайджест...")
# Запуск каждый будний день в 9 утра
scheduler.add_job(send_digest, 'cron', day_of_week='mon-fri', hour=9, minute=0)
scheduler.start()
La libreria gestisce il tracciamento del tempo, calcola gli offset e richiama le funzioni al momento giusto.
Opzioni di pianificazione
Sono disponibili quattro tipi di trigger integrati:
- Trigger cron. Sintassi familiare stile Linux. Puoi impostare flessibilmente i giorni della settimana, i mesi, ore e minuti specifici.
- Intervalli. Esegui ogni N secondi, minuti o ore. Utile per il polling regolare di API esterne.
- Trigger calendario. Necessario quando l'intervallo dipende dalla lunghezza di un mese o anno. Ad esempio, esegui un'attività rigorosamente il primo giorno di ogni mese a mezzogiorno.
- Esecuzione singola. Si attiva esattamente una volta in una data e ora futura specificata.
Se le condizioni integrate non sono sufficienti, i trigger possono essere combinati usando regole composte o puoi scrivere la tua classe con logica personalizzata.
Protezione dai guasti comuni
In pratica, le attività in background incontrano regolarmente sovraccarichi e ritardi. APScheduler include diversi meccanismi utili che sono spesso trascurati nelle soluzioni personalizzate costruite su while True e sleep.
Innanzitutto, la limitazione delle esecuzioni simultanee. Se hai configurato un'esportazione di report pesante ogni cinque minuti, ma l'esecuzione precedente si è bloccata per sette minuti, lo scheduler non avvierà una seconda istanza in parallelo e non sovraccaricherà il database.
In secondo luogo, il parametro jitter. Aggiunge un ritardo casuale all'ora di inizio dell'attività. Immagina di avere cento worker, e tutti devono aggiornare la cache esattamente alle 00:00. Senza jitter, il database riceverà un picco di carico istantaneo. Con un offset casuale di un paio di secondi, il carico viene distribuito uniformemente.
In terzo luogo, la gestione del tempo di tolleranza per i mancati avvii. Se il server si è bloccato sotto carico o si stava riavviando, lo scheduler controlla quanto in ritardo è l'attività. Se il ritardo rientra nel limite accettabile, l'attività verrà eseguita; in caso contrario, verrà saltata senza accumulo nella coda.
Archiviazione e modalità distribuita
Per script semplici, le attività possono essere mantenute in memoria. Ma se il servizio si riavvia, la pianificazione verrà reimpostata. Per evitare ciò, la libreria supporta l'archiviazione persistente:
- PostgreSQL
- MySQL
- SQLite
- MongoDB
Nella versione quattro, lo scheduler ha imparato a funzionare in un cluster distribuito. Più istanze dell'applicazione si connettono a un database condiviso e a un broker di eventi (sono supportati Redis, PostgreSQL LISTEN/NOTIFY e MQTT).
Questo consente la scalabilità orizzontale: un nodo si arresta, gli altri raccolgono le attività dalla coda.
# Пример концепции работы с постоянным хранилищем
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
jobstores = {
'default': SQLAlchemyJobStore(url='postgresql+asyncpg://user:pass@localhost/mydb')
}
scheduler = AsyncIOScheduler(jobstores=jobstores)
Codice sincrono e asincrono
La libreria si integra bene nello stack Python moderno. Sono disponibili diverse implementazioni di scheduler:
AsyncIOSchedulere integrazione con Trio per backend asincroni moderni come FastAPI, Litestar o Aiohttp.BackgroundSchedulereBlockingSchedulerper script sincroni, Django e Flask.
Non devi wrappare le funzioni asincrone in workaround sincroni con asyncio.run() — lo scheduler nativamente attende le coroutine all'interno del loop principale degli eventi.
Dove può essere utile
Di solito ricorro a APScheduler in tre situazioni tipiche:
- Microservizi piccoli e bot. Quando distribuire Celery o RQ sarebbe eccessivo, e sono necessarie attività periodiche direttamente all'interno del processo.
- Azioni utente posticipate. Ad esempio, invia un'email chiedendo di valutare un ordine esattamente 24 ore dopo la consegna.
- Pulizia di dati temporanei e sincronizzazione periodica di dati di riferimento da sistemi esterni.
Vale la pena usarla in un progetto
Se scrivi in Python e hai bisogno di un'esecuzione prevedibile di attività basate sul tempo, APScheduler è una delle opzioni più mature.
L'unica sfumatura al momento: il periodo di transizione tra le versioni 3.x e 4.0. Il terzo branch è stato testato in produzione per anni, è massimamente stabile, ma ha limitazioni nel funzionamento distribuito. La versione 4.0 porta un'architettura moderna e scalabilità, ma l'autore onestamente avverte di possibili modifiche API breaking prima del rilascio finale.
Per la produzione attuale, è più sicuro attenersi al branch stabile 3.x, e la versione 4.0 vale la pena di provarla in progetti personali o nuovi microservizi tenendo d'occhio i prossimi cambiamenti.
Progetti correlati