โดยทั่วไปมีสามวิธีในการใช้งานล็อคแบบกระจาย: 1. การล็อคฐานข้อมูลในแง่ดี; 2. ล็อคแบบกระจายตาม Redis; 3. ล็อคแบบกระจายตาม Keepkeeper บล็อกนี้จะแนะนำวิธีที่สองซึ่งคือการใช้การล็อคแบบกระจายตาม Redis แม้ว่าจะมีบล็อกต่าง ๆ บนอินเทอร์เน็ตที่แนะนำการใช้งานของ Redis Distributed Locks แต่การใช้งานของพวกเขามีปัญหาต่าง ๆ เพื่อหลีกเลี่ยงเด็กที่ทำให้เข้าใจผิดบล็อกนี้จะแนะนำรายละเอียดเกี่ยวกับวิธีการใช้งานล็อคแบบกระจาย REDIS อย่างถูกต้อง
ความน่าเชื่อถือ
ก่อนอื่นเพื่อให้แน่ใจว่ามีการล็อคแบบกระจายอย่างน้อยเราจะต้องตรวจสอบให้แน่ใจว่าการดำเนินการล็อคเป็นไปตามเงื่อนไขสี่ข้อต่อไปนี้ในเวลาเดียวกัน:
การยกเว้นซึ่งกันและกัน เมื่อใดก็ได้ลูกค้าเพียงรายเดียวเท่านั้นที่สามารถถือล็อคได้
ไม่มีการหยุดชะงักเกิดขึ้น แม้ว่าลูกค้าจะล่มในช่วงระยะเวลาการล็อคโดยไม่ต้องปลดล็อคอย่างแข็งขัน แต่ก็สามารถมั่นใจได้ว่าลูกค้ารายอื่นสามารถเพิ่มล็อคได้
ทนต่อความผิดพลาด ตราบใดที่โหนด Redis ส่วนใหญ่ทำงานตามปกติไคลเอนต์สามารถล็อคและปลดล็อคได้
คนที่ผูกระฆังจะต้องมัด การล็อคและปลดล็อคจะต้องเป็นลูกค้าเดียวกันและลูกค้าไม่สามารถปลดล็อคที่ผู้อื่นเพิ่ม
การใช้รหัส
การพึ่งพาส่วนประกอบ
ก่อนอื่นเราจำเป็นต้องแนะนำส่วนประกอบโอเพ่นซอร์สเจไดผ่าน Maven และเพิ่มรหัสต่อไปนี้ในไฟล์ pom.xml:
<Ederency> <sderctId> redis.Clients </groupId> <ratifactid> Jedis </artifactid> <version> 2.9.0 </Serve> </perdency>
รหัสล็อค
แก้ไขท่าทาง
พูดคุยราคาถูกแสดงรหัสให้ฉันดู แสดงรหัสก่อนจากนั้นอธิบายว่าทำไมสิ่งนี้จึงถูกนำไปใช้:
Public Class Redistool {สตริงคงสุดท้ายส่วนตัว lock_success = "ตกลง"; สตริงสุดท้ายคงที่ส่วนตัว set_if_not_exist = "nx"; สตริงสุดท้ายคงที่ส่วนตัว set_with_expire_time = "px";/*** พยายามที่จะได้รับการล็อคแบบกระจาย* @param jedis redis ได้รับความสำเร็จ*/บูลีนแบบคงที่สาธารณะ TrygetDistributeDlock (Jedis Jedis, String Lockkey, String RequestId, Int หมดอายุ) {string result = jedis.set (lockkey, requestId, set_if_exist, set_with_expire_timeอย่างที่คุณเห็นเราเพิ่งเพิ่มรหัสบรรทัดเดียว: jedis.set (StringKey, StringValue, StringNXXX, StringExpx, Inttime) วิธีการนี้ () มีพารามิเตอร์อย่างเป็นทางการห้าพารามิเตอร์ทั้งหมด:
อันแรกคือคีย์เราใช้คีย์เป็นล็อคเพราะคีย์ไม่ซ้ำกัน
อันที่สองคือค่า สิ่งที่เรากำลังส่งต่อคือคำขอ รองเท้าเด็กหลายคนอาจไม่เข้าใจ มันไม่เพียงพอที่จะมีกุญแจเป็นล็อคหรือไม่? ทำไมเรายังต้องใช้ค่า? เหตุผลก็คือเมื่อเราพูดคุยเกี่ยวกับความน่าเชื่อถือข้างต้นล็อคแบบกระจายจะต้องเป็นไปตามเงื่อนไขที่สี่เพื่อคลายซิประฆัง โดยการกำหนดค่าให้กับ RequestID เราจะทราบว่าคำขอใดที่เพิ่มเข้ามาในล็อคและจะมีพื้นฐานสำหรับการปลดล็อค สามารถสร้างคำขอได้โดยใช้วิธี uuid.randomuuid (). วิธี ToString ()
อันที่สามคือ NXXX เรากรอกพารามิเตอร์นี้ NX ซึ่งหมายถึง setifNotExist นั่นคือเมื่อไม่มีคีย์อยู่เราทำการดำเนินการตั้งค่า หากคีย์มีอยู่แล้วจะไม่มีการดำเนินการใด ๆ
อันที่สี่คือ expx เราผ่านพารามิเตอร์นี้ PX ซึ่งหมายความว่าเราต้องการเพิ่มการตั้งค่าที่หมดอายุลงในคีย์นี้ เวลาที่กำหนดจะถูกกำหนดโดยพารามิเตอร์ที่ห้า
ที่ห้าคือเวลาซึ่งสะท้อนพารามิเตอร์ที่สี่และแสดงถึงเวลาหมดอายุของคีย์
โดยทั่วไปการดำเนินการวิธีการตั้งค่า () ด้านบนจะนำไปสู่ผลลัพธ์สองประการเท่านั้น: 1. ไม่มีการล็อคในปัจจุบัน (ไม่มีคีย์) จากนั้นการดำเนินการล็อคจะดำเนินการและการล็อคถูกตั้งค่าให้ถูกต้องสำหรับการล็อคและค่าแสดงถึงไคลเอนต์ล็อค 2. มีล็อคอยู่แล้วและไม่มีการดำเนินการ
หากคุณระมัดระวังคุณจะพบว่ารหัสล็อคของเราเป็นไปตามเงื่อนไขสามประการที่อธิบายไว้ในความน่าเชื่อถือของเรา ก่อนอื่น Set () เพิ่มพารามิเตอร์ NX ซึ่งสามารถมั่นใจได้ว่าหากมีคีย์อยู่แล้วฟังก์ชั่นจะไม่ถูกเรียกสำเร็จนั่นคือลูกค้าเพียงรายเดียวเท่านั้นที่สามารถล็อคไว้เพื่อตอบสนอง Mutex ประการที่สองเนื่องจากเราตั้งเวลาหมดอายุสำหรับการล็อคแม้ว่าตัวยึดล็อคจะเกิดปัญหาในการชนครั้งต่อไปและไม่ปลดล็อคล็อคจะถูกปลดล็อคโดยอัตโนมัติเนื่องจากถึงเวลาหมดอายุ (นั่นคือคีย์จะถูกลบ) และจะไม่มีการหยุดชะงัก ในที่สุดเนื่องจากเรากำหนดค่าให้กับการร้องขอซึ่งแสดงถึงตัวตนของคำขอลูกค้าที่ถูกล็อคดังนั้นไคลเอนต์จึงสามารถตรวจสอบได้ว่าเป็นไคลเอนต์เดียวกันเมื่อปลดล็อค เนื่องจากเราพิจารณาสถานการณ์การปรับใช้แบบสแตนด์อโลนของ Redis เท่านั้นเราจะไม่พิจารณาการยอมรับความผิดพลาดในขณะนี้
ตัวอย่างข้อผิดพลาด 1
ตัวอย่างข้อผิดพลาดทั่วไปคือการใช้การรวมกันของ jedis.setnx () และ jedis.expire () เพื่อให้บรรลุการล็อค รหัสมีดังนี้:
โมฆะคงที่สาธารณะผิด unglegetLock1 (Jedis Jedis, String Lockkey, String RequestId, Int หมดอายุ) {Long Result = Jedis.setNx (Lockkey, RequestId); ถ้า (ผลลัพธ์ == 1) {// ถ้าโปรแกรมเกิดขึ้นที่นี่ฟังก์ชั่นของวิธี SetNX () คือ SetIfNotExist และวิธีการหมดอายุ () คือการเพิ่มเวลาหมดอายุลงในล็อค เมื่อมองแวบแรกดูเหมือนว่าจะเหมือนกับวิธีการก่อนหน้า () อย่างไรก็ตามเนื่องจากสิ่งเหล่านี้เป็นคำสั่ง Redis สองคำจึงไม่ได้เป็นอะตอม หากโปรแกรมขัดข้องหลังจากดำเนินการ setnx () ทันทีการล็อคจะไม่ตั้งเวลาหมดอายุ จากนั้นการหยุดชะงักจะเกิดขึ้น เหตุผลที่บางคนใช้สิ่งนี้บนอินเทอร์เน็ตคือรุ่นที่ต่ำกว่าของ Jedis ไม่รองรับวิธีการหลายพารามิเตอร์ ()
ข้อผิดพลาดตัวอย่าง 2
ตัวอย่างข้อผิดพลาดนี้ยากต่อการค้นหาปัญหาและการใช้งานก็ซับซ้อนกว่า แนวคิดการใช้งาน: ใช้คำสั่ง jedis.setnx () เพื่อใช้การล็อคโดยที่คีย์คือการล็อคและค่าคือเวลาหมดอายุของการล็อค กระบวนการดำเนินการ: 1. ลองเพิ่มการล็อคผ่านวิธี SetNX () หากไม่มีการล็อคปัจจุบันล็อคจะถูกส่งคืนสำเร็จ 2. หากล็อคอยู่แล้วให้รับเวลาหมดอายุของการล็อคและเปรียบเทียบกับเวลาปัจจุบัน หากการล็อคหมดอายุให้ตั้งค่าเวลาหมดอายุใหม่และเพิ่มการล็อคกลับได้สำเร็จ รหัสมีดังนี้:
บูลีนแบบคงที่สาธารณะผิด GongetLock2 (Jedis Jedis, String Lockkey, Int Expiretime) {Long Expires = System.currentTimeMillis () + Expiretime; String ExpiressTr = String.valueof (หมดอายุ); หากมีการล็อคเวลาหมดอายุของล็อคจะได้รับสตริง currentvaluestr = jedis.get (lockkey); ถ้า (currentvaluestr! = null && long.parselong (currentvaluestr) <system.currenttimeLis () {// การล็อคหมดเวลา jedis.getSet (Lockkey, ExpiressTr); ถ้า (oldvaluestr! = null && oldvaluestr.equals (currentValuestr)) {// พิจารณากรณีของการเกิดขึ้นหลายเธรดแล้วรหัสนี้มีปัญหาอะไร? 1. เนื่องจากไคลเอนต์สร้างเวลาหมดอายุเองจึงจำเป็นต้องบังคับเวลาของลูกค้าแต่ละรายที่จะซิงโครไนซ์ภายใต้วิธีการกระจาย 2. เมื่อล็อคหมดอายุหากลูกค้าหลายรายดำเนินการเมธอด jedis.getSet () ในเวลาเดียวกันแม้ว่าลูกค้าเพียงรายเดียวเท่านั้นที่สามารถล็อคได้ในที่สุดเวลาหมดอายุของการล็อคของลูกค้านี้อาจถูกเขียนทับโดยลูกค้ารายอื่น 3. ล็อคไม่มีโลโก้เจ้าของนั่นคือลูกค้าทุกคนสามารถปลดล็อคได้
ปลดล็อกรหัส
แก้ไขท่าทาง
มาแสดงรหัสก่อนแล้วจึงอธิบายอย่างช้าๆว่าทำไมสิ่งนี้จึงถูกนำไปใช้:
Redistool คลาสสาธารณะ {ส่วนตัวคงที่สุดท้าย release_success = 1l;/*** ปล่อยล็อคแบบกระจาย* @param jedis redis ไคลเอ็นต์* @param lockkey lock* @param Request Request ID* @@return ว่าการเปิดตัวสำเร็จ Keys [1]) == Argv [1] จากนั้นส่งคืน redis.call ('del', ปุ่ม [1]) อื่นกลับ 0 end "; object result = jedis.eval (สคริปต์, คอลเลกชันอย่างที่คุณเห็นเราต้องการรหัสสองบรรทัดเพื่อปลดล็อก! ในบรรทัดแรกของรหัสเราเขียนรหัสสคริปต์ LUA อย่างง่าย ครั้งสุดท้ายที่เราเห็นภาษาการเขียนโปรแกรมนี้อยู่ใน "แฮ็กเกอร์และจิตรกร" แต่เราไม่ได้คาดหวังว่าจะใช้ในครั้งนี้ ในบรรทัดที่สองของรหัสเราส่งรหัส LUA ไปยังวิธี jedis.eval () และกำหนดคีย์พารามิเตอร์ [1] ให้กับ Lockkey และ Argv [1] เพื่อขอ วิธีการประเมิน () คือการมอบรหัส LUA ให้กับเซิร์ฟเวอร์ Redis เพื่อดำเนินการ
ฟังก์ชั่นของรหัส Lua นี้คืออะไร? ในความเป็นจริงมันง่ายมาก ขั้นแรกให้รับค่าที่สอดคล้องกับการล็อคตรวจสอบว่าเท่ากับการร้องขอและหากเท่ากันให้ลบล็อค (ปลดล็อค) เหตุใดจึงต้องใช้ภาษา LUA เพื่อนำไปใช้ เพราะมีความจำเป็นเพื่อให้แน่ใจว่าการดำเนินการข้างต้นเป็นอะตอม สำหรับปัญหาใดที่อะตอมจะนำมาคุณสามารถอ่าน [รหัสปลดล็อค - ข้อผิดพลาดตัวอย่าง 2] เหตุใดฉันจึงสามารถดำเนินการวิธีการประเมิน () ทำให้มั่นใจได้ว่าเป็นอะตอมซึ่งเกิดจากลักษณะของ Redis นี่คือคำอธิบายบางส่วนของคำสั่ง eval ในเว็บไซต์ทางการ:
พูดง่ายๆเมื่อคำสั่ง eval ดำเนินการรหัส LUA รหัส LUA จะถูกเรียกใช้เป็นคำสั่งและ REDIS จะไม่เรียกใช้คำสั่งอื่น ๆ จนกว่าคำสั่ง eval จะถูกดำเนินการ
ตัวอย่างข้อผิดพลาด 1
รหัสปลดล็อคที่พบมากที่สุดคือการใช้วิธี jedis.del () โดยตรงเพื่อลบการล็อค วิธีการปลดล็อคโดยตรงนี้โดยไม่ตัดสินเจ้าของล็อคจะทำให้ลูกค้าใด ๆ ปลดล็อคได้ตลอดเวลาแม้ว่าการล็อคจะไม่ใช่
โมฆะคงที่สาธารณะผิด ungreleaselock1 (Jedis Jedis, String Lockkey) {Jedis.del (Lockkey); -ข้อผิดพลาดตัวอย่าง 2
เมื่อมองแวบแรกรหัสปลดล็อคนี้ก็ใช้ได้ ฉันเกือบจะใช้มันเช่นนี้มาก่อนซึ่งคล้ายกับท่าทางที่ถูกต้อง ความแตกต่างเพียงอย่างเดียวคือมันแบ่งออกเป็นสองคำสั่งเพื่อดำเนินการ รหัสมีดังนี้:
โมฆะคงที่สาธารณะผิด RELELEASELOCK2 (JEDIS JEDIS, String Lockkey, String RequestId) {// ตรวจสอบว่าการล็อคและการปลดล็อคเป็นไคลเอนต์เดียวกันหรือไม่ถ้า (requestId.equals (jedis.get (lockkey)))) {// ถ้าล็อคนี้ไม่ได้มาจากไคลเอนต์นี้สำหรับความคิดเห็นของรหัสปัญหาคือถ้ามีการเรียกเมธอด jedis.del () การล็อคจะถูกปลดล็อคเมื่อไม่ได้เป็นของไคลเอนต์ปัจจุบันอีกต่อไป มีสถานการณ์จริง ๆ หรือไม่? คำตอบคือใช่ ตัวอย่างเช่นไคลเอนต์ล็อคและหลังจากระยะเวลาหนึ่งไคลเอนต์จะปลดล็อค ก่อนที่จะดำเนินการ jedis.del () ล็อคจะหมดอายุทันที ในเวลานี้ไคลเอนต์ B พยายามล็อคสำเร็จจากนั้นไคลเอนต์ A จะดำเนินการวิธี DEL () จากนั้นล็อคของลูกค้า B จะถูกปลดล็อค
สรุป
บทความนี้ส่วนใหญ่แนะนำวิธีการใช้ Redis Distributed Lock อย่างถูกต้องโดยใช้รหัส Java มีตัวอย่างข้อผิดพลาดคลาสสิกสองตัวอย่างสำหรับการล็อคและปลดล็อค ในความเป็นจริงมันไม่ยากที่จะใช้ล็อคแบบกระจายผ่าน Redis ตราบใดที่มันรับประกันว่าจะเป็นไปตามเงื่อนไขทั้งสี่ในความน่าเชื่อถือ
มีการใช้ล็อคแบบกระจายในสถานการณ์ใดเป็นหลัก? ในกรณีที่จำเป็นต้องมีการซิงโครไนซ์เช่นการแทรกชิ้นส่วนของข้อมูลต้องตรวจสอบล่วงหน้าว่าฐานข้อมูลมีข้อมูลที่คล้ายกันหรือไม่ เมื่อมีการแทรกหลายคำขอในเวลาเดียวกันอาจพิจารณาได้ว่าฐานข้อมูลไม่มีข้อมูลที่คล้ายกันและสามารถเพิ่มทั้งหมดได้ ในเวลานี้จำเป็นต้องมีการประมวลผลแบบซิงโครนัส แต่ตารางการล็อคฐานข้อมูลโดยตรงใช้เวลานานเกินไปดังนั้นจึงใช้การล็อคแบบกระจาย Redis ในเวลาเดียวกันมีเพียงเธรดเดียวเท่านั้นที่สามารถดำเนินการของการแทรกข้อมูลและเธรดอื่น ๆ กำลังรออยู่
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้เกี่ยวกับภาษา Java ที่อธิบายการใช้งานที่ถูกต้องของ Redis Distributed Locks ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน เพื่อนที่สนใจสามารถอ้างถึงหัวข้ออื่น ๆ ที่เกี่ยวข้องในเว็บไซต์นี้ต่อไป หากมีข้อบกพร่องใด ๆ โปรดฝากข้อความไว้เพื่อชี้ให้เห็น ขอบคุณเพื่อนที่ให้การสนับสนุนเว็บไซต์นี้!