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

การใช้หน่วยความจำ
อีกด้านที่เครื่องมือ Scala ที่เหมาะสมสามารถสร้างความแตกต่างได้มากคือการใช้หน่วยความจำ ที่นี่อีกครั้ง เล่น! ผลักคุณไปในทิศทางที่ถูกต้อง (ปรับขนาดได้) ในโลกของ Java สำหรับเว็บแอปพลิเคชัน "ปกติ" ที่เขียนด้วยเซิร์ฟเล็ต API (เช่น เกือบทุกเฟรมเวิร์กของ Java หรือ Scala) เป็นเรื่องที่น่าดึงดูดใจมากที่จะใส่ขยะจำนวนมากในเซสชันผู้ใช้เพราะ API เสนอให้ง่ายต่อการ- วิธีการโทรที่อนุญาตให้คุณทำ:
session.setAttribute("attrName", attrValue);
เนื่องจากการเพิ่มข้อมูลในเซสชันผู้ใช้ทำได้ง่ายมาก จึงมักถูกใช้ในทางที่ผิด เป็นผลให้ความเสี่ยงในการใช้หน่วยความจำมากเกินไปโดยไม่มีเหตุผลที่ดีก็สูงเท่ากัน
ด้วยการเล่น! เฟรมเวิร์ก นี่ไม่ใช่ตัวเลือก—เฟรมเวิร์กนั้นไม่มีพื้นที่เซสชันฝั่งเซิร์ฟเวอร์ การเล่น! เซสชันผู้ใช้เฟรมเวิร์กถูกเก็บไว้ในคุกกี้ของเบราว์เซอร์ และคุณต้องอยู่กับมัน ซึ่งหมายความว่าพื้นที่เซสชันถูกจำกัดในขนาดและประเภท: คุณสามารถเก็บสตริงเท่านั้น หากคุณต้องการเก็บอ็อบเจ็กต์ คุณจะต้องใช้กลไกการแคชที่เราพูดถึงก่อนหน้านี้ ตัวอย่างเช่น คุณอาจต้องการเก็บที่อยู่อีเมลหรือชื่อผู้ใช้ของผู้ใช้ปัจจุบันในเซสชัน แต่คุณจะต้องใช้แคช หากคุณต้องการเก็บวัตถุผู้ใช้ทั้งหมดจากโมเดลโดเมนของคุณ
อีกครั้ง นี้อาจดูเหมือนเจ็บปวดในตอนแรก แต่ในความเป็นจริง เล่น! ช่วยให้คุณถูกทาง บังคับให้คุณต้องพิจารณาการใช้หน่วยความจำของคุณอย่างรอบคอบ ซึ่งสร้างรหัสรหัสผ่านแรกซึ่งพร้อมสำหรับคลัสเตอร์จริง โดยเฉพาะอย่างยิ่งเนื่องจากไม่มีเซสชันฝั่งเซิร์ฟเวอร์ที่จะต้องเผยแพร่ทั่วทั้งคลัสเตอร์ของคุณ ทำให้ชีวิต ง่ายขึ้นอย่างไม่สิ้นสุด
การสนับสนุน Async
ต่อไปในละครเรื่องนี้! ทบทวนกรอบงาน เราจะมาดูกันว่า Play! ยังส่องแสงในการสนับสนุน async(hronous) และนอกเหนือจากคุณสมบัติดั้งเดิมของมันแล้ว เล่น! ให้คุณฝัง Akka ซึ่งเป็นเครื่องมืออันทรงพลังสำหรับการประมวลผลแบบอะซิงโครนัส
Altough Lojinha ยังไม่ได้ใช้ประโยชน์จาก Akka อย่างเต็มที่ การรวมเข้ากับ Play! ทำให้มันง่ายมากที่จะ:
- กำหนดเวลาบริการอีเมลแบบอะซิงโครนัส
- ดำเนินการเสนอผลิตภัณฑ์ต่างๆ พร้อมกัน
โดยสังเขป Akka คือการดำเนินการของ Actor Model ที่ Erlang โด่งดัง หากคุณไม่คุ้นเคยกับ Akka Actor Model ลองนึกภาพว่าเป็นหน่วยเล็ก ๆ ที่สื่อสารผ่านข้อความเท่านั้น
ในการส่งอีเมลแบบอะซิงโครนัส อันดับแรก ฉันต้องสร้างข้อความและนักแสดงที่เหมาะสม สิ่งที่ฉันต้องทำคือ:
EMail.actor ! BidToppedMessage(item.name, itemUrl, bidderEmail)
ตรรกะการส่งอีเมลถูกนำมาใช้ในตัวนักแสดง และข้อความจะบอกนักแสดงว่าเราต้องการส่งอีเมลใด สิ่งนี้ทำในรูปแบบไฟไหม้และลืม หมายความว่าบรรทัดด้านบนส่งคำขอแล้วดำเนินการต่อไปสิ่งที่เรามีหลังจากนั้น (กล่าวคือ มันไม่ได้บล็อก)
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ Async ดั้งเดิมของ Play! โปรดดูเอกสารอย่างเป็นทางการ
บทสรุป
โดยสรุป: ฉันพัฒนาแอปพลิเคชั่นขนาดเล็กอย่างรวดเร็ว Lojinha ซึ่งสามารถขยายและขยายได้ดีมาก เมื่อฉันพบปัญหาหรือพบปัญหาคอขวด การแก้ไขนั้นรวดเร็วและง่ายดาย โดยได้รับเครดิตมากมายจากเครื่องมือที่ฉันใช้ (เล่น!, สกาลา, อัคคา และอื่นๆ) ซึ่งผลักดันให้ฉันปฏิบัติตามแนวทางปฏิบัติที่ดีที่สุดในแง่ของประสิทธิภาพและ ความสามารถในการขยายขนาด ด้วยความกังวลเกี่ยวกับประสิทธิภาพเพียงเล็กน้อย ฉันสามารถปรับขนาดเป็นคำขอพร้อมกันนับพันรายการ
เมื่อพัฒนาแอปพลิเคชันครั้งต่อไป ให้พิจารณาเครื่องมือของคุณอย่างรอบคอบ