เล่นสเกล! ถึงคำขอพร้อมกันนับพัน

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

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

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

การเพิกเฉยต่อความสามารถในการปรับขนาดไม่ได้เลวร้ายอย่างที่คิด—หากคุณใช้เครื่องมือที่เหมาะสม

โลจินฮากับการเล่น! กรอบ

ไม่นานมานี้ ฉันเริ่มโครงการที่ชื่อว่า Lojinha (ซึ่งแปลว่า “ร้านค้าเล็กๆ” ในภาษาโปรตุเกส) ความพยายามของฉันที่จะสร้างเว็บไซต์ประมูล (อีกอย่าง โครงการนี้เป็นโอเพ่นซอร์ส) แรงจูงใจของฉันมีดังนี้:

  • อยากขายของเก่าที่ไม่ได้ใช้แล้ว
  • ฉันไม่ชอบไซต์ประมูลแบบดั้งเดิม โดยเฉพาะไซต์ที่เรามีในบราซิล
  • ฉันต้องการ "เล่น" กับ Play! กรอบที่ 2 (ปุนตั้งใจ)

เห็นได้ชัดว่าฉันตัดสินใจใช้ Play! กรอบ. ฉันไม่นับระยะเวลาที่แน่นอนในการสร้าง แต่ไม่นานฉันก็จะมีเว็บไซต์และทำงานด้วยระบบง่ายๆ ที่ http://lojinha.jcranky.com อันที่จริง ฉันใช้เวลาอย่างน้อยครึ่งหนึ่งในการพัฒนาการออกแบบ ซึ่งใช้ Twitter Bootstrap (จำไว้ว่า: ฉันไม่ใช่นักออกแบบ…)

ย่อหน้าด้านบนควรทำให้ชัดเจนอย่างน้อยหนึ่งอย่าง: ฉันไม่ได้กังวลเรื่องประสิทธิภาพมากเกินไป ถ้าฉันสร้าง Lojinha เลย

และนั่นคือจุดสำคัญของฉัน: มีพลังในการใช้เครื่องมือที่เหมาะสม เครื่องมือที่ช่วยให้คุณถูกทาง เครื่องมือที่ส่งเสริมให้คุณปฏิบัติตามแนวทางการพัฒนาที่ดีที่สุดโดยการสร้างของพวกเขาเอง

ในกรณีนี้ เครื่องมือเหล่านั้นคือ Play! เฟรมเวิร์กและภาษาสกาล่า โดยอัคก้าทำ "แขกรับเชิญ"

ให้ฉันแสดงให้คุณเห็นว่าฉันหมายถึงอะไร

ไม่เปลี่ยนรูปและแคช

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

การเล่น! เฟรมเวิร์กของ Scala ทำให้คุณใช้การไม่เปลี่ยนรูปในช่วงเวลาที่ดี และภาษา Scala ก็เช่นกัน ตัวอย่างเช่น ผลลัพธ์ที่สร้างโดยคอนโทรลเลอร์จะไม่เปลี่ยนแปลง บางครั้งคุณอาจคิดว่าความไม่เปลี่ยนรูปนี้ “น่ารำคาญ” หรือ “น่ารำคาญ” แต่ “แนวปฏิบัติที่ดี” เหล่านี้ “ดี” ด้วยเหตุผล

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

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

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

สำหรับการอ้างอิง นี่คือรหัส Scala สำหรับการโหลดหน้าเริ่มต้นพร้อมรายการผลิตภัณฑ์โดยไม่ต้องแคช:

 def index = Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) }

ตอนนี้เพิ่มแคช:

 def index = Cached("index", 5) { Action { implicit request => Ok(html.index(body = html.body(Items.itemsHigherBids(itemDAO.all(false))), menu = mainMenu)) } }

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

หลังจากการแคช ปริมาณงานเพิ่มขึ้นถึง 800 คำขอต่อวินาที นั่นคือการปรับปรุงมากกว่า 4 เท่าสำหรับโค้ดน้อยกว่าสองบรรทัด

เพื่อทดสอบผลกระทบของการเปลี่ยนแปลงนี้ ฉันได้รันการทดสอบ JMeter (รวมอยู่ใน repo GitHub) ในเครื่อง ก่อนเพิ่มแคช ฉันได้รับปริมาณงานประมาณ 180 คำขอต่อวินาที หลังจากการแคช ปริมาณงานเพิ่มขึ้นถึง 800 คำขอต่อวินาที นั่นคือการปรับปรุงมากกว่า 4 เท่า สำหรับโค้ดน้อยกว่าสองบรรทัด

นี่คือวิธีที่ฉันใช้ Play! แคชเพื่อปรับปรุงประสิทธิภาพบนเว็บไซต์ประมูล Scala ของฉัน

การใช้หน่วยความจำ

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

 session.setAttribute("attrName", attrValue);

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

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

เล่น! ช่วยให้คุณถูกทาง บังคับให้คุณต้องพิจารณาการใช้หน่วยความจำของคุณอย่างรอบคอบ ซึ่งจะสร้างรหัสผ่านแรกซึ่งพร้อมสำหรับคลัสเตอร์ในทางปฏิบัติ

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

การสนับสนุน Async

ต่อไปในละครเรื่องนี้! ทบทวนกรอบงาน เราจะมาดูกันว่า Play! ยังส่องแสงในการสนับสนุน async(hronous) และนอกเหนือจากคุณสมบัติดั้งเดิมของมันแล้ว เล่น! ให้คุณฝัง Akka ซึ่งเป็นเครื่องมืออันทรงพลังสำหรับการประมวลผลแบบอะซิงโครนัส

Altough Lojinha ยังไม่ได้ใช้ประโยชน์จาก Akka อย่างเต็มที่ การรวมเข้ากับ Play! ทำให้มันง่ายมากที่จะ:

  1. กำหนดเวลาบริการอีเมลแบบอะซิงโครนัส
  2. ดำเนินการเสนอผลิตภัณฑ์ต่างๆ พร้อมกัน

โดยสังเขป Akka คือการดำเนินการของ Actor Model ที่ Erlang โด่งดัง หากคุณไม่คุ้นเคยกับ Akka Actor Model ลองนึกภาพว่าเป็นหน่วยเล็ก ๆ ที่สื่อสารผ่านข้อความเท่านั้น

ในการส่งอีเมลแบบอะซิงโครนัส อันดับแรก ฉันต้องสร้างข้อความและนักแสดงที่เหมาะสม สิ่งที่ฉันต้องทำคือ:

 EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ Async ดั้งเดิมของ Play! โปรดดูเอกสารอย่างเป็นทางการ

บทสรุป

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

เมื่อพัฒนาแอปพลิเคชันครั้งต่อไป ให้พิจารณาเครื่องมือของคุณอย่างรอบคอบ

ที่เกี่ยวข้อง: ลดรหัส Boilerplate ด้วย Scala Macros และ Quasiquotes