sadeghhp
sadeghhp
خواندن ۸ دقیقه·۲ سال پیش

چگونه اکتورها بعد از خراب شدن بازیابی میشوند؟

سوال: چگونه از نابود شدن اکتور سیستم در زمانی که مشکلی وجود دارد جلوگیری میکنیم؟

جواب: با نظارت

نظارت (Supervision) در اکتور سیستم چیست و چرا باید به آن اهمیت داد؟

نظارت چیست؟

"آهان! ببین چه گندی زدی! حالا درستش کن"

شوخی کردم. ولی احتمالا همچین چیزی تا به حال به گوشتان خورده. این چیزی است که نظارت در اکتور مدل تقریبا شبیه آن است: یک اکتور والد خطاهای فرزندانش را مشاهده و نظارت میکند و تصمیم میگیرد که به چه صورت خرابی را رفع کند و چه اتفاقی بیفتد.

اصلا چه اهمیتی دارد؟

نظارت مفهومی است که اجازه میدهد در اکتور سیستم به سرعت مشکل را مشخص و خرابی را برطرف کنید.

نظارت از بالا به پایین در سلسله مراتب اکتور ها تضمین میکند که وقتی قسمتی از سیستم دچار یک خطای ناخواسته شود ( مثلا network timeout) آن خطا و مشکل صرفا در همان قسمت از سیستم بماند و همانجا فقط تاثیر بگذارد. تمام اکتور های دیگر به کار خودشان ادامه میدهند بصورتی که انگار هیچ اتفاقی نیفتاده است. به این وضعیت Failure Isolation یا جداسازی شکست میگوییم.

سلسله مراتب اکتور ها

نخست یک یادآوری جزئی این که هر اکتوری والد دارد و بعضی از اکتور ها فرزند دارند.

از آنجایی که والد ها بر فرزندان خود نظارت می کنند در نتیجه تمام اکتورها ناظر دارند و بعضی از اکتور ها ناظر هستند.

در اکتور سیستم ، اکتور ها به صورت سلسله مراتبی مرتب شده اند. یک نمای کلی از این ساختار به شکل زیر است:


نگهبانان، پایه ی همه اکتورها (Guardians)

اکتور های نگهبان ریشه ی اصلی تمام اکتور ها در سیستم هستند. به تصویر زیر توجه کنید:

اکتور /

اکتور "/" ریشه اصلی در کل اکتور سیستم است و به آن ریشه نگهبان هم گفته میشود. این اکتور وظیفه نظارت بر دو اکتور ریشه ای دیگر به نام های /user و /system را به عهده دارد.

تمام اکتور ها به والد نیاز دارند غیر از این اکتور. به این اکتور bubble-walker هم گفته میشود چون خارج از حباب اکتور سیستم است.

اکتور system/

کار اصلی این اکتور این است که تضمین کند سیستم به صورت درست و مرتب خاموش شود و بر اکتور هایی که قابلیت های چارچوب akka را پیاده سازی کرده اند مانند لاگ و … نظارت میکند.

اکتور user/

این شاخه نقطه ای است که کار ما شروع میشود و به عنوان یک توسعه دهنده تمام وقت ما در این قسمت صرف میشود. از نگاه کاربر، user/ ریشه ی اصلی اکتور سیستم است و معمولا به آن اکتور ریشه گفته میشود.

به عنوان یک کاربر لازم نیست نگران اکتور های نگهبان باشید. فقط باید مطمئن باشید که به طریق درستی از نظارت ذیل user/ استفاده کرده ایم تا خطا ها به سطح نگهبانان نرسد و کل سیستم را خراب نکند.

سلسله مراتب در اکتور user/

هرچه هست همین جا است! تمام اکتورهایی که در برنامه ی خود تعریف می کنیم اینجا قرار میگیرد:

اکتورهایی که مستقیما از user/ منشعب می شوند "top level actors" نامیده می شوند.

اکتور ها همیشه به عنوان فرزندان دیگر اکتور ها ساخته میشوند.
وقتی یک اکتور را مستقیما از کانتکست اکتور سیستم ایجاد میکنید ، آن اکتور یک تاپ لول اکتور میشود:

https://gist.github.com/sadeghhp/fc03612ded4f59e91eb4d7d726e9eab5#file-actorhierarchy1-cs


حالا اگر بخواهیم برای اکتور a2 فرزندی ایجاد کنیم با استفاده از context آن به صورت زیر عمل میکنیم:

https://gist.github.com/sadeghhp/b13e70fb9857a4ab12c08f58be651c6d#file-actorhierarchy2-cs


مسیر اکتور (Actor Path) برابر است با جایگاه اکتور در سلسله مراتب

هر اکتوری یک آدرس منحصر به فرد دارد. برای ارسال پیام از یک اکتور به اکتور دیگر لازم است که آدرس آن (ActorPath) را داشته باشید. برای یادآوری تصویر زیر ساختار آدرس یک اکتور را نمایش میدهد:

قسمت path از آدرس یک اکتور توضیحی است از جایی که اکتور در سلسله مراتب اکتور ها قرار گرفته است. هر سطح از این سلسله مراتب با یک / مشخص شده است.

برای مثال اگر روی localhost باشیم آدرس کامل اکتور b2 به صورت زیر است:

akka.tcp://MyActorSystem@localhost:9001/user/a1/b2

سوالی که ممکن است مطرح شود این است که "آیا اکتور ها فقط میتوانند در یک نقطه خاص از این سلسله قرار داشته باشند؟" مثلا اگر یک اکتورکلاس FooActor داشته باشیم آیا فقط میتوان آن را به عنوان فرزند BarActor در سلسله مراتب ایجاد کنیم؟

پاسخ این است که هر اکتور ممکن است هرجایی از سلسله مراتب قرار داده شود.

نظارت در سلسله مراتب اکتور ها چگونه است؟

حالا که نحوه مدیریت اکتور ها در ساختار سلسله مراتبی را درک کرده ایم باید این را بدانیم که اکتور ها فقط فرزند مستقیم خود را نظارت میکنند و نوه ها و نتیجه ها و … را نظارت نمی کند.

چه موقع نظارت وارد بازی میشود؟

موقعی که مشکلی پیدا شود! هر موقع که برای فرزند یک unhandled exception پیش بیاید و دچار خرابی شود به والدش خبر میدهد و میپرسد که چه کاری انجام دهد.

در واقع فرزند به والد خود یک پیام می فرستد که از نوع خرابی است (Failure class) و حالا با والد است که تصمیم بگیرد چه کاری انجام دهد.

والد چگونه میتواند مشکل را برطرف کند؟

دو عامل تعیین کننده برای حل مشکل وجود دارد:

  1. چگونه فرزند دچار خرابی شده است. (چه نوع خطایی در پیام فرزند به والد وجود دارد)
  2. چه دستورالعملی را والد در پاسخ به خرابی فرزند اجرا میکند.(با SupervisionStrategy تعیین می شود)

ترتیب رویدادهایی که هنگام خطا رخ میدهد

  1. خطای مدیریت نشده ای در اکتور c1 که توسط والد آن p1 نظارت می شود، رخ میدهد
  2. اکتور c1 تمام عملیات های خود را معلق میکند
  3. سیستم یک پیام خرابی از c1 به p1 ارسال میکند که شامل exception آن است.
  4. اکتور p1 به اکتور c1 اطلاع میدهد که چه کاری باید انجام دهد
  5. زندگی از نو آغاز میشود و قسمت هایی از سیستم که تحت تاثیر قرار گرفته اند خودشان را درست میکنند بدون اینکه تمام سیستم دچار مشکل شود.

بخشنامه های نظارتی - Supervision Directives

هنگامی که یک اکتور والد پیام خرابی فرزندش را دریافت میکند ، میتواند بر اساس یکی از موارد(Directives) عمل نماید. استراتژی های نظارتی بر اساس خرابی های مختلف با دیرکتیو مربوط انطباق پیدا میکند و این به سیستم اجازه میدهد در حالت های مختلف به طرق مختلفی با مشکل مواجه شود.

انواع دیرکتیوهای نظارتی به صورت زیر است:

  • راه اندازی مجدد - Restart
  • توقف - Stop
  • تشدید - Escalate
  • از سرگیری - Resume

نکته قابل توجه در این مورد این است که هر اقدامی در والد اتخاذ شود ، آن تصمیم و اقدام به فرزندان منتشر می شود. مثلا اگر والدی متوقف شود تمام فرزندانش نیز متوقف خواهند شد و اگر راه اندازی مجدد شود تمام فرزندانش نیز دوباره راه اندازی میشوند.

استراتژی های نظارت - Supervision Strategies

دو استراتژی توکار وجود دارد

  1. استراتژی یکی برای یکی (پیش فرض) - One-For-One
  2. استراتژی همه برای یکی - All-For-One

تفاوت اساسی بین این دو مورد در میزان شیوع این تاثیر توسط دستورالعمل مورد استفاده آن است.

در یکی برای یکی ، دستورالعملی که برای والد تعیین میشود فقط روی فرزندی که خراب است اعمال میشود و روی فرزند های دیگر آن والد تاثیری ندارد. این استراتژی پیش فرض است در صورتی که استراتژی دیگری تعیین نشود.

در همه برای یکی ، دستورالعملی که برای والد مشخص میشود برای فرزند خراب و همه ی فرزندان والد اعمال میشود.

یک مورد مهم دیگر در انتخاب استراتژی های نظارت این است که چند بار یک فرزند می تواند و مجاز است که در یک بازه زمانی مشخص، خراب شود قبل از اینکه به طور کامل متوقف شود. مثلا تنها 10 خطا در 60 ثانیه مجاز باشد تا متوقف نشود.

به مثال زیر توجه کنید:

https://gist.github.com/sadeghhp/831f03a8d74d29a4f72799d2c8b561d6#file-supervisionstartegy-cs


نکته چیست؟ مهار پذیری

تمام مطلب در استراتژی های نظارت این است که بتوان خرابی را داخل سیستم با استفاده از قابلیت خود التیام بخشی مهار کرد.

بدین صورت که عملیات هایی که بالقوه خطرناک هستند را از والد به فرزند منتقل میکنیم. فرزندی که تنها وظیفه اش انجام همام عملیات بالقوه خطرناک است.

فرض کنید سیستمی داریم که قرار است امتیازات جام جهانی فوتبال و آمار بازیکنان را نگهداری و پردازش کند و به صورت تناوبی باید با فراخوانی داده ها از api که فیفا در اختیار آن قرار داده بروز رسانی شود. این فراخوانی api همیشه با خطر همراه است. به این معنی که اگر این درخواست دچار خطا شود ، اکتوری که این درخواست را ایجاد کرده خراب میشود.

ما آمار و اعداد را در اکتور والد نگه داری میکنیم و کار فراخوانی api را به یک اکتور فرزند می سپاریم. در این حالت اگر فرزند دچار خرابی شود روی والد تاثیری ندارد و تمام داده های مهم و حساس در والد بصورت امن باقی میماند. با این کار وسعت خرابی را محدود می کنیم و از انتشار آن در کل سیستم جلوگیری میکنیم.

بخاطر داشته باشید که می توانیم چندین کلون از این ساختار داشته باشیم تا به صورت موازی مثلا برای هر بازی فوتبال داده ها را جمع کرده و دنبال کند و لازم نیست کد جدیدی برای این کار اضافه کنیم.

فرزند چقدر باید منتظر والد باشد؟

این یک سوال رایج است که اگر تعدادی پیام در صندوق اکتور ناظر منتظر پردازش باشند چقدر طول میکشد تا پیام خطا و خرابی فرزند به والد برسد؟ آیا فرزند نباید منتظر بماند تا پیام های صندوق والد پردازش شود تا برسد به پیام اکتور فرزند و پاسخ دهد؟

پاسخ: در واقع خیر. وقتی اکتوری پیام خطا برای ناظر خود ارسال میکند ، این پیام از نوع ویژه ای از پیام های سیستمی به ناظر میرسد و در اول صف پیام های صندوق ناظر قرار میگیرد و قبل از اینکه ناظر به حالت عادی برگردد پردازش میشود.

وضعیت پیام در حال پردازش هنگام وقوع خطا

بدون در نظر گرفتن اینکه خرابی در اکتور پردازنده ی پیام اتفاق افتاده یا در والد آن که منجر به توقف آن شده ، پیام می تواند ذخیره شود تا دوباره بعد از راه اندازی مجدد اکتور، پردازش شود. چند راه برای این موضوع وجود دارد. یکی از رایج ترین روش های موجود استفاده از متد preRestart است. اکتور میتواند پیام را به خودش ارسال کند. در این حالت پیام به جای محل ذخیره موقت در حافظه، به صندوق ماندگار منتقل میشود (persistent mailbox).

https://gist.github.com/sadeghhp/f5651eada45a676d792f418cd769996c#file-persistentmailbox-cs


اگر درست عمل کنید، اکتور سیستم شما خود ترمیم کننده ای خوب میشود و به طور باور نکردنی انعطاف پذیر و میشود و هر نوع خطا و خرابی را تحمل میکند و شما یک نفس راحت خواهید کشید.

حالا باید یک درک کلی و خوب از این داشته باشید که نظارت در اکتور سیستم چطور عمل میکند و چطور یک سیستم منعطف اما برگشت پذیر را میتوان با این روش ایجاد کرد.

اکتور سیستمactor modelsupervisionنظارت
شاید از این پست‌ها خوشتان بیاید