Skip to content

فصل ۱۲ — کیف پول دیجیتال (Digital Wallet)

خلاصهٔ تحلیلی — این متن چکیدهٔ مفاهیم فصل به زبان فارسی است، نه ترجمهٔ واژه‌به‌واژه.

گام ۱ — نیازمندی‌ها و برآورد

طراحی بک‌اند کیف پول با تمرکز فقط بر انتقال موجودی بین دو کیف. نیازمندی‌ها: ۱ میلیون TPS، دسترس‌پذیری ۹۹٫۹۹٪، تضمین تراکنشی و — مهم‌تر از همه — reproducibility: بتوان موجودی تاریخی هر لحظه را با بازپخش داده‌ها از ابتدا بازسازی کرد (reconciliation فقط مغایرت را نشان می‌دهد، نه علت را).

برآورد: هر انتقال دو عملیات (کسر + واریز) است ← ۲ میلیون TPS واقعی. با ~۱۰۰۰ TPS به‌ازای هر نود دیتابیس، ~۲۰۰۰ نود لازم است؛ پس یک هدف طراحی، بالا بردن TPS هر نود برای کاهش هزینهٔ سخت‌افزار است.

گام ۲ — سه طرح تکاملی

تنها API: POST /v1/wallet/balance_transfer با from/to، amount (باز هم string، نه double)، currency و transaction_id (کلید حذف تکرار).

طرح ۱: شاردینگ در حافظه (Redis)

نگاشت «حساب←موجودی» بین چند نود Redis با hash(account) % n پخش می‌شود؛ اطلاعات پارتیشن در ZooKeeper؛ wallet service بی‌حالت دستور را به دو نود مربوط می‌فرستد. ایراد قاطع: دو به‌روزرسانی روی دو نود، اتمیک نیستند — اگر سرویس بین دو به‌روزرسانی سقوط کند، انتقال ناقص می‌ماند.

طرح ۲: تراکنش توزیع‌شده روی دیتابیس‌های رابطه‌ای

Redis با دیتابیس‌های تراکنشی جایگزین می‌شود؛ اما انتقال بین دو شارد هنوز نیازمند تراکنش توزیع‌شده است:

  • 2PC (دو-فازی): راه‌حل سطح-پایین متکی به خود دیتابیس (استاندارد X/Open XA). فاز prepare قفل‌ها را تا رسیدن پاسخ همهٔ نودها نگه می‌دارد ← کارایی ضعیف؛ coordinator هم نقطهٔ شکست واحد است.
  • TC/C (Try-Confirm/Cancel): تراکنش جبرانی سطح-برنامه. Try: کسر از A (تراکنش محلی کامل)؛ Confirm: واریز به C؛ Cancel: برگرداندن پول به A اگر Try شکست. برخلاف 2PC که فازهایش درون یک تراکنش‌اند، هر فاز TC/C تراکنش محلی مستقلی است — فاز دوم 2PC «تکمیل تراکنش ناتمام» است، فاز دوم TC/C «خنثی‌سازی تراکنش کامل‌شده (undo)». مزیت: مستقل از نوع دیتابیس؛ هزینه: پیچیدگی در لایهٔ برنامه.
    • جدول phase status (در دیتابیسِ حساب مبدأ) پیشرفت TC/C را ذخیره می‌کند تا پس از restart سرویس، ادامهٔ کار مشخص باشد.
    • حالت نامتوازن: پایان فاز Try، ۱ دلار «گم» است (A−۱ و C دست‌نخورده) — نقض موقت اصل حسابداری که چون برنامه‌محور است، برنامه آن را می‌بیند و مدیریت می‌کند.
    • ترتیب معتبر عملیات: فقط «اول کسر، بعد واریز» درست است. واریز زودهنگام به C خطر برداشته‌شدن پول توسط دیگری قبل از Cancel را دارد؛ اجرای موازی کسر/واریز هم پیچیدگی خطا می‌سازد.
    • اجرای out-of-order: ممکن است Cancel قبل از Try به نودی برسد ← نود پرچم out-of-order ثبت می‌کند و Try بعدی با دیدن پرچم، شکست برمی‌گرداند.
  • Saga: استاندارد de-facto در microservice ها. عملیات به‌ترتیب خطی اجرا می‌شوند؛ در شکست، از همان نقطه به عقب با تراکنش‌های جبرانی rollback می‌شود (برای n عملیات، 2n عملیات آماده کنید). هماهنگی یا choreography (رویدادمحورِ غیرمتمرکز — با زیاد شدن سرویس‌ها مدیریت state machine داخلی سخت می‌شود) یا orchestration (هماهنگ‌کنندهٔ مرکزی — انتخاب معمول کیف پول). مقایسه: TC/C امکان اجرای موازی دارد و برای سیستم‌های حساس به تأخیر با عملیات زیاد بهتر است؛ Saga خطی اما هم‌راستا با معماری microservice.

طرح ۳: Event sourcing (پاسخ به auditor ها)

تراکنش توزیع‌شده کار می‌کند اما ردگیری علت تغییر موجودی و ممیزی را نمی‌دهد. Event sourcing (از DDD) با چهار مفهوم:

  • Command: نیت بیرونی (درخواست انتقال) — در صف FIFO (مثل Kafka).
  • Event: نتیجهٔ اعتبارسنجی command؛ واقعیت قطعی و deterministic (زمان گذشته: «منتقل شد»). یک command می‌تواند صفر تا چند event بسازد و تولید event می‌تواند تصادفی/وابسته به I/O باشد — اما خود event ها قطعی‌اند. ترتیب event ها تابع ترتیب command هاست.
  • State: موجودی همهٔ حساب‌ها (نگاشت حساب←موجودی).
  • State machine: اعتبارسنجی command ← تولید event، و اعمال event ← به‌روزرسانی state. باید قطعی باشد (بدون تصادف و I/O بیرونی).

Reproducibility مزیت محوری است: همهٔ تغییرات ابتدا به‌صورت تاریخچهٔ تغییرناپذیر ذخیره می‌شوند و دیتابیس فقط «نمای» موجودی است. با بازپخش event ها از ابتدا: موجودی هر لحظهٔ تاریخی به دست می‌آید، درستی موجودی فعلی اثبات می‌شود، و درستی کد جدید با اجرای دو نسخه روی همان event ها مقایسه می‌شود — پاسخ هر سه سؤال auditor.

CQRS: به‌جای انتشار state، خود event ها منتشر می‌شوند؛ یک state machine نویسنده و چندین state machine فقط-خواندنی که نماهای دلخواه (موجودی برای کوئری کلاینت، نمای بازهٔ زمانی برای بررسی double charge و غیره) می‌سازند. نماهای خواندنی عقب‌ترند اما می‌رسند — eventually consistent.

گام ۳ — بررسی عمیق

Event sourcing پرکارایی (محلی‌سازی)

  • Command/event روی دیسک محلی به‌جای Kafka: append فقط-افزودنی = نوشتن ترتیبی بسیار سریع (حتی روی HDD)؛ کش اخیرها در حافظه. تکنیک mmap هر دو را یکجا می‌دهد (فایل دیسک به‌صورت آرایهٔ حافظه‌ای).
  • State محلی: SQLite (رابطه‌ای فایل‌محور) یا RocksDB (key-value مبتنی بر LSM tree، بهینه برای نوشتن).
  • Snapshot: ذخیرهٔ دوره‌ای state (مثلاً ساعت ۰۰:۰۰ برای تیم مالی) در object storage؛ بازپخش از snapshot ادامه می‌یابد نه از ابتدا.

قابلیت اطمینان با اجماع

تحلیل: computation از روی داده بازیابی می‌شود؛ پس فقط دوام داده مهم است. state و snapshot از event بازتولید می‌شوند؛ command هم کافی نیست چون تولید event غیرقطعی است ← فقط event list نیازمند تضمین اطمینان بالاست. راه‌حل: replication مبتنی بر اجماع (Raft) — تا وقتی اکثریت نودها زنده‌اند (۳ از ۵)، event list ها یکسان و بدون از دست رفتن داده و با حفظ ترتیب‌اند. leader دستورها را می‌گیرد، event می‌سازد و replicate می‌کند؛ همهٔ نودها event ها را اعمال می‌کنند. سقوط leader ← انتخاب خودکار leader جدید (کلاینت command پاسخ‌نگرفته را دوباره می‌فرستد)؛ سقوط follower ← retry تا بازگشت.

Event sourcing توزیع‌شده

دو محدودیت باقی می‌ماند: پاسخ CQRS به کلاینت کند است (polling) و ظرفیت یک گروه Raft محدود است.

  • Pull به Push: به‌جای polling دوره‌ای، reverse proxy بین کاربر و نودها می‌نشیند و state machine فقط-خواندنی به‌محض دریافت event، وضعیت را به proxy push می‌کند — پاسخ شبه‌real-time و اجرای هم‌زمان‌نما.
  • شاردینگ + تراکنش توزیع‌شده: داده بین چند گروه Raft پارتیشن می‌شود و TC/C یا Saga روی آن‌ها اجرا می‌شود. جریان happy path با Saga: coordinator رکورد phase status می‌سازد ← A:−$1 به پارتیشن ۱ (leader ← command list ← event ← اجماع Raft ← اجرا ← مسیر خواندن CQRS وضعیت را push می‌کند) ← ثبت موفقیت ← C:+$1 به پارتیشن ۲ با همان چرخه ← تکمیل تراکنش و پاسخ به کاربر.

نکات کلیدی فصل

  • مسیر تکامل پاسخ را حفظ کنید: Redis (سریع اما غیراتمیک) ← دیتابیس + 2PC/TC/C/Saga (اتمیک اما غیرقابل‌ممیزی) ← event sourcing (قابل‌بازتولید) ← فایل‌محور + Raft + CQRS + شاردینگ (سریع، مطمئن، مقیاس‌پذیر).
  • در TC/C فقط «اول کسر، بعد واریز» ترتیب معتبر است و پرچم out-of-order را فراموش نکنید.
  • تنها دادهٔ نیازمند دوام سخت‌گیرانه event list است — بقیه بازتولیدشدنی‌اند.
  • قطعی بودن state machine شرط لازم reproducibility است؛ تصادف و I/O فقط در مرحلهٔ تولید event مجاز است.
  • نوشتن ترتیبی روی دیسک محلی (mmap، LSM) از دیتابیس/صف شبکه‌ای بسیار سریع‌تر است — کلید رسیدن به TPS بالای هر نود.

یادداشت‌های مترجم

  • event sourcing، CQRS، snapshot، 2PC، TC/C، Saga و Raft لاتین حفظ شده‌اند.