چرا پارادایم اکتور مدل مهم است؟
«همزمانی ایمن و مقیاسپذیر بدون قفلگذاری، چگونه مدل اکتور (Actor Model) پیچیدگی پردازشهای موازی در نرمافزارهای مدرن را حل میکند؟»
همه ما با برنامه نویسی شیء گرا آشنا هستیم. کم و بیش با Aspect Oriented Programming آشنایی داریم. اخیراً Reactive Programming فراگیرتر شده است. ولی خیلی از ما چیز زیادی راجع به پارادایم Actor Model نمیدانیم. با اینکه در دوره دانشجویی اشاراتی به این پارادایم شد، ولی من درک خوبی از اهمیت و کاربرد آن پیدا نکردم. در سالهای بعد از دانشگاه هر از چندی گذارم به سایت akka می افتاد ولی زیاد بند نمیشدم. گویا برای یادگیری و استفاده از آن اینرسی داشتم.
اواخر سال 96 مسئولیت پژوهش درباره «معماری مقیاسپذیر پیامرسان» به شرکت اعوان سپرده شد. در این مطالعات متوجه شدیم بسیاری از پیامرسانهای معروف از «اکتور مدل» استفاده میکنند. عبرت آموز آنکه باز سعی داشتم، به جای استفاده از پلتفرمهای اکتوری با ترفندهایی خاصیت اکتورها را شبیهسازی کنم. در نهایت پس از بررسی پیامرسان متن باز actor.im مجاب شدم اکتور مدل را امتحان کنیم. اینجا بود که فهمیدم نسبت به یک پارادایم مهم برنامهنویسی غفلت کامل داشتم.
سعی میکنم برخی از خواص برنامهنویسی با مدل اکتور را توضیح دهم با این امید که توجه بیشتری به این پارادایم مفید و پرکاربرد در ایران ایجاد شود.
مزیت اول - راه حل متفاوتی برای مدیریت همروندی (Concurrency Management)
از پیامرسان مثال میزنم. میخواهیم تعداد بازدیدهای یک پست در یک کانال پرمخاطب را بشماریم. در روش متعارف اینطور عمل میکنیم:
- یک میکروسرویس مسئول شمردن تعداد بازدید پستهاست. برای مقیاسپذیری (scalability) و دسترسیپذیری (availability) چندین نمونه (instance) از این میکرسرویس بالا میآوریم. این میکروسرویسها کاملاً stateless هستند.
- تعداد بازدیدها را در یک پایگاه داده sql (مثل postgres) یا no-sql (مثل redis) نگه میداریم.
- برای اینکه چند نود یا چند thread مستقل با دستکاری همزمانِ «تعداد بازدید یک پست» خرابکاری نکنند، باید از یک قفل توزیع شده (distributed lock) استفاده شود. یعنی thread ای که میخواهد تعداد بازدید را زیاد کند باید block شود تا وقتی اختیار قفل توزیع شده را به دست بیاورد و مطمئن شود thread دیگری روی این رکورد فعال نیست. آنگاه میتواند عدد قبلی را بخواند و عدد جدید را بنویسد.
حالا ببینیم این مساله با مدل اکتور چطور حل میشود:
- یک میکروسرویس اکتوری (مثلاً مبتنی بر akka) میسازیم و چند نمونه (instance) از آن بالا میآوریم. این نودها در یک کلاستر akka قرار میگیرند و با هم هماهنگ میشوند.
- به هر کانال در پیامرسان یک اکتور اختصاص میدهیم. هر اکتور کانال روی یکی از نودهای کلاستر جانمایی میشود. در واقع اکتور کانال یک شیء (Object) است که رفتار (Behavior) و وضعیت (State) آن کانال را در خود جای میدهد.
- اِعمال و پاسخگویی همه رویدادهای مربوط به یک کانال، به اکتور وی واگذار میشود. رویدادها در یک صف (Mailbox) قرار میگیرند و یکی یکی برای پردازش تحویل اکتور میشوند.
- وضعیت اکتور، یک شیء معمولی با فیلدهای دلخواه است. مثلاً میتواند یک Map از «شناسه پستها» به «تعداد بازدید» داشته باشد. حتی به ConcurrentMap نیاز نیست. چون رویدادها یکی یکی از Mailbox خارج و اعمال میشوند، مشکل همروندی پیش نمیآید.
- هر وقت اکتور یک «رویداد بازدید» از Mailbox بردارد، در Map، تعداد بازدیدهای آن پست را بدون نگرانی از همروندی یکی زیاد میکند. راجع به ذخیرهسازی این دادهها (Persistency) ان شاء الله در پست دیگری توضیح خواهم داد.
در این مدل، مساله این است که بتوانیم اکتور مناسب را در کلاستر پیدا کنیم و رویداد را در Mailbox او قرار دهیم. در واقع مساله distributed lock در راهحل کلاسیک، تبدیل به مساله distributed actor lookup میشود. مزیت این مدل آن است که برای مدیریت همروندی نیازی به IO نیست و thread ها بلاک نمیشوند. موقع کد زدن نگران همروندی نیستیم. گویا برنامه Single Thread است.
ان شاء الله در پستهای بعدی به مزیتهای دیگر اکتور مدل خواهیم پرداخت.
برای آشنایی بیشتر با مدل اکتور اینجا را ملاحظه بفرمایید. اگر علاقهمند به کاری چالشی با akka هستید، اینجا منتظر شما هستیم.
سید جمالالدین پیشوایی
رئیس هیئت مدیره و مدیر فنی علاقهمند به معماری راهحلهای نرمافزاری
اگر این موضوع در سازمان شما به یک چالش تبدیل شده...
تیم اعوان میتواند در ارزیابی معماری و زیرساخت فعلی شما همراه باشد. تجربه ما در پروژههای بانکی و... برای همین موقعیتها جمعآوری شده است.