Concurrency และ Fault Tolerance ทำได้ง่าย: บทช่วยสอน Akka พร้อมตัวอย่าง

เผยแพร่แล้ว: 2022-03-11

ความท้าทาย

การเขียนโปรแกรมพร้อมกันนั้นยาก การที่ต้องจัดการกับเธรด การล็อก สภาพการแข่งขัน และอื่นๆ มักเกิดข้อผิดพลาดได้ง่าย และอาจนำไปสู่โค้ดที่อ่าน ทดสอบ และบำรุงรักษาได้ยาก

หลายคนจึงชอบหลีกเลี่ยงการทำมัลติเธรดทั้งหมด แต่ใช้กระบวนการแบบเธรดเดียวโดยเฉพาะ โดยอาศัยบริการภายนอก (เช่น ฐานข้อมูล คิว ฯลฯ) เพื่อจัดการการดำเนินการพร้อมกันหรือแบบอะซิงโครนัสที่จำเป็น แม้ว่าวิธีการนี้จะเป็นทางเลือกที่ถูกต้องในบางกรณี แต่ก็มีหลายสถานการณ์ที่ไม่สามารถทำได้ ระบบเรียลไทม์จำนวนมาก เช่น แอปพลิเคชันการซื้อขายหรือธนาคาร หรือเกมแบบเรียลไทม์ ไม่จำเป็นต้องรอให้กระบวนการแบบเธรดเดียวเสร็จสมบูรณ์ (พวกเขาต้องการคำตอบทันที!) ระบบอื่นๆ มีการประมวลผลหรือใช้ทรัพยากรมากจนต้องใช้เวลามากเกินไป (เป็นชั่วโมงหรือเป็นวันในบางกรณี) เพื่อรันโดยไม่ทำให้เกิดการทำให้เป็นคู่ขนานในโค้ด

วิธีการแบบเธรดเดียวที่ใช้กันทั่วไป (เช่น ใช้กันอย่างแพร่หลายในโลก Node.js) คือการใช้กระบวนทัศน์ที่ไม่บล็อกตามเหตุการณ์ แม้ว่าสิ่งนี้จะช่วยประสิทธิภาพการทำงานโดยการหลีกเลี่ยงการสลับบริบท การล็อก และการบล็อก แต่ก็ยังไม่สามารถแก้ไขปัญหาของการใช้โปรเซสเซอร์หลายตัวพร้อมกันได้ (การทำเช่นนั้นจะต้องมีการเปิดใช้และประสานงานระหว่างกระบวนการอิสระหลายๆ

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

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

Akka Framework คืออะไร?

โพสต์นี้จะแนะนำ Akka และสำรวจวิธีการอำนวยความสะดวกและลดความยุ่งยากในการใช้งานแอปพลิเคชันแบบกระจายพร้อมกัน

Akka เป็นชุดเครื่องมือและรันไทม์สำหรับการสร้างแอปพลิเคชันที่มีการทำงานพร้อมกัน กระจาย และทนต่อข้อผิดพลาดบน JVM Akka เขียนด้วยภาษา Scala โดยมีการผูกภาษาสำหรับทั้ง Scala และ Java

แนวทางของ Akka ในการจัดการภาวะพร้อมกันนั้นขึ้นอยู่กับตัวแบบนักแสดง ในระบบที่อิงตามนักแสดง ทุกอย่างคือนักแสดง ในลักษณะเดียวกับที่ทุกอย่างเป็นวัตถุในการออกแบบเชิงวัตถุ ความแตกต่างที่สำคัญ แม้ว่า – โดยเฉพาะอย่างยิ่งที่เกี่ยวข้องกับการสนทนาของเราคือ – โมเดลนักแสดงได้รับการออกแบบและออกแบบมาโดยเฉพาะเพื่อใช้เป็นแบบจำลองที่เกิดขึ้นพร้อมกันในขณะที่แบบจำลองเชิงวัตถุไม่ใช่ โดยเฉพาะอย่างยิ่ง ในระบบนักแสดงของสกาลา นักแสดงโต้ตอบและแบ่งปันข้อมูล โดยไม่มีข้อสันนิษฐานใด ๆ เกี่ยวกับลำดับ กลไกที่นักแสดงแบ่งปันข้อมูลระหว่างกันและงานซึ่งกันและกันคือการส่งข้อความ

ความซับซ้อนทั้งหมดในการสร้างและกำหนดเวลาเธรด การรับและส่งข้อความ การจัดการสภาพการแข่งขันและการซิงโครไนซ์ ถูกลดระดับลงในเฟรมเวิร์กเพื่อจัดการอย่างโปร่งใส

อัคคาสร้างเลเยอร์ระหว่างนักแสดงและระบบพื้นฐานที่นักแสดงเพียงแค่ต้องประมวลผลข้อความ ความซับซ้อนทั้งหมดในการสร้างและกำหนดเวลาเธรด การรับและส่งข้อความ การจัดการสภาพการแข่งขันและการซิงโครไนซ์ ถูกลดระดับลงในเฟรมเวิร์กเพื่อจัดการอย่างโปร่งใส

Akka ปฏิบัติตามคำประกาศปฏิกิริยาอย่างเคร่งครัด แอปพลิเคชันแบบโต้ตอบมีจุดมุ่งหมายเพื่อแทนที่แอปพลิเคชันแบบมัลติเธรดแบบเดิมด้วยสถาปัตยกรรมที่ตรงตามข้อกำหนดต่อไปนี้อย่างน้อยหนึ่งข้อ:

  • ขับเคลื่อนด้วยเหตุการณ์ เมื่อใช้ Actors เราสามารถเขียนโค้ดที่จัดการคำขอแบบอะซิงโครนัสและใช้การดำเนินการที่ไม่ปิดกั้นเท่านั้น
  • ปรับขนาดได้ ใน Akka คุณสามารถเพิ่มโหนดโดยไม่ต้องแก้ไขโค้ดได้ ต้องขอบคุณทั้งการส่งข้อความและความโปร่งใสของตำแหน่ง
  • ยืดหยุ่น. แอปพลิเคชันใด ๆ จะพบข้อผิดพลาดและล้มเหลวในบางช่วงเวลา Akka จัดให้มีกลยุทธ์ "การกำกับดูแล" (ความทนทานต่อข้อผิดพลาด) เพื่ออำนวยความสะดวกให้กับระบบการรักษาตนเอง
  • ตอบสนอง แอปพลิเคชั่นประสิทธิภาพสูงและตอบสนองอย่างรวดเร็วในปัจจุบันจำนวนมากจำเป็นต้องให้คำติชมแก่ผู้ใช้อย่างรวดเร็ว ดังนั้นจึงจำเป็นต้องตอบสนองต่อเหตุการณ์อย่างทันท่วงที กลยุทธ์การส่งข้อความแบบไม่บล็อกของ Akka ช่วยให้บรรลุเป้าหมายนี้

นักแสดงใน Akka คืออะไร?

โดยพื้นฐานแล้วนักแสดงไม่มีอะไรมากไปกว่าวัตถุที่ได้รับข้อความและดำเนินการเพื่อจัดการกับพวกเขา มันแยกออกจากแหล่งที่มาของข้อความและความรับผิดชอบเพียงอย่างเดียวของมันคือการรับรู้ประเภทของข้อความที่ได้รับอย่างถูกต้องและดำเนินการตามนั้น

เมื่อได้รับข้อความ นักแสดงอาจดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้:

  • ดำเนินการบางอย่างด้วยตนเอง (เช่น การคำนวณ การคงข้อมูล การเรียกใช้บริการเว็บภายนอก และอื่นๆ)
  • ส่งต่อข้อความหรือข้อความที่ได้รับไปยังนักแสดงคนอื่น
  • ยกตัวอย่างนักแสดงใหม่และส่งต่อข้อความไปยังมัน

อีกทางหนึ่ง นักแสดงอาจเลือกที่จะเพิกเฉยต่อข้อความทั้งหมด (เช่น อาจเลือกไม่ดำเนินการ) หากเห็นว่าเหมาะสมที่จะทำเช่นนั้น

ในการปรับใช้นักแสดง จำเป็นต้องขยายคุณสมบัติ akka.actor.Actor และใช้วิธีการรับ วิธีการรับของนักแสดงถูกเรียกใช้ (โดย Akka) เมื่อข้อความถูกส่งไปยังนักแสดงคนนั้น การใช้งานโดยทั่วไปประกอบด้วยการจับคู่รูปแบบ ดังแสดงในตัวอย่าง Akka ต่อไปนี้ เพื่อระบุประเภทข้อความและตอบสนองตามนั้น:

 import akka.actor.Actor import akka.actor.Props import akka.event.Logging class MyActor extends Actor { def receive = { case value: String => doSomething(value) case _ => println("received unknown message") } }

การจับคู่รูปแบบเป็นเทคนิคที่ค่อนข้างหรูหราสำหรับการจัดการข้อความ ซึ่งมีแนวโน้มที่จะสร้างโค้ดที่ "สะอาดกว่า" และง่ายต่อการนำทางมากกว่าการใช้งานที่เทียบเคียงกันได้โดยใช้การเรียกกลับ ตัวอย่างเช่น พิจารณาการใช้งานคำขอ/การตอบสนอง HTTP แบบง่าย

ขั้นแรก เรามาปรับใช้สิ่งนี้โดยใช้กระบวนทัศน์แบบเรียกกลับใน JavaScript:

 route(url, function(request){ var query = buildQuery(request); dbCall(query, function(dbResponse){ var wsRequest = buildWebServiceRequest(dbResponse); wsCall(wsRequest, function(wsResponse) { sendReply(wsResponse); }); }); });

ตอนนี้ เรามาเปรียบเทียบสิ่งนี้กับการใช้งานแบบจับคู่ตามรูปแบบ:

 msg match { case HttpRequest(request) => { val query = buildQuery(request) dbCall(query) } case DbResponse(dbResponse) => { var wsRequest = buildWebServiceRequest(dbResponse); wsCall(dbResponse) } case WsResponse(wsResponse) => sendReply(wsResponse) }

แม้ว่าโค้ด JavaScript ที่ใช้การโทรกลับจะมีขนาดกะทัดรัด แต่ก็อ่านและนำทางได้ยากกว่าอย่างแน่นอน ในการเปรียบเทียบ โค้ดที่ใช้การจับคู่รูปแบบทำให้เห็นได้ในทันทีว่ามีการพิจารณากรณีใดบ้างและแต่ละกรณีได้รับการจัดการอย่างไร

ระบบนักแสดง

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

ในการออกแบบตามนักแสดง การใช้เทคนิคนี้ช่วยอำนวยความสะดวกในการจัดระเบียบเชิงตรรกะของนักแสดงให้อยู่ในโครงสร้างแบบลำดับชั้นที่เรียกว่าระบบนักแสดง ระบบนักแสดงจัดเตรียมโครงสร้างพื้นฐานที่นักแสดงโต้ตอบกัน

ตัวอย่างวิธีการทำงานของระบบนักแสดงในกรอบงาน Akka

ใน Akka วิธีเดียวที่จะสื่อสารกับนักแสดงคือผ่าน ActorRef ActorRef แสดงถึงการอ้างอิงถึงนักแสดงที่ขัดขวางอ็อบเจ็กต์อื่น ๆ จากการเข้าถึงหรือจัดการภายในและสถานะของนักแสดงโดยตรง ข้อความอาจถูกส่งไปยังนักแสดงผ่าน ActorRef โดยใช้หนึ่งในโปรโตคอลไวยากรณ์ต่อไปนี้:

  • ! (“บอก”) – ส่งข้อความและส่งคืนทันที
  • ? (“ถาม”) – ส่งข้อความและส่งคืนอนาคตที่แสดงถึงการตอบกลับที่เป็นไปได้

นักแสดงแต่ละคนมีกล่องจดหมายที่ส่งข้อความเข้ามา มีการใช้งานกล่องจดหมายหลายแบบให้เลือก โดยมีการใช้งานเริ่มต้นเป็น FIFO

นักแสดงประกอบด้วยตัวแปรอินสแตนซ์จำนวนมากเพื่อรักษาสถานะในขณะที่ประมวลผลหลายข้อความ Akka ทำให้แน่ใจว่าแต่ละอินสแตนซ์ของนักแสดงทำงานในเธรดที่มีน้ำหนักเบาของตัวเอง และข้อความจะได้รับการประมวลผลทีละรายการ ด้วยวิธีนี้ สถานะของนักแสดงแต่ละคนสามารถรักษาไว้ได้อย่างน่าเชื่อถือโดยที่นักพัฒนาไม่จำเป็นต้องกังวลอย่างชัดเจนเกี่ยวกับการซิงโครไนซ์หรือสภาวะการแข่งขัน

นักแสดงแต่ละคนจะได้รับข้อมูลที่เป็นประโยชน์ต่อไปนี้สำหรับการปฏิบัติงานผ่าน Akka Actor API:

  • sender : นักแสดงผู้ ActorRef ถึงผู้ส่งข้อความที่กำลังดำเนินการอยู่
  • context : ข้อมูลและวิธีการที่เกี่ยวข้องกับบริบทที่ตัวแสดงกำลังดำเนินการอยู่ (รวมถึง ตัวอย่าง actorOf วิธีการของตัวแสดงเพื่อสร้างตัวอย่างตัวแสดงใหม่)
  • supervisionStrategy Strategy : กำหนดกลยุทธ์ที่จะใช้สำหรับการกู้คืนจากข้อผิดพลาด
  • self : the ActorRef สำหรับตัวนักแสดงเอง
Akka ทำให้แน่ใจว่าแต่ละอินสแตนซ์ของนักแสดงทำงานในเธรดที่มีน้ำหนักเบาของตัวเอง และข้อความจะได้รับการประมวลผลทีละรายการ ด้วยวิธีนี้ สถานะของนักแสดงแต่ละคนสามารถรักษาไว้ได้อย่างน่าเชื่อถือโดยที่นักพัฒนาไม่จำเป็นต้องกังวลอย่างชัดเจนเกี่ยวกับการซิงโครไนซ์หรือสภาวะการแข่งขัน

เพื่อช่วยเชื่อมโยงบทช่วยสอนเหล่านี้เข้าด้วยกัน ให้ลองพิจารณาตัวอย่างง่ายๆ ของการนับจำนวนคำในไฟล์ข้อความ

สำหรับจุดประสงค์ของตัวอย่าง Akka เราจะแยกปัญหาออกเป็นสองงานย่อย กล่าวคือ (1) งาน "ลูก" ในการนับจำนวนคำในบรรทัดเดียวและ (2) งาน "หลัก" ในการสรุปจำนวนคำต่อบรรทัดเพื่อให้ได้จำนวนคำทั้งหมดในไฟล์

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

(โปรดทราบว่าตัวอย่างโค้ดบทช่วยสอน Akka ที่ให้ไว้ด้านล่างนี้มีไว้สำหรับการสอนเท่านั้น ดังนั้นจึงไม่จำเป็นต้องกังวลเกี่ยวกับเงื่อนไขขอบทั้งหมด การเพิ่มประสิทธิภาพการทำงาน และอื่นๆ ทั้งหมด นอกจากนี้ เวอร์ชันตัวอย่างโค้ดที่คอมไพล์ได้ทั้งหมดที่แสดงด้านล่างยังมีอยู่ใน สาระสำคัญนี้.)

มาดูตัวอย่างการใช้งานคลาสลูก StringCounterActor กันก่อน:

 case class ProcessStringMsg(string: String) case class StringProcessedMsg(words: Integer) class StringCounterActor extends Actor { def receive = { case ProcessStringMsg(string) => { val wordsInLine = string.split(" ").length sender ! StringProcessedMsg(wordsInLine) } case _ => println("Error: message not recognized") } }

นักแสดงนี้มีงานที่ง่ายมาก: ใช้ข้อความ ProcessStringMsg (มีบรรทัดข้อความ) นับจำนวนคำในบรรทัดที่ระบุ และส่งคืนผลลัพธ์ไปยังผู้ส่งผ่านข้อความ StringProcessedMsg โปรดทราบว่าเราได้ดำเนินการคลาสของเราเพื่อใช้ ! วิธี (“tell”) เพื่อส่งข้อความ StringProcessedMsg (เช่น เพื่อส่งข้อความและส่งคืนทันที)

ตกลง ตอนนี้เราหันมาสนใจคลาส WordCounterActor หลัก:

 1. case class StartProcessFileMsg() 2. 3. class WordCounterActor(filename: String) extends Actor { 4. 5. private var running = false 6. private var totalLines = 0 7. private var linesProcessed = 0 8. private var result = 0 9. private var fileSender: Option[ActorRef] = None 10. 11. def receive = { 12. case StartProcessFileMsg() => { 13. if (running) { 14. // println just used for example purposes; 15. // Akka logger should be used instead 16. println("Warning: duplicate start message received") 17. } else { 18. running = true 19. fileSender = Some(sender) // save reference to process invoker 20. import scala.io.Source._ 21. fromFile(filename).getLines.foreach { line => 22. context.actorOf(Props[StringCounterActor]) ! ProcessStringMsg(line) 23. totalLines += 1 24. } 25. } 26. } 27. case StringProcessedMsg(words) => { 28. result += words 29. linesProcessed += 1 30. if (linesProcessed == totalLines) { 31. fileSender.map(_ ! result) // provide result to process invoker 32. } 33. } 34. case _ => println("message not recognized!") 35. } 36. }

มีหลายสิ่งหลายอย่างเกิดขึ้นที่นี่ ดังนั้น ให้ตรวจสอบแต่ละรายการโดยละเอียดมากขึ้น (โปรดทราบว่าหมายเลขบรรทัดที่อ้างอิงในการสนทนาที่ตามมานั้นอิงตามตัวอย่างโค้ดด้านบน)

ก่อนอื่น ให้สังเกตว่าชื่อไฟล์ที่จะประมวลผลถูกส่งไปยังตัวสร้าง WordCounterActor (บรรทัดที่ 3) สิ่งนี้บ่งชี้ว่านักแสดงใช้เพื่อประมวลผลไฟล์เดียวเท่านั้น นอกจากนี้ยังช่วยลดความยุ่งยากในงานเขียนโค้ดสำหรับนักพัฒนา โดยหลีกเลี่ยงความจำเป็นในการรีเซ็ตตัวแปรสถานะ ( running , totalLines , linesProcessed และ result ) เมื่องานเสร็จสิ้น เนื่องจากอินสแตนซ์ใช้เพียงครั้งเดียว (เช่น เพื่อประมวลผลไฟล์เดียว) แล้วทิ้ง

ต่อไป ให้สังเกตว่า WordCounterActor จัดการกับข้อความสองประเภท:

  • StartProcessFileMsg (บรรทัดที่ 12)
    • ได้รับจากนักแสดงภายนอกที่เริ่ม WordCounterActor ในขั้นต้น
    • เมื่อได้รับแล้ว WordCounterActor จะตรวจสอบก่อนว่าไม่ได้รับคำขอที่ซ้ำซ้อน
    • หากคำขอซ้ำซ้อน WordCounterActor จะสร้างคำเตือนและไม่มีอะไรทำอีก (บรรทัดที่ 16)
    • หากคำขอไม่ซ้ำซ้อน:
      • WordCounterActor เก็บข้อมูลอ้างอิงถึงผู้ส่งในตัวแปรอินสแตนซ์ fileSender (โปรดทราบว่านี่คือ Option[ActorRef] แทนที่จะเป็น Option[Actor] - ดูบรรทัดที่ 9) จำเป็นต้อง ActorRef นี้เพื่อเข้าถึงและตอบกลับในภายหลังเมื่อประมวลผล StringProcessedMsg สุดท้าย (ซึ่งได้รับจากลูกของ StringCounterActor ตามที่อธิบายไว้ด้านล่าง)
      • จากนั้น WordCounterActor จะอ่านไฟล์ และเมื่อมีการโหลดแต่ละบรรทัดในไฟล์ ระบบ StringCounterActor จะถูกสร้างขึ้นและข้อความที่มีบรรทัดที่จะประมวลผลจะถูกส่งต่อ (บรรทัดที่ 21-24)
  • StringProcessedMsg (บรรทัดที่ 27)
    • ได้รับจาก StringCounterActor ลูกเมื่อประมวลผลบรรทัดที่กำหนดเสร็จสิ้น
    • เมื่อได้รับ WordCounterActor จะเพิ่มตัวนับบรรทัดสำหรับไฟล์ และหากบรรทัดทั้งหมดในไฟล์ได้รับการประมวลผลแล้ว (เช่น เมื่อ totalLines และ linesProcessed เท่ากัน) จะส่งผลสุดท้ายไปยัง fileSender ดั้งเดิม (บรรทัดที่ 28-31)

สังเกตอีกครั้งว่าใน Akka กลไกเดียวสำหรับการสื่อสารระหว่างนักแสดงคือการส่งข้อความ ข้อความเป็นสิ่งเดียวที่นักแสดงแบ่งปัน และเนื่องจากนักแสดงสามารถเข้าถึงข้อความเดียวกันได้พร้อมกัน จึงเป็นสิ่งสำคัญสำหรับพวกเขาที่จะไม่เปลี่ยนรูป เพื่อหลีกเลี่ยงสภาพการแข่งขันและพฤติกรรมที่ไม่คาดคิด

คลาสเคสใน Scala เป็นคลาสปกติที่มีกลไกการสลายตัวแบบเรียกซ้ำผ่านการจับคู่รูปแบบ

ดังนั้นจึงเป็นเรื่องปกติที่จะส่งข้อความในรูปแบบของคลาสเคส เนื่องจากข้อความจะไม่เปลี่ยนตามค่าเริ่มต้น และเนื่องจากการผสานรวมเข้ากับการจับคู่รูปแบบได้อย่างราบรื่น

มาสรุปตัวอย่างด้วยตัวอย่างโค้ดเพื่อเรียกใช้แอปทั้งหมด

 object Sample extends App { import akka.util.Timeout import scala.concurrent.duration._ import akka.pattern.ask import akka.dispatch.ExecutionContexts._ implicit val ec = global override def main(args: Array[String]) { val system = ActorSystem("System") val actor = system.actorOf(Props(new WordCounterActor(args(0)))) implicit val timeout = Timeout(25 seconds) val future = actor ? StartProcessFileMsg() future.map { result => println("Total number of words " + result) system.shutdown } } }
ในการเขียนโปรแกรมพร้อมกัน "อนาคต" เป็นวัตถุตัวยึดตำแหน่งสำหรับผลลัพธ์ที่ยังไม่เป็นที่ทราบ

สังเกตว่าคราวนี้ ? วิธีที่ใช้ในการส่งข้อความ ด้วยวิธีนี้ ผู้โทรสามารถใช้ Future ที่ส่งคืนเพื่อพิมพ์ผลลัพธ์สุดท้ายเมื่อพร้อมใช้งาน และเพื่อออกจากโปรแกรมโดยปิด ActorSystem

กลยุทธ์การทนต่อความผิดพลาดและหัวหน้างานของ Akka

ในระบบนักแสดง นักแสดงแต่ละคนเป็นผู้ดูแลบุตรหลานของตน หากนักแสดงไม่สามารถจัดการกับข้อความได้ ผู้ดำเนินการจะระงับตัวเองและบุตรหลานทั้งหมดและส่งข้อความ ซึ่งมักจะอยู่ในรูปแบบของข้อยกเว้นไปยังหัวหน้างาน

ใน Akka กลยุทธ์ของผู้บังคับบัญชาเป็นกลไกหลักและตรงไปตรงมาในการกำหนดพฤติกรรมที่ทนต่อข้อผิดพลาดของระบบของคุณ

ใน Akka วิธีที่ผู้บังคับบัญชาตอบสนองและจัดการกับข้อยกเว้นที่กระจายไปถึงตัวมันจากลูกๆ ของเขาเรียกว่าเป็นกลยุทธ์ของผู้บังคับบัญชา กลยุทธ์หัวหน้างานเป็นกลไกหลักและตรงไปตรงมาโดยที่คุณกำหนดพฤติกรรมการทนต่อข้อผิดพลาดของระบบของคุณ

เมื่อข้อความที่แสดงถึงความล้มเหลวส่งถึงหัวหน้า สามารถดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้:

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

ยิ่งไปกว่านั้น นักแสดงสามารถตัดสินใจใช้การกระทำนี้กับเด็กที่ล้มเหลวหรือพี่น้องเท่านั้น มีสองกลยุทธ์ที่กำหนดไว้ล่วงหน้าสำหรับสิ่งนี้:

  • OneForOneStrategy : ใช้การดำเนินการที่ระบุกับลูกที่ล้มเหลวเท่านั้น
  • AllForOneStrategy : ใช้การดำเนินการที่ระบุกับรายการย่อยทั้งหมด

นี่เป็นตัวอย่างง่ายๆ โดยใช้ OneForOneStrategy :

 import akka.actor.OneForOneStrategy import akka.actor.SupervisorStrategy._ import scala.concurrent.duration._ override val supervisorStrategy = OneForOneStrategy() { case _: ArithmeticException => Resume case _: NullPointerException => Restart case _: IllegalArgumentException => Stop case _: Exception => Escalate }

หากไม่มีการระบุกลยุทธ์ จะใช้กลยุทธ์เริ่มต้นต่อไปนี้:

  • หากมีข้อผิดพลาดขณะเริ่มต้นนักแสดงหรือถ้านักแสดงถูกฆ่า นักแสดงจะหยุด
  • หากมีข้อยกเว้นประเภทอื่น นักแสดงก็จะเริ่มต้นใหม่

การนำกลยุทธ์เริ่มต้นนี้ไปใช้โดย Akka มีดังต่อไปนี้:

 final val defaultStrategy: SupervisorStrategy = { def defaultDecider: Decider = { case _: ActorInitializationException ⇒ Stop case _: ActorKilledException ⇒ Stop case _: Exception ⇒ Restart } OneForOneStrategy()(defaultDecider) }

Akka อนุญาตให้ใช้กลยุทธ์ของผู้บังคับบัญชาที่กำหนดเองได้ แต่ตามที่เอกสารของ Akka เตือน ให้ดำเนินการด้วยความระมัดระวังเนื่องจากการใช้งานที่ไม่ถูกต้องอาจนำไปสู่ปัญหาต่างๆ เช่น ระบบนักแสดงที่ถูกบล็อก (เช่น นักแสดงที่ถูกระงับอย่างถาวร)

ความโปร่งใสของตำแหน่ง

สถาปัตยกรรม Akka รองรับความโปร่งใสของตำแหน่ง ทำให้นักแสดงสามารถไม่เชื่อเรื่องพระเจ้าโดยสมบูรณ์ว่าข้อความที่ได้รับมาจากที่ใด ผู้ส่งข้อความอาจอยู่ใน JVM เดียวกันกับนักแสดงหรือใน JVM ที่แยกจากกัน (ไม่ว่าจะทำงานบนโหนดเดียวกันหรือโหนดอื่น) Akka ช่วยให้แต่ละกรณีเหล่านี้ได้รับการจัดการในลักษณะที่โปร่งใสอย่างสมบูรณ์ต่อนักแสดง (และนักพัฒนาซอฟต์แวร์) ข้อแม้เพียงอย่างเดียวคือข้อความที่ส่งผ่านหลายโหนดจะต้องทำให้เป็นอนุกรมได้

สถาปัตยกรรม Akka รองรับความโปร่งใสของตำแหน่ง ทำให้นักแสดงสามารถไม่เชื่อเรื่องพระเจ้าโดยสมบูรณ์ว่าข้อความที่ได้รับมาจากที่ใด

ระบบนักแสดงได้รับการออกแบบให้ทำงานในสภาพแวดล้อมแบบกระจายโดยไม่ต้องใช้รหัสพิเศษใดๆ Akka ต้องการเพียงการมีอยู่ของไฟล์การกำหนดค่า ( application.conf ) ที่ระบุโหนดเพื่อส่งข้อความถึง ต่อไปนี้คือตัวอย่างง่ายๆ ของไฟล์การกำหนดค่า:

 akka { actor { provider = "akka.remote.RemoteActorRefProvider" } remote { transport = "akka.remote.netty.NettyRemoteTransport" netty { hostname = "127.0.0.1" port = 2552 } } }

เคล็ดลับการจากลา…

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

  • ในขอบเขตสูงสุด นักแสดงแต่ละคนควรได้รับมอบหมายงานที่เล็กที่สุดเท่าที่จะเป็นไปได้ (ตามที่กล่าวไว้ก่อนหน้านี้ ตามหลักการความรับผิดชอบเดียว)
  • ผู้ดำเนินการควรจัดการกับเหตุการณ์ (เช่น ประมวลผลข้อความ) แบบอะซิงโครนัสและไม่ควรปิดกั้น มิฉะนั้น การสลับบริบทจะเกิดขึ้นซึ่งอาจส่งผลเสียต่อประสิทธิภาพการทำงาน โดยเฉพาะอย่างยิ่ง เป็นการดีที่สุดที่จะดำเนินการบล็อก (IO เป็นต้น) ในอนาคตเพื่อไม่ให้บล็อกนักแสดง เช่น:

     case evt => blockingCall() // BAD case evt => Future { blockingCall() // GOOD }
  • ตรวจสอบให้แน่ใจว่าข้อความของคุณไม่สามารถเปลี่ยนรูปแบบได้ เนื่องจากนักแสดงที่ส่งต่อกันจะทำงานพร้อมกันในชุดข้อความของตนเอง ข้อความที่ไม่แน่นอนมักส่งผลให้เกิดพฤติกรรมที่ไม่คาดคิด
  • เนื่องจากข้อความที่ส่งระหว่างโหนดจะต้องเป็นแบบซีเรียลไลซ์ได้ สิ่งสำคัญคือต้องจำไว้ว่ายิ่งข้อความมีขนาดใหญ่เท่าใด ก็จะใช้เวลาในการทำให้เป็นอนุกรม ส่ง และดีซีเรียลไลซ์ข้อความนานขึ้น ซึ่งอาจส่งผลเสียต่อประสิทธิภาพการทำงาน

บทสรุป

Akka เขียนด้วยภาษา Scala ช่วยลดความยุ่งยากและอำนวยความสะดวกในการพัฒนาแอปพลิเคชันที่ทำงานพร้อมกัน กระจาย และทนต่อข้อผิดพลาด โดยซ่อนความซับซ้อนส่วนใหญ่จากนักพัฒนา การแสดงความยุติธรรมอย่างเต็มรูปแบบของ Akka นั้นต้องการมากกว่าบทช่วยสอนเดี่ยวนี้ แต่หวังว่าการแนะนำนี้และตัวอย่างจะดึงดูดใจคุณมากพอที่จะทำให้คุณอยากอ่านเพิ่มเติม

Amazon, VMWare และ CSC เป็นเพียงตัวอย่างเล็กๆ น้อยๆ ของบริษัทชั้นนำที่ใช้ Akka อย่างจริงจัง เยี่ยมชมเว็บไซต์อย่างเป็นทางการของ Akka เพื่อเรียนรู้เพิ่มเติมและสำรวจว่า Akka อาจเป็นคำตอบที่ถูกต้องสำหรับโครงการของคุณหรือไม่