การล็อคแบบกระจายโดยใช้ Redis

เฮ้ ฮับ!

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

การล็อกแบบกระจายเป็นประโยชน์อย่างมากที่ใช้ในสภาพแวดล้อมต่างๆ ซึ่งกระบวนการที่แตกต่างกันจะต้องทำงานบนทรัพยากรที่ใช้ร่วมกันในลักษณะที่ไม่เกิดร่วมกัน

มีไลบรารีและโพสต์จำนวนมากที่อธิบายวิธีใช้งาน DLM (Distributed Lock Manager) โดยใช้ Redis แต่แต่ละไลบรารีใช้แนวทางที่แตกต่างกัน และการรับประกันที่มอบให้นั้นค่อนข้างอ่อนแอเมื่อเทียบกับสิ่งที่ทำได้ด้วยการออกแบบที่ซับซ้อนกว่าเล็กน้อย

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

การดำเนินการ

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

  • Redlock-rb (การใช้งานสำหรับ Ruby) นอกจากนี้ยังมี ส้อม Redlock-rb ซึ่งเพิ่มแพ็คเกจ (อัญมณี) เพื่อความสะดวกในการแจกจ่าย และไม่เพียงเท่านั้น
  • Redlock-py (การใช้งานหลาม)
  • ไอเรดล็อค (การใช้งานสำหรับ Asyncio Python)
  • Redlock-php.ini (การใช้งานสำหรับ PHP)
  • PHPRedisMutex.php (การใช้งานอื่นสำหรับ PHP)
  • cheprasov/php-redis-lock.php (ไลบรารี PHP สำหรับการล็อค)
  • เรดซิงค์ (การใช้งานสำหรับ Go)
  • เรดิสสัน (การใช้งานสำหรับ Java)
  • Redis::DistLock (การใช้งานสำหรับ Perl)
  • เรดล็อค-cpp (การใช้งานสำหรับ C ++)
  • Redlock-cs (การใช้งานสำหรับ C#/.NET)
  • RedLock.net (การใช้งานสำหรับ C#/.NET) ด้วยการรองรับส่วนขยาย async และ lock
  • สการ์เล็ตล็อค (การใช้งานสำหรับ C# .NET พร้อมที่เก็บข้อมูลที่กำหนดค่าได้)
  • เรดล็อค4เน็ต (การใช้งานสำหรับ C# .NET)
  • โหนด redlock (การใช้งานสำหรับ NodeJS) รวมถึงการสนับสนุนการขยายล็อค

การรับประกันความปลอดภัยและความพร้อม

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

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

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

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

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

แน่นอนว่าในโมเดลดังกล่าว สภาพการแข่งขันจะเกิดขึ้น:

  1. ลูกค้า A ได้รับการล็อคบนต้นแบบ
  2. ต้นแบบล้มเหลวก่อนที่รายการคีย์จะถูกโอนไปยังทาส
  3. ผู้ตามได้รับการเลื่อนขั้นเป็นผู้นำ
  4. ลูกค้า B ได้รับการล็อคบนทรัพยากรเดียวกันกับที่ A ได้ล็อคไว้แล้ว การละเมิดความปลอดภัย!

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

การใช้งานที่ถูกต้องด้วยอินสแตนซ์เดียว

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

หากต้องการรับล็อค ให้ทำดังนี้:

SET resource_name my_random_value NX PX 30000

คำสั่งนี้จะติดตั้งคีย์เฉพาะในกรณีที่ยังไม่มีคีย์ (ตัวเลือก NX) โดยมีระยะเวลาที่ใช้ได้ 30000 มิลลิวินาที (ตัวเลือก PX) กุญแจถูกตั้งค่าเป็น “myrandomvalue" ค่านี้ต้องไม่ซ้ำกันระหว่างไคลเอ็นต์ทั้งหมดและคำขอล็อกทั้งหมด
โดยพื้นฐานแล้ว ค่าสุ่มจะถูกนำมาใช้เพื่อปลดล็อคอย่างปลอดภัย โดยมีสคริปต์บอก Redis ว่าให้ลบคีย์ออกหากมีอยู่เท่านั้น และค่าที่เก็บไว้ในนั้นก็เป็นสิ่งที่คาดหวังไว้ทุกประการ สามารถทำได้โดยใช้สคริปต์ Lua ต่อไปนี้:

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

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

สตริงสุ่มนี้ควรเป็นอย่างไร ฉันเดาว่ามันควรจะเป็น 20 ไบต์จาก /dev/urandom แต่คุณสามารถหาวิธีที่ถูกกว่าในการทำให้สตริงมีเอกลักษณ์เพียงพอสำหรับวัตถุประสงค์ของคุณ ตัวอย่างเช่น เป็นการดีที่จะ seed RC4 ด้วย /dev/urandom จากนั้นสร้างสตรีมแบบสุ่มหลอกจากมัน วิธีแก้ปัญหาที่ง่ายกว่านั้นเกี่ยวข้องกับการรวมเวลายูนิกซ์ในความละเอียดระดับไมโครวินาทีบวกกับรหัสไคลเอนต์ มันไม่ปลอดภัย แต่อาจขึ้นอยู่กับงานในบริบทส่วนใหญ่

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

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

อัลกอริธึม Redlock

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

ในการรับการล็อค ไคลเอนต์ดำเนินการต่อไปนี้:

  1. รับเวลาปัจจุบันเป็นมิลลิวินาที
  2. พยายามรับการล็อกบนอินสแตนซ์ N ทั้งหมดตามลำดับ โดยใช้ชื่อคีย์เดียวกันและค่าสุ่มในทุกกรณี ในขั้นที่ 2 เมื่อไคลเอนต์ตั้งค่าการล็อคตามอินสแตนซ์ ไคลเอนต์จะใช้การหน่วงเวลาเพื่อรับล็อคที่สั้นเพียงพอเมื่อเปรียบเทียบกับเวลาที่ปลดล็อคโดยอัตโนมัติ ตัวอย่างเช่น หากระยะเวลาการบล็อกคือ 10 วินาที ความล่าช้าอาจอยู่ในช่วง ~5-50 มิลลิวินาที วิธีนี้จะขจัดสถานการณ์ที่ไคลเอนต์อาจยังคงถูกบล็อกเป็นเวลานานในการพยายามเข้าถึงโหนด Redis ที่ล้มเหลว: หากอินสแตนซ์ไม่พร้อมใช้งาน เราจะพยายามเชื่อมต่อกับอินสแตนซ์อื่นโดยเร็วที่สุด
  3. เพื่อทำการล็อค ลูกค้าจะคำนวณระยะเวลาที่ผ่านไป เมื่อต้องการทำเช่นนี้ จะลบออกจากค่าเวลาจริงของการประทับเวลาที่ได้รับในขั้นตอนที่ 1 ถ้าและเฉพาะในกรณีที่ไคลเอ็นต์สามารถรับการล็อกบนอินสแตนซ์ส่วนใหญ่ (อย่างน้อย 3) และเวลาทั้งหมดที่ใช้ในการ ได้รับการล็อคน้อยกว่าระยะเวลาล็อคถือว่าได้รับล็อคแล้ว
  4. หากได้รับการล็อค ระยะเวลาการล็อคจะถือเป็นระยะเวลาการล็อคเดิมลบด้วยเวลาที่ผ่านไปซึ่งคำนวณในขั้นตอนที่ 3
  5. หากไคลเอ็นต์ไม่สามารถล็อกได้ด้วยเหตุผลบางประการ (ไม่สามารถล็อกอินสแตนซ์ N/2+1 ได้ หรือระยะเวลาล็อกเป็นลบ) ไคลเอ็นต์จะพยายามปลดล็อกอินสแตนซ์ทั้งหมด (แม้ว่าไคลเอ็นต์จะคิดว่าไม่สามารถบล็อกได้ก็ตาม ).

อัลกอริทึมเป็นแบบอะซิงโครนัสหรือไม่?

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

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

บทความที่น่าสนใจต่อไปนี้จะอธิบายเพิ่มเติมเกี่ยวกับระบบดังกล่าวที่ต้องอาศัยการประสานงานของช่วงเวลา: การเช่า: กลไกการทนทานต่อข้อผิดพลาดที่มีประสิทธิภาพเพื่อความสม่ำเสมอของแคชไฟล์แบบกระจาย.

ลองอีกครั้งเมื่อเกิดความล้มเหลว

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

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

ปลดล็อค

การปลดล็อคเป็นการดำเนินการง่ายๆ ที่ต้องปลดล็อคอินสแตนซ์ทั้งหมด โดยไม่คำนึงว่าไคลเอนต์จะล็อคอินสแตนซ์ใดอินสแตนซ์หนึ่งสำเร็จหรือไม่

ข้อควรพิจารณาด้านความปลอดภัย

อัลกอริทึมปลอดภัยหรือไม่? ลองจินตนาการถึงสิ่งที่เกิดขึ้นในสถานการณ์ต่างๆ

ขั้นแรก สมมติว่าไคลเอ็นต์สามารถได้รับการล็อกบนอินสแตนซ์ส่วนใหญ่ได้ แต่ละอินสแตนซ์จะมีคีย์ที่มีอายุการใช้งานเท่ากันสำหรับทุกคน อย่างไรก็ตาม แต่ละคีย์เหล่านี้ได้รับการติดตั้งในเวลาที่ต่างกัน ดังนั้นคีย์เหล่านี้จะหมดอายุในเวลาที่ต่างกัน แต่หากติดตั้งคีย์แรกในแต่ละครั้งไม่แย่ไปกว่า T1 (เวลาที่เราเลือกก่อนติดต่อกับเซิร์ฟเวอร์เครื่องแรก) และคีย์สุดท้ายถูกติดตั้งในแต่ละครั้งไม่แย่กว่า T2 (เวลาที่รับการตอบกลับ จากเซิร์ฟเวอร์ล่าสุด) จากนั้นเรามั่นใจว่าคีย์แรกในชุดที่หมดอายุจะยังคงอยู่อย่างน้อย MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT. คีย์อื่นๆ ทั้งหมดจะหมดอายุในภายหลัง ดังนั้นเราจึงมั่นใจได้ว่าคีย์ทั้งหมดจะใช้ได้พร้อมกันอย่างน้อยในครั้งนี้

ในช่วงเวลาที่คีย์ส่วนใหญ่ยังคงใช้งานได้ ไคลเอ็นต์อื่นจะไม่สามารถรับการล็อกได้ เนื่องจากการดำเนินการ N/2+1 SET NX จะไม่สำเร็จหากมีคีย์ N/2+1 อยู่แล้ว ดังนั้นเมื่อได้รับล็อคแล้ว จะไม่สามารถรับได้อีกในเวลาเดียวกัน (ซึ่งจะถือเป็นการละเมิดคุณสมบัติการยกเว้นร่วมกัน)
อย่างไรก็ตาม เราต้องการให้แน่ใจว่าไคลเอนต์หลายตัวที่พยายามรับการล็อคในเวลาเดียวกันไม่สามารถประสบความสำเร็จในเวลาเดียวกันได้

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

คุณสามารถแสดงหลักฐานการรักษาความปลอดภัยอย่างเป็นทางการ ระบุอัลกอริธึมที่คล้ายกันที่มีอยู่ หรือค้นหาข้อบกพร่องข้างต้นได้หรือไม่

ข้อควรพิจารณาในการเข้าถึง

ความพร้อมใช้งานของระบบขึ้นอยู่กับคุณลักษณะหลักสามประการ:

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

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

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

ประสิทธิภาพ เฟลโอเวอร์ และ fsync

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

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

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

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

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

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

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

เราเพิ่มความพร้อมใช้งานของอัลกอริทึม: เราขยายการบล็อก

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

ไคลเอนต์ควรพิจารณาว่าจะต้องทำการล็อคอีกครั้งหากสามารถล็อคอินสแตนซ์ส่วนใหญ่ภายในระยะเวลาที่มีผลใช้ได้เท่านั้น

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

ที่มา: will.com

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