paint-brush
स्प्रिंग वेबफ्लक्स एप्लिकेशन को कैसे डिबग करेंद्वारा@vladimirf
7,475 रीडिंग
7,475 रीडिंग

स्प्रिंग वेबफ्लक्स एप्लिकेशन को कैसे डिबग करें

द्वारा Vladimir Filipchenko7m2023/05/09
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

स्प्रिंग वेबफ्लक्स एप्लिकेशन को डिबग करना एक चुनौतीपूर्ण कार्य हो सकता है, खासकर जब जटिल प्रतिक्रियाशील धाराओं से निपटना हो। ब्लॉकिंग कोड, समवर्ती समस्याएं, और दौड़ की स्थिति जैसे मुद्दे सभी सूक्ष्म बग पैदा कर सकते हैं जिनका निदान करना मुश्किल है। मूल कारण उन लोगों के लिए स्पष्ट हो सकता है जो रिएक्टिव ऐप्स से परिचित हैं। हालाँकि, नीचे दी गई कुछ प्रथाएँ अभी भी संशोधित करने में बहुत सहायक हो सकती हैं।
featured image - स्प्रिंग वेबफ्लक्स एप्लिकेशन को कैसे डिबग करें
Vladimir Filipchenko HackerNoon profile picture
0-item
1-item

स्प्रिंग वेबफ्लक्स एप्लिकेशन को डिबग करना एक चुनौतीपूर्ण कार्य हो सकता है, खासकर जब जटिल प्रतिक्रियाशील धाराओं से निपटना हो। पारंपरिक अवरोधक अनुप्रयोगों के विपरीत, जहां स्टैक ट्रेस किसी समस्या के मूल कारण का स्पष्ट संकेत प्रदान करता है, प्रतिक्रियाशील अनुप्रयोगों को डीबग करना कठिन हो सकता है। ब्लॉकिंग कोड, समवर्ती समस्याएं, और दौड़ की स्थिति जैसे मुद्दे सभी सूक्ष्म बग पैदा कर सकते हैं जिनका निदान करना मुश्किल है।


परिदृश्य

बग से निपटने पर यह हमेशा कोड से संबंधित समस्या नहीं होती है। यह कारकों का एक समूह हो सकता है, जैसे हाल ही में रिफैक्टरिंग, टीम परिवर्तन, कठिन समय सीमा आदि। वास्तविक जीवन में, कुछ समय पहले कंपनी छोड़ने वाले लोगों द्वारा किए गए बड़े अनुप्रयोगों का समस्या निवारण करना एक सामान्य बात है, और आप अभी शामिल हुए हैं।


किसी डोमेन और तकनीकों के बारे में थोड़ा-बहुत जानने से आपका जीवन आसान नहीं हो जाएगा।


नीचे दिए गए कोड उदाहरण में, मैं कल्पना करना चाहता था कि हाल ही में एक टीम में शामिल होने वाले व्यक्ति के लिए एक बग्गी कोड कैसा दिख सकता है।


इस कोड को चुनौती के बजाय एक यात्रा की तरह डिबग करने पर विचार करें। मूल कारण उन लोगों के लिए स्पष्ट हो सकता है जो रिएक्टिव ऐप्स से परिचित हैं। हालाँकि, नीचे दी गई कुछ प्रथाएँ अभी भी संशोधित करने में बहुत सहायक हो सकती हैं।


 @GetMapping("/greeting/{firstName}/{lastName}") public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { return Flux.fromIterable(Arrays.asList(firstName, lastName)) .filter(this::wasWorkingNiceBeforeRefactoring) .transform(this::senselessTransformation) .collect(Collectors.joining()) .map(names -> "Hello, " + names); } private boolean wasWorkingNiceBeforeRefactoring(String aName) { // We don't want to greet with John, sorry return !aName.equals("John"); } private Flux<String> senselessTransformation(Flux<String> flux) { return flux .single() .flux() .subscribeOn(Schedulers.parallel()); }


तो, कोड का यह टुकड़ा क्या करता है: यह पैरामीटर के रूप में प्रदान किए गए नामों के लिए "हैलो," जोड़ता है।

आपका सहकर्मी जॉन आपको बता रहा है कि उसके लैपटॉप पर सब कुछ काम करता है। यह सच है:


 > curl localhost:8080/greeting/John/Doe > Hello, Doe


लेकिन जब आप इसे curl localhost:8080/greeting/Mick/Jagger की तरह चलाते हैं, तो आप अगला स्टैकट्रेस देखते हैं:


 java.lang.IndexOutOfBoundsException: Source emitted more than one item at reactor.core.publisher.MonoSingle$SingleSubscriber.onNext(MonoSingle.java:134) ~[reactor-core-3.5.5.jar:3.5.5] Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: Error has been observed at the following site(s): *__checkpoint ⇢ Handler com.example.demo.controller.GreetingController#greeting(String, String) [DispatcherHandler] *__checkpoint ⇢ HTTP GET "/greeting/Mick/Jagger" [ExceptionHandlingWebHandler] Original Stack Trace: <18 internal lines> at java.base/java.util.concurrent.FutureTask.run(FutureTask.java) ~[na:na] (4 internal lines)


अच्छा, कोई भी निशान उपरोक्त कोड नमूने की ओर नहीं जाता है।


इससे पता चलता है कि 1) यह GreetingController#greeting विधि में हुआ, और 2) क्लाइंट ने `HTTP GET "/ग्रीटिंग/मिक/जैगर का प्रदर्शन किया

.doOnError()


सबसे पहले और सबसे आसान काम है ग्रीटिंग चेन के अंत में `.doOnError()` कॉलबैक जोड़ना।


 @GetMapping("/greeting/{firstName}/{lastName}") public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { return Flux.fromIterable(Arrays.asList(firstName, lastName)) // <...> .doOnError(e -> logger.error("Error while greeting", e)); }


अच्छा प्रयास है, लेकिन लॉग में कोई सुधार नहीं दिख रहा है।


फिर भी, रिएक्टर का आंतरिक स्टैक ट्रेस:



यहाँ कुछ तरीके दिए गए हैं जो doOnError डिबगिंग के दौरान मददगार हो सकते हैं/नहीं हो सकते हैं:

  1. लॉगिंग : आप त्रुटि संदेशों को लॉग करने के लिए doOnError उपयोग कर सकते हैं और आपकी प्रतिक्रियात्मक स्ट्रीम में क्या गलत हुआ, इसके बारे में अधिक संदर्भ प्रदान कर सकते हैं। कई ऑपरेटरों के साथ एक जटिल स्ट्रीम में समस्याओं को डीबग करते समय यह विशेष रूप से सहायक हो सकता है।

  2. पुनर्प्राप्ति : doOnError उपयोग त्रुटियों से पुनर्प्राप्त करने और स्ट्रीम को संसाधित करना जारी रखने के लिए भी किया जा सकता है। उदाहरण के लिए, आप त्रुटि के मामले में फ़ॉलबैक मान या स्ट्रीम प्रदान करने के लिए onErrorResume उपयोग कर सकते हैं।

  3. डिबगिंग : संभवतया doOnError लॉग में जो आपने पहले ही देखा है, उसके अलावा कोई बेहतर स्टैकट्रेस प्रदान नहीं करेगा। एक अच्छे समस्यानिवारक के रूप में उस पर भरोसा न करें।


लकड़ी का लट्ठा()


अगला पड़ाव पहले जोड़े गए doOnError() log() मेथड कॉल से बदलना है। जितना सरल हो जाता है। log() डिफ़ॉल्ट रूप से सभी प्रतिक्रियाशील स्ट्रीम संकेतों को देखता है और उन्हें INFO स्तर के तहत लॉग में ट्रेस करता है।


आइए देखें कि अब हम लॉग में कौन सी अतिरिक्त जानकारी देखते हैं:


हम देख सकते हैं कि प्रतिक्रियाशील विधियों को क्या कहा गया है ( onSubscribe , request और onError )। इसके अतिरिक्त, यह जानना कि कौन से थ्रेड्स (पूल) इन विधियों से बुलाए गए हैं, बहुत उपयोगी जानकारी हो सकती है। हालांकि, यह हमारे मामले के लिए प्रासंगिक नहीं है।


थ्रेड पूल के बारे में


थ्रेड नाम ctor-http-nio-2 reactor-http-nio-2 के लिए खड़ा है। आईओ थ्रेड पूल (शेड्यूलर) पर onSubscribe() और request() पर प्रतिक्रियात्मक तरीके निष्पादित किए गए थे। इन कार्यों को तुरंत उस थ्रेड पर निष्पादित किया गया जिसने उन्हें सबमिट किया था।


.subscribeOn(Schedulers.parallel()) के अंदर senselessTransformation होने से हमने रिएक्टर को दूसरे थ्रेड पूल पर और तत्वों की सदस्यता लेने का निर्देश दिया है। यही कारण है कि parallel-1 थ्रेड पर onError निष्पादित किया गया है।


आप इस लेख में थ्रेड पूल के बारे में अधिक पढ़ सकते हैं।


log() विधि आपको अपनी स्ट्रीम में लॉगिंग स्टेटमेंट जोड़ने की अनुमति देती है, जिससे डेटा के प्रवाह को ट्रैक करना और समस्याओं का निदान करना आसान हो जाता है। यदि हमारे पास फ्लैटपाइप, सबचेन्स, ब्लॉकिंग कॉल्स इत्यादि जैसी चीजों के साथ अधिक जटिल डेटा प्रवाह होता है, तो हम इसे लॉग डाउन करने से बहुत लाभान्वित होंगे। यह दैनिक उपयोग के लिए बहुत ही आसान और अच्छी चीज है। हालाँकि, हम अभी भी मूल कारण नहीं जानते हैं।


हुक.ऑनऑपरेटरडीबग ()


निर्देश Hooks.onOperatorDebug() रिएक्टर को प्रतिक्रियाशील स्ट्रीम में सभी ऑपरेटरों के लिए डिबग मोड को सक्षम करने के लिए कहता है, जिससे अधिक विस्तृत त्रुटि संदेश और स्टैक ट्रेस की अनुमति मिलती है।


आधिकारिक दस्तावेज के मुताबिक:

जब बाद में त्रुटियां देखी जाती हैं, तो वे मूल असेंबली लाइन स्टैक का विवरण देने वाले एक दमित अपवाद के साथ समृद्ध होंगे। उत्पादकों (जैसे Flux.map, Mono.fromCallable) को वास्तव में सही स्टैक जानकारी को इंटरसेप्ट करने के लिए कॉल करने से पहले कॉल किया जाना चाहिए।


निर्देश को प्रति रनटाइम एक बार बुलाया जाना चाहिए। सबसे अच्छे स्थानों में से एक कॉन्फ़िगरेशन या मुख्य वर्ग होगा। हमारे उपयोग के मामले में यह होगा:


 public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { Hooks.onOperatorDebug(); return // <...> }


Hooks.onOperatorDebug() को जोड़कर हम अंततः अपनी जांच में प्रगति कर सकते हैं। स्टैकट्रेस अधिक उपयोगी है:



और लाइन 42 पर हमारे पास single() कॉल है।


ऊपर स्क्रॉल न करें, senselessTransformation अगला दिखता है:

 private Flux<String> senselessTransformation(Flux<String> flux) { return flux .single() // line 42 .flux() .subscribeOn(Schedulers.parallel()); }


यही मूल कारण है।


single() फ्लक्स स्रोत से एक आइटम उत्सर्जित करता है या एक से अधिक तत्वों वाले स्रोत के लिए IndexOutOfBoundsException सिग्नल करता है। इसका मतलब है कि विधि में फ्लक्स 1 से अधिक आइटम का उत्सर्जन करता है। कॉल पदानुक्रम में ऊपर जाकर हम देखते हैं कि मूल रूप से दो तत्वों Flux.fromIterable(Arrays.asList(firstName, lastName)) के साथ एक Flux है।


फ़िल्टरिंग विधि wasWorkingNiceBeforeRefactoring किसी आइटम को फ्लक्स से हटाती है जब वह जॉन के बराबर होता है। यही कारण है कि कोड जॉन नामक कॉलेज के लिए काम करता है। हुह।


Hooks.onOperatorDebug() विशेष रूप से जटिल प्रतिक्रियाशील धाराओं को डीबग करते समय उपयोगी हो सकता है, क्योंकि यह स्ट्रीम को कैसे संसाधित किया जा रहा है, इसके बारे में अधिक विस्तृत जानकारी प्रदान करता है। हालाँकि, डिबग मोड को सक्षम करने से आपके एप्लिकेशन का प्रदर्शन प्रभावित हो सकता है (आबाद स्टैक ट्रेस के कारण), इसलिए इसका उपयोग केवल विकास और डिबगिंग के दौरान किया जाना चाहिए, न कि उत्पादन में।


चौकियों


लगभग उसी प्रभाव को प्राप्त करने के लिए जैसा कि Hooks.onOperatorDebug() न्यूनतम प्रदर्शन प्रभाव देता है, एक विशेष checkpoint() ऑपरेटर होता है। यह धारा के उस भाग के लिए डिबग मोड को सक्षम करेगा, जबकि शेष धारा को अप्रभावित छोड़ देगा।


फ़िल्टरिंग के बाद और परिवर्तन के बाद दो चौकियों को जोड़ते हैं:


 public Mono<String> greeting(@PathVariable String firstName, @PathVariable String lastName) { return Flux.fromIterable(Arrays.asList(firstName, lastName)) .filter(this::wasWorkingNiceBeforeRefactoring) /* new */ .checkpoint("After filtering") .transform(this::senselessTransformation) /* new */ .checkpoint("After transformation") .collect(Collectors.joining()) .map(names -> "Hello, " + names); }


लॉग पर एक नज़र डालें:


यह चेकपॉइंट ब्रेकडाउन हमें बताता है कि हमारे दूसरे चेकपॉइंट के बाद परिवर्तन के रूप में वर्णित होने के बाद त्रुटि देखी गई है। इसका मतलब यह नहीं है कि निष्पादन के दौरान पहली चौकी नहीं पहुंची है। यह था, लेकिन त्रुटि दूसरे के बाद ही दिखाई देने लगी। इसलिए हम फ़िल्टर करने के बाद नहीं देखते हैं।


आप डिस्पैचरहैंडलर और एक्सेप्शनहैंडलिंगवेबहैंडलर से ब्रेकडाउन में उल्लिखित दो और चेकपॉइंट भी देख सकते हैं। कॉल पदानुक्रम के नीचे, हमारे द्वारा सेट किए गए के बाद वे पहुंचे थे।


विवरण के अलावा, आप checkpoint() विधि में दूसरे पैरामीटर के रूप में true जोड़कर रिएक्टर को अपने चेकपॉइंट के लिए स्टैकट्रेस उत्पन्न करने के लिए बाध्य कर सकते हैं। यह नोट करना महत्वपूर्ण है कि जनरेट किया गया स्टैकट्रेस आपको चेकपॉइंट वाली लाइन तक ले जाएगा। यह मूल अपवाद के लिए स्टैकट्रेस को पॉप्युलेट नहीं करेगा। तो यह बहुत मायने नहीं रखता है क्योंकि आप विवरण प्रदान करके आसानी से चेकपॉइंट ढूंढ सकते हैं।


निष्कर्ष


इन सर्वोत्तम प्रथाओं का पालन करके, आप डिबगिंग प्रक्रिया को सरल बना सकते हैं और अपने स्प्रिंग वेबफ्लक्स एप्लिकेशन में समस्याओं की शीघ्रता से पहचान और समाधान कर सकते हैं। चाहे आप एक अनुभवी डेवलपर हों या प्रतिक्रियाशील प्रोग्रामिंग के साथ शुरुआत कर रहे हों, ये युक्तियाँ आपके कोड की गुणवत्ता और विश्वसनीयता में सुधार करने में आपकी मदद करेंगी, और आपके उपयोगकर्ताओं के लिए बेहतर अनुभव प्रदान करेंगी।