Distributed Microservices Platform
یک سامانهٔ فروش بلیت را با هفت میکروسرویس مستقل میسازید: API gateway، ارتباط gRPC، رویدادها روی Kafka، saga برای سفارش و اجرا روی Kubernetes، با این شرط که هیچ بلیتی دو بار فروخته نشود.
در این صفحه
هدف
در پایان این پروژه یک سامانهٔ توزیعشده دارید که:
- از هفت سرویس مستقل ساخته شده و هر سرویس پایگاه دادهٔ خودش را دارد.
- درخواستهای بیرونی را از یک API gateway میگیرد و درون خود با gRPC حرف میزند.
- تغییرها را با رویداد روی Kafka به بقیهٔ سرویسها خبر میدهد.
- سفارش را با saga پیش میبرد و در صورت شکست، کار انجامشده را جبران میکند.
- با از کار افتادن یک سرویس، بقیه را با خود پایین نمیکشد.
- روی Kubernetes بدون قطعی بهروزرسانی میشود.
این پروژه پایهٔ پروژهٔ ۱۲ است. اینجا سامانه را درست میسازید و آنجا آن را تا میلیونها کاربر مقیاس میدهید.
چه چیزی میسازید
یک سامانهٔ فروش بلیت رویداد: کاربر رویدادها را میبیند، صندلی انتخاب میکند، پرداخت میکند و بلیت میگیرد.
این مسئله عمداً انتخاب شده، چون سه سختی واقعی سیستمهای توزیعشده را با هم دارد:
- درستی زیر فشار: ده هزار نفر همزمان پانصد صندلی را میخواهند. باید دقیقاً پانصد بلیت فروخته شود.
- تراکنش میان چند سرویس: رزرو صندلی، ثبت سفارش و پرداخت در سه سرویس و سه پایگاه داده انجام میشود. تراکنش واحدی وجود ندارد.
- بار ناهمگون: دیدن فهرست رویدادها هزار برابر خرید تکرار میشود. این دو نباید منابع یکسانی بگیرند.
سرویسها
معماری
چهار قاعده این معماری را سالم نگه میدارد:
- هر سرویس مالک دادهٔ خودش است. هیچ سرویسی جدول سرویس دیگر را نمیخواند. اگر دادهای لازم دارد، یا میپرسد یا به رویدادها گوش میدهد.
- فراخوانی همزمان فقط برای پرسشهای ضروری است. هر جا پاسخ فوری لازم نیست، رویداد میفرستید.
- هر فراخوانی deadline دارد.
contextاز gateway تا آخرین سرویس منتقل میشود و با تمام شدن زمان، همهٔ زنجیره متوقف میشود. - هر عملیات تغییردهنده idempotent است. تکرار یک درخواست با همان کلید، نتیجهٔ تازهای نمیسازد.
saga سفارش
تراکنش میان سرویسها ممکن نیست. بهجای آن، سفارش گامبهگام پیش میرود و هر گام یک عمل جبرانی دارد:
وضعیت saga در پایگاه دادهٔ order ذخیره میشود. اگر سرویس وسط کار از کار بیفتد، پس از بالا آمدن از همان گام ادامه میدهد.
outbox
سرویسی که هم پایگاه داده را تغییر میدهد و هم رویداد میفرستد، دو نوشتن جدا دارد و ممکن است یکی انجام شود و دیگری نه. راهحل، الگوی outbox است:
رویداد در همان تراکنش تغییر داده، در جدول outbox نوشته میشود. یک relay آن را به Kafka میرساند. پیام ممکن است دو بار برسد، پس مصرفکننده باید تکرار را تشخیص دهد.
مقایسه با دنیای Python
پیشنیازها
- فاز ۳ کامل: goroutine، channel،
contextو الگوهای لغو. - فاز ۴:
net/http، PostgreSQL، migration و احراز هویت. - فاز ۵: gRPC، observability و Docker.
- پروژهٔ ۷: نخستین تجربهٔ چند سرویس با gRPC و صف پیام.
- از فاز ۷: درسهای ۱ تا ۷، از مبانی مقیاسپذیری تا saga.
گامهای پروژه
مرزبندی سرویسها و قراردادها
ساختار مخزن، قراردادهای Protobuf، بررسی سازگاری قرارداد در CI و یک قالب مشترک برای سرویس با health check، پیکربندی و graceful shutdown.
معیار پذیرش: تغییر ناسازگار در یک قرارداد، CI را قرمز میکند. هر سرویس خالی با یک دستور بالا میآید و با
SIGTERMدرخواستهای در جریان را تمام میکند.سرویسهای پایه و gRPC
سرویسهای
identityوcatalog، هر کدام با پایگاه داده و migration خودش، و interceptor برای log، احراز هویت و انتقال deadline.معیار پذیرش: درخواستی که مهلتش در gateway تمام شده، در سرویس پاییندست هم لغو میشود. این با تست یکپارچه نشان داده میشود.
API gateway
تبدیل REST به gRPC، اعتبارسنجی token، rate limit، شناسهٔ درخواست و سقف زمان برای هر مسیر.
معیار پذیرش: هیچ سرویس درونی از بیرون در دسترس نیست. درخواست بدون token معتبر به هیچ سرویسی نمیرسد.
رزرو بدون فروش دوباره
سرویس
inventoryبا رزرو مدتدار، قفل در سطح ردیف و کلید idempotency.معیار پذیرش: در تست همروندی، ده هزار خریدار همزمان برای پانصد صندلی رقابت میکنند و دقیقاً پانصد رزرو موفق ثبت میشود. تکرار یک درخواست با همان کلید، رزرو دوم نمیسازد.
رویدادها با Kafka و outbox
جدول outbox، relay، مصرفکنندهٔ idempotent و صف پیامهای ناموفق.
معیار پذیرش: با قطع و وصل کردن Kafka وسط کار، هیچ رویدادی گم نمیشود و هیچ رویدادی دو بار اثر نمیگذارد.
saga سفارش
ماشین حالت سفارش، گامهای جبرانی، مهلت برای هر گام و ادامه پس از راهاندازی دوباره.
معیار پذیرش: اگر سرویس
orderوسط saga متوقف شود، پس از بالا آمدن سفارش را به پایان میرساند یا جبران میکند. هیچ صندلیای در حالت رزرو بلاتکلیف نمیماند.تابآوری
circuit breaker، تلاش دوباره با backoff و jitter، جداسازی منابع و بودجهٔ زمان.
معیار پذیرش: با از کار انداختن
payment، دیدن رویدادها و انتخاب صندلی همچنان کار میکند و خطاها در کمتر از یک ثانیه برمیگردند، نه پس از timeout طولانی.observability و Kubernetes
trace سرتاسری، metric نرخ و خطا و مدت برای هر سرویس، log با شناسهٔ trace و manifestهای Kubernetes با probe و انتشار تدریجی.
معیار پذیرش: مسیر یک خرید از gateway تا Kafka در یک trace دیده میشود. هنگام بهروزرسانی یک سرویس زیر بار، هیچ درخواستی شکست نمیخورد.
ساختار مخزن
تصمیمهای مهندسی که باید از آنها دفاع کنید
- مرز سرویسها: چرا
inventoryازcatalogجداست؟ چه چیزی این دو را از هم مستقل میکند؟ - هماهنگی saga: orchestration در سرویس
orderیا choreography با رویداد؟ هر کدام چه چیزی را سخت میکند؟ - همزمان یا رویدادی: کدام فراخوانیها باید gRPC بمانند و کدام باید رویداد شوند؟
- کد مشترک: چه چیزی حق دارد در
pkgباشد؟ کد مشترک زیاد، سرویسها را دوباره به هم میچسباند. - یک مخزن یا چند مخزن: برای تیم کوچک و تیم بزرگ پاسخ یکی نیست.
اشتباههای رایج
نتیجهٔ نهایی
- هفت سرویس با تست واحد، تست یکپارچه و
-raceسبز - تست همروندی که نبودِ فروش دوباره را ثابت میکند
- اجرای محلی با یک دستور و manifestهای Kubernetes
- trace سرتاسری و داشبورد metric
- گزارش آزمون شکست: چه چیزی را از کار انداختید و چه شد
- یادداشت تصمیمهای مهندسی