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

เฮ้ ฮับ!

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ผลที่ตามมา:

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

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 เดียวกันนี้ใช้แนวคิดการโฮสต์เนื้อหาแบบคงที่เพื่อแสดงรูปภาพมานานแล้ว เกมออนไลน์จำนวนมากใช้รูปแบบ Sharding เพื่อแบ่งผู้เล่นออกตามภูมิภาคหรือแยกตำแหน่งของเกม (หากโลกเป็นหนึ่งเดียวกัน) แนวทางการจัดหากิจกรรมถูกนำมาใช้อย่างแข็งขันในอีเมล แอปพลิเคชันการซื้อขายส่วนใหญ่ที่ได้รับข้อมูลอย่างต่อเนื่องนั้นสร้างขึ้นตามแนวทาง CQRS เพื่อให้สามารถกรองข้อมูลที่ได้รับได้ มีการใช้มาตราส่วนแนวนอนในบริการหลายอย่างมาเป็นเวลานานแล้ว

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

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

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

ที่มา: will.com

เพิ่มความคิดเห็น