Concurrency และ Fault Tolerance ทำได้ง่าย: บทช่วยสอน Akka พร้อมตัวอย่าง
เผยแพร่แล้ว: 2022-03-11ความท้าทาย
การเขียนโปรแกรมพร้อมกันนั้นยาก การที่ต้องจัดการกับเธรด การล็อก สภาพการแข่งขัน และอื่นๆ มักเกิดข้อผิดพลาดได้ง่าย และอาจนำไปสู่โค้ดที่อ่าน ทดสอบ และบำรุงรักษาได้ยาก
หลายคนจึงชอบหลีกเลี่ยงการทำมัลติเธรดทั้งหมด แต่ใช้กระบวนการแบบเธรดเดียวโดยเฉพาะ โดยอาศัยบริการภายนอก (เช่น ฐานข้อมูล คิว ฯลฯ) เพื่อจัดการการดำเนินการพร้อมกันหรือแบบอะซิงโครนัสที่จำเป็น แม้ว่าวิธีการนี้จะเป็นทางเลือกที่ถูกต้องในบางกรณี แต่ก็มีหลายสถานการณ์ที่ไม่สามารถทำได้ ระบบเรียลไทม์จำนวนมาก เช่น แอปพลิเคชันการซื้อขายหรือธนาคาร หรือเกมแบบเรียลไทม์ ไม่จำเป็นต้องรอให้กระบวนการแบบเธรดเดียวเสร็จสมบูรณ์ (พวกเขาต้องการคำตอบทันที!) ระบบอื่นๆ มีการประมวลผลหรือใช้ทรัพยากรมากจนต้องใช้เวลามากเกินไป (เป็นชั่วโมงหรือเป็นวันในบางกรณี) เพื่อรันโดยไม่ทำให้เกิดการทำให้เป็นคู่ขนานในโค้ด
วิธีการแบบเธรดเดียวที่ใช้กันทั่วไป (เช่น ใช้กันอย่างแพร่หลายในโลก Node.js) คือการใช้กระบวนทัศน์ที่ไม่บล็อกตามเหตุการณ์ แม้ว่าสิ่งนี้จะช่วยประสิทธิภาพการทำงานโดยการหลีกเลี่ยงการสลับบริบท การล็อก และการบล็อก แต่ก็ยังไม่สามารถแก้ไขปัญหาของการใช้โปรเซสเซอร์หลายตัวพร้อมกันได้ (การทำเช่นนั้นจะต้องมีการเปิดใช้และประสานงานระหว่างกระบวนการอิสระหลายๆ
นี่หมายความว่าคุณไม่มีทางเลือกอื่นนอกจากต้องเดินทางลึกเข้าไปในส่วนลึกของเกลียว ล็อค และสภาพการแข่งขันเพื่อสร้างแอปพลิเคชันพร้อมกันใช่หรือไม่
ขอบคุณเฟรมเวิร์กของ Akka คำตอบคือไม่ บทช่วยสอนนี้จะแนะนำตัวอย่าง Akka และสำรวจวิธีการอำนวยความสะดวกและลดความยุ่งยากในการปรับใช้แอปพลิเคชันแบบกระจายพร้อมกัน
Akka Framework คืออะไร?
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 วิธีเดียวที่จะสื่อสารกับนักแสดงคือผ่าน ActorRef
ActorRef
แสดงถึงการอ้างอิงถึงนักแสดงที่ขัดขวางอ็อบเจ็กต์อื่น ๆ จากการเข้าถึงหรือจัดการภายในและสถานะของนักแสดงโดยตรง ข้อความอาจถูกส่งไปยังนักแสดงผ่าน ActorRef
โดยใช้หนึ่งในโปรโตคอลไวยากรณ์ต่อไปนี้:
-
!
(“บอก”) – ส่งข้อความและส่งคืนทันที -
?
(“ถาม”) – ส่งข้อความและส่งคืนอนาคตที่แสดงถึงการตอบกลับที่เป็นไปได้
นักแสดงแต่ละคนมีกล่องจดหมายที่ส่งข้อความเข้ามา มีการใช้งานกล่องจดหมายหลายแบบให้เลือก โดยมีการใช้งานเริ่มต้นเป็น FIFO
นักแสดงประกอบด้วยตัวแปรอินสแตนซ์จำนวนมากเพื่อรักษาสถานะในขณะที่ประมวลผลหลายข้อความ Akka ทำให้แน่ใจว่าแต่ละอินสแตนซ์ของนักแสดงทำงานในเธรดที่มีน้ำหนักเบาของตัวเอง และข้อความจะได้รับการประมวลผลทีละรายการ ด้วยวิธีนี้ สถานะของนักแสดงแต่ละคนสามารถรักษาไว้ได้อย่างน่าเชื่อถือโดยที่นักพัฒนาไม่จำเป็นต้องกังวลอย่างชัดเจนเกี่ยวกับการซิงโครไนซ์หรือสภาวะการแข่งขัน
นักแสดงแต่ละคนจะได้รับข้อมูลที่เป็นประโยชน์ต่อไปนี้สำหรับการปฏิบัติงานผ่าน Akka Actor API:
-
sender
: นักแสดงผู้ActorRef
ถึงผู้ส่งข้อความที่กำลังดำเนินการอยู่ -
context
: ข้อมูลและวิธีการที่เกี่ยวข้องกับบริบทที่ตัวแสดงกำลังดำเนินการอยู่ (รวมถึง ตัวอย่างactorOf
วิธีการของตัวแสดงเพื่อสร้างตัวอย่างตัวแสดงใหม่) -
supervisionStrategy
Strategy : กำหนดกลยุทธ์ที่จะใช้สำหรับการกู้คืนจากข้อผิดพลาด -
self
: theActorRef
สำหรับตัวนักแสดงเอง
เพื่อช่วยเชื่อมโยงบทช่วยสอนเหล่านี้เข้าด้วยกัน ให้ลองพิจารณาตัวอย่างง่ายๆ ของการนับจำนวนคำในไฟล์ข้อความ
สำหรับจุดประสงค์ของตัวอย่าง 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 กลไกเดียวสำหรับการสื่อสารระหว่างนักแสดงคือการส่งข้อความ ข้อความเป็นสิ่งเดียวที่นักแสดงแบ่งปัน และเนื่องจากนักแสดงสามารถเข้าถึงข้อความเดียวกันได้พร้อมกัน จึงเป็นสิ่งสำคัญสำหรับพวกเขาที่จะไม่เปลี่ยนรูป เพื่อหลีกเลี่ยงสภาพการแข่งขันและพฤติกรรมที่ไม่คาดคิด
ดังนั้นจึงเป็นเรื่องปกติที่จะส่งข้อความในรูปแบบของคลาสเคส เนื่องจากข้อความจะไม่เปลี่ยนตามค่าเริ่มต้น และเนื่องจากการผสานรวมเข้ากับการจับคู่รูปแบบได้อย่างราบรื่น
มาสรุปตัวอย่างด้วยตัวอย่างโค้ดเพื่อเรียกใช้แอปทั้งหมด
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 วิธีที่ผู้บังคับบัญชาตอบสนองและจัดการกับข้อยกเว้นที่กระจายไปถึงตัวมันจากลูกๆ ของเขาเรียกว่าเป็นกลยุทธ์ของผู้บังคับบัญชา กลยุทธ์หัวหน้างานเป็นกลไกหลักและตรงไปตรงมาโดยที่คุณกำหนดพฤติกรรมการทนต่อข้อผิดพลาดของระบบของคุณ
เมื่อข้อความที่แสดงถึงความล้มเหลวส่งถึงหัวหน้า สามารถดำเนินการอย่างใดอย่างหนึ่งต่อไปนี้:
- ให้เด็กกลับมาทำงานต่อ (และลูกๆ ของเด็ก) โดยคงสถานะภายในไว้ กลยุทธ์นี้สามารถใช้ได้เมื่อสถานะย่อยไม่ได้รับความเสียหายจากข้อผิดพลาด และสามารถทำงานต่อไปได้อย่างถูกต้อง
- รีสตาร์ทเด็ก (และลูกของมัน) ล้างสถานะภายใน กลยุทธ์นี้สามารถใช้ในสถานการณ์ตรงข้ามกับสถานการณ์ที่เพิ่งอธิบายไป หากสถานะย่อยได้รับความเสียหายจากข้อผิดพลาด จำเป็นต้องรีเซ็ตสถานะก่อนจึงจะสามารถใช้งานได้ในอนาคต
- หยุดเด็ก (และลูก ๆ ของมัน) อย่างถาวร กลยุทธ์นี้สามารถใช้ได้ในกรณีที่เชื่อว่าเงื่อนไขข้อผิดพลาดไม่สามารถแก้ไขได้ แต่ไม่เป็นอันตรายต่อการดำเนินการที่เหลือ ซึ่งสามารถทำได้โดยที่ไม่มีเด็กที่ล้มเหลว
- หยุดตัวเองและเพิ่มข้อผิดพลาด จ้างเมื่อผู้บังคับบัญชาไม่ทราบวิธีจัดการกับความล้มเหลวจึงส่งต่อไปยังหัวหน้างานของตนเอง
ยิ่งไปกว่านั้น นักแสดงสามารถตัดสินใจใช้การกระทำนี้กับเด็กที่ล้มเหลวหรือพี่น้องเท่านั้น มีสองกลยุทธ์ที่กำหนดไว้ล่วงหน้าสำหรับสิ่งนี้:
-
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 ต้องการเพียงการมีอยู่ของไฟล์การกำหนดค่า ( 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 อาจเป็นคำตอบที่ถูกต้องสำหรับโครงการของคุณหรือไม่