FactoryFinder प्रदर्शन / बुरा कैशिंग


9

मुझे एक बड़ा वर्ग जावा के साथ एक बड़ा java ee अनुप्रयोग मिला है, जो बहुत सारे xml प्रसंस्करण करता है। वर्तमान में मैं अपने कुछ कार्यों को गति देने और नमूना प्रोफाइलरों के माध्यम से धीमा कोड पथों का पता लगाने की कोशिश कर रहा हूं।

एक बात जिस पर मैंने गौर किया, वह यह कि हमारे कोड के कुछ हिस्सों में, जैसे कि हमारे पास कॉल आते TransformerFactory.newInstance(...)हैं, बेहद सुस्त हैं। मैंने इस FactoryFinderपद्धति को findServiceProviderहमेशा एक नया ServiceLoaderउदाहरण बनाने के लिए ट्रैक किया । में ServiceLoader जावाडोक मैं कैशिंग के बारे में निम्न नोट मिला:

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

अब तक सब ठीक है। यह OpenJDKs FactoryFinder#findServiceProviderविधि का एक हिस्सा है :

private static <T> T findServiceProvider(final Class<T> type)
        throws TransformerFactoryConfigurationError
    {
      try {
            return AccessController.doPrivileged(new PrivilegedAction<T>() {
                public T run() {
                    final ServiceLoader<T> serviceLoader = ServiceLoader.load(type);
                    final Iterator<T> iterator = serviceLoader.iterator();
                    if (iterator.hasNext()) {
                        return iterator.next();
                    } else {
                        return null;
                    }
                 }
            });
        } catch(ServiceConfigurationError e) {
            ...
        }
    }

हर पुकार पर findServiceProviderपुकार ServiceLoader.load। यह हर बार एक नया ServiceLoader बनाता है । इस तरह से ऐसा लगता है कि सर्विसवैलर्स कैशिंग तंत्र का कोई उपयोग नहीं है। प्रत्येक कॉल अनुरोधित ServiceProvider के लिए classpath को स्कैन करता है।

मैंने पहले से ही क्या प्रयास किया है:

  1. मुझे पता है कि आप javax.xml.transform.TransformerFactoryएक विशिष्ट कार्यान्वयन निर्दिष्ट करने के लिए सिस्टम गुण सेट कर सकते हैं । इस तरह से FactoryFinder ServiceLoader प्रक्रिया और उसके सुपर फास्ट का उपयोग नहीं करता है। अफसोस की बात है कि यह एक jvm विस्तृत संपत्ति है और मेरे jvm में चल रही अन्य जावा प्रक्रियाओं को प्रभावित करती है। उदाहरण के लिए सैक्सन के साथ मेरे एप्लिकेशन शिप और com.saxonica.config.EnterpriseTransformerFactoryमुझे एक और एप्लिकेशन मिला है, जो सैक्सन के साथ शिप नहीं करता है। जैसे ही मैं सिस्टम प्रॉपर्टी सेट करता हूं, मेरा अन्य एप्लिकेशन शुरू होने में विफल रहता है, क्योंकि com.saxonica.config.EnterpriseTransformerFactoryइसके क्लासपाथ पर कोई भी नहीं है । इसलिए यह मेरे लिए कोई विकल्प नहीं लगता है।
  2. मैंने पहले से ही हर उस जगह को रिफैक्ट कर दिया है जहां a TransformerFactory.newInstanceको कॉल किया जाता है और TransformerFactory को कैश करता है। लेकिन मेरी निर्भरता में विभिन्न स्थान हैं जहां मैं कोड को रिफलेक्टर नहीं कर सकता।

मेरा सवाल है: क्यों FactoryFinder एक ServiceLoader का पुन: उपयोग नहीं करता है? क्या सिस्टम के गुणों का उपयोग करने के अलावा इस पूरी ServiceLoader प्रक्रिया को तेज करने का कोई तरीका है? क्या इसे JDK में नहीं बदला जा सकता है ताकि किसी FactoryFinder ने ServiceLoader उदाहरण का पुन: उपयोग किया हो? इसके अलावा यह किसी एकल FactoryFinder के लिए विशिष्ट नहीं है। यह bahaviour javax.xmlपैकेज में सभी FactoryFinder वर्गों के लिए समान है जिसे मैंने अब तक देखा है।

मैं OpenJDK 8/11 का उपयोग कर रहा हूं। मेरे आवेदन एक Tomcat 9 उदाहरण में तैनात हैं।

संपादित करें: अधिक विवरण प्रदान करना

यहाँ एक एकल XMLInputFactory.newInstance कॉल के लिए कॉल स्टैक दिया गया है: यहां छवि विवरण दर्ज करें

जहां ज्यादातर संसाधनों का उपयोग किया जाता है ServiceLoaders$LazyIterator.hasNextService। यह विधि फ़ाइल getResourcesको पढ़ने के लिए ClassLoader पर कॉल करती META-INF/services/javax.xml.stream.XMLInputFactoryहै। हर बार अकेले कॉल करने में लगभग 35ms लगते हैं।

क्या इन फ़ाइलों को बेहतर कैश करने के लिए टॉमकैट को निर्देश देने का एक तरीका है ताकि उन्हें तेजी से सेवा दी जाए?


मैं FactoryFinder.java के आपके आकलन से सहमत हूं। ऐसा लगता है कि यह ServiceLoader को कैशिंग करना चाहिए। क्या आपने Openjdk स्रोत को डाउनलोड करने और इसे बनाने की कोशिश की है। मुझे पता है कि यह एक बड़े काम की तरह लगता है लेकिन यह नहीं हो सकता है। इसके अलावा, FactoryFinder.java के खिलाफ एक मुद्दा लिखने और यह देखने के लिए कि क्या कोई इस मुद्दे को उठाता है और समाधान पेश करता है, इसके लायक हो सकता है।
djhallx

क्या आपने -Dअपनी Tomcatप्रक्रिया में ध्वज का उपयोग करके संपत्ति सेट करने का प्रयास किया है? उदाहरण के लिए: -Djavax.xml.transform.TransformerFactory=<factory class>.इसे अन्य ऐप्स के लिए संपत्तियों को ओवरराइड नहीं करना चाहिए। आपकी पोस्ट अच्छी तरह से वर्णित है और शायद आपने इसे आज़माया है, लेकिन मैं पुष्टि करना चाहता हूं। Javax.xml.transform.TransformerFactory सिस्टम प्रॉपर्टी को सेट करने का तरीका देखें , Tomcat में HeapMemory या JVM Arguments कैसे सेट करें
Michał Ziober

जवाबों:


1

35 एमएस लगता है कि डिस्क एक्सेस बार शामिल है, और यह ओएस कैशिंग के साथ एक समस्या की ओर इशारा करता है।

यदि क्लासपाथ पर कोई निर्देशिका / गैर-जार प्रविष्टियाँ हैं जो चीजों को धीमा कर सकती हैं। इसके अलावा, यदि संसाधन पहले स्थान पर मौजूद नहीं है, जिसकी जाँच की गई है।

ClassLoader.getResourceओवरराइड किया जा सकता है यदि आप थ्रेड संदर्भ वर्ग लोडर सेट कर सकते हैं, या तो कॉन्फ़िगरेशन के माध्यम से (मैंने सालों तक टॉमकैट को नहीं छुआ है) या बस Thread.setContextClassLoader


ऐसा लगता है कि काम कर सकते हैं। मैं जल्द या बाद में इस पर एक नज़र डालूंगा। धन्यवाद!
वाग्नेर माइकल

1

मुझे इस पर डिबग करने के लिए एक और 30 मिनट का समय मिल सकता है और देखा कि कैसे टॉस्कैट संसाधन कैशिंग करता है।

विशेष रूप से CachedResource.validateResources(जो ऊपर के लौंग में पाया जा सकता है) मेरे लिए रुचि का था। trueयदि यह CachedResourceअभी भी मान्य है तो यह लौटाता है:

protected boolean validateResources(boolean useClassLoaderResources) {
        long now = System.currentTimeMillis();
        if (this.webResources == null) {
            ...
        }

        // TTL check here!!
        if (now < this.nextCheck) {
            return true;
        } else if (this.root.isPackedWarFile()) {
            this.nextCheck = this.ttl + now;
            return true;
        } else {
            return false;
        }
    }

एक कैश्ड सोर्स जैसा लगता है वास्तव में जीने का समय है (ttl)। TomTat में वास्तव में cacheTtl को कॉन्फ़िगर करने का एक तरीका है लेकिन आप केवल इस मान को बढ़ा सकते हैं। संसाधन कैशिंग कॉन्फ़िगरेशन वास्तव में लचीला नहीं है आसानी से ऐसा लगता है।

तो मेरे टॉमकैट में कॉन्फ़िगर किए गए 5000 एमएस का डिफ़ॉल्ट मान है। प्रदर्शन परीक्षण करते समय इसने मुझे धोखा दिया क्योंकि मेरे अनुरोधों (रेखांकन और सामान को देखकर) के बीच 5 सेकंड से थोड़ा अधिक समय था। इसलिए मेरे सभी अनुरोध मूल रूप से कैश के बिना चले गए और ZipFile.openहर बार यह भारी हो गया ।

तो जैसा कि मैं वास्तव में टॉमकैट कॉन्फ़िगरेशन के साथ बहुत अनुभवी नहीं हूं, मुझे अभी तक यकीन नहीं है कि यहां सही समाधान क्या है। CacheTTL को बढ़ाने से कैश लंबे समय तक रहता है लेकिन लंबे समय में समस्या को ठीक नहीं करता है।

सारांश

मुझे लगता है कि यहां वास्तव में दो अपराधी हैं।

  1. FactoryFinder वर्ग एक ServiceLoader का पुन: उपयोग नहीं कर रहा है। एक वैध कारण हो सकता है कि वे उनका पुन: उपयोग क्यों न करें - मैं वास्तव में एक के बारे में नहीं सोच सकता।

  2. वेब एप्लिकेशन संसाधन के लिए एक निश्चित समय के बाद टॉमकट को निकाल देना (क्लासपाथ में फाइल - एक ServiceLoaderकॉन्फ़िगरेशन की तरह )

यह सेवाओवलेडर वर्ग के लिए सिस्टम प्रॉपर्टी को परिभाषित नहीं करने के साथ जोड़ते हैं और आपको हर cacheTtlसेकंड में एक धीमी FactoryFinder कॉल मिलती है ।

अभी के लिए मैं लंबे समय तक cacheTtl को बढ़ाकर जी सकता हूं। मैं ओवरहाइडिंग के टॉम हॉटींस के सुझाव पर भी एक नज़र डाल Classloader.getResourcesसकता हूं, भले ही मुझे लगता है कि यह इस प्रदर्शन की अड़चन से छुटकारा पाने का एक कठोर तरीका है। हालांकि यह देखने लायक हो सकता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.