آن چه در این مقاله خواهید خواند:
معرفی Spring MVC Async و Spring WebFlux
من ارسلان میربزرگی در این مقاله، به بررسی Async annotation@ در Spring MVC میپردازم و سپس با Spring WebFlux شما را آشنا خواهم کرد . هدف درک بهتر تفاوتهای این دو Framework است.
Implementation
در این بخش میخواهیم به کمک انتخاب یک سناریو نشان دهیم چگونه میتوان یک Web application ساده را با هر یک از این API ها پیادهسازی کرد. علاوه بر این، در ادامه مدیریت Thread و Blocking یا Non-blocking ورودی/ خروجی (I/O) در هر مورد را بررسی میکنیم.
اگر یک Web application با یک Endpoint را انتخاب کنیم بهعنوان Response یک String را برمیگرداند. نکته این است که در اینجا درخواست با یک تأخیر کوچک 200 میلیثانیهای از یک فیلتر عبور میکند و سپس Controller برای محاسبه و برگرداندن نتیجه به 500 میلیثانیه نیاز دارد.
در مرحلهی بعدی، میخواهیم Load را با Apache ab در هر دو Endpoint، Simulate کرده و رفتار برنامه را با JConsole بررسی کنیم.
Spring MVC Async
Async annotation@ توسط Spring 3.0 معرفی شد. هدف Async@ این است که به برنامه اجازه دهد کارهای Heavy-load را در یک Thread جداگانه اجرا کند. همچنین، درخواست دهنده میتواند منتظر نتیجه باشد؛ بنابراین نوع بازگشت جواب نباید از نوعی باشد که Throw exception شود و میتواند هر یک از انواع Future، CompletableFuture یا ListenableFuture در نظر گرفته شود.
علاوه بر این، Spring 3.2 بستهی org.springframework.web.context.request.async را معرفی كرد كه همراه با Servlet 3.0، فرآیند Asynchronous را به لایههای وب میافزاید؛ بنابراین، بعد از Spring 3.2، Async@ میتواند در کلاسهای Annotate شده با عنوان Controller یا RestController مورداستفاده قرار گیرد.
هنگامیکه Client درخواستی را شروع میکند تا زمانی که به DispatcherServlet برسد از Filter chain ها عبور میکند.
سپس، Servlet سعی میکند درخواستها را بهصورت همزمان Dispatch کند. با صدازدن AsyncWebRequest#startAsync، درخواست آغازشده را Mark میکند، رسیدگی به درخواستها را به WebSyncManager منتقل میکند و بدون Commit پاسخ، کار خود را به پایان میرساند. Filter chain نیز در جهت معکوس بهطرف Root بازمیگردد.
اما WebAsyncManager کار پردازش درخواست را در ExecutorService مرتبط با خود ارسال کرده و هر زمان که نتیجه آماده شد، برای ارسال پاسخ به Client به DispatcherServlet اطلاع میدهد.
Spring Async Implementation
Implementation را با نوشتن یک کلاس نمونه به نام AsyncVsWebFluxApp برای برنامهی خود شروع میکنیم. در اینجا، @EnableAsync کار فعال کردن Async برای برنامهی Spring Boot را انجام میدهد:
سپس AsyncFilter را داریم که javax.servlet.Filter را پیادهسازی میکند. فراموش نکنید که تأخیر درروش doFilter را Simulate کنید:
سرانجام، AsyncController خود را با Endpoint ای به نام “/ async_result” توسعه میدهیم:
به دلیل @Async نام برده در getResultAsync، این روش در یک Thread جداگانه در ExecutorService پیشفرض برنامه اجرا میشود. بااینوجود، میتوان ExecutorService خاصی را برای روش خود تنظیم کنید.
برای اجرای برنامه، Apache ab یا هر ابزاری را برای شبیهسازی Load نصب کنید. سپس میتوانید یک گروه از درخواستهای Concurrent را از طریق Endpoint ای با نام “async_result” ارسال کنید. همچنین میتوانید JConsole را اجرا کرده و آن را به برنامه جاوا خود متصل کنید تا فرایند را Monitor کند:
Spring WebFlux
Spring 5.0 برای پشتیبانی از Reactive web به روش Non-blocking، پلتفرم WebFlux را معرفی کرده است. WebFlux یک اجرای عالی دیگر از Reactive stream و بر اساس API Reactor است.
Spring WebFlux با Non -blocking I/O از Reactive backpressure و +Servlet 3.1 پشتیبانی میکند. ازاینرو میتوان آن را روی Netty، Undertow، Jetty، Tomcat یا هر سرور سازگار با +Servlet 3.1 اجرا کرد.
اگرچه همهی سرورها از مدل کنترل Thread و کنترل Concurrency استفاده نمیکنند، اما Spring WebFlux تا زمانی که از Non -blocking I/O و Reactive backpressure پشتیبانی کند، خوب کار خواهد کرد.
Spring WebFlux به شما اجازه میدهد تا Logic را به روش Declarative با Mono، Flux و مجموعه عملگرهای آنها Decompose كنید. علاوه بر این، میتوانید علاوه بر نقاط Annotate شدهی Controller@، Endpoint های Functional نیز داشته باشید، اگرچه اکنون میتوانید در Spring MVC نیز از اینها استفاده کنید.
Spring WebFlux Implementation
برای پیادهسازی WebFlux، طبق همان روش Async پیش میرویم؛ بنابراین در ابتدا، AsyncVsWebFluxApp را ایجاد میکنیم:
سپس باید WebFluxFilter مربوط به برنامهی خود را بنویسیم که WebFilter را پیادهسازی میکند. یک تأخیر عمدی ایجاد خواهیم کرد و سپس درخواست را به Filter chain منتقل خواهیم کرد:
سرانجام، WebFluxController مربوط به خود را خواهیم داشت که یک Endpoint به نام “/ flux_result” را نشان میدهد و
برای تست برنامه، همان رویکردی را که در برنامهی نمونه Async خود اعمال کردهایم انجام میدهیم:
تفاوت Spring MVC Async و Spring WebFlux چیست؟
Spring Async از Servlet 3.0 پشتیبانی میکند، اما Spring WebFlux از +Servlet 3.1 پشتیبانی میکند. این مورد باعث ایجاد تفاوت های زیر میشود:
- مدل I / O Spring Async در حین برقراری ارتباط با مشتری از نوع Block است که ممکن است باعث ایجاد مشکل در Performance برای مشتریانی که سرعت برقراری ارتباطشان کمتر است، میشود. از طرف دیگر، Spring WebFlux یک مدل Non-blocking I/O را ارائه میدهد.
- خواندن متن درخواست یا قسمتهای درخواست در Spring Async از نوع Block است، درحالیکه در Spring WebFlux اینگونه نیست.
- در Spring Async، فیلترها و Servlet ها بهطور Synchronous کار میکنند، اما Spring WebFlux از ارتباط کاملاً Asynchronous پشتیبانی میکند.
- Spring WebFlux برخلاف Spring Async، با طیف گستردهای از سرورهای Web/Application مانند Netty و Undertow سازگار است.
علاوه بر موارد ذکر شده، Spring WebFlux از Reactive backpressure پشتیبانی میکند، بنابراین کنترل بیشتری نسبت به MVC Async و Spring MVC خواهیم داشت. Spring Flux همچنین به لطف Reactor API، توانایی برنامهنویسی به سبک Functional و Decompose کردن API های Declarative دارد.
و در آخر
سؤال اینجاست که آیا همهی این موارد ما را به سمت استفاده از Spring WebFlux سوق میدهد؟ Spring Async یا حتی Spring MVC بسته به Load scalability مناسب یا Availability سیستم، ممکن است راهحل درستی برای بسیاری از پروژههای موجود باشند. در مورد Scalability، استفاده از Spring Async نتایج بهتری نسبت به اجرای Synchronous Spring MVC به ما میدهد و Spring WebFlux به دلیل ماهیت Reactive ای که دارد، قابلیت Elasticity و Availability بالاتری را فراهم میکند.