بررسی عمکرد spring webflux و spring mvc


بنام خدا
تو این پست در مورد این بحث میکنیم که spring webflux که بصورت reactive کار میکنه، چقدر با spring mvc تفاوت های عملکردی و عملیاتی داره.

قبل از ادامه مطلب این نکات رو در نظر بگیرید:

۱. پردازش ها به ۳ صورت در زبان های امروزی اجرا میشن:

  • الف) synchronous blocking : به این صورت که یک تردی داریم که میتونه برنامه رو بلاک یا فریز کنه . spring mvc در این دسته قرار داره


  • ب) asynchronous blocking : به این صورت که چندین ترد داریم ولی باز این ترد های میتونن فریز بشن و وقتی دیگه بیشتر از این تعداد نتونیم ترد بسازیم،‌ برنامه فریز میشه. درسته که spring mvc در دسته sync/blocking هست ولی سرور هایی مثل تامکت، بصورت دیفالت ۲۰۰ ترد (یوزر) رو میتونه هندل کنه. پس جایگاه اصلی spring mvc در این دسته هست


  • ج) asynchronous non-blocking : به این صورت که یک event loop ای موجود هست که تعداد محدودی ترد داره و این ترد ها همیشه مشغولن. تفاوتی که این مدل با بقیه داره در اینه که هر ترد دیگه منتظر جواب یا ریسپانس نمیمونه و کانتکس سویچ انجام میشه و ترد میره به یه کار دیگه میرسه. هر وقت هم نتیجه ریکوئست قبلی آماده بود،‌ با سیگنال onComplete برمیگرده. تعداد event loop ها وابسته به تعداد core ها متغیر هست. به این مدل reactive هم گفته میشود به این دلیل که برنامه به آماده شدن یا به ارور خوردن ریکوئست، واکنش نشون میده. spring webflux در این دسته قرار داره

۲. مشخصات سیستم :

cpu: core i3-9100f
ram : 16GB
os : arch linux

۳. تمام لود تست ها در لوکال انجام شده درنتیجه latency نداریم

۴. تاخیر یا latency در شبکه، مدت‌ زمانی است که طول می‌کشد تا یک درخواست از مبدا به مقصد ارسال شود

۵. تست ها با ابزار jmeter گرفته شده اند
۶. از اینجا به بعد تفاوت های مدل reactive و async/blocking که حالت کلی تری از خود spring mvc و spring webflux هست رو بررسی میکنیم


تعریف برنامه ها

دو برنامه spring boot ایجاد کرده ایم. یکی بصورت mvc پیاده شده و یکی هم بصورت reactive.
این دو برنامه دقیقا عین هم نوشته شده اند و هر کدوم به دیتابیسی وصل میشن و اطلاعات رو مینویسن و میخونن.

لینک پروژه mvc

لینک پروژه reactive

لود تست بدون تاخیر

در نمودار زیر لود تست این دوتا برنامه رو انجام داده ایم که از ۱۰ کاربر همزمان شروع کرده ایم تا ۳۰ هزار کاربر همزمان:

نمودار لود تست بدون تاخیر
نمودار لود تست بدون تاخیر

احتمالا تا حالا انتظار داشتین که مدل reactive بهتر عمل کنه در مقابل async/blocking . اما واقعیت اینه که در مثال ما،‌ تا ۱۷۵۰۰ کاربر همزمان، مدل mvc سرعت بهتری در پاسخگوئی به ریکوئست ها داره. اما این یک موضوع نسبیه. دلیل اینکه مدل reactive تا ۱۷۵۰۰ کاربر همزمان، سرعت پاسخگوئی کمتری داره اینه که context switch در event loop انجام میشه و باعث میشه به نسبت سرعت کمتری در این بازه داشته باشیم.

اما این همه ماجرا نیست. در مدل async/blocking مشکل بدتری وجود داره اونم این که ریکوئست ها fail میشن ! تا ۵ هزار ریکوئست ما خطایی نداریم. در تست هایی که انجام دادم، وقتی ۱۰ هزار کاربر همزمان ریکوئست میزنن، درصدی از ریکوئست ها fail میشن (قاعدتا موقع بروز اولین خطا ها موقعی هست که تعداد کاربرا زیر ۱۰ هزار و بالای ۵ هزار هست) و این fail شدن ها،‌هرچقد تعداد کاربران افزایش پیدا کنه، بیشتر میشن، درحالیکه که توی مدل reactive اصلا fail شدن وجود نداره و درصد fail ها صفره !

درصد شکست های مدل async/blocking
درصد شکست های مدل async/blocking


لود تست با تاخیر یا دیلی

تا حالا قدرت اصلی reactive رو ندیدیم. یه مثال در دنیای واقعی رو در نظر بگیرید. اینکه ممکنه شما در برنامه یا سرویستون به سرویس های دیگه یا api های دیگه ریکوئست بزنین و اطلاعاتی رو دریافت کنین. طبیعتا این کار به این معنی هست که زمانی بین وصل شدن، ارسال و دریافت اطلاعات به اون سرویس یا api وجود داره. همچنین ممکنه اون سرویس یا api هم به مشکل بخوره که تو این مثال ما این مشکلات سرویس های خارجی در نظر نگرفته ایم و صرفا دیلی رو درنظر گرفته ایم. گرچه مدل reactive هم راهکاری هایی برای ارور هندلینگ داره.
در نمودار زیر لود تست این دوتا برنامه رو با ۱۰ هزار کاربر همزمان با دیلی هایی از ۱ تا ۵ ثانیه به ازای هر ریکوئست رو آورده ایم:

نمودار لود تست با دیلی تعداد کاربران همزان: ۱۰ هزار
نمودار لود تست با دیلی تعداد کاربران همزان: ۱۰ هزار


نتایج شگفت انگیز نیست؟
نمودار مدل reactive همزمان که دیلی هر ریکوئست بیشتر میشه، تقریبا ثابت هست. حتی وقتی که یک ثانیه دیلی داره،‌ با مثال قبلی که ۱۰ هزار کاربر همزمان بدون دیلی انجام شده،‌ زمان جواب دادن به ریکوئست یکیه.
قدرت اصلی reactive در اینجاس. همزمان که به کاربرا در سریع ترین زمان ممکن جواب رو برمیگردونه، بدون شکست هم هست! درحالیکه مدل async/blocking رفته رفته خطاهاش بیشتر و بیشتر میشه و باعث میشه انتخاب بدی تو این شرایط باشه‌ :

درصد شکست های مدل async/blocking
درصد شکست های مدل async/blocking

ستون آخر این نمودار به این معنیه که شما به ۱۷ درصد کاربراتون جواب نمیدید!

نکته: وقتی delay وجود داره به این معنی نیست که ریکوئستی که حداقل ۵ ثانیه زمان میبره تا جواب بده، در مدل reactive کاهش پیدا کنه و بشه ۱ ثانیه. نه.
به این معنی هست که میانگین زمان و حداکثر زمان هر ریکوئست ها در تعداد بالا رو کاهش بده. در مثال ما در مدل reactive، میانگین هر درخواست با ۵ ثانیه دیلی حدودا ۵٫۵ ثانیه هست در حالی که در مدل async/blocking این میانگین حدود ۱۰۶ ثانیه هست. به این معنی که یک کاربری ممکنه ۱۰۶ ثانیه منتظر بمونه تا درخواستش جواب داده بشه!


چه مواقعی از کدوم استفاده کنیم؟

  • از async/blocking زمانی استفاده کنید که مطمئنید تعداد کاربرانتون که همزمان میتونن درخواست به سرور شما بزنن، زیر ۱۰ هزار تا باشه (مقدار عددی رو خودتون باید توی vps یا سرور ابریتون حساب کنید) و api call ای وجود نداره
  • از reactive زمانی استفاده کنید که پیشبینی میکنید، تعداد کابرانتون خیلی بیشتر از ۱۰ هزار تا باشه و مطمئنید که هر ریکوئست ممکنه دیلی داشته باشه. این دیلی میتونه api call یا عملیات های io دیگری باشه

خلاصه

از مجموع این مباحث به این نتایج میرسیم:

  • در مدل async/blocking هر ریکوئست به معنی یک ترد هست
  • در مدل async/blocking چون به ازای هر ریکوئست یک ترد استفاده یا ایجاد میشه، درنتیجه مموری بیشتری استفاده میشه
  • در مدل async/blocking هر ترد ممکنه بلاک بشه در نتیجه cpu زمانی رو در انتظار میمونه و کاری نمیکنه
  • در مدل reactive تعداد محدودی ترد در thread pool که زمانبندی میشن، وجود دارن و در نتیجه، مموری کمتری استفاده میشه
  • در مدل reactive چون این گپ انتظار رو از مدل async/blocking حذف کرده،‌ پس cpu کار بیشتری انجام میده و همیشه مشغول پردازش هست.


منابع:
کتاب reactive spring
کتاب modern java in action
projectreactor.io
docs.spring.io
stackoverflow.com<br/>

هرجا سوال، پیشنهاد، اصلاحی داشتید ممنون میشم در کامنتا من و بقیه رو مطلع کنید ?