2017-04-25 6 views
12

विन्यास
वेब सर्वर: Nginx
अनुप्रयोग सर्वर: 200 अनुरोध पेश धागे
अपने सर्वर के लिए अपेक्षित प्रतिक्रिया समय के डिफ़ॉल्ट विन्यास के साथ बिलाव: < 30 सेकंड (वहाँ तृतीय पक्ष निर्भरता के बहुत सारे)पुन: उपयोग बिल्ला धागे इंतजार कर रहे हैं, जबकि "लंबे समय" समय

SCENARIO
हर 10 सेकंड आवेदन इसके उपयोग के लिए टोकन जेनरेट करने की आवश्यकता होगी रहे हैं। टोकन पीढ़ी के लिए अपेक्षित समय लगभग 5 सेकंड है, लेकिन चूंकि इसकी तीसरी पार्टी प्रणाली नेटवर्क पर संपर्क की जा रही है, यह स्पष्ट रूप से सुसंगत नहीं है और 10 सेकंड तक जा सकती है।
टोकन पीढ़ी की प्रक्रिया के दौरान प्रति सेकंड आने वाले अनुरोधों का लगभग 80% इंतजार करना होगा।

मैं क्या विश्वास करना चाहिए नहीं HAPPEN.Yes
टोकन पीढ़ी के लिए इंतजार कर रहे अनुरोधों के बाद से एक "लंबे समय" समय तक इंतजार करना होगा, इन अनुरोध पुन: उपयोग किया जा करने के लिए की सेवा के लिए इंतज़ार कर, जबकि अन्य आने वाले अनुरोधों की सेवा करने के लिए कोई कारण नहीं है टोकन पीढ़ी की प्रक्रिया को पूरा करने के लिए।
मूल रूप से, यह समझ में आता है कि मेरा 20% सेवा जारी रहेगा। यदि प्रतीक्षा धागे का उपयोग अन्य अनुरोधों के लिए नहीं किया जा रहा है, तो टोमकैट अनुरोध सेवा सीमा तक पहुंच जाएगी और सर्वर अनिवार्य रूप से चकित होगा, जो कुछ ऐसा होने के लिए वास्तव में उत्साहित नहीं है।

मैं क्या कोशिश की
शुरू में मैं बिल्ला NIO कनेक्टर का उपयोग करने जा यह काम करना होगा उम्मीद है। लेकिन this तुलना करने के बाद, मैं वास्तव में आशावादी नहीं था। फिर भी, मैंने अनुरोधों को 10 सेकंड के लिए इंतजार करने के लिए मजबूर कर दिया और यह काम नहीं किया।
अब मैं उन लाइनों पर विचार कर रहा हूं जिन्हें मुझे अनुरोध के दौरान अनुरोध को शेल्व करने की आवश्यकता है और टॉमकैट को सिग्नल करने की आवश्यकता है कि यह धागा पुन: उपयोग करने के लिए स्वतंत्र है। इसी तरह, अनुरोध को आगे बढ़ने के लिए तैयार होने पर मुझे अपने थ्रेडपूल से धागा देने के लिए टोमकैट की आवश्यकता होगी। लेकिन मैं इसे कैसे करना है या यहां तक ​​कि यदि यह संभव है तो मैं अंधेरा हूं।

कोई मार्गदर्शन या सहायता?

+0

आपने कहा "* टोकन पीढ़ी की प्रक्रिया के दौरान प्रति सेकंड आने वाले अनुरोधों में से लगभग 80% इंतजार करने की आवश्यकता होगी। *", यह सभी के लिए स्पष्ट नहीं है कि इन 80% आने वाले अनुरोध आपके आवेदन के आने वाले अनुरोध हैं या अनुरोध है कि आपने टोकन पीढ़ी के लिए तीसरे पक्ष के सिस्टम को भेजा है। मुझे लगता है कि आपको अपने पूरे उत्तर में यह स्पष्ट करने की जरूरत है कि जब आप किस चीज के बारे में बात कर रहे हैं, क्योंकि जैसा कि मैंने कहा था कि यह सभी के लिए स्पष्ट नहीं हो सकता है, कृपया स्पष्ट करें और आपके पास समाधान प्राप्त करने की संभावना अधिक हो सकती है। – hagrawal

+0

@ हाग्रावल इनकमिंग अनुरोधों का 80% तीसरे पक्ष की प्रतीक्षा कर रहे हैं। –

उत्तर

6

आपको एक एसिंक्रोनस सर्वलेट की आवश्यकता है लेकिन आपको बाहरी टोकन जनरेटर को एसिंक्रोनस HTTP कॉल की भी आवश्यकता है। यदि आप अभी भी प्रति टोकन अनुरोध पर एक थ्रेड बनाते हैं तो आपको थ्रेड पूल के साथ सर्वलेट से एक्जिक्यूटर्स सर्विस पर अनुरोधों को पास करके कुछ हासिल नहीं होगा। आपको HTTP अनुरोधों से थ्रेड को कम करना होगा ताकि एक थ्रेड एकाधिक HTTP अनुरोधों को संभाल सके। यह अपाचे Asynch HttpClient या Async Http Client जैसे एसिंक्रोनस HTTP क्लाइंट के साथ हासिल किया जा सकता है।

सबसे पहले आप यह एक

public class ProxyService extends HttpServlet { 

    private CloseableHttpAsyncClient httpClient; 

    @Override 
    public void init() throws ServletException { 
     httpClient = HttpAsyncClients.custom(). 
       setMaxConnTotal(Integer.parseInt(getInitParameter("maxtotalconnections"))).    
       setMaxConnPerRoute(Integer.parseInt(getInitParameter("maxconnectionsperroute"))). 
       build(); 
     httpClient.start(); 
    } 

    @Override 
    public void destroy() { 
     try { 
      httpClient.close(); 
     } catch (IOException e) { } 
    } 

    @Override 
    public void doGet(HttpServletRequest request, HttpServletResponse response) { 
     AsyncContext asyncCtx = request.startAsync(request, response); 
     asyncCtx.setTimeout(ExternalServiceMock.TIMEOUT_SECONDS * ExternalServiceMock.K);  
     ResponseListener listener = new ResponseListener(); 
     asyncCtx.addListener(listener); 
     Future<String> result = httpClient.execute(HttpAsyncMethods.createGet(getInitParameter("serviceurl")), new ResponseConsumer(asyncCtx), null); 
    } 

} 

यह सर्वलेट अपाचे Asynch HttpClient का उपयोग कर एक अतुल्यकालिक HTTP कॉल करता है की तरह एक अतुल्यकालिक सर्वलेट बनाना होगा। ध्यान दें कि आप प्रति मार्ग अधिकतम कनेक्शन कॉन्फ़िगर करना चाहते हैं क्योंकि आरएफसी 2616 spec के अनुसार HttpAsyncClient केवल डिफ़ॉल्ट रूप से एक ही मेजबान के अधिकतम दो समवर्ती कनेक्शनों को अनुमति देगा। और ऐसे कई अन्य विकल्प हैं जिन्हें आप HttpAsyncClient configuration में दिखाए गए अनुसार कॉन्फ़िगर कर सकते हैं। HttpAsyncClient बनाने के लिए महंगा है, इसलिए आप प्रत्येक जीईटी ऑपरेशन पर इसका एक इंस्टालेशन नहीं बनाना चाहते हैं।

एक श्रोता AsyncContext से जुड़ा हुआ है, यह श्रोता केवल टाइमआउट को संभालने के लिए ऊपर दिए गए उदाहरण में उपयोग किया जाता है।

public class ResponseListener implements AsyncListener { 

    @Override 
    public void onStartAsync(AsyncEvent event) throws IOException { 
    } 

    @Override 
    public void onComplete(AsyncEvent event) throws IOException { 
    } 

    @Override 
    public void onError(AsyncEvent event) throws IOException { 
     event.getAsyncContext().getResponse().getWriter().print("error:"); 
    } 

    @Override 
    public void onTimeout(AsyncEvent event) throws IOException { 
     event.getAsyncContext().getResponse().getWriter().print("timeout:"); 
    } 

} 

फिर आपको HTTP क्लाइंट के लिए उपभोक्ता की आवश्यकता है। यह उपभोक्ता complete() पर कॉल करके AsyncContext को सूचित करता है जब buildResult() को कॉलर ProxyService सर्वलेट पर Future<String> वापस करने के चरण के रूप में HttpClient द्वारा आंतरिक रूप से निष्पादित किया जाता है।

public class ResponseConsumer extends AsyncCharConsumer<String> { 

    private int responseCode; 
    private StringBuilder responseBuffer; 
    private AsyncContext asyncCtx; 

    public ResponseConsumer(AsyncContext asyncCtx) { 
     this.responseBuffer = new StringBuilder(); 
     this.asyncCtx = asyncCtx; 
    } 

    @Override 
    protected void releaseResources() { } 

    @Override 
    protected String buildResult(final HttpContext context) { 
     try { 
      PrintWriter responseWriter = asyncCtx.getResponse().getWriter(); 
      switch (responseCode) { 
       case javax.servlet.http.HttpServletResponse.SC_OK: 
        responseWriter.print("success:" + responseBuffer.toString()); 
        break; 
       default: 
        responseWriter.print("error:" + responseBuffer.toString()); 
       } 
     } catch (IOException e) { } 
     asyncCtx.complete();   
     return responseBuffer.toString(); 
    } 

    @Override 
    protected void onCharReceived(CharBuffer buffer, IOControl ioc) throws IOException { 
     while (buffer.hasRemaining()) 
      responseBuffer.append(buffer.get()); 
    } 

    @Override 
    protected void onResponseReceived(HttpResponse response) throws HttpException, IOException {   
     responseCode = response.getStatusLine().getStatusCode(); 
    } 

} 

ProxyService सर्वलेट के लिए web.xml विन्यास की तरह

<?xml version="1.0" encoding="UTF-8"?> 
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
     id="WebApp_ID" version="3.0" metadata-complete="true"> 
    <display-name>asyncservlet-demo</display-name> 

    <servlet> 
    <servlet-name>External Service Mock</servlet-name> 
    <servlet-class>ExternalServiceMock</servlet-class> 
    <load-on-startup>1</load-on-startup> 
    </servlet> 

    <servlet> 
    <servlet-name>Proxy Service</servlet-name> 
    <servlet-class>ProxyService</servlet-class> 
    <load-on-startup>1</load-on-startup> 
    <async-supported>true</async-supported> 
    <init-param> 
     <param-name>maxtotalconnections</param-name> 
     <param-value>200</param-value> 
    </init-param> 
    <init-param> 
     <param-name>maxconnectionsperroute</param-name> 
     <param-value>4</param-value> 
    </init-param> 
    <init-param> 
     <param-name>serviceurl</param-name> 
     <param-value>http://127.0.0.1:8080/asyncservlet/externalservicemock</param-value> 
    </init-param> 
    </servlet> 

    <servlet-mapping> 
    <servlet-name>External Service Mock</servlet-name> 
    <url-pattern>/externalservicemock</url-pattern> 
    </servlet-mapping> 

    <servlet-mapping> 
    <servlet-name>Proxy Service</servlet-name> 
    <url-pattern>/proxyservice</url-pattern> 
    </servlet-mapping> 

</web-app> 

और सेकंड में देरी से टोकन जनरेटर के लिए एक नकली सर्वलेट हो सकता है हो सकता है:

public class ExternalServiceMock extends HttpServlet{ 

    public static final int TIMEOUT_SECONDS = 13; 
    public static final long K = 1000l; 

    public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 
     Random rnd = new Random(); 
     try { 
      Thread.sleep(rnd.nextInt(TIMEOUT_SECONDS) * K); 
     } catch (InterruptedException e) { } 
     final byte[] token = String.format("%10d", Math.abs(rnd.nextLong())).getBytes(ISO_8859_1); 
     response.setContentType("text/plain"); 
     response.setCharacterEncoding(ISO_8859_1.name()); 
     response.setContentLength(token.length); 
     response.getOutputStream().write(token); 
    } 

} 

आप कर सकते हैं fully working example at GitHub प्राप्त करें।

1

यह समस्या अनिवार्य रूप से कारण है कि कई "प्रतिक्रियाशील" पुस्तकालय और टूलकिट मौजूद हैं।

यह कोई समस्या नहीं है जिसे टॉमकैट कनेक्टर को ट्वीक या स्वैप करके हल किया जा सकता है।
आपको मूल रूप से सभी अवरुद्ध आईओ कॉल को हटाने की आवश्यकता है, उन्हें गैर-अवरुद्ध आईओ के साथ बदलने के लिए आवेदन के बड़े हिस्सों को फिर से लिखने की आवश्यकता होगी।
आपके HTTP सर्वर को गैर-अवरुद्ध करने की आवश्यकता है, आपको सर्वर पर एक गैर-अवरुद्ध API का उपयोग करने की आवश्यकता है (जैसे servlet 3.1) और तृतीय पक्ष API को आपकी कॉल को अवरुद्ध करने की आवश्यकता है।
वर्ट.एक्स और आरएक्सजेवा जैसे पुस्तकालय इस सब के साथ मदद करने के लिए टूलिंग प्रदान करते हैं।

अन्यथा केवल एकमात्र अन्य विकल्प केवल थ्रेडपूल के आकार को बढ़ाने के लिए है, ऑपरेटिंग सिस्टम पहले ही सीपीयू को शेड्यूल करने का ख्याल रखता है ताकि निष्क्रिय धागे बहुत अधिक प्रदर्शन हानि न करें, लेकिन हमेशा होने वाला है एक प्रतिक्रियाशील दृष्टिकोण की तुलना में अधिक ओवरहेड।

अपने आवेदन के बारे में और जानने के बिना किसी विशिष्ट दृष्टिकोण पर सलाह देना मुश्किल है।

+0

खैर मुझे लगता है कि प्रश्न नीचे उबलता है: 'टॉमकैट ने थ्रेड का पुन: उपयोग शुरू करने के लिए क्या स्थितियों के लिए, और मैं ऐसी स्थिति कैसे उत्पन्न कर सकता हूं?'। RxJava अभी तक कोशिश नहीं की है लेकिन नमूना कोड चलाने के बाद अद्यतन होगा। –

+0

टॉमकैट थ्रेड का पुन: उपयोग करता है जब थ्रेड का नियंत्रण उस पर वापस आ जाता है, आपके मामले में थ्रेड को आपके नेटवर्क कॉल में कहीं भी अवरुद्ध किया जाता है, जो तृतीय पक्ष API पर जाता है। बेशक आपको उस कॉल के परिणाम के लिए ग्राहक को अपनी प्रतिक्रिया उत्पन्न करने की प्रतीक्षा करनी होगी। प्रतिक्रिया के लिए प्रतीक्षा करने के बजाय एक प्रतिक्रियाशील दृष्टिकोण है, आप प्रतिक्रिया उत्पन्न करने के लिए एक कॉलबैक फ़ंक्शन सेट अप करते हैं। इस तरह आप तुरंत थ्रेड के नियंत्रण को टॉमकैट पर वापस कर सकते हैं। – Magnus

0

एसिंक सर्वलेट अनुरोध या प्रतिक्रियाशील पुस्तकालयों का उपयोग करना (जैसा कि अन्य उत्तरों में उल्लिखित है) मदद कर सकता है लेकिन प्रमुख वास्तुशिल्प परिवर्तनों की मांग करेगा।

एक और विकल्प टोकन उपयोगों से टोकन अपडेट को अलग करना है।

public class TokenHolder { 
    public static volatile Token token = null; 
    private static Timer timer = new Timer(true); 
    static { 
     // set the token 1st time 
     TokenHolder.token = getNewToken(); 

     // schedule updates periodically 
     timer.schedule(new TimerTask(){ 
      public void run() { 
       TokenHolder.token = getNewToken(); 
      } 
     }, 10000, 10000); 
    } 
} 

अब आपके अनुरोधों सिर्फ TokenHolder.token उपयोग कर सकते हैं सेवा का उपयोग करने:

यहाँ एक अनुभवहीन कार्यान्वयन है।

वास्तविक एप्लिकेशन में आप शायद अधिक उन्नत शेड्यूलिंग टूल का उपयोग करेंगे।

संबंधित मुद्दे