स्प्रिंग वेबफ्लक्स एप्लिकेशन को डिबग करना एक चुनौतीपूर्ण कार्य हो सकता है, खासकर जब जटिल प्रतिक्रियाशील धाराओं से निपटना हो। पारंपरिक अवरोधक अनुप्रयोगों के विपरीत, जहां स्टैक ट्रेस किसी समस्या के मूल कारण का स्पष्ट संकेत प्रदान करता है, प्रतिक्रियाशील अनुप्रयोगों को डीबग करना कठिन हो सकता है। ब्लॉकिंग कोड, समवर्ती समस्याएं, और दौड़ की स्थिति जैसे मुद्दे सभी सूक्ष्म बग पैदा कर सकते हैं जिनका निदान करना मुश्किल है। परिदृश्य बग से निपटने पर यह हमेशा कोड से संबंधित समस्या नहीं होती है। यह कारकों का एक समूह हो सकता है, जैसे हाल ही में रिफैक्टरिंग, टीम परिवर्तन, कठिन समय सीमा आदि। वास्तविक जीवन में, कुछ समय पहले कंपनी छोड़ने वाले लोगों द्वारा किए गए बड़े अनुप्रयोगों का समस्या निवारण करना एक सामान्य बात है, और आप अभी शामिल हुए हैं। किसी डोमेन और तकनीकों के बारे में थोड़ा-बहुत जानने से आपका जीवन आसान नहीं हो जाएगा। नीचे दिए गए कोड उदाहरण में, मैं कल्पना करना चाहता था कि हाल ही में एक टीम में शामिल होने वाले व्यक्ति के लिए एक बग्गी कोड कैसा दिख सकता है। इस कोड को चुनौती के बजाय एक यात्रा की तरह डिबग करने पर विचार करें। मूल कारण उन लोगों के लिए स्पष्ट हो सकता है जो रिएक्टिव ऐप्स से परिचित हैं। हालाँकि, नीचे दी गई कुछ प्रथाएँ अभी भी संशोधित करने में बहुत सहायक हो सकती हैं। @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) यह विधि में हुआ, और 2) क्लाइंट ने `HTTP GET "/ग्रीटिंग/मिक/जैगर का प्रदर्शन किया GreetingController#greeting .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 : आप त्रुटि संदेशों को लॉग करने के लिए उपयोग कर सकते हैं और आपकी प्रतिक्रियात्मक स्ट्रीम में क्या गलत हुआ, इसके बारे में अधिक संदर्भ प्रदान कर सकते हैं। कई ऑपरेटरों के साथ एक जटिल स्ट्रीम में समस्याओं को डीबग करते समय यह विशेष रूप से सहायक हो सकता है। लॉगिंग doOnError : उपयोग त्रुटियों से पुनर्प्राप्त करने और स्ट्रीम को संसाधित करना जारी रखने के लिए भी किया जा सकता है। उदाहरण के लिए, आप त्रुटि के मामले में फ़ॉलबैक मान या स्ट्रीम प्रदान करने के लिए उपयोग कर सकते हैं। पुनर्प्राप्ति doOnError onErrorResume : संभवतया लॉग में जो आपने पहले ही देखा है, उसके अलावा कोई बेहतर स्टैकट्रेस प्रदान नहीं करेगा। एक अच्छे समस्यानिवारक के रूप में उस पर भरोसा न करें। डिबगिंग doOnError लकड़ी का लट्ठा() अगला पड़ाव पहले जोड़े गए मेथड कॉल से बदलना है। जितना सरल हो जाता है। डिफ़ॉल्ट रूप से सभी प्रतिक्रियाशील स्ट्रीम संकेतों को देखता है और उन्हें INFO स्तर के तहत लॉग में ट्रेस करता है। doOnError() log() log() आइए देखें कि अब हम लॉग में कौन सी अतिरिक्त जानकारी देखते हैं: हम देख सकते हैं कि प्रतिक्रियाशील विधियों को क्या कहा गया है ( , और )। इसके अतिरिक्त, यह जानना कि कौन से थ्रेड्स (पूल) इन विधियों से बुलाए गए हैं, बहुत उपयोगी जानकारी हो सकती है। हालांकि, यह हमारे मामले के लिए प्रासंगिक नहीं है। 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()); } यही मूल कारण है। फ्लक्स स्रोत से एक आइटम उत्सर्जित करता है या एक से अधिक तत्वों वाले स्रोत के लिए सिग्नल करता है। इसका मतलब है कि विधि में फ्लक्स 1 से अधिक आइटम का उत्सर्जन करता है। कॉल पदानुक्रम में ऊपर जाकर हम देखते हैं कि मूल रूप से दो तत्वों के साथ एक Flux है। single() IndexOutOfBoundsException Flux.fromIterable(Arrays.asList(firstName, lastName)) फ़िल्टरिंग विधि किसी आइटम को फ्लक्स से हटाती है जब वह के बराबर होता है। यही कारण है कि कोड जॉन नामक कॉलेज के लिए काम करता है। हुह। 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 निष्कर्ष इन सर्वोत्तम प्रथाओं का पालन करके, आप डिबगिंग प्रक्रिया को सरल बना सकते हैं और अपने स्प्रिंग वेबफ्लक्स एप्लिकेशन में समस्याओं की शीघ्रता से पहचान और समाधान कर सकते हैं। चाहे आप एक अनुभवी डेवलपर हों या प्रतिक्रियाशील प्रोग्रामिंग के साथ शुरुआत कर रहे हों, ये युक्तियाँ आपके कोड की गुणवत्ता और विश्वसनीयता में सुधार करने में आपकी मदद करेंगी, और आपके उपयोगकर्ताओं के लिए बेहतर अनुभव प्रदान करेंगी।