Percubaan Semula, Keidempotenan dan Pengendalian Surat Mati
Jadikan tugasan tahan lasak dengan pengunduran eksponen, kunci keidempotenan dan penghalaan mesej surat mati.
Percubaan Semula, Keidempotenan dan Pengendalian Surat Mati ialah pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI percuma di CoddyKit. Ini ialah pelajaran 3 daripada 4. Sebanyak 3 pelajaran dalam laluan pembelajaran ini boleh dibaca sepenuhnya secara percuma — selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan praktikal dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Pelajaran ini merupakan sebahagian daripada laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, dan kemajuan anda disegerakkan merentas web serta aplikasi CoddyKit. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Mengapa Tugasan Memerlukan Ketahanan
Dalam bahagian belakang FastAPI, anda menghantar kerja lambat (menghantar e-mel, mengenakan caj pada kad, memanggil API pihak ketiga) kepada worker Celery supaya permintaan HTTP memberikan respons dengan cepat. Namun, kerja latar berjalan dalam keadaan yang penuh cabaran: rangkaian terputus-putus, API mengehadkan kadar anda dan worker boleh ranap ketika tugasan sedang berjalan.
Tugasan yang tahan lasak mesti bertahan daripada tiga mod kegagalan:
- Kegagalan sementara — cuba semula dengan undur eksponen supaya anda tidak membebankan perkhidmatan yang sedang bermasalah.
- Penghantaran pendua — mesej yang sama mungkin diproses dua kali, jadi tugasan mestilah idempoten.
- Mesej beracun — tugasan yang gagal selama-lamanya mesti dihalakan ke baris gilir surat mati dan bukannya berulang tanpa henti.
Pelajaran ini menggabungkan ketiga-tiganya.
Penghantaran Sekurang-kurangnya Sekali
Perantara Celery (RabbitMQ, Redis) memberikan penghantaran sekurang-kurangnya sekali, bukannya tepat sekali. Mesej diakui (ack) hanya selepas tugasan selesai. Jika worker terhenti selepas melakukan kerja tetapi sebelum mengakui mesej, perantara akan menghantar semula mesej itu dan tugasan berjalan sekali lagi.
Dengan acks_late=True, pengakuan berlaku selepas pelaksanaan — lebih selamat daripada ranap, tetapi ini menjamin bahawa sesetengah tugasan akan berjalan dua kali. Inilah sebab utama idempoten bukan pilihan.
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
# Recommended resilience defaults
app.conf.update(
task_acks_late=True, # ack only after the task body returns
task_reject_on_worker_lost=True, # requeue if the worker is killed
worker_prefetch_multiplier=1, # don't hoard messages on one worker
)Cubaan Semula Automatik dengan autoretry_for
Cara paling mudah untuk mencuba semula adalah dengan mengisytiharkan pengecualian yang boleh dicuba semula. Celery menangkapnya dan menjadualkan semula tugasan secara automatik.
autoretry_for— kelas pengecualian yang mencetuskan cubaan semula.max_retries— had sebelum tugasan ditandakan sebagai gagal.retry_backoff— menghidupkan undur eksponen (jeda menjadi dua kali ganda pada setiap cubaan).
Hanya cuba semula untuk ralat sementara (tamat masa, 5xx, sambungan ditetapkan semula). Jangan cuba semula ValueError secara membuta tuli akibat input yang tidak sah — ia akan gagal dengan cara yang sama setiap kali.
import requests
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(
autoretry_for=(requests.exceptions.RequestException,),
max_retries=5,
retry_backoff=True, # 1s, 2s, 4s, 8s, ...
retry_backoff_max=600, # cap the delay at 10 minutes
retry_jitter=True, # randomize to avoid thundering herd
)
def call_payment_api(charge_id: str):
resp = requests.post("https://api.example.com/charge", json={"id": charge_id}, timeout=10)
resp.raise_for_status()
return resp.json()Undur Eksponen, Dijelaskan
Undur eksponen bermaksud tempoh menunggu antara cubaan meningkat secara geometri: kelewatan bagi cubaan n kira-kira base * 2 ** n, dengan had maksimum. Ini memberikan masa kepada perkhidmatan hiliran yang bermasalah untuk pulih, dan bukannya terus dicuba semula sehingga lumpuh.
Jitter menambahkan unsur rawak supaya ribuan tugasan yang gagal pada detik yang sama tidak semuanya mencuba semula pada detik yang sama ("gerombolan yang menggemparkan"). Berikut ialah matematik yang digunakan oleh Celery, sebagai program kendiri biasa.
import random
def backoff_delay(attempt, base=1, cap=600, jitter=True):
delay = min(cap, base * (2 ** attempt))
if jitter:
delay = random.uniform(0, delay) # full jitter
return delay
for attempt in range(8):
raw = min(600, 1 * (2 ** attempt))
print(f"attempt {attempt}: raw={raw:>3}s jittered~={backoff_delay(attempt):6.1f}s")Cubaan Semula Manual dengan self.retry
Apabila anda memerlukan logik tersuai — menentukan kelewatan daripada pengepala respons atau hanya mencuba semula untuk kod status tertentu — ikat tugasan dan panggil self.retry() secara jelas.
Gunakan bind=True untuk mendapatkan self, baca self.request.retries untuk mengetahui nombor cubaan dan hantarkan countdown sebagai kelewatan. Menaikkan hasil self.retry() menghentikan pelaksanaan semasa dengan kemas.
import requests
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(bind=True, max_retries=5)
def sync_inventory(self, sku: str):
resp = requests.get(f"https://api.example.com/stock/{sku}", timeout=5)
if resp.status_code == 429: # rate limited
wait = int(resp.headers.get("Retry-After", 2 ** self.request.retries))
raise self.retry(countdown=wait)
resp.raise_for_status()
return resp.json()["qty"]Maksud Sebenar Idempoten
Sesuatu operasi adalah idempoten jika menjalankannya dua kali memberikan kesan yang sama seperti menjalankannya sekali. Oleh sebab Celery menghantar sekurang-kurangnya sekali, setiap task yang mengubah keadaan (mengecaj kad, mencipta rekod, mengurangkan stok) mestilah idempoten, jika tidak pelanggan akan dicaj dua kali.
Alat standard ialah kunci idempoten: pengecam unik untuk niat perniagaan, bukannya untuk mesej. Anda merekodkan “kunci X telah diproses” dalam storan tahan lama dan menghentikan pemprosesan secara awal apabila penghantaran seterusnya diterima.
- Hantar kunci daripada permintaan API (pelanggan juga boleh membekalkannya).
- Simpan kunci itu dalam jadual atau Redis dengan kekangan
UNIQUE. - Kekangan tersebut — bukannya logik aplikasi — yang menjadikannya selamat daripada perlumbaan.
Pengawal Idempoten
Berikut ialah corak ini secara berasingan: pengawal yang mengingati kunci yang telah selesai dan enggan menjalankan kesan itu dua kali, walaupun terdapat panggilan serentak. Dalam kod sebenar, set seen menjadi Redis SET NX atau baris pangkalan data dengan kunci unik, tetapi logiknya sama.
import threading
class IdempotencyGuard:
def __init__(self):
self._seen = set()
self._lock = threading.Lock()
def run_once(self, key, effect):
with self._lock: # the unique-constraint stand-in
if key in self._seen:
return "skipped (duplicate)"
self._seen.add(key)
return effect()
guard = IdempotencyGuard()
charges = []
def charge():
charges.append(99)
return "charged 99"
print(guard.run_once("order-123", charge))
print(guard.run_once("order-123", charge)) # duplicate delivery
print("total charges applied:", len(charges))Idempotensi dalam Task Celery
Dalam amalan, anda membungkus kesan tersebut dalam transaksi pangkalan data dan membiarkan kekangan UNIQUE menjadi sumber kebenaran. Masukkan kunci idempoten terlebih dahulu; jika sisipan menimbulkan pelanggaran unik, penghantaran sebelumnya (atau serentak) telah mengendalikannya, maka anda kembali secara awal.
Ini memastikan semakan dan kesan sampingan adalah atomik — tiada tempoh apabila satu penghantaran melihat “belum selesai” sementara penghantaran lain sedang mengecaj.
from sqlalchemy.exc import IntegrityError
from celery import Celery
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(bind=True, acks_late=True, max_retries=3, retry_backoff=True)
def charge_order(self, order_id: str, idem_key: str):
with db_session() as s:
try:
s.add(ProcessedKey(key=idem_key)) # UNIQUE column
s.flush() # raises on duplicate
except IntegrityError:
s.rollback()
return {"status": "already_processed", "order_id": order_id}
amount = payment_gateway.charge(order_id, idempotency_key=idem_key)
s.commit()
return {"status": "charged", "amount": amount}Mesej Beracun dan Baris Gilir Mesej Mati
Sesetengah mesej tidak mungkin berjaya: muatan yang tidak sah, rujukan kepada baris yang telah dipadamkan, atau pepijat yang sentiasa menimbulkan ralat. Mencuba semula mesej tersebut tanpa henti membazirkan pekerja dan membanjiri log anda. Mesej ini ialah mesej beracun.
Baris gilir mesej mati (DLQ) ialah baris gilir berasingan tempat mesej yang telah kehabisan percubaan atau ditolak diletakkan untuk pemeriksaan, pemberitahuan, atau dimainkan semula secara manual. Dengan RabbitMQ, anda mengisytiharkan baris gilir dengan argumen x-dead-letter-exchange; mesej yang ditolak (nack dengan requeue=False) atau yang melebihi TTL akan dihantar ke sana secara automatik oleh broker.
from kombu import Exchange, Queue
dead_exchange = Exchange("dlx", type="direct")
task_queues = (
Queue(
"payments",
Exchange("payments"),
routing_key="payments",
queue_arguments={
"x-dead-letter-exchange": "dlx",
"x-dead-letter-routing-key": "payments.dead",
},
),
Queue("payments_dead", dead_exchange, routing_key="payments.dead"),
)Menghalakan Task yang Kehabisan Percubaan ke DLQ
Broker menghantar mesej ke baris gilir mesej mati apabila mesej ditolak, tetapi mekanisme percubaan semula Celery tidak menolak secara automatik apabila max_retries dicapai — ia hanya menandakan task sebagai FAILED. Untuk menghantar task yang kehabisan percubaan ke DLQ, anda menangkap MaxRetriesExceededError (atau mengesan percubaan terakhir) dan memajukan muatan itu secara jelas kepada task atau baris gilir mesej mati anda.
Pengendali mesej mati tidak sepatutnya memproses semula mesej — ia merekodkan kegagalan, menjana pemberitahuan, dan menyimpan muatan supaya pengendali boleh memainkannya semula selepas membaiki punca asas.
from celery import Celery
from celery.exceptions import MaxRetriesExceededError
app = Celery("jobs", broker="redis://localhost:6379/0")
@app.task(bind=True, max_retries=5, retry_backoff=True)
def process_event(self, payload: dict):
try:
do_work(payload)
except TransientError as exc:
try:
raise self.retry(exc=exc)
except MaxRetriesExceededError:
dead_letter.delay(payload, reason=str(exc)) # park it
except PermanentError as exc:
dead_letter.delay(payload, reason=str(exc)) # never retry
@app.task
def dead_letter(payload: dict, reason: str):
store_failed_message(payload, reason)
alert_oncall(reason)Menggabungkan Semuanya
Task berdaya tahan bertaraf produksi menggabungkan semua bahagian:
- acks_late supaya kerosakan menyebabkan penghantaran semula dan bukannya kehilangan kerja.
- autoretry_for hanya untuk ralat sementara, dengan undur eksponen + penggentar.
- Kunci idempoten yang dilindungi oleh kekangan unik supaya penghantaran semula tidak mendatangkan kesan.
- Laluan mesej mati untuk ralat kekal dan percubaan semula yang kehabisan.
Model pemikiran: cuba semula yang sementara, nyahganda yang berganda, hantar ke mesej mati yang tidak dapat diselamatkan. Setiap mekanisme menangani mod kegagalan yang berbeza — bersama-sama, mekanisme ini membolehkan pekerja gagal dengan selamat dan bukannya merosakkan data secara senyap.
@app.task(
bind=True, acks_late=True,
autoretry_for=(TransientError,),
max_retries=5, retry_backoff=True, retry_jitter=True,
)
def handle_webhook(self, payload: dict, idem_key: str):
if already_processed(idem_key): # unique-constraint check
return "duplicate-ignored"
try:
result = apply_effect(payload, idem_key)
except PermanentError as exc:
dead_letter.delay(payload, reason=str(exc))
return "dead-lettered"
mark_processed(idem_key)
return resultSemakan Ringkas
Task Celery anda mengecaj kad kredit dan berjalan dengan acks_late=True. Oleh sebab broker menghantar sekurang-kurangnya sekali, mesej yang sama kadangkala diproses dua kali. Apakah pertahanan utama yang betul terhadap pengecasan dua kali?
Ulang Kaji
Anda telah mempelajari cara menjadikan task Celery bertahan terhadap kegagalan dunia sebenar:
- Celery menghantar sekurang-kurangnya sekali;
acks_late=Truemelindungi daripada kerosakan pekerja tetapi menjamin berlakunya pelaksanaan pendua sekali-sekala. - Undur eksponen dengan penggentar (melalui
retry_backoff/autoretry_foratauself.retrysecara manual) menangani kegagalan sementara tanpa membebankan perkhidmatan hiliran — cuba semula hanya ralat sementara. - Kunci idempoten yang disokong oleh kekangan unik menjadikan penghantaran pendua tidak mendatangkan kesan dan memastikan semakan atomik dengan kesan sampingan.
- Baris gilir mesej mati menyimpan mesej beracun dan percubaan semula yang kehabisan untuk pemberitahuan serta main balik manual, dan bukannya mengulang tanpa henti.
Ingat peraturannya: cuba semula yang sementara, nyahganda yang berganda, hantar ke mesej mati yang tidak dapat diselamatkan.
Pelajari Kem Intensif Pembangunan Bahagian Belakang FastAPI dengan tutor kecerdasan buatan — percuma
Tulis dan jalankan kod sebenar dalam pelayar anda, dapatkan bantuan segera daripada tutor kecerdasan buatan yang tersedia 24/7, dan sambung semula dari tempat anda berhenti di web atau dalam aplikasi.
- Kursus
- 21
- Pelajaran
- 84
Soalan Lazim
Adakah pelajaran “Percubaan Semula, Keidempotenan dan Pengendalian Surat Mati” percuma?
Ya — sebanyak 3 pelajaran dalam laluan pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI, termasuk “Percubaan Semula, Keidempotenan dan Pengendalian Surat Mati”, boleh dibaca sepenuhnya secara percuma di web ini. Selepas itu, CoddyKit PRO membuka akses kepada semua pelajaran, serta latihan interaktif dengan penyunting kod terbina dalam dan tutor kecerdasan buatan yang tersedia 24/7. Kursus Kem Intensif Pembangunan Bahagian Belakang FastAPI merangkumi sejumlah 4 pelajaran.
Apakah yang akan saya pelajari dalam “Percubaan Semula, Keidempotenan dan Pengendalian Surat Mati”?
Jadikan tugasan tahan lasak dengan pengunduran eksponen, kunci keidempotenan dan penghalaan mesej surat mati. Anda berlatih Kem Intensif Pembangunan Bahagian Belakang FastAPI menggunakan kod praktikal yang dijalankan terus dalam pelayar, manakala tutor kecerdasan buatan 24/7 menjawab soalan anda semasa anda mengikuti pelajaran.
Adakah saya memerlukan pengalaman untuk memulakan Kem Intensif Pembangunan Bahagian Belakang FastAPI?
Tiada pengalaman terdahulu diperlukan. Pembelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI di CoddyKit disusun untuk pelajar daripada peringkat pemula hingga lanjutan, jadi anda boleh bermula di sini atau dari awal dan belajar mengikut kadar anda sendiri. Ini ialah pelajaran 3 daripada 4.
Berapa lamakah pelajaran “Percubaan Semula, Keidempotenan dan Pengendalian Surat Mati” diambil?
Kebanyakan pelajaran CoddyKit mengambil masa kira-kira 5–10 minit. Setiap pelajaran ringkas dan interaktif, jadi anda boleh membuat kemajuan secara berterusan dan menyambung tepat dari tempat anda berhenti di web atau aplikasi.
Bolehkah saya menulis dan menjalankan kod dalam pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI ini?
Ya. Setiap pelajaran Kem Intensif Pembangunan Bahagian Belakang FastAPI menyertakan penyunting kod terbina dalam, jadi anda boleh menulis dan menjalankan kod sebenar terus dalam pelayar serta menerima maklum balas kecerdasan buatan serta-merta — tanpa memerlukan persediaan setempat.
Semua pelajaran dalam kursus ini
- Pengalihan Kerja Ringan dengan BackgroundTasks
- Menyambungkan Pekerja Celery kepada Aplikasi FastAPI
- Percubaan Semula, Keidempotenan dan Pengendalian Surat Mati
- Kerja Berjadual dan Berkala dengan Celery Beat