دقیقا یک بار در مقابل حداقل یک بار
1) چرا حتی در مورد معانی بحث می شود
معناشناسی تحویل تعیین می کند که چند بار گیرنده پیام را هنگام سقوط و ردیابی می بیند:- حداکثر یک بار - بدون تکرار، اما از دست دادن امکان پذیر است (به ندرت قابل قبول است).
- حداقل یک بار - از دست دادن نیست، اما تکراری امکان پذیر است (به طور پیش فرض اکثر کارگزاران/صف).
- دقیقا یک بار - هر پیام دقیقا یک بار از نظر اثر مشاهده شده پردازش می شود.
حقیقت کلیدی: در یک جهان توزیع شده بدون معاملات جهانی و سازگاری همزمان، یک «تمیز» پایان به پایان دقیقا یک بار غیر قابل دستیابی است. ما به طور موثر یک بار می سازیم: ما اجازه می دهیم تکرار در حمل و نقل، اما ما پردازش را به طور کامل انجام می دهیم به طوری که اثر مشاهده شده «به عنوان یک بار» است.
2) الگوی شکست و جایی که تکراری رخ می دهد
تکرار به نظر می رسد به دلیل:- ضرر و زیان ack/commit (تولید کننده/کارگزار/مصرف کننده «تایید» را نمی شنوند).
- انتخاب مجدد رهبران/کپی، بازیابی پس از شکستن شبکه.
- زمانبندی/عقب نشینی در هر منطقه (kliyent → کارگزار → konsyumer → سینک).
نتیجه: شما نمی توانید بر «منحصر به فرد بودن تحویل» حمل و نقل تکیه کنید. مدیریت اثرات: نوشتن به پایگاه داده، پرداخت پول، ارسال نامه و غیره
3) دقیقا یک بار در ارائه دهندگان و آنچه در آن واقعا است
3. 1 کافکا
می دهد آجر:- تولید کننده بی نظیر ("فعال کنید. idempotence = درست ') - جلوگیری از تکراری در طرف تولید کننده در هنگام retracting.
- معاملات - به صورت اتمی پیام ها را در چندین دسته منتشر می کند و مرتکب جبران مصرف می شود (الگوی خواندن-فرآیند-نوشتن بدون «شکاف»).
- تراکم - آخرین مقدار را با کلید ذخیره می کند.
اما «پایان زنجیره» (سینک: DB/پرداخت/ایمیل) هنوز هم نیاز به idempointency. در غیر این صورت، double کنترل کننده باعث دو برابر شدن اثر می شود.
3. 2 NATS/خرگوش/SQS
پیش فرض حداقل یک بار با ack/redelivery است. دقیقا یک بار در سطح برنامه به دست می آید: کلید، deadstore، upsert.
نتیجه گیری: دقیقا یک بار حمل و نقل ≠ اثر دقیقا یک بار. این کار در آخوند انجام میشود.
4) چگونه برای ساخت به طور موثر دقیقا یک بار بیش از حداقل یک بار
4. 1 کلید Idempotency
هر دستور/رویداد دارای یک کلید طبیعی است: «payment _ id»، «order _ id # step»، «saga _ id # n». دستیار:- چک «دیده می شود ؟» - Dedup-stor (Redis/DB) با TTL/Retsch.
- اگر دیدید، نتیجه محاسبه شده قبلی را تکرار کنید یا بدون عمل انجام دهید.
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 در پایه (idempotent cink)
نوشته ها از طریق UPSERT/ON CONFLICT با چک کردن نسخه/مقدار ساخته شده است.
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: یک معامله تجاری و یک رویداد برای انتشار در همان معامله پایگاه داده رخ می دهد. ناشر پس زمینه صندوق پستی را می خواند و به کارگزار می فرستد → هیچ اختلافی بین دولت و رویداد وجود ندارد.
صندوق ورودی: برای دستورات ورودی، «message _ id» و نتیجه را قبل از اجرا ذخیره کنید ؛ پردازش مجدد رکورد را می بیند و عوارض جانبی را تکرار نمی کند.
4. 4 پردازش زنجیره ای سازگار (خواندن → نوشتن)
کافکا: معامله «خواندن افست → نوشت: نتایج حاصل از → ارتکاب» در یک بلوک اتمی.
بدون معاملات: «ابتدا نتیجه/صندوق ورودی را بنویسید، سپس ack» ؛ با crash، تکراری صندوق ورودی را مشاهده می کند و بدون هیچ مشکلی پایان می یابد.
4. 5 SAGA/جبران خسارت
هنگامی که idempotency غیر ممکن است (ارائه دهنده خارجی پول را نوشت)، ما با استفاده از عملیات جبران (بازپرداخت/از درجه اعتبار ساقط) و رابط های برنامه کاربردی خارجی idempotent (تکرار «POST» با همان «Idempotency-کلید» می دهد همان نتیجه).
5) هنگامی که حداقل یک بار کافی است
به روز رسانی از حافظه های پنهان/materialized نمایش با تراکم مبتنی بر کلید.
شمارنده/متریک که در آن افزایش مجدد قابل قبول است (یا deltas فروشگاه با نسخه).
اعلان هایی که نامه ثانویه مهم نیست (به هر حال بهتر است یک کلید قرار دهید).
قانون: اگر دوگانه معنی کسب و کار را تغییر ندهد یا ما به راحتی می توانیم → حداقل یک بار + حفاظت جزئی.
6) عملکرد و هزینه
دقیقاً یک بار (حتی «مؤثر») هزینه بیشتری دارد: سوابق اضافی (صندوق ورودی/صندوق خروجی)، ذخیره کلیدها، معاملات، تشخیص دشوارتر است.
حداقل یک بار ارزان تر/ساده تر است، بهتر است در توان/p99.
ارزیابی: قیمت دو برابر × احتمال دو برابر هزینه حفاظت.
7) تنظیمات نمونه و کد
7. 1 تولید کننده کافکا (idempotence + معاملات)
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 کنسول صندوق ورودی (کد شبه)
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 های خارجی)
POST /payments
Idempotency-Key: 7f1c-42-...
Body: { "payment_id": "p-123", "amount": 10.00 }
POST تکراری با همان کلید → همان نتیجه/وضعیت.
8) قابلیت مشاهده و معیارها
'duplicate _ attempts _ total' - تعداد دفعاتی که یک double گرفته شده است (با توجه به صندوق ورودی/Redis).
'idempotency _ hit _ rate' - نسبت تکرارهای «ذخیره شده» توسط idempotency.
'txn _ abort _ rate' (Kafka/DB) - سهم عقبگرد.
'outbox _ backlog' - تأخیر در انتشار.
'exactly _ once _ path _ latency {p95, p99}' در مقابل 'at _ least _ once _ path _ latency' - سربار.
سیاهههای مربوط به حسابرسی: یک دسته از «message _ id»، «idempotency _ key»، «saga _ id»، «تلاش».
9) دفترچه های تست (روزهای بازی)
ارسال پخش: تولید کننده با زمان های مصنوعی تکرار می کند.
تصادف بین «سینک و ACK»: اطمینان حاصل کنید که صندوق ورودی/Upsert جلوگیری از دو برابر است.
تحویل مجدد: افزایش تحویل مجدد در کارگزار ؛ dedup را بررسی کنید.
Idempotency API های خارجی: POST تکراری با همان کلید همان پاسخ است.
تغییر سرب/شکستن شبکه: معاملات کافکا/رفتار مصرف کنندگان را بررسی کنید.
10) ضد الگوهای
تکیه بر حمل و نقل: «ما کافکا با دقیقا یک بار، بنابراین شما می توانید بدون کلید» - نه.
No-op ack before to recording: ackled but sink کاهش یافته است → از دست دادن
عدم عقب نشینی DLQ/jitter: تکرار بی پایان و طوفان.
UUID های تصادفی به جای کلیدهای طبیعی: هیچ چیز برای deduplicate.
مخلوط کردن صندوق ورودی/خروجی با جداول تولید بدون شاخص: قفل داغ و دم p99.
معاملات تجاری بدون API idempotent در ارائه دهندگان خارجی.
11) چک لیست انتخاب
1. قیمت دوگانه (پول/قانونی/UX) در مقابل قیمت حفاظت (تاخیر/پیچیدگی/هزینه).
2. آیا یک کلید رویداد/عملیات طبیعی وجود دارد ؟ اگر نه، با یک پایدار همراه شوید.
3. سینک از Upsert/versioning پشتیبانی می کند ؟ در غیر این صورت - صندوق + جبران خسارت.
4. آیا به معاملات جهانی نیاز دارید ؟ اگر نه، بخش به SAGA.
5. نیاز به پخش/نگهداری طولانی ؟ کافکا + صندوق پستی نیاز به RPC سریع/تاخیر کم ؟ NATS + Idempotency-کلید.
6. چند اجاره و سهمیه: جداسازی کلید/فضا.
7. قابلیت مشاهده: معیارهای idempotency و backlog شامل می شوند.
12) سوالات متداول
س: آیا ممکن است برای رسیدن به «ریاضی» دقیقا یک بار پایان به پایان ؟
A: فقط در سناریوهای باریک با یک فروشگاه سازگار و معاملات تمام راه. در حالت کلی، نه ؛ به طور موثر دقیقا یک بار از طریق idempotency استفاده کنید.
س: کدام سریعتر است ؟
A: حداقل یک بار. دقیقا یک بار اضافه می کند معاملات/ذخیره سازی کلید → بالاتر از P99 و هزینه.
س: کلیدهای idempotence را کجا ذخیره کنید ؟
A: توقف سریع (Redis) با TTL، یا جدول صندوق ورودی (PK = message _ id). برای پرداخت - طولانی تر (روز/هفته).
س: چگونه کلیدهای TTL dedup را انتخاب کنیم ؟
A: حداقل = حداکثر زمان تحویل مجدد + حاشیه عملیاتی (معمولا 24-72 ساعت). برای امور مالی - بیشتر
س: آیا من نیاز به یک کلید اگر من تراکم توسط کلید در کافکا ؟
پاسخ: بله. تراکم ذخیره سازی را کاهش می دهد، اما همگام سازی شما را بی نظیر نمی کند.
13) مجموع
حداقل یک بار - معانی اساسی و قابل اعتماد حمل و نقل.
دقیقا یک بار به عنوان یک اثر کسب و کار در سطح پردازنده به دست می آید: Idempotency-Key، Inbox/Outbox، Upsert/نسخه ها، SAGA/جبران خسارت.
انتخاب سازش هزینه ↔ خطر تکرار ↔ سهولت عمل است. طراحی کلید های طبیعی، ایجاد کبودی idemotent، اضافه کردن مشاهده و به طور منظم بازی روز بازی - پس از آن خطوط لوله خود را قابل پیش بینی و امن حتی در یک طوفان از retras و شکست خواهد بود.