GoCasts آموزش Go به زبان ساده

برای ثبت‌نام در بوتکمپ آموزش بکند و گولنگ اینجا را کلیک کنید

چالش معماری شماره یک

سعی می کنم به فراخور ان شاءالله چالش های معماری مختلفی رو در GoCasts راه اندازی کنم تا هم کمی بیشتر دانش خودمون رو به چالش بکشیم و هم اینکه از تجربیات و ایده های دیگران استفاده کنیم.

صورت مساله

خب بی مقدمه بریم سراغ صورت مساله:

  • سرویس core در هاستی در آلمان قرار داره (laravel) شامل دیتابیس حاوی جداول user و payment
  • سرویس اختصاصی مشتریان بصورت جداگانه در سروری در ایران قراره توسعه داده بشه با golang
  • سرویس مشتریان احتیاج داره دیتای user و payment رو داشته باشه
  • سرویس core رو نمیتونیم از هاست آلمان خارج کنیم، چون بشدت مهمه که اونجا downtime پایینی داره
  • سرویس مشتریان چون گولنگیه طبیعتا نمیشه روی همون هاست باشه و مجبور هستیم distributed بهش نگاه کنیم
  • دوست داریم سرویس core رو بیاریم روی گولنگ ولی فعلا نمیشه این اتفاق بیفته پس فراموشش کنید
  • اهمیت سرویس core خیلی بیشتره نباید down بودن سرویس مشتریان باعث بشه درخواست به core هم fail بشه
  • دقت کنید core روی هاست هست و دیتابیس کنارشه، محدودیت زیادی روی هاست داریم
  • دیتای payment رو immutable در نظر بگیرید ولی دیتای user میتونه آپدیت بش

نیازمندی ها

  • میخوایم دیتای core رو sync کنیم روی دیتابیس مشتریان (یکطرفه)

معیارهای نرم افزاری مهم

  • به هیچ وجه inconsistency نداشته باشه دیتامون ولی eventual consistency قابل قبوله
  • کمترین میزان ارسال دیتا رو داشته باشیم (مجبور نباشیم هربار همه چیز رو ارسال کنیم یا رکوردهای redundant الکی ارسال کنیم مجدد)
  • سرعت sync شدن دیتا بالا باشه

راه حل های پیشنهادی

استفاده از replica

یک سری از دوستان پیشنهاد دادند که برای دیتابیس اصلی یک replica اضافه بشه و سرویس دوم دیتای مورد نیازش رو از replica بخونه.

از نظر من مزیت این روش اینه که همه دغدغه های consistency و availability بر عهده خود دیتابیس هست و لازم نیست برنامه نویس کار خاصی انجام بده. اما دو تا نکته منفی هم داره. اولیش اینه که ممکنه نگهداری سرویس در حالت replica چالش های خودشو داشته باشه و یه سربار اضافه مضاعف بشه. دومین و مهم ترین نکته منفی برای چالش پیش رو اینه که عموما دیتابیس های استفاده شده در هاست قابلیت رپلیکا شدن ندارند، پس این روش قابل استفاده نیست.

استفاده از event bus

یکی از دوستان پیشنهاد دادند که یک event bus به سیستم اضافه بشه و همه eventهای مرتبط علاوه بر اعمال شدن روی دیتابیس اصلی روی event bus هم ذخیره بشه. در نتیجه سرویس دوم میتونه event هارو بخونه و مصرف کنه.

خب این راه حل تا حدودی به جواب مد نظر من نزدیکه، اما مشکل اینه که هیچ اشاره ای نشد که برای حفظ consistency چطور میخوان eventها رو بصورت transactional به event bus ارسال کنند. چون همانطور که اشاره شد یکپارچگی داده ها خیلی مهمه و در روشی که مطرح شد اشاره ای نشد که چطور میخوان این موضوع رو تضمین کنند.

استفاده از read cache

یکی از دوستان پیشنهاد دادن که سرویس هایی که به داده نیاز دارند از read cache استفاده کنند و داده هایی که در cache موجود نیست رو از سرویس اصلی درخواست کنند. یکی از فرضیات مساله این بود که داده ها تغییرناپذیر هستند.

مزیت این روش اینه که پیاده سازیش ساده است و سرویس اصلی رو به هیچ وجه درگیر نمیکنه که خیلی مزیت مهمیه. عیبی که به نظر من میرسه اینه که این کار باعث میشه سرویس دوم به سرویس اول کاملا وابسته بمونه و اگه سرویس اول از دسترس خارج بشه کارایی سرویس دوم هم مختل بشه. ضمنا برای یک سری از درخواست ها latency زیاد میشه چون باید داده رو از سرویس اصلی درخواست کنه و داده ها از قبل در دسترس سرویس دوم قرار نگرفته اند. ایراد دیگه ای که به این روش هست که البته ایراد نیست، و واقعیت چالش هست اینه که داده های کاربر تغییر پذیرهستند و با فرض راه حل مغایرت داره.

استفاده از saga pattern

یک سری از دوستان راه حل رو در استفاده از saga pattern دیدند که خب راه حل درستیه، اما مشکل اصلی اینه که پیچیدگی خیلی زیادی به مساله اضافه میکنه چون saga pattern جایی کاربرد داره که یک transaction چند سرویس مختلف رو درگیر کنه و برای اینکه atomic انجام بشه شما لازم دارید compensating transaction هارو هم پیاده سازی کنید که پیچیدگی زیادی به سیستم اضافه میکنه

استفاده از transactional outbox

راه حل ساده تر از saga pattern استفاده از transactional outbox هست که یکی از دوستان تقریبا به درستی روش رو توضیح دادند. این روش کجا کاربرد داره؟ وقتی که شما میخوای یه event یا message رو بصورت transactional و distributed بین ۲ تا دیتابیس یا بهتره بگیم ۲ تا storage service مختلف انجام بدی. خیلی وقت ها پیش میاد که ما لازم داریم دیتایی که در دیتابیس اصلی ذخیره میشه رو در یک storage دیگه مثلا مثل message broker یا event bus هم داشته باشیم. که این مورد خیلی به صورت مساله ما نزدیکه. چون ما لازم داریم که دیتای دیتابیس اصلی رو در سرویس دوم مون هم داشته باشیم و نمیخوام داده ای رو از دست بدیم.

حالا روش کار چطوریه؟ خب همانطور که گفتم ممکنه ذاتا ۲ تا سرویسی که قراره دیتا رو داشته باشن از ۲ جنس متفاوت باشن، مثلا یکیش دیتابیس mysql باشه و دیگری nats باشه، پس ما هیچ راهی نداریم که دیتارو بصورت atomic بین این ۲ سرویس ذخیره کنیم. از طرفی هم واقعا نمیخوایم atomic باشه چون اونوقت availability یکی روی کل تراکنش تاثیر میذاره، در حالیکه ما در صورت مساله اشاره کردیم که میخوام یه کپی از دیتارو در سرویس دوم داشته باشیم و availability سرویس دوم نباید روی تراکنش تاثیر بذاره. ایده transactional outbox اینه که شما تو همون دیتابیس اصلی ای که داری مثلا mysql یه جدول جداگانه در نظر بگیر به اسم مثلا outbox و بصورت atomic transaction داده ای که میخوای رو هم در جدول اصلی ذخیره کن مثلا جدول user و هم در جدول outbox ذخیره کن. بعدش یه سرویس به اسم message relay داشته باش که میتونه یه cron job باشه، این سرویس هر چند وقت یکبار(یا حتی بصورت آنی به کمک هوک های دیتابیس) جدول outbox رو چک میکنه و داده هایی که ارسال نشدند رو به سرویس دوم یا broker مورد نظر ارسال میکنه و وضعیت رکورد رو بروز میکنه. طبیعتا سرویس دوم باید نسبت به پیام های دریافتی خودکفا باشه، یعنی چی خودکفا باشه؟ یعنی اگه یه پیام رو ۲ بار دریافت کرد فقط یکبار اونو اعمال کنه. این قضیه به message relay کمک میکنه که با خیال راحت یه پیام رو بیش از یکبار بفرسته و خودشو درگیر چالش های ack گرفتن از سرویس دوم نکنه.

خب مزایای این روش چیه؟ پیاده سازی نسبتا ساده ای که داره و تضمین یکپارچگی داده ها از مزیت هاشه. عیبش چیه؟ عیبش اینه که سرویس اصلی رو برای پیاده سازی درگیر میکنه یه ذره، البته اگه سرویس اصلی مثلا بصورت pod روی kubernetes ارائه میشد اونوقت این message relay میتونست به صورت یه side car container بالا بیاد.

از لینک زیر میتونید بیشتر در موردش بخونید: Transactional Outbox

اگر در طول مسیر یادگیری احتیاج به کمک دارید می توانید با بنده از طریق ایمیل در ارتباط باشید. همچنین برای اطلاع از مطالب جدید می توانید کانال تلگرام GoCasts را دنبال کنید.