คุณต้องการรับการแจ้งเตือนเกี่ยวกับเนื้อหาใหม่หรือไม่
ความล้มเหลวเกิดขึ้นเสมอ
เมื่อใดก็ตามที่บริการหนึ่งหรือระบบเรียกใช้กันเอง ความล้มเหลวก็สามารถเกิดขึ้นได้ ความล้มเหลวเหล่านี้อาจมาจากหลายปัจจัย ซึ่งรวมถึงเซิร์ฟเวอร์ เครือข่าย โหลดบาลานเซอร์ ซอฟต์แวร์ ระบบปฏิบัติการ หรือแม้แต่ความผิดพลาดจากผู้ให้บริการระบบ เราออกแบบระบบมาเพื่อลดโอกาสเกิดความล้มเหลว แต่การที่จะสร้างระบบที่ไม่มีวันล้มเหลวนั้นเป็นไปไม่ได้ ด้วยเหตุนี้ Amazon จึงได้ออกแบบระบบเพื่อให้สามารถทนและลดโอกาสเกิดความล้มเหลว รวมถึงหลีกเลี่ยงการลุกลามของความล้มเหลวเล็กน้อยไปสู่การหมดหนทางอย่างสมบูรณ์ ในการสร้างระบบที่ยืดหยุ่น เราใช้เครื่องมือที่จำเป็นสามประการ คือ การหมดเวลา การลองใหม่ และการถอยกลับ
ความล้มเหลวหลายประเภทเห็นได้ชัดเนื่องจากคำขอใช้เวลานานกว่าปกติและอาจไม่เสร็จสมบูรณ์ เมื่อไคลเอ็นต์รอให้การร้องขอเสร็จสมบูรณ์นานกว่าปกติ ก็จะมีการเก็บทรัพยากรที่ใช้สำหรับการร้องขอดังกล่าวเป็นเป็นเวลานานยิ่งขึ้น เมื่อมีคำขอจำนวนหนึ่งรออยู่ในที่เดียวกับทรัพยากรเป็นเวลานาน ทรัพยากรดังกล่าวก็สามารถหมดไปจากเซิร์ฟเวอร์ได้ ทรัพยากรเหล่านี้อาจรวมถึงหน่วยความจำ เธรด การเชื่อมต่อ พอร์ตชั่วคราว หรือสิ่งอื่นๆ ที่มีอยู่อย่างจำกัด ในการหลีกเลี่ยงสถานการณ์เหล่านี้ ไคลเอ็นต์จะกำหนดการหมดเวลา การหมดเวลาคือจำนวนเวลาสูงสุดที่ไคลเอ็นต์รอให้คำขอเสร็จสมบูรณ์
การลองคำขออีกครั้งทำให้คำยอสำเร็จอยู่บ่อยครั้ง ซึ่งเกิดขึ้นเนื่องจากระบบประเภทเหล่านี้ที่เราสร้าง มักจะไม่ล้มเหลวในหน่วยเดียว แต่พบกับความล้มเหลวบางส่วนหรือชั่วคราวแทน ความล้มเหลวบางส่วนเกิดเมื่อคำขอประสบความสำเร็จแม้เพียงหนึ่งเปอร์เซ็นต์ ความล้มเหลวชั่วคราวเกิดเมื่อคำขอล้มเหลวเพียงช่วงเวลาสั้นๆ การลองใหม่ช่วยให้ไคลเอ็นต์รอดจากความล้มเหลวบางส่วนที่เกิดขึ้นแบบสุ่มนี้ และทำงานต่อไปได้เมื่อเกิดความล้มเหลวชั่วคราวจากการส่งคำขอเดียวกันอีกครั้ง
การลองใหม่ไม่ได้มีความปลอดภัยเสมอ การลองใหม่สามารถเพิ่มโหลดในระบบที่มีการเรียก หากระบบล้มเหลวอยู่แล้วเนื่องจากเข้าสู่การโอเวอร์โหลด เพื่อหลีกเลี่ยงปัญหานี้ เราใช้ไคลเอ็นต์ของเราเพื่อถอยกลับ ซึ่งจะเพิ่มเวลาระหว่างการลองใหม่ในภายหลัง ที่ทำให้การโหลดบนแบ็คเอนด์เท่ากัน ปัญหาเกี่ยวกับการลองใหม่คือการเรียกใช้ระยะไกลบางรายการจะมีผลข้างเคียง การหมดเวลาหรือความล้มเหลวไม่ได้แปลว่าผลข้างเคียงจะไม่เกิดขึ้น หากการทำผลข้างเคียงหลายครั้งเป็นสิ่งที่ไม่พึงประสงค์ แนวทางปฏิบัติที่ดีที่สุดคือการออกแบบ API ให้เป็นค่าเดิมเสมอ ซึ่งหมายความว่าสามารถลองใหม่ได้อย่างปลอดภัย
ในที่สุด การรับส่งข้อมูลจะไม่ได้มาถึงบริการของ Amazon ในอัตราคงที่ แต่อัตราการมาถึงของคำขอมักจะมีจำนวนมากแทน ขำนวนที่มีมากนี้อาจเกิดจากพฤติกรรมของลูกค้า การกู้คืนความล้มเหล และแม้กระทั่งสิ่งที่ง่ายเหมือนงาน Cron ที่เป็นระยะ หากเกิดข้อผิดพลาดจากการโหลด การลองใหม่อาจไม่ได้ผลหากลูกค้าทั้งหมดลองใหม่ในเวลาเดียวกัน เพื่อหลีกเลี่ยงปัญหานี้ เราได้ใช้ความผันแปร ซึ่งเป็นระยะเวลาแบบสุ่มก่อนที่จะส่งคำขอใหม่ขอหรือลองใหม่ เพื่อช่วยป้องกันโหลดจำนวนมากด้วยการกระจายอัตราการมาถึง
แต่ละโซลูชันเหล่านี้จะได้รับกล่าวถึงในส่วนที่ตามมา
การหมดเวลา
• เวลาแฝงของแบ็คเอนด์ที่เพิ่มขึ้นเล็กน้อยที่นำไปสู่การหยุดทำงานเนื่องจากการลองคำขอทั้งหมดใหม่
• วิธีการนี้ยังใช้กับบริการที่มีขอบเขตเวลาแฝงใกล้กันซึ่ง p99.9 ใกล้กับ p50 ในกรณีเหล่านี้ การเพิ่มช่องว่างภายในช่วยให้เราหลีกเลี่ยงเวลาแฝงที่เพิ่มขึ้น ซึ่งทำให้เกิดการหมดเวลาจำนวนมาก
• เราพบข้อผิดพลาดทั่วไปเมื่อใช้การหมดเวลา Linux's SO_RCVTIMEO มีประสิทธิภาพ แต่มีข้อเสียบางประการที่ทำให้ไม่เหมาะกับการหมดเวลาซ็อกเก็ตแบบต้นทางถึงปลายทาง บางภาษา เช่น Java แสดงการควบคุมนี้โดยตรง ภาษาอื่นๆ เช่น Go ให้กลไกการหมดเวลาที่มีประสิทธิภาพมากขึ้น
• นอกจากนี้ยังมีการใช้การหมดเวลาที่ไม่ครอบคลุมการเรียกใช้ระยะไกลทั้งหมด เช่น การจับมือ DNS หรือ TLS โดยทั่วไปแล้ว เราเลือกใช้การหมดเวลาที่สร้างไว้ในไคลเอ็นต์ที่ผ่านการทดสอบเป็นอย่างดี หากเราใช้การหมดเวลาของเราเอง เราก็จะให้ความสำคัญกับความหมายที่แท้จริงของตัวเลือกซ็อกเก็ตการหมดเวลาและสิ่งที่กำลังดำเนินการอยู่
การลองใหม่ และการถอยกลับ
การลองใหม่คือการ “เห็นแก่ตัว” หรือพูดได้ว่า เมื่อลูกค้าลองใหม่ ก็จะต้องใช้เวลาของเซิร์ฟเวอร์มากขึ้นเพื่อให้โอกาสในการประสบความสำเร็จสูงขึ้น ในกรณีที่ความล้มเหลวเกิดขึ้นไม่บ่อยหรือชั่วคราว ข้อนี้ไม่ถือเป็นปัญหา เพราะจำนวนรวมของคำขอลองใหม่มีขนาดเล็กและการแลกเปลี่ยนของการเพิ่มความพร้อมใช้งานที่ชัดเจนนั้นทำงานได้ดี เมื่อความล้มเหลวเกิดจากการโอเวอร์โหลด การลองใหม่ที่เพิ่มโหลดอาจทำให้ระบบแย่ลงอย่างมีนัยสำคัญ อีกทั้งยังสามารถชะลอการกู้คืนโดยทำให้การโหลดสูงเป็นเวลานานหลังจากปัญหาเดิมได้รับการแก้ไข การลองใหม่คล้ายกับยาที่ทรงพลัง ก็คือมีประโยชน์ในปริมาณที่เหมาะสม แต่อาจทำให้เกิดความเสียหายอย่างมีนัยสำคัญเมื่อใช้งานมากเกินไป น่าเสียดายที่ในระบบแบบกระจายนั้นแทบจะไม่มีทางประสานงานระหว่างลูกค้าทั้งหมดเพื่อให้ได้จำนวนการลองใหม่ที่เหมาะสม
โซลูชันที่เราเลือกใช้ใน Amazon ได้แก่การย้อนกลับ ไคลเอ็นต์จะรอเวลาระหว่างการลองใหม่แต่ละครั้ง แทนที่จะลองใหม่ในทันที รูปแบบที่พบบ่อยที่สุดคือการถอยกลับแบบทวีคูณซึ่งเวลารอจะเพิ่มขึ้นแบบทวีคูณหลังจากพยายามทุกครั้ง การถอยกลับแบบทวีคูณสามารถนำไปสู้เวลาถอยกลับที่ยาวนาน เนื่องจากการเติบโตแบบทวีคูณอย่างรวดเร็ว เพื่อหลีกเลี่ยงไม่ให้การลองใหม่ใช้เวลานานเกินไป การปรับใช้มักจะจำกัดถอยกลับให้เป็นค่าสูงสุด ซึ่งเรียกกันตามที่คาดเดาได้ว่าการถอยกลับแบบทวีคูณที่มีขีดจำกัด แต่สิ่งนี้ก็นำปัญหาอื่นเข้ามา ตอนนี้ไคลเอ็นต์ทั้งหมดกำลังลองใหม่อย่างต่อเนื่องในอัตราที่กำหนด ในแทบทุกกรณี โซลูชันของเราคือการจำกัดจำนวนครั้งที่ไคลเอ็นต์ลองใหม่ และจัดการกับความล้มเหลวที่เกิดขึ้นก่อนหน้านี้ในสถาปัตยกรรมที่มุ่งเน้นบริการ ในกรณีส่วนใหญ่ ลูกค้าจะล้มเลิกการโทรเนื่องจากมีการหมดเวลาของตัวเอง
มีปัญหาอื่นๆ จากการลองใหม่ตามที่อธิบายไว้ดังนี้
• ระบบแบบกระจายมักจะมีหลายชั้น นึกถึงระบบที่การโทรของลูกค้าทำให้เกิดการเรียกใช้ติดต่อซ้อนห้าครั้ง ซึ่งจบลงด้วยการสืบค้นไปยังฐานข้อมูล และการลองใหม่สามครั้งในแต่ละชั้น จะเกิดอะไรขึ้นเมื่อฐานข้อมูลเริ่มทำให้การค้นหาในโหลดล้มเหลว หากแต่ละชั้นลองใหม่โดยแยกกัน โหลดบนฐานข้อมูลจะเพิ่มขึ้น 243 เท่าทำให้ดูเหมือนจะไม่สามารถกู้คืนได้ ซึ่งเป็นเพราะการลองใหม่ในแต่ละเพิ่มขึ้นแบบทวีคูณ เริ่มที่สามครั้ง จากนั้นเก้าครั้ง และต่อไปเรื่อยๆ ในทางกลับกัน การลองใหม่ที่ชั้นสูงสุดของการซ้อนอาจทำให้งานเสียจากการเรียกใช้ก่อนหน้า ซึ่งลดประสิทธิภาพลง โดยทั่วไปสำหรับการดำเนินงาน Control Plane และ Data Plane ที่มีราคาต่ำ แนวทางปฏิบัติที่ดีที่สุดของเราคือลองใหม่ที่จุดเดียวในการซ้อน
• โหลด แม้จะมีการลองใหม่ในชั้นเดียว การรับส่งข้อมูลก็ยังคงเพิ่มขึ้นอย่างมากเมื่อเกิดข้อผิดพลาด เซอร์กิตเบรกเกอร์ซึ่งเกิดขึ้นเมื่อการเรียกใช้ไปยังบริการดาวน์สตรีมหยุดลงอย่างสิ้นเชิงเมื่อเกินขอบเขตข้อผิดพลาด ได้มีการนำมาใช้แก้ไขปัญหานี้ แต่น่าเสียดายที่เซอร์กิตเบรกเกอร์ทำให้มีพฤติกรรมฐานนิยมในระบบที่ทดสอบได้ยาก และสามารถเพิ่มเวลาในการเพิ่มที่สำคัญในการกู้คืน เราพบว่าสามารถลดความเสี่ยงนี้ได้โดยจำกัดการลองใหม่ในพื้นที่โดยใช้บัคเก็ตโทเค็น วิธีนี้ช่วยให้การเรียกใช้ทั้งหมดมีการลองใหม่ตราบเท่าที่มีโทเค็น แล้วลองอีกครั้งในอัตราคงที่เมื่อโทเค็นหมด AWS เพิ่มพฤติกรรมนี้ใน AWS SDK ในปี 2016 ดังนั้นลูกค้าที่ใช้ SDK จะมีพฤติกรรมการควบคุมปริมาณนี้อยู่ในตัว
• ตัดสินใจเวลาที่จะลองใหม่ โดยทั่วไป มุมมองของเราคือ API ที่มีผลข้างเคียงไม่ปลอดภัยสำหรับการลองใหม่เว้นแต่จะให้ค่าเดิมเสมอ ซึ่งช่วยรับประกันได้ว่าผลข้างเคียงเกิดขึ้นเพียงครั้งเดียวไม่ว่าคุณจะลองใหม่บ่อยเพียงใด โดยทั่วไปแล้ว API แบบอ่านอย่างเดียวจะเป็นค่าเดิมเสมอ ในขณะที่ API การสร้างทรัพยากรอาจไม่เป็นเช่นนั้น API บางอย่างเช่น RunInstances API ของ Amazon Elastic Compute Cloud (Amazon EC2) มอบกลไกที่ใช้โทเค็นอย่างชัดเจนเพื่อให้เป็นค่าเดิมเสมอและลองใหม่ได้อย่างปลอดภัย การออกแบบ API ที่ดีและการเอาใจใส่เมื่อใช้งานไคลเอ็นต์เป็นสิ่งจำเป็นเพื่อป้องกันผลข้างเคียงที่ซ้ำกัน
• รู้ว่าความล้มเหลวใดควรลองใหม่ HTTP มอบความแตกต่างที่ชัดเจนระหว่างข้อผิดพลาดของไคลเอ็นต์และเซิร์ฟเวอร์ โดยบ่งชี้ว่าข้อผิดพลาดของลูกค้าไม่ควรลองใหม่ด้วยคำขอเดียวกัน เพราะจะไม่ประสบความสำเร็จในภายหลัง ในขณะที่ข้อผิดพลาดเซิร์ฟเวอร์อาจประสบความสำเร็จในการลองใหม่ครั้งต่อไป แต่น่าเสียดายที่ eventual consistency ในระบบมองข้ามบรรทัดนี้ ข้อผิดพลาดของไคลเอ็นต์หนึ่งช่วงเวลาอาจเปลี่ยนเป็นความสำเร็จในช่วงเวลาต่อไปเมื่อกระจายสถานะ
แม้จะมีความเสี่ยงและอุปสรรคเหล่านี้ การลองใหม่เป็นกลไกที่มีประสิทธิภาพในการให้ความพร้อมใช้งานสูง เมื่อเผชิญกับข้อผิดพลาดชั่วคราวและข้อผิดพลาดแบบสุ่มที่เกิดขึ้น การตัดสินจะต้องค้นหาการแลกเปลี่ยนที่ถูกต้องสำหรับแต่ละบริการ จากประสบการณ์ของเรา จุดเริ่มต้นที่ดีคือการจำไว้ว่าการลองใหม่เป็นการเห็นแก่ตัว การลองใหม่เป็นวิธีสำหรับไคลเอ็นต์ในการยืนยันถึงความสำคัญของคำขอและความต้องการให้บริการใช้ทรัพยากรมากขึ้นเพื่อจัดการกับปัญหา หากไคลเอ็นต์เห็นแก่ตัวมากเกินไป ก็จะสามารถสร้างปัญหาในวงกว้างยิ่งขึ้น
มีความผันแปร
เมื่อความล้มเหลวเกิดจากการโอเวอร์โหลดหรือการช่วงชิง การสำรองข้อมูลบ่อยครั้งไม่ได้ช่วยอะไรมากเท่าที่ควร ซึ่งเป็นเพราะความสัมพันธ์ หากการเรียกใช้ที่ล้มเหลวทั้งหมดถอยกลับพร้อมกัน ก็จะทำให้เกิดการช่วงชิงหรือการโอเวอร์โหลดอีกครั้งเมื่อลองใหม่ โซลูชันของเราคือความผันแปร ความผันแปรมอบจำนวนการสุ่มให้กับการถอยกลับเพื่อกระจายการลองใหม่ไปตามเวลา สำหรับข้อมูลเพิ่มเติมเกี่ยวกับจำนวนความผันแปรที่จะเพิ่ม และวิธีที่ดีที่สุดในการเพิ่มให้ดูที่การถอยกลับแบบทวีคูณและความผันแปร
ความผันแปรไม่ได้มีไว้เพียงการลองใหม่เท่านั้น ประสบการณ์การดำเนินงานได้สอนเราว่าการรับส่งข้อมูลไปยังบริการของเรา รวมถึงทั้ง Control Plane และ Data Plane มีแนวโน้มที่จะเพิ่มขึ้นอย่างมาก การเพิ่มขึ้นของข้อมูลเหล่านี้อาจมีระยะสั้นมาก และมักจะซ่อนไว้โดยตัววัดที่รวบรวมไว้ เมื่อสร้างระบบ เราจะพิจารณาเพิ่มความผันแปรให้กับตัวจับเวลา งานตามระยะเวลา และงานที่ล่าช้าอื่นๆ ทั้งหมด ซึ่งจะช่วยกระจายการเพิ่มงานออกไป และทำให้บริการดาวน์สตรีมสามารถปรับปริมาณงานได้ง่ายขึ้น
เมื่อเพิ่มความผันแปรไปยังงานที่กำหนดเวลา เราจะไม่เลือกความผันแปรในแต่ละโฮสต์แบบสุ่ม แต่เราใช้วิธีการที่สอดคล้องกันซึ่งสร้างหมายเลขเดียวกันทุกครั้งบนโฮสต์เดียวกันแทน หากมีการให้บริการโอเวอร์โหลดหรือสภาพการแข่งขันวิธีนี้ ก็จะเกิดขึ้นในลักษณะเดียวกันตามรูปแบบ มนุษย์อย่างพวกเราทีเก่งในด้านการระบุรูปแบบ และมีแนวโน้มที่จะหาสาเหตุหลัก การใช้วิธีการแบบสุ่มทำให้มั่นใจได้ว่าหากมีการใช้ทรัพยากรถ จะเกิดขึ้นโดยการสุ่ม ซึ่งทำให้แก้ไขปัญหาได้ยาก
สำหรับระบบที่เราพัฒนาอยู่ เช่น Amazon Elastic Block Store (Amazon EBS) และ AWS Lambda เราพบว่าไคลเอ็นต์มักส่งคำขอตามช่วงเวลาปกติ เช่น หนึ่งครั้งต่อนาที อย่างไรก็ตาม เมื่อไคลเอ็นต์มีเซิร์ฟเวอร์หลายตัวที่ทำงานในลักษณะเดียวกัน ก็สามารถจัดเรียงและทริกเกอร์คำขอได้ในเวลาเดียวกัน ซึ่งอาจเป็นวินาทีแรกของนาที หรือสองสามวินาทีแรกหลังเที่ยงคืนสำหรับงานประจำวัน การให้ความสนใจกับโหลดต่อวินาทีและการทำงานกับลูกค้าเพื่อสร้างความผันแปรให้ปริมาณงานเป็นระยะ หมายความว่าเราได้ทำงานในปริมาณเดียวกันโดยมีความจุของเซิร์ฟเวอร์น้อยลง
เรามีการควบคุมที่น้อยกว่าในการรับส่งข้อมูลของลูกค้า อย่างไรก็ตาม แม้สำหรับงานที่ลูกค้าทริกเกอร์ การเพิ่มความผันแปรที่ไม่ส่งผลกระทบต่อประสบการณ์ของลูกค้าถือเป็นความคิดที่ดี
สรุป
ในระบบแบบกระจาย ความล้มเหลวชั่วคราวหรือเวลาแฝงในการโต้ตอบระยะไกลเป็นสิ่งที่หลีกเลี่ยงไม่ได้ การหมดเวลาป้องกันไม่ให้ระบบค้างไว้นานเกินไป การลองใหม่สามารถปกปิดความล้มเหลวเหล่านั้น และการถอยกลับและความผันแปร สามารถปรับปรุงการใช้ประโยชน์และลดความแออัดของระบบ
ที่ Amazon เราได้เรียนรู้ว่าการลองใหม่อย่างระมัดระวังเป็นสิ่งสำคัญ การลองใหม่สามารถขยายโหลดบนระบบแบบพึ่งพา หากการเรียกใช้ไปยังระบบหมดเวลา และระบบนัดังกล่าวโอเวอร์โหลด การลองใหม่อาจทำให้โอเวอร์โหลดแย่ลงแทนที่จะดีขึ้น เราหลีกเลี่ยงการขยายตัวนี้โดยลองใหม่เมื่อสังเกตเห็นว่าการขึ้นต่อกันอยู่ในสภาพที่ดี เราหยุดลองใหม่เมื่อการลองไม่ได้ช่วยปรับปรุงความพร้อมใช้งาน
เกี่ยวกับผู้เขียน
Marc Brooker คือวิศวกรใหญ่ระดับอาวุโสที่ Amazon Web Services เขาทำงานที่ AWS ในด้านบริการต่างๆ เช่น EC2, EBS และ IoT มาตั้งแต่ปี 2008 ในปัจจุบัน งานของเขาจะเน้นไปที่ AWS Lambda รวมถึงงานด้านการขยายซอฟต์แวร์และการสร้างระบบเสมือน (Virtualization) Marc ชื่นชอบการอ่าน COE และการชันสูตรในขั้นตอนการพัฒนาซอฟต์แวร์ เขาจบการศึกษาปริญญาเอกสาขาวิศวกรรมไฟฟ้า