คำขอการจัดส่ง spray.routing.HttpService เป็นอย่างไร

ข้อจำกัดความรับผิดชอบ: ตอนนี้ฉันไม่มีประสบการณ์สกาล่า ดังนั้นคำถามของฉันจึงเกี่ยวข้องกับพื้นฐานมาก

พิจารณาตัวอย่างต่อไปนี้ (อาจไม่สมบูรณ์):

import akka.actor.{ActorSystem, Props}
import akka.io.IO
import spray.can.Http
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import akka.actor.Actor
import spray.routing._
import spray.http._

object Boot extends App {
  implicit val system = ActorSystem("my-actor-system")
  val service = system.actorOf(Props[MyActor], "my")
  implicit val timeout = Timeout(5.seconds)
  IO(Http) ? Http.Bind(service, interface = "localhost", port = 8080)
}

class MyActor extends Actor with MyService {
  def actorRefFactory = context

  def receive = runRoute(myRoute)
}

trait MyService extends HttpService {
  val myRoute =
    path("my") {
      post {
        complete {
          "PONG"
        }
      }
    }
}

คำถามของฉันคือ: จะเกิดอะไรขึ้นเมื่อการควบคุมถึงบล็อก complete ดูเหมือนว่าคำถามจะกว้างเกินไป ดังนั้นให้ฉันแยกมันออกไป

  1. ฉันเห็นการสร้างนักแสดงคนเดียวในตัวอย่างนี้ หมายความว่าแอปพลิเคชันเป็นแบบเธรดเดียวและใช้ซีพียูคอร์เดียวเท่านั้นใช่หรือไม่
  2. จะเกิดอะไรขึ้นถ้าฉันบล็อกการโทรภายใน complete?
  3. ถ้าหน้า 1 เป็นจริงและ p 2 จะบล็อก ฉันจะส่งคำขอเพื่อใช้ CPU ทั้งหมดได้อย่างไร ฉันเห็นสองวิธี: นักแสดงต่อคำขอ และนักแสดงต่อการเชื่อมต่อ อันที่สองดูเหมือนจะสมเหตุสมผล แต่ฉันไม่สามารถหาวิธีทำได้โดยใช้ไลบรารี่สเปรย์
  4. หากคำถามก่อนหน้าไม่เกี่ยวข้อง detach คำสั่งจะทำหรือไม่ แล้วการส่งฟังก์ชันส่งกลับ Future ไปที่ complete directive ล่ะ? อะไรคือความแตกต่างระหว่างฟังก์ชันแยกและการส่งผ่านที่ส่งคืนอนาคต?
  5. วิธีที่เหมาะสมในการกำหนดค่าจำนวนเธรดการทำงานและคำขอ/การเชื่อมต่อที่สมดุลคืออะไร?

คงจะดีไม่น้อยหากคุณชี้คำอธิบายให้ฉันทราบในเอกสารอย่างเป็นทางการ มันกว้างขวางมากและฉันเชื่อว่าฉันขาดอะไรบางอย่างไป

ขอบคุณ


person Unforgiven    schedule 16.08.2015    source แหล่งที่มา


คำตอบ (1)


คำตอบนี้ที่นี่โดย Mathias หนึ่งในผู้เขียน Spray . คัดลอกคำตอบของเขาเพื่อใช้อ้างอิง:

ท้ายที่สุดสิ่งเดียวที่ทำให้คำขอเสร็จสมบูรณ์ได้จริงๆ คือการเรียกไปที่ requestContext.complete ดังนั้นจึงไม่สำคัญว่าการโทรนี้จะเกิดขึ้นจากเธรดหรือบริบทของนักแสดงคนใด สิ่งสำคัญคือมันจะเกิดขึ้นภายในระยะเวลา "การร้องขอ-หมดเวลา" ที่กำหนดค่าไว้ แน่นอนว่าคุณสามารถเรียกสิ่งนี้ด้วยตัวเองไม่ทางใดก็ทางหนึ่ง แต่สเปรย์จะให้โครงสร้างที่กำหนดไว้ล่วงหน้าจำนวนหนึ่งซึ่งอาจเหมาะกับสถาปัตยกรรมของคุณดีกว่าการส่ง RequestContext จริงไปรอบๆ ส่วนใหญ่ได้แก่:

  1. คำสั่ง complete ซึ่งเพียงแค่ใส่น้ำตาลไว้ด้านบนของฟังก์ชันลิเทอรัล "raw" ctx => ctx.complete(…)
  2. Future Marshaller ซึ่งเรียก ctx.complete จากตัวจัดการ future.onComplete
  3. คำสั่ง produce ซึ่งจะแยกฟังก์ชัน T => Unit ที่สามารถนำมาใช้ในภายหลังเพื่อดำเนินการตามคำขอด้วยอินสแตนซ์ประเภทที่กำหนดเองได้

ในกรณีส่วนใหญ่ในทางสถาปัตยกรรม เป็นความคิดที่ดีที่จะไม่ให้เลเยอร์ API "รั่วไหลเข้าสู่" แกนหลักของแอปพลิเคชันของคุณ เช่น. แอปพลิเคชันไม่ควรรู้อะไรเกี่ยวกับเลเยอร์ API หรือ HTTP ควรจัดการกับออบเจ็กต์ของโมเดลโดเมนของตัวเองเท่านั้น ดังนั้นการส่ง RequestContext ไปยังแกนแอปพลิเคชันโดยตรงจึงไม่ใช่วิธีที่ดีที่สุด

การใช้ "ถาม" และการพึ่งพา Future Marshaller เป็นทางเลือกที่ชัดเจน เข้าใจดี และค่อนข้างง่าย มันมาพร้อมกับข้อเสียเปรียบ (เล็กน้อย) ที่การถามมาพร้อมกับการตรวจสอบการหมดเวลาบังคับซึ่งไม่จำเป็นในเชิงตรรกะ (เนื่องจากชั้นสเปรย์สามารถดูแลการหมดเวลาของคำขออยู่แล้ว) จำเป็นต้องมีการหมดเวลาในการถามด้วยเหตุผลทางเทคนิค (ดังนั้น PromiseActorRef ที่ซ่อนอยู่จึงสามารถล้างข้อมูลได้หากไม่มีการตอบกลับที่คาดหวัง)

อีกทางเลือกหนึ่งในการส่ง RequestContext ก็คือคำสั่ง produce (เช่น produce(instanceOf[Foo]) { completer => …) แยกฟังก์ชันที่คุณสามารถส่งต่อไปยังแกนแอปพลิเคชันได้ เมื่อตรรกะหลักของคุณเรียก complete(foo) ตรรกะการดำเนินการเสร็จสมบูรณ์จะถูกรันและคำขอเสร็จสมบูรณ์ ด้วยเหตุนี้ แกนแอปพลิเคชันจึงยังคงแยกออกจากเลเยอร์ API และมีค่าใช้จ่ายเพียงเล็กน้อย ข้อเสียของแนวทางนี้มีสองเท่า ประการแรก ฟังก์ชันตัวสมบูรณ์ไม่สามารถทำให้เป็นอนุกรมได้ ดังนั้นคุณจึงไม่สามารถใช้วิธีนี้ข้ามขอบเขต JVM ได้ และประการที่สอง ตรรกะการทำให้เสร็จสมบูรณ์กำลังทำงานโดยตรงในบริบทนักแสดงของแกนแอปพลิเคชัน ซึ่งอาจเปลี่ยนพฤติกรรมรันไทม์ในลักษณะที่ไม่พึงประสงค์ หาก Marshaller[Foo] ต้องทำงานที่ไม่สำคัญ

ทางเลือกที่สามคือการวางไข่นักแสดงตามคำขอในเลเยอร์ API และให้จัดการการตอบสนองที่กลับมาจากแกนแอปพลิเคชัน จากนั้นคุณไม่จำเป็นต้องใช้การถาม ถึงกระนั้น คุณก็จบลงด้วยปัญหาเดียวกันกับที่ PromiseActorRef อ้างอิงการถาม: จะล้างข้อมูลอย่างไรหากไม่มีการตอบสนองกลับมาจากแกนกลางของแอปพลิเคชัน ด้วยนักแสดงที่ส่งคำขอซ้ำ คุณมีอิสระเต็มที่ในการปรับใช้วิธีแก้ปัญหาสำหรับคำถามนี้ อย่างไรก็ตาม หากคุณตัดสินใจที่จะใช้การหมดเวลา (เช่น ผ่าน context.setReceiveTimeout) ประโยชน์ที่ได้รับจาก "ask" อาจไม่มีอยู่จริง

โซลูชันใดที่อธิบายไว้เหมาะกับสถาปัตยกรรมของคุณมากที่สุด คุณต้องตัดสินใจด้วยตัวเอง อย่างไรก็ตาม ตามที่ฉันหวังว่าจะสามารถแสดงได้ คุณมีทางเลือกสองทางให้เลือก

เพื่อตอบคำถามเฉพาะบางข้อของคุณ: มีเพียงนักแสดง/ตัวจัดการเพียงคนเดียวที่ให้บริการเส้นทาง ดังนั้นหากคุณทำให้มันบล็อก สเปรย์ก็จะบล็อก ซึ่งหมายความว่าคุณต้องการดำเนินการตามเส้นทางให้เสร็จสิ้นทันทีหรือจัดส่งงานโดยใช้ตัวเลือกใดตัวเลือกหนึ่งจาก 3 ตัวเลือกข้างต้น

มีตัวอย่างมากมายบนเว็บสำหรับ 3 ตัวเลือกนี้ วิธีที่ง่ายที่สุดคือการล้อมโค้ดของคุณด้วย Future ตรวจสอบตัวเลือก/ตัวอย่าง "นักแสดงต่อคำขอ" ด้วย ในที่สุดสถาปัตยกรรมของคุณจะเป็นตัวกำหนดวิธีที่เหมาะสมที่สุด

สุดท้าย Spray ทำงานบน Akka ดังนั้นการกำหนดค่า Akka ทั้งหมดจึงยังคงมีผลอยู่ ดู HOCON reference.conf และ application.conf สำหรับการตั้งค่าเธรดนักแสดง

person yǝsʞǝla    schedule 17.08.2015