รูปแบบสถาปัตยกรรมที่สะดวกสบาย

เฮ้ ฮับ!

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

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

การปรับขนาดแนวนอน

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

เพื่อเป็นตัวอย่าง ฉันจะใช้พื้นที่จัดเก็บไฟล์บนคลาวด์แบบนามธรรม นั่นคือ แอปที่มีลักษณะคล้ายกับ OwnCloud, OneDrive และอื่นๆ

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

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

ซีคิวอาร์เอส

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

ประโยชน์ประการแรกของวิธีการนี้คือความสามารถในการตัดการเชื่อมต่อ (ในความหมายกว้างๆ ของคำ) ระหว่างการสืบค้นข้อมูลแบบยาว ยกตัวอย่างเช่น ลองพิจารณาลำดับมาตรฐานคร่าวๆ:

  1. ไคลเอนต์ส่งคำขอไปยังเซิร์ฟเวอร์
  2. เซิร์ฟเวอร์เริ่มประมวลผลเป็นเวลานาน
  3. เซิร์ฟเวอร์ตอบกลับไปยังไคลเอนต์ด้วยผลลัพธ์

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

  1. ไคลเอนต์ได้สมัครรับการอัพเดต
  2. ไคลเอนต์ส่งคำขอไปยังเซิร์ฟเวอร์
  3. เซิร์ฟเวอร์ตอบกลับว่า "คำขอได้รับการยอมรับ"
  4. เซิร์ฟเวอร์ตอบกลับด้วยผลลัพธ์ผ่านช่องทางจากจุด "1"

รูปแบบสถาปัตยกรรมที่สะดวกสบาย

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

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

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

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

การจัดหากิจกรรม

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

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

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

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

รูปแบบสถาปัตยกรรมที่สะดวกสบาย

คุณสมบัติที่สำคัญของแนวทางนี้:

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

ขอเตือนไว้ก่อนว่าเรากำลังพิจารณาเรื่องการจัดเก็บไฟล์ออนไลน์ ในกรณีนี้ ระบบจะมีลักษณะดังนี้:

รูปแบบสถาปัตยกรรมที่สะดวกสบาย

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

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

รูปแบบสถาปัตยกรรมที่สะดวกสบาย

โบนัสจากการผสมผสานดังกล่าว:

  • บริการประมวลผลข้อมูลถูกแยกออกจากกัน คิวก็ถูกแยกออกจากกันเช่นกัน หากเราต้องการเพิ่มปริมาณงานของระบบ เราเพียงแค่ต้องเปิดบริการเพิ่มเติมบนเซิร์ฟเวอร์หลายเครื่อง
  • เมื่อเราได้รับข้อมูลจากผู้ใช้ เราไม่จำเป็นต้องรอให้ข้อมูลถูกบันทึกจนครบถ้วน เพียงแค่ตอบว่า "ตกลง" แล้วค่อยเริ่มประมวลผล คิวยังช่วยปรับค่าพีคให้ราบรื่นขึ้น เนื่องจากการเพิ่มออบเจ็กต์ใหม่เกิดขึ้นอย่างรวดเร็ว และผู้ใช้ไม่จำเป็นต้องรอให้กระบวนการทั้งหมดเสร็จสิ้น
  • ยกตัวอย่างเช่น ผมได้เพิ่มบริการกำจัดข้อมูลซ้ำซ้อน (deduplication service) ที่พยายามผสานไฟล์ที่เหมือนกันเข้าด้วยกัน หากใช้เวลานานเพียง 1% ในการดำเนินการ ไคลเอนต์แทบจะไม่สังเกตเห็น (ดูด้านบน) ซึ่งถือเป็นข้อดีอย่างมาก เพราะเราไม่ต้องการความเร็วและความน่าเชื่อถือ 100% อีกต่อไป

อย่างไรก็ตาม ข้อเสียก็ปรากฏชัดเจนทันที:

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

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

ผลลัพธ์ที่ได้:

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

sharding

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

  • แยกไฟล์ตามประเภท เช่น สามารถถอดรหัสรูปภาพ/วิดีโอ และเลือกรูปแบบที่มีประสิทธิภาพมากขึ้นได้
  • แยกบัญชีตามประเทศ กฎหมายหลายฉบับอาจกำหนดให้ทำเช่นนี้ แต่สถาปัตยกรรมนี้รองรับความสามารถนี้โดยอัตโนมัติ

รูปแบบสถาปัตยกรรมที่สะดวกสบาย

หากคุณต้องการย้ายข้อมูลจากที่เก็บข้อมูลหนึ่งไปยังอีกที่เก็บข้อมูลหนึ่ง เครื่องมือมาตรฐานไม่สามารถทำได้ น่าเสียดายที่ในกรณีนี้ คุณต้องหยุดคิว ดำเนินการย้ายข้อมูล แล้วจึงเริ่มใหม่ โดยทั่วไปแล้ว คุณไม่สามารถย้ายข้อมูลแบบ "on the fly" ได้ อย่างไรก็ตาม หากคิวเหตุการณ์ถูกจัดเก็บไว้ทั้งหมด และคุณมีสแนปช็อตของสถานะการเก็บข้อมูลก่อนหน้า คุณสามารถเล่นเหตุการณ์ซ้ำได้ดังนี้:

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

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

ดังนั้น หากเราดำเนินตัวอย่างการจัดเก็บไฟล์ออนไลน์ต่อไป สถาปัตยกรรมนี้จะมอบโบนัสให้กับเราหลายประการ:

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

การโฮสต์เนื้อหาแบบคงที่

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

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

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

  • เซิร์ฟเวอร์จะให้ URL สำหรับดาวน์โหลด ซึ่งอาจอยู่ในรูปแบบ file_id + key โดยที่ key คือลายเซ็นดิจิทัลขนาดเล็กที่ให้สิทธิ์เข้าถึงทรัพยากรใน 24 ชั่วโมงถัดไป
  • การแจกจ่ายไฟล์จะดำเนินการโดย nginx ง่ายๆ โดยมีตัวเลือกดังต่อไปนี้:
    • การแคชเนื้อหา เนื่องจากบริการนี้สามารถโฮสต์บนเซิร์ฟเวอร์แยกต่างหาก เราจึงได้จัดเตรียมการป้องกันในอนาคตโดยการจัดเก็บไฟล์ที่ดาวน์โหลดล่าสุดทั้งหมดไว้ในดิสก์
    • การตรวจสอบคีย์เมื่อสร้างการเชื่อมต่อ
  • ตัวเลือก: การประมวลผลเนื้อหาแบบสตรีม ตัวอย่างเช่น หากเราบีบอัดไฟล์ทั้งหมดในบริการ เราสามารถแตกไฟล์ได้โดยตรงในโมดูลนี้ ส่งผลให้การดำเนินการ IO ดำเนินการในที่ที่ควรอยู่ โปรแกรมเก็บถาวร Java สามารถจัดสรรหน่วยความจำที่ไม่จำเป็นจำนวนมากได้อย่างง่ายดาย แต่การเขียนบริการใหม่ด้วยตรรกะทางธุรกิจใน Rust/C++ ทั่วไปอาจไม่มีประสิทธิภาพเช่นกัน ในกรณีของเรา เราใช้กระบวนการ (หรือแม้แต่บริการ) ที่แตกต่างกัน ซึ่งหมายความว่าเราสามารถแยกตรรกะทางธุรกิจและการดำเนินการ IO ออกจากกันได้อย่างมีประสิทธิภาพ

รูปแบบสถาปัตยกรรมที่สะดวกสบาย

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

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

อย่างไรก็ตาม หากเรากลับไปที่ระบบของเรา เราจะได้รับรูปแบบที่คล้ายกัน:

รูปแบบสถาปัตยกรรมที่สะดวกสบาย

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

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

ข้อสรุป

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

อย่างไรก็ตาม ที่สำคัญที่สุด รูปแบบทั้งหมดนี้สามารถนำไปประยุกต์ใช้ในแอปพลิเคชันสมัยใหม่ได้ง่ายมาก (แน่นอนว่าหากเหมาะสม) คลาวด์คอมพิวติ้งนำเสนอการแบ่งส่วนข้อมูล (sharding) และการปรับขนาดแนวนอนตั้งแต่เริ่มต้น ซึ่งง่ายกว่าการสั่งซื้อเซิร์ฟเวอร์เฉพาะแยกต่างหากในศูนย์ข้อมูลต่างๆ CQRS ง่ายขึ้นมาก แม้จะเกิดจากการพัฒนาไลบรารีอย่าง RX ก็ตาม สิบปีที่แล้ว มีเว็บไซต์เพียงไม่กี่แห่งเท่านั้นที่รองรับสิ่งนี้ได้ การตั้งค่า Event Sourcing ก็ง่ายอย่างเหลือเชื่อด้วยคอนเทนเนอร์ Apache Kafka สำเร็จรูป เมื่อสิบปีที่แล้ว สิ่งนี้ถือเป็นนวัตกรรม แต่ปัจจุบันกลายเป็นเรื่องปกติ ในทำนองเดียวกัน ด้วย Static Content Hosting เทคโนโลยีที่ใช้งานง่ายยิ่งขึ้น (รวมถึงเอกสารประกอบโดยละเอียดและฐานข้อมูลตอบกลับขนาดใหญ่) ทำให้วิธีการนี้ง่ายยิ่งขึ้นไปอีก

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

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

ที่มา: will.com

ซื้อโฮสติ้งที่เชื่อถือได้สำหรับไซต์ที่มีการป้องกัน DDoS เซิร์ฟเวอร์ VPS VDS 🔥 ซื้อบริการเว็บโฮสติ้งที่เชื่อถือได้ พร้อมระบบป้องกัน DDoS และเซิร์ฟเวอร์ VPS/VDS | ProHoster