GH GambleHub

Exact o dată vs Cel puțin o dată

1) De ce chiar discuta semantica

Semantica de livrare determină cât de des destinatarul va vedea mesajul atunci când se blochează și se retrage:
  • Cel mult o dată - fără repetare, dar pierderea este posibilă (rareori acceptabilă).
  • Cel puțin o dată - nu pierde, dar duplicate sunt posibile (implicit de cele mai multe brokeri/cozi).
  • Exact o dată - fiecare mesaj este procesat exact o dată în ceea ce privește efectul observat.

Adevărul cheie: într-o lume distribuită, fără tranzacții globale și consistență sincronă, un „curat” end-to-end exact-o dată este de neatins. Construim eficient exact-o dată: permitem repetiții pe transport, dar facem procesarea idempotentă, astfel încât efectul observat să fie „ca o dată”.


2) Eșec model și în cazul în care apar duplicate

Reluările apar din cauza:
  • Pierderi ack/comite (producătorul/brokerul/consumatorul „nu a auzit” confirmarea).
  • Realegerea liderilor/replicilor, recuperări după întreruperi de reţea.
  • Timeouts/retrageri în orice zone (kliyent→broker→konsyumer→sink).

Consecință: nu vă puteți baza pe „unicitatea livrării” transportului. Gestionați efectele: scrierea în baza de date, debitarea banilor, trimiterea unei scrisori etc.


3) Exact-o dată în furnizori și ceea ce este cu adevărat

3. 1 Kafka

Dă cărămizi:
  • Producător Idempotent ("activează. idempotence = true ') - previne duplicatele pe partea producătorului atunci când se retrage.
  • Tranzacții - publicarea atomică a mesajelor în mai multe loturi și comiterea compensărilor de consum (model de citire-proces-scriere fără „lacune”).
  • Compactare - stochează ultima valoare după cheie.

Dar „sfârșitul lanțului” (chiuvetă: DB/plată/e-mail) necesită în continuare idempotență. În caz contrar, dublura handler-ului va provoca un efect dublu.

3. 2 NATS/Iepure/SQS

Implicit este cel puțin o dată cu ack/redelivery. Exact-o dată se realizează la nivelul aplicației: chei, deadstore, upsert.

Concluzie: Transportul exact o dată ≠ efect exact o dată. Acesta din urmă se face în handler.


4) Cum de a construi în mod eficient exact-o dată peste cel puțin o dată

4. 1 Cheie de idempotență

Fiecare comandă/eveniment poartă o cheie naturală: 'payment _ id',' order _ id # step ',' saga _ id # n'. Handler:
  • Cecuri „deja văzut?” - Dedup-stor (Redis/DB) cu TTL/Retsch.
  • Dacă ați văzut, repetă rezultatul calculat anterior sau nu-op.
Redis schiță:
lua
-- SET key if not exists; expires in 24h local ok = redis.call("SET", KEYS[1], ARGV[1], "NX", "EX", 86400)
if ok then return "PROCESS" else return "SKIP" end

4. 2 Upsert în bază (chiuvetă idempotentă)

Intrările se fac prin UPSERT/ON CONFLICT cu verificarea versiunii/sumei.

PostgreSQL:
sql
INSERT INTO payments(id, status, amount, updated_at)
VALUES ($1, $2, $3, now())
ON CONFLICT (id) DO UPDATE
SET status = EXCLUDED.status,
updated_at = now()
WHERE payments.status <> EXCLUDED.status;

4. 3 Outbox tranzactional/Inbox

Outbox: o tranzacție de afaceri și o intrare eveniment-publicare apar în aceeași tranzacție de bază de date. Editorul de fundal citește outbox și trimite brokerului → nu există nici o discrepanță între stat și eveniment.
Inbox: pentru comenzile primite, salvați "message _ id' și rezultatul înainte de execuție; reprocesarea vede înregistrarea și nu repetă efectele secundare.

4. 4 Prelucrarea consistentă a lanțului (read→process→write)

Kafka: tranzacția „a citit offsetul → a notat rezultatele → angajamentului” într-un bloc atomic.
Fără tranzacții: „mai întâi notați rezultatul/Inbox, apoi ack”; cu accident, duplicatul va vedea Inbox și se va termina fără op.

4. 5 SAGA/compensează

Când idempotența este imposibilă (furnizorul extern a notat banii), folosim operațiuni compensatorii (rambursare/anulare) și API-uri externe idempotente (repetarea „POST” cu aceeași „cheie de idempotență” dă același rezultat).


5) Când cel puțin o dată este suficient

Actualizări ale cache-urilor/vizualizări materializate cu compactare bazată pe cheie.
Contoare/valori în cazul în care re-increment este acceptabil (sau stoca delta cu versiunea).
Notificări în cazul în care scrisoarea secundară nu este critică (este mai bine să puneți o cheie oricum).

Regulă: în cazul în care dubla nu schimbă sensul de afaceri sau putem găsi cu ușurință → cel puțin o dată + protecție parțială.


6) Performanță și cost

Exact o dată (chiar și „eficient”) costă mai mult: înregistrările suplimentare (Inbox/Outbox), cheile de stocare, tranzacțiile, diagnosticarea sunt mai dificile.
Cel puțin o dată este mai ieftin/mai simplu, mai bun la debit/p99.
Evaluați: prețul dublei probabilități de × dublă față de costul de protecție.


7) Configurații de probă și cod

7. 1 Producător Kafka (idempotence + tranzacții)

properties enable.idempotence=true acks=all retries=INT_MAX max.in.flight.requests.per.connection=5 transactional.id=orders-writer-1
java producer.initTransactions();
producer.beginTransaction();
producer.send(recordA);
producer.send(recordB);
// также можно atomically commit consumer offsets producer.commitTransaction();

7. 2 Consola Inbox (pseudo cod)

pseudo if (inbox.exists(msg.id)) return inbox.result(msg.id)
begin tx if!inbox.insert(msg.id) then return inbox.result(msg.id)
result = handle(msg)
sink.upsert(result)     # идемпотентный синк inbox.set_result(msg.id, result)
commit ack(msg)

7. 3 HTTP Idempotency-Key (API-uri externe)


POST /payments
Idempotency-Key: 7f1c-42-...
Body: { "payment_id": "p-123", "amount": 10.00 }

POST repetat cu aceeași cheie → același rezultat/stare.


8) Observabilitate și valori

'duplicate _ tentations _ total' - de câte ori a fost prinsă o dublă (conform Inbox/Redis).
„idempotency _ hit _ rate” - proporția repetițiilor „salvate” prin idempotență.
'txn _ abort _ rate' (Kafka/DB) - cota de rollback-uri.
'outbox _ restlog' - publicare lag.
'exactly _ once _ path _ latency {p95, p99}' vs 'at _ least _ once _ path _ latency' - deasupra capului.
Jurnale de audit: o grămadă de 'message _ id',' idempotency _ key ',' saga _ id', 'încercare'.


9) Cărți de joc de testare (Zilele jocului)

Trimiteți reluarea: retraiele producătorului cu temporizări artificiale.
Accident între „chiuvetă și ack”: Asigurați-vă că Inbox/Upsert preveni o dublă.
Re-livrare: creșterea redelivery în broker; verifica dedup.
Idempotența API-urilor externe: POST repetat cu aceeași cheie este același răspuns.
Schimbarea plumbului/întreruperea rețelei: Verificați comportamentul tranzacțiilor/consumatorilor Kafka.


10) Anti-modele

Bazați-vă pe transport: „avem Kafka cu exact-o dată, astfel încât să puteți fără chei” - nu.
No-op ack înainte de înregistrare: ackled, dar chiuveta a scăzut pierderea →.
Lipsa de retrageri DLQ/jitter: reluări interminabile și furtună.
UUID-uri aleatorii în loc de chei naturale: nimic de deduplicat.
Amestecarea Inbox/Outbox cu tabele de producție index-free: încuietori fierbinți și cozi p99.
Tranzacții de afaceri fără API idempotent la furnizori externi.


11) Lista de verificare a selecției

1. Preț dublu (bani/legal/UX) vs preț de protecție (latență/complexitate/cost).
2. Există un eveniment natural/operare cheie? Dacă nu, vino cu unul stabil.
3. Chiuveta suportă Upsert/versioning? În caz contrar - Inbox + compensare.
4. Aveți nevoie de tranzacții globale? Dacă nu, segmentați în SAGA.
5. Aveți nevoie de o reluare/retenție lungă? Kafka + Outbox. Aveți nevoie de RPC rapid/latență scăzută? NATS + Idempotency-Key.
6. Multi-chirie și cote: izolarea cheie/spațiu.
7. Observabilitate: sunt incluse măsurătorile idempotenței și restanțelor.


12) ÎNTREBĂRI FRECVENTE

Î: Este posibil să se realizeze „matematic” exact o dată end-to-end?
R: Numai în scenarii înguste cu un singur magazin consistent și tranzacții până la capăt. În cazul general, nu; utilizați în mod eficient exact o dată prin idempotență.

Î: Care este mai rapid?
R: Cel puțin o dată. Exact-o dată adaugă tranzacții/ → de stocare cheie peste p99 și costul.

Î: În cazul în care pentru a stoca cheile idempotence?
R: Oprire rapidă (Redis) cu TTL sau tabel Inbox (PK = message _ id). Pentru plăți - mai mult (zile/săptămâni).

Î: Cum de a alege tastele TTL dedup?
R: Minim = timp maxim de re-livrare + marjă operațională (de obicei 24-72 ore). Pentru finanțe - mai mult.

Î: Am nevoie de o cheie dacă am compactare prin cheie în Kafka?
R: Da. Compactarea va reduce stocarea, dar nu va face sincronizarea idempotentă.


13) Totaluri

Cel puțin o dată - semantică de transport de bază, fiabilă.
Exact o dată ca efect de afaceri se realizează la nivel de procesor: Idempotency-Key, Inbox/Outbox, Upsert/versiuni, SAGA/compensare.
Alegerea este un compromis al costului ↔ riscului de duplicare ↔ ușurința de funcționare. Proiectați cheile naturale, faceți vânătăile idempotente, adăugați observabilitate și jucați în mod regulat zile de joc - atunci conductele vor fi predictibile și sigure chiar și într-o furtună de retras și eșecuri.

Contact

Contactați-ne

Scrieți-ne pentru orice întrebare sau solicitare de suport.Suntem mereu gata să ajutăm!

Pornește integrarea

Email-ul este obligatoriu. Telegram sau WhatsApp sunt opționale.

Numele dumneavoastră opțional
Email opțional
Subiect opțional
Mesaj opțional
Telegram opțional
@
Dacă indicați Telegram — vă vom răspunde și acolo, pe lângă Email.
WhatsApp opțional
Format: cod de țară și număr (de exemplu, +40XXXXXXXXX).

Apăsând butonul, sunteți de acord cu prelucrarea datelor dumneavoastră.