shayan hoseini
shayan hoseini
خواندن ۹ دقیقه·۶ سال پیش

هرچه تندتر کندتر...

عموما پیش فرض ما برای معماری نرم افزارها با وجود مشکلاتی مثل عدم مقیاس پذیری و عدم توانایی مدیریت منابع بصورت درخواست-پاسخ (Request-Response) انجام میشود. شاید به این دلیل که وقتی همه آن چیزی که داریم فقط یک چکش باشه، هر چیزی رو میخ میبینیم.

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

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

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

سیستم های ناهمگام یا غیر همزمان (Asynchronicity) این امکان را میدهند تا درخواست کننده و پاسخ دهنده همزمان به سیستم متصل (connect) نباشند و در خواست ها یا پیام ها بدون اینکه نیازی به پردازش یا پاسخ فوری باشند، بر روی یک صف (Message Queue) فرستاده شوند. از طرفی این طراحی ما را قادر میسازد تا بتوانیم درخواست های درون سیستم را به نحوی که اولویت دارد مسیریابی (routing) کنیم.(به مثال رستوران برگردید)

البته ین معماری باعث سخت تر شدن نگهداری و اگر به درستی مدیریت نشوند کاهش کارایی (performance) و قابلیت اطمینان خواهد شد. همینطور باید بدانیم که تست کردن (test driven development) این معماری کمی پیچیده تر خواهد بود.

معیارهای مهم سنجش شرایط Message Queue:

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

ماندگاری (Durability) : قابلیت اطمینان برای عدم از بین رفتن پیام ها در صورتیکه برای سرور صف ها مشکلی پیش بیاید.

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

نقطه به نقطه (point-to-point) در مقابل انتشار و اشتراک (publish/subscribe) : در مدل نقطه به نقطه پیام فرستنده از طریق صف به گیرنده یا مصرف کننده میرسد. میتوانیم بیش از چند گیرنده داشته باشیم که در انتظار پیام به صف گوش بدهند اما فقط یکی از آنها واقعا میتواند پیام را دریافت کند. اما در مدل انتشار و اشتراک همانطور که از نامش پیداست مصرف کننده ها میتوانند به همه پیام های روی صف دسترسی داشته باشند.

پیام تصدیق (Message Acknowledgements) : هم فرستنده و هم گیرنده پیام نیاز دارند تا از دریافت پیام و در حال پردازش قرار گرفتن آن مطلع شوند.

هر سفارش چندبار باید از پذیرش به اشپزخانه فرستاده شود؟

سیاست های تحویل(Delivery Policies): اینکه هر پیام چندبار باید تحویل داده شود شود.

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

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

قابلیت مقیاس پذیری(Scalability): توانایی پردازش در حجم بالا و مدیریت منابع با زیاد شدن تقاضا برای استفاده از آنها.

قابلیت دسترسی: آیا صف ها در زمانی که برای سرور اتفاق می افتد قابل دسترسی هستند؟

چند پلتفرمی (Cross platform interoperability): آیا بین چند سیستم با تکنولوژی های مختلف کارایی دارد؟ در واقع message queue باید بعنوان واسطی (broker) عمل کند که بخش های مختلف سیستم بوسیله آن با یکدیگر ارتباط بگیرند.

تلاش اول - پیاده سازی Message Queue با استفاده از دیتابیس:

برای این کار باید هر درخواست (سفارش غذا توسط مشتری در مثال رستوران ) را درون یک جدول از دیتابیس خود ذخیره(insert) کنید. برای هر درخواست یکی از سه وضعیت جدید،درحال پردازش، پایان یافته (New, Processing, Finished) در نظر بگیرید. سرویس شما باید بارها و بارها تلاش کند تا به سرور متصل شود و آخرین درخواست را دریافت کنند.

هرچند که معمولا کو‌‌ئری زدن ها در دیتابیس به تنهایی اعمال سریعی هستند اما نه وقتی که در یک زمان چندین کوئری روی یک جدول مشخص زده شود.(تعداد سفارش های غذا توسط مشتریان خیلی زیاد میشود)

اما حالا فرض کنید بیش از یک مصرف کننده به دیتابیس خود متصل کنید.( در مثال رستوران شما دو آشپز اضافه میکنید تا سفارش ها زودتر آماده و به دست مشتری برسند ) هر آشپز (worker) سعی خواهد کرد به درخواست ها در یک زمان مشخص دسترسی پیدا کند و آنرا به وضعیت در حال پردازش(Processing) تغییر حالت بدهد. اینجاست که برای جلوگیری از نوشتن همزمان و شرایط رقابتی (race condition) ایجاد شده یک مصرف کننده در ابتدا باید سطر (row) درخواست مورد نظر را قفل کند و بعد از انجام کامل عمل خود آنرا آزاد کند.

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

هزینه فایده

این روش فقط از مدل point to point پشتیانی میکند. پیام ها یا درخواست ها تا زمانی که وضعیت آنها تغییر پیدا نکند، فقط توسط یک مصرف کننده قابل مصرف هستند و هیچگونه سیاست مسریابی برای دریافت پیام ها اعمال نشده است. ( اول آب یا استیک ؟ در مثال رستوران ) بعلاوه اندازه پیام ها نیز برای ذخیره در دیتابیس محدودیت دارد.

تلاش دوم - استفاده از آمازون SQS بعنوان واسط (broker) پیام ها:

در Amazon Simple Queue Service ، سرویس A پیام ها را به صف SQS میفرستد. یکی از مصرف کننده های سرویس B که به انتظار نشسته است پیام یا درخواست را میگیرد. SQS اجازه نمیدهد یک درخواست به چند مصرف کننده برسد و جلوی همزمانی خوانده شدن توسط چند مصرف کننده را میگیرد.

پیام دریافت شده توسط سرویس B همچنان در SQS باقی میماند ولی به مصرف کننده دیگری سپرده نمیشود و یک شمارش معکوس (به میزان دلخواه)برای آن در نظر گرفته میشود تا مصرف کننده آنرا پردازش کند و آنرا پاک کند.

هزینه فایده

این روش هم فقط از مدل Point to Point استفاده میکند. پیام ها به صورت همیشگی نیستند و پس از زمانی (به میزان دلخواه) حذف خواهند شد. این زمان باید به اندازه زمانی بیشتر از زمان طولانی ترین مدت زمان پردازش توسط مصرف کننده تنظیم شود تا پیام ها زودتر از مصرف و به دلیل تمام شدن زمان (timeout) حذف نشوند. همچین روشی برای سیاست ها مسیریابی پیام دیده نشده است.

تلاش سوم - استفاده از JMS بعنوان واسط (broker) پیام ها:

در ساده ترین روش که مصرف کننده و تولید کننده هردو به زبان جاوا باشند با استفاده Active MQ که از پروتکل open wire استفاده میکنند میتوانند با هم ارتباط برقرار کنند اما فرض کنید مصرف کننده به زبان جاوا و تولید کننده به زبان پایتون نوشته شده باشند.

جاوا باید از کلاینت Active MQ برای ارتباط با واسط (broker) استفاده کند اما پایتون از کلاینت PyActive MQ باید استفاده کند چرا که نیازمند پروتکل STOMP (یکی از معروف ترین پروتکل های پشتیبانی کننده) برای ارتباط واسط است.

هزینه فایده

هر دوی این پروتکل ها ممکن است از یک نوع فرمت پیام برای فرستاد و دریافت استفاده نکنند و این روش هر چقدر هم که برای برنامه ای به زبان جاوا کارا باشد برای چند پلتفرمی (cross platform) بودن مناسب نیست.

برای مشکل ایجاد شده در JMS استاندارد پروتکل باز Advanced Message Queue برای ایجاد قابلیت ارتباط در سیستم ها بین کلاینت و واسط پیام ها (broker) ساخته شده است.

تلاش چهارم - استفاده از AMQP بعنوان واسط پیام ها:

در این روش پیام ها به چیزی شبیه به اداره پست (exchange) فرستاده میشوند. سپس اداره پست (exchange) نسخه ای از پیام ها را به صف با قوانین (binding) مربوط به خود تحویل میدهد. نسخه های مختلفی از واسط با پروتکل AMQ پیاده سازی شده امد که در اینجا به معروفترین آنها RabbitMQ میپردازیم.

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

مصرف کننده به محض پردازش پیام بسته به شرایطی که توسعه دهنده برنامه (developer) ایجاد میکند علامت تصدیقی (acknowledge) را به واسط AMQ میفرستد.

مصرف کننده ها برای گرفتن به نوبت پیام های روی واسط (broker) صفی را ایجاد میکنند و به پیام دریافتی گوش میدهند و تا زمانیکه پیامی برای آنها ارسال نشود صفی هم ایجاد نشده است.

پیام ها میتواند به همه مصرف کننده هایی که با صف به اداره پست (exchange) متصل باشند ارسال شود. (Fanout) و یا با استفاده کلید مسیریابی (routing key) دقیقا به همان مصرف کننده ای برسند که بین پیام ها به آن کلید مشخص گوش میدهد.(Direct) همچنین این مسیر میتواند به شکل یک الگو تعریف شود(Topic)

هزینه فایده

با استفاده از روش های (direct, fanout, topic etc) هم مدل Point to Point و هم Publish-Subscribe قابل پشتیبانی است و مسیریابی با تنوع کامل فیلتر گذاری روی پیام ها برای اینکه هر مصرف کننده معطل بقیه پردازش ها نشود قابل مدیریت توسط توسعه دهنده است.

تلاش پنجم - استفاده از Kafka بعنوان واسط پیام ها:

در Kafka هر پیام داری کلید، ارزش و زمان است و بر خلاف RabbitMQ که واسط همه متا دیتا را دریافت میکرد و همه چیز را در مورد پیام ها میدانست. برای Kafka واسط نادان و بی اطلاع از همه چیز در نظر گرفته میشه و این مصرف کننده ها که توسط برنامه نویس توسعه داده شده اند که نسبت به پیام ها هوشمند هستند. Kafka اهمیتی نمیدهد که کدام پیام ها خوانده شده اند و پیام های مصرف نشده را نگه دارد. در عوض همه پیام ها را برای مدت زمانی نگه میدارد و مصرف کننده ها را نسبت به گروه یا محدوده خود مسئول میداند.

هزینه فایده

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

در نهایت باید بدانیم پیاده سازی Message Queue ما را درگیر پیچیدگی های عملیاتی خواهد کرد. جدا از انتخاب اینکه کدام روش مناسب شما خواهد بود باید از ابزارهایی برای مانیتور، کانفیگ استفاده کرد.

صف پیامmessage queuebrokerrabbitmq
back-end developer
شاید از این پست‌ها خوشتان بیاید