scala - कसभ - विधानसभा चुनाव 2019




अक्का HTTP: भविष्य में ब्लॉक करना सर्वर को ब्लॉक करता है (2)

अजीब है, लेकिन मेरे लिए सब कुछ ठीक काम करता है (कोई अवरुद्ध नहीं)। यहाँ कोड है:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import akka.stream.ActorMaterializer

import scala.concurrent.Future


object Main {

  implicit val system = ActorSystem()
  implicit val executor = system.dispatcher
  implicit val materializer = ActorMaterializer()

  val routes: Route = (post & entity(as[String])) { e =>
    complete {
      Future {
        Thread.sleep(5000)
        e
      }
    }
  } ~
    (get & path(Segment)) { r =>
      complete {
        "get"
      }
    }

  def main(args: Array[String]) {

    Http().bindAndHandle(routes, "0.0.0.0", 9000).onFailure {
      case e =>
        system.shutdown()
    }
  }
}

इसके अलावा, आप async कोड को onComplete या onSuccess निर्देश में लपेट सकते हैं:

onComplete(Future{Thread.sleep(5000)}){e} 

onSuccess(Future{Thread.sleep(5000)}){complete(e)}

मैं अपने अनुरोध को मूल रूप से प्रमाणित करने के लिए अक्का HTTP का उपयोग करने का प्रयास कर रहा हूं। ऐसा होता है कि मेरे पास प्रमाणित करने के लिए एक बाहरी संसाधन है, इसलिए मुझे इस संसाधन के लिए एक आराम कॉल करना होगा।

यह कुछ समय लेता है, और जब यह प्रसंस्करण होता है, तो ऐसा लगता है कि मेरे बाकी एपीआई अवरुद्ध हैं, इस कॉल की प्रतीक्षा कर रहे हैं। मैंने इसे बहुत सरल उदाहरण के साथ पुन: प्रस्तुत किया है:

// used dispatcher:
implicit val system = ActorSystem()
implicit val executor = system.dispatcher
implicit val materializer = ActorMaterializer()


val routes = 
  (post & entity(as[String])) { e =>
    complete {
      Future{
        Thread.sleep(5000)
        e
      }
    }
  } ~
  (get & path(Segment)) { r =>
    complete {
      "get"
    }
  }

अगर मैं लॉग एंडपॉइंट पर पोस्ट करता हूं, तो मेरा गेट एंडपॉइंट भी 5 सेकंड के इंतजार में अटक जाता है, जिसे लॉग एंड पॉइंट तय करता है।

क्या यह अपेक्षित व्यवहार है, और यदि है, तो मैं अपने पूरे एपीआई को अवरुद्ध किए बिना अवरुद्ध संचालन कैसे करूं?


आप जो व्यवहार करते हैं वह अपेक्षित व्यवहार है - फिर भी निश्चित रूप से यह बहुत बुरा है। अच्छा है कि ज्ञात समाधान और सर्वोत्तम प्रथाएं इसके खिलाफ रक्षा के लिए मौजूद हैं। इस उत्तर में, मैं इस मुद्दे को छोटा, लंबा और फिर गहराई से समझाने के लिए कुछ समय बिताना चाहता हूँ - पढ़ने का आनंद लें!

संक्षिप्त उत्तर : " रूटिंग इन्फ्रास्ट्रक्चर को ब्लॉक न करें! ", हमेशा ब्लॉकिंग ऑपरेशन के लिए एक समर्पित डिस्पैचर का उपयोग करें।

देखे गए लक्षण के कारण: समस्या यह है कि आप context.dispatcher का उपयोग कर रहे हैं। context.dispatcher भेजने वाले के रूप में डिस्पैचर को अवरुद्ध वायदा पर निष्पादित करें। एक ही डिस्पैचर (जो साधारण शब्दों में "थ्रेड्स का एक गुच्छा है") का उपयोग राउटिंग इन्फ्रास्ट्रक्चर द्वारा वास्तव में आने वाले अनुरोधों को संभालने के लिए किया जाता है - इसलिए यदि आप सभी उपलब्ध थ्रेड्स को ब्लॉक करते हैं, तो आप रूटिंग इन्फ्रास्ट्रक्चर को भूखा रखते हैं। (बहस और बेंचमार्किंग के लिए एक बात यह है कि अगर अक्का HTTP इससे बचा सकता है, तो मैं इसे अपने शोध टूडू सूची में जोड़ दूंगा)।

ब्लॉकिंग को विशेष देखभाल के साथ एक ही डिस्पैचर के अन्य उपयोगकर्ताओं को प्रभावित नहीं करने के लिए इलाज किया जाना चाहिए (यही कारण है कि हम इसे अलग-अलग लोगों पर अलग-अलग निष्पादन के लिए सरल बनाते हैं), जैसा कि अक्का डॉक्स अनुभाग में समझाया गया है: ब्लॉकिंग को सावधानीपूर्वक प्रबंधन की आवश्यकता है

कुछ और जो मैं यहां ध्यान दिलाना चाहता था वह यह है कि यदि संभव हो तो एपीआई को अवरुद्ध करने से बचना चाहिए - यदि आपका लंबे समय तक चलने वाला ऑपरेशन वास्तव में एक ऑपरेशन नहीं है, लेकिन एक श्रृंखला है, तो आप उन्हें अलग-अलग अभिनेताओं, या अनुक्रमित वायदा पर अलग कर सकते थे। वैसे भी, केवल इंगित करना चाहता था - यदि संभव हो, तो इस तरह की अवरुद्ध कॉल से बचें, फिर भी यदि आपको - करना है, तो निम्नलिखित बताते हैं कि उन लोगों के साथ ठीक से कैसे व्यवहार करें।

गहराई से विश्लेषण और समाधान :

अब जब हम जानते हैं कि गलत क्या है, तो वैचारिक रूप से, आइए एक नजर डालते हैं कि उपरोक्त कोड में वास्तव में क्या टूटा हुआ है, और इस समस्या का सही समाधान कैसा दिखता है:

रंग = धागा राज्य:

  • फ़िरोज़ा - सो रहा है
  • नारंगी - प्रतीक्षा
  • हरा - जंगम

अब कोड के 3 टुकड़ों और डिस्पैचर्स पर प्रभाव और ऐप के प्रदर्शन की जांच करते हैं। इस व्यवहार को लागू करने के लिए ऐप को निम्नलिखित लोड के तहत रखा गया है:

  • [ए] जीईटी अनुरोधों का अनुरोध करते रहें (इसके लिए प्रारंभिक प्रश्न में उपरोक्त कोड देखें), यह वहां अवरुद्ध नहीं है
  • [ख] फिर २००० की आग के बाद २००० POST अनुरोध, जो भविष्य में लौटने से पहले ५second अवरोध का कारण बनेगा

1) [bad] खराब कोड पर डिस्पैचर व्यवहार :

// BAD! (due to the blocking in Future):
implicit val defaultDispatcher = system.dispatcher

val routes: Route = post { 
  complete {
    Future { // uses defaultDispatcher
      Thread.sleep(5000)                    // will block on the default dispatcher,
      System.currentTimeMillis().toString   // starving the routing infra
    }
  }
}

इसलिए हम अपने ऐप को [a] लोड के लिए एक्सपोज़ करते हैं, और आप पहले से ही akka.actor.default-dispatcher थ्रेड्स की एक संख्या देख सकते हैं - वे अनुरोधों को संभाल रहे हैं - छोटे हरे स्निपेट, और नारंगी का अर्थ है कि अन्य वास्तव में निष्क्रिय हैं।

फिर हम [b] लोड शुरू करते हैं, जो इन थ्रेड्स को ब्लॉक करने का कारण बनता है - आप एक शुरुआती थ्रेड "डिफ़ॉल्ट-डिस्पैचर-2,3,4" देख सकते हैं जो पहले निष्क्रिय होने के बाद ब्लॉकिंग में जा रहा है। हम यह भी देखते हैं कि पूल बढ़ता है - नए धागे "डिफ़ॉल्ट-डिस्पैचर -18,19,20,21 ..." शुरू हो जाते हैं, हालांकि वे तुरंत सो जाते हैं! () - हम यहां अनमोल संसाधन बर्बाद कर रहे हैं!

ऐसे शुरू किए गए थ्रेड्स की संख्या डिफ़ॉल्ट डिस्पैचर कॉन्फ़िगरेशन पर निर्भर करती है, लेकिन संभावना 50 या उससे अधिक नहीं होगी। चूँकि हमने सिर्फ 2k ब्लॉकिंग ऑप्स को निकाल दिया, हम पूरे थ्रेडपूल को भूखा रखते हैं - ब्लॉकिंग ऑपरेशन्स ऐसे हावी हैं कि राउटिंग इन्फ्रा के पास अन्य अनुरोधों को संभालने के लिए कोई धागा उपलब्ध नहीं है - बहुत खराब!

आइए इसके बारे में कुछ करें (जो कि अक्का बेस्ट प्रैक्टिस btw है - हमेशा नीचे दिखाए गए तरह अवरुद्ध व्यवहार को अलग करें):

2) [good!] डिस्पैचर व्यवहार अच्छा संरचित कोड / प्रेषण :

अपने application.conf ब्लॉकिंग व्यवहार के लिए समर्पित इस डिस्पैचर को कॉन्फ़िगर करें:

my-blocking-dispatcher {
  type = Dispatcher
  executor = "thread-pool-executor"
  thread-pool-executor {
    // in Akka previous to 2.4.2:
    core-pool-size-min = 16
    core-pool-size-max = 16
    max-pool-size-min = 16
    max-pool-size-max = 16
    // or in Akka 2.4.2+
    fixed-pool-size = 16
  }
  throughput = 100
}

यहां विभिन्न विकल्पों को समझने के लिए आपको अक्का डिस्पैचर प्रलेखन में अधिक पढ़ना चाहिए। हालांकि मुख्य बिंदु यह है कि हमने एक ThreadPoolExecutor चुना है जिसमें थ्रेड्स की एक कठिन सीमा है जो इसे अवरुद्ध ऑप्स के लिए उपलब्ध रखता है। आकार सेटिंग इस बात पर निर्भर करती है कि आपका ऐप क्या करता है, और आपके सर्वर में कितने कोर हैं।

अगला हमें डिफ़ॉल्ट के बजाय इसका उपयोग करने की आवश्यकता है:

// GOOD (due to the blocking in Future):
implicit val blockingDispatcher = system.dispatchers.lookup("my-blocking-dispatcher")

val routes: Route = post { 
  complete {
    Future { // uses the good "blocking dispatcher" that we configured, 
             // instead of the default dispatcher – the blocking is isolated.
      Thread.sleep(5000)
      System.currentTimeMillis().toString
    }
  }
}

हम एक ही लोड का उपयोग करके ऐप पर दबाव डालते हैं, पहले थोड़ा सामान्य अनुरोध करते हैं और फिर हम ब्लॉकिंग को जोड़ते हैं। इस प्रकार थ्रेडपूल इस मामले में व्यवहार करेगा:

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

अब जब हम ब्लॉकिंग ऑप्स जारी करना शुरू करते हैं, तो my-blocking-dispatcher-* किक करता है और कॉन्फ़िगर किए गए थ्रेड्स की संख्या तक शुरू होता है। यह वहां सभी स्लीपिंग को संभालता है। इसके अलावा, उन धागों पर कुछ नहीं होने की निश्चित अवधि के बाद, यह उन्हें बंद कर देता है। यदि हम सर्वर को हिट करने के लिए थे, तो पूल को अवरुद्ध करने के एक और झुंड के साथ नए धागे शुरू होंगे जो नींद का ख्याल रखेंगे () - उन्हें इंग करें, लेकिन इस बीच - हम अपने कीमती धागे को बर्बाद नहीं कर रहे हैं "बस वहीं रहें और" कुछ मत करो"।

इस सेटअप का उपयोग करते समय, सामान्य GET अनुरोधों के थ्रूपुट को प्रभावित नहीं किया गया था, वे अभी भी (अभी भी बहुत मुक्त) डिफ़ॉल्ट डिस्पैचर पर खुशी से सेवा कर रहे थे।

यह प्रतिक्रियाशील अनुप्रयोगों में किसी भी प्रकार के अवरोध से निपटने का अनुशंसित तरीका है। इसे अक्सर "bulkheading" (या "अलग-थलग") के रूप में संदर्भित किया जाता है, जो किसी ऐप के खराब व्यवहार वाले भागों में होता है, इस मामले में बुरा व्यवहार नींद / अवरुद्ध होता है।

3) [workaround-ish] डिस्पैचर व्यवहार blocking ठीक से blocking पर :

इस उदाहरण में हम स्कैलाडोक का उपयोग scala.concurrent.blocking विधि के लिए scala.concurrent.blocking जो अवरुद्ध ऑप्स के साथ सामना करने में मदद कर सकता है। यह आमतौर पर अधिक थ्रेड्स को अवरुद्ध संचालन से बचने के लिए थूकने का कारण बनता है।

// OK, default dispatcher but we'll use `blocking`
implicit val dispatcher = system.dispatcher

val routes: Route = post { 
  complete {
    Future { // uses the default dispatcher (it's a Fork-Join Pool)
      blocking { // will cause much more threads to be spun-up, avoiding starvation somewhat, 
                 // but at the cost of exploding the number of threads (which eventually
                 // may also lead to starvation problems, but on a different layer)
        Thread.sleep(5000)
        System.currentTimeMillis().toString
       }
    }
  }
}

एप्लिकेशन इस तरह व्यवहार करेगा:

आप देखेंगे कि नए थ्रेड्स का एक बहुत बनाया जाता है, इसका कारण यह है कि "ओह पर संकेत अवरुद्ध करना, यह अवरुद्ध हो जाएगा, इसलिए हमें अधिक धागे की आवश्यकता है"। इसका कारण यह है कि कुल समय हम 1 से छोटा है) उदाहरण के लिए, हालांकि तब हमारे पास सैकड़ों धागे हैं जो अवरुद्ध ऑप्स के समाप्त होने के बाद कुछ भी नहीं कर रहे हैं ... निश्चित रूप से, वे अंततः बंद हो जाएंगे (एफजेपी ऐसा करता है ), लेकिन थोड़ी देर के लिए हमारे पास 2) समाधान के विपरीत चलने वाले थ्रेड्स की एक बड़ी (अनियंत्रित) मात्रा होगी, जहां हमें पता है कि हम अवरुद्ध व्यवहार के लिए कितने थ्रेड समर्पित कर रहे हैं।

सारांश : डिफ़ॉल्ट डिस्पैचर को कभी भी ब्लॉक न करें :-)

सबसे अच्छा अभ्यास 2) में दिखाए गए पैटर्न का उपयोग करना है 2) , उपलब्ध संचालन के लिए एक डिस्पैचर रखना और उन्हें वहां निष्पादित करना है।

आशा है कि यह मदद करता है, खुश हॉकिंग !

अक्का HTTP वर्जन पर चर्चा : 2.0.1

Profiler का उपयोग किया: कई लोगों ने मुझे इस उत्तर के जवाब में निजी तौर पर पूछा कि ऊपर के चित्रों में थ्रेड स्टेट्स की कल्पना करने के लिए मैंने किस प्रोफाइलर का उपयोग किया है, इसलिए इस जानकारी को यहां जोड़ा गया: मैंने YourKit उपयोग किया जो एक भयानक वाणिज्यिक प्रोफाइलर (OSS के लिए मुफ्त) है, हालांकि आप OpenJDK से मुक्त VisualVM का उपयोग करके समान परिणाम प्राप्त कर सकते हैं।







akka-http