1. บทนำสู่ล็อคแบบกระจาย
ล็อคแบบกระจายส่วนใหญ่จะใช้เพื่อปกป้องทรัพยากรที่ใช้ร่วมกันในกระบวนการข้ามโฮสต์และข้ามเครือข่ายในสภาพแวดล้อมแบบกระจายเพื่อให้ได้การเข้าถึงแบบพิเศษร่วมกันเพื่อให้แน่ใจว่าข้อมูลสอดคล้องกัน
2. การแนะนำสถาปัตยกรรม
ก่อนที่จะแนะนำการใช้ Zookeeper เพื่อใช้งานล็อคแบบกระจายก่อนอื่นให้ดูที่แผนภาพสถาปัตยกรรมระบบปัจจุบัน
คำอธิบาย: พื้นที่ทั้งหมดทางด้านซ้ายแสดงถึงคลัสเตอร์ Zookeeper ล็อกเกอร์เป็น โหนดถาวร ของ Zookeeper และ Node_1, Node_2 และ Node_3 เป็น โหนดลำดับชั่วคราว ภายใต้โหนดถาวรของล็อกเกอร์ client_1, client_2, client_n หมายถึงลูกค้าหลายรายและบริการหมายถึงทรัพยากรที่ใช้ร่วมกันซึ่งต้องการการเข้าถึงแบบเอกสิทธิ์เฉพาะบุคคลร่วมกัน
แนวคิดสำหรับการได้มาซึ่งล็อคแบบกระจาย
1. แนวคิดโดยรวมของการได้รับล็อคแบบกระจาย
เมื่อได้รับล็อคแบบกระจายให้สร้างโหนดลำดับชั่วคราวภายใต้โหนดล็อกเกอร์และลบโหนดชั่วคราวเมื่อปล่อยล็อค ไคลเอนต์เรียกใช้เมธอด createnode เพื่อสร้างโหนดลำดับชั่วคราวภายใต้ล็อกเกอร์จากนั้นเรียก getChildren ("ล็อกเกอร์") เพื่อรับโหนดลูกทั้งหมดภายใต้ล็อกเกอร์ โปรดทราบว่าไม่จำเป็นต้องมีผู้เฝ้าดูในเวลานี้ หลังจากที่ลูกค้าได้รับเส้นทางโหนดเด็กทั้งหมดหากพบว่าหมายเลขโหนดลูกที่สร้างขึ้นก่อนหน้านี้มีขนาดเล็กที่สุดก็จะถือว่าลูกค้าได้รับการล็อค หากคุณพบว่าโหนดที่คุณสร้างนั้นไม่ใช่สิ่งที่เล็กที่สุดในบรรดาเด็ก ๆ ทั้งหมดของล็อกเกอร์นั่นหมายความว่าคุณยังไม่ได้รับการล็อค ในเวลานี้ลูกค้าจำเป็นต้องค้นหาโหนดที่เล็กกว่านั้นจากนั้นเรียกใช้เมธอดที่มีอยู่ () และลงทะเบียนฟังเหตุการณ์ หลังจากนั้นหากโหนดที่คุณกังวลถูกลบผู้เฝ้าดูของลูกค้าจะได้รับการแจ้งเตือนที่สอดคล้องกัน ในเวลานี้คุณจะพิจารณาอีกครั้งว่าโหนดที่คุณสร้างขึ้นนั้นเป็นหมายเลขซีเรียลที่เล็กที่สุดในโหนดเด็กล็อกเกอร์หรือไม่ Rugao ได้รับการล็อค ถ้าไม่ทำซ้ำขั้นตอนข้างต้นเพื่อดำเนินการต่อเพื่อรับโหนดที่เล็กกว่าคุณและลงทะเบียนเพื่อฟัง ยังมีการตัดสินเชิงตรรกะมากมายในกระบวนการปัจจุบัน
2. กระบวนการอัลกอริทึมหลักสำหรับการรับล็อคแบบกระจาย
ต่อไปนี้เป็นผังงานเดียวกันในการวิเคราะห์อัลกอริทึมที่สมบูรณ์สำหรับการรับล็อคแบบกระจายดังนี้:
คำอธิบาย: เมื่อไคลเอนต์ A ต้องการรับล็อคแบบกระจายก่อนอื่นให้สร้างโหนดลำดับชั่วคราว (Node_N) ภายใต้ล็อกเกอร์จากนั้นจะได้รับโหนดลูก (ระดับแรก) ทั้งหมดทันทีภายใต้ล็อกเกอร์
ในเวลานี้เนื่องจากลูกค้าหลายรายจะแข่งขันเพื่อล็อคในเวลาเดียวกันจำนวนโหนดเด็กภายใต้ล็อกเกอร์จะมากกว่า 1 สำหรับโหนดต่อเนื่องลักษณะเฉพาะคือมีตัวเลขตัวเลขหลังจากชื่อโหนด หมายเลขจำนวนของโหนดที่สร้างขึ้นก่อนมีขนาดเล็กกว่าที่สร้างขึ้นในภายหลัง ดังนั้นโหนดเด็กสามารถจัดเรียงจากขนาดเล็กถึงใหญ่ตามลำดับของคำต่อท้ายของชื่อโหนด ด้วยวิธีนี้อันแรกคือโหนดลำดับที่สร้างขึ้นก่อน ในเวลานี้มันหมายถึงลูกค้าที่มุ่งมั่นครั้งแรกสำหรับการล็อค! ในเวลานี้พิจารณาว่าโหนดที่เล็กที่สุดคือ node_n ที่สร้างโดยไคลเอนต์ A ก่อนหน้านี้หรือไม่ ถ้าเป็นเช่นนั้นหมายความว่าลูกค้า A ได้รับการล็อค ถ้าไม่ได้หมายความว่าลูกค้ารายอื่นได้รับการล็อค ดังนั้นไคลเอนต์ A ต้องรอให้ปล่อยล็อคนั่นคือไคลเอนต์ B ที่ได้รับการล็อคจะลบโหนดที่สร้างขึ้น
ในเวลานี้เราจะรู้ว่าไคลเอนต์ B ได้เปิดตัวล็อคหรือไม่โดยการฟังเหตุการณ์การลบของโหนดลำดับที่เล็กกว่า Node_N ครั้ง ถ้าเป็นเช่นนั้นไคลเอนต์ A จะได้รับเด็ก ๆ ทุกคนภายใต้ล็อกเกอร์อีกครั้งและเปรียบเทียบกับโหนด node_n ที่สร้างขึ้นด้วยตัวเองจนกระทั่ง node_n ที่สร้างขึ้นด้วยตัวเองเป็นหมายเลขลำดับที่เล็กที่สุดในหมู่เด็ก ๆ ของล็อกเกอร์ซึ่งหมายความว่าลูกค้า A ได้รับล็อค!
4. การใช้รหัสการล็อคแบบกระจายตาม Zookeeper
1. กำหนดอินเทอร์เฟซล็อคแบบกระจาย
อินเทอร์เฟซล็อคแบบกระจายที่กำหนดไว้มีดังนี้:
อินเทอร์เฟซสาธารณะ DistributedLock { / ** ได้รับการล็อคหากไม่ได้รับให้รอ* / โมฆะสาธารณะที่ได้มา () โยนข้อยกเว้น; / *** รับการล็อคจนหมดเวลา* @param เวลาหมดเวลา* @param หน่วยหน่วยหน่วยเวลาหน่วยเวลาพารามิเตอร์* @@return ว่าได้รับการล็อคหรือไม่ / *** ปล่อยล็อค* @throws Exception*/ Public Void Release () โยนข้อยกเว้น;}2. กำหนด mutex ง่ายๆ
กำหนดคลาสล็อค mutex ใช้อินเตอร์เฟสล็อคที่กำหนดไว้ด้านบนและสืบทอดการจัดอันดับพื้นฐานของคลาสพื้นฐาน คลาสฐานนี้ส่วนใหญ่จะใช้ในการโต้ตอบกับ Zookeeper รวมถึงวิธีการพยายามหาล็อคและล็อคการปล่อย
/** การใช้งานเฉพาะของอินเทอร์เฟซล็อคนั้นส่วนใหญ่ทำได้โดยคลาสแม่ที่สืบทอดมา คลาสหลักจะถูกนำไปใช้ตามรายละเอียดเฉพาะของ Zookeeper เพื่อใช้งานล็อคแบบกระจาย* /คลาสสาธารณะ SimpleDributedLockMutex ขยายการใช้งานที่ใช้งาน DistributedLock { /*ใช้เพื่อบันทึกโหนดที่ใช้การล็อคแบบกระจายใน Zookeeper สร้างโหนดลำดับชั่วคราวภายใต้โหนดนี้เพื่อใช้งานล็อคแบบกระจาย*/ สตริงสุดท้ายส่วนตัว /*คำนำหน้าล็อคชื่อ ตัวอย่างเช่นโหนดตามลำดับที่สร้างขึ้นภายใต้ล็อกเกอร์เริ่มต้นด้วยการล็อค-ซึ่งอำนวยความสะดวกในการกรองของโหนดที่ไม่เกี่ยวข้อง*โหนดที่สร้างขึ้นนั้นคล้ายกับ: ล็อค-00000001, ล็อค-00000002*/ สตริงคงที่ส่วนตัวล็อค _name = "ล็อค-"; /* ใช้เพื่อบันทึกโหนดตามลำดับที่ประสบความสำเร็จที่สร้างโดยไคลเอนต์ภายใต้ล็อกเกอร์สำหรับการดำเนินการที่เกี่ยวข้องในภายหลัง (เช่นการตัดสิน)*/ สตริงส่วนตัวของเราล็อคพา ธ ; /*** ใช้เพื่อรับทรัพยากรล็อคและรับการล็อคผ่านวิธีการรับล็อคของคลาสแม่* @param เวลาเพื่อรับเวลาหมดเวลาของการล็อค* @param หน่วยเวลาหน่วยเวลาหน่วยเวลาหน่วยเวลาหน่วยเวลา* @กลับมาว่าการล็อคนั้นได้รับการยกเว้น สำหรับรายละเอียดโปรดดูที่การใช้งานของ Litchlock ourlockpath = trymlock (เวลา, หน่วย); ส่งคืนของเรา lockpath! = null; } /*** ส่งผ่านในวัตถุการเชื่อมต่อไคลเอนต์ Zookeeper และ basepath* @param ไคลเอนต์ zookeeper การเชื่อมต่อไคลเอนต์ @param basepath basepath เป็นโหนดถาวร* /สาธารณะ simpledistributedlockmutex (zkclientext ไคลเอน โหนด *บันทึกการอ้างอิงของ basepath ไปยังแอตทริบิวต์คลาสปัจจุบัน */ super (ไคลเอนต์, basepath, lock_name); this.basepath = basepath; } /** การรับล็อคจนหมดเวลาและข้อยกเว้นจะถูกโยนลงหลังจากหมดเวลา* /โมฆะสาธารณะได้รับ () โยนข้อยกเว้น {//-1 หมายความว่าการหมดเวลาไม่ได้ถูกตั้งค่าและการหมดเวลาจะถูกกำหนดโดย Zookeeper ถ้า (! internallock (-1, null)) }} / *** ได้รับการล็อคด้วยหมดเวลา* / บูลีนสาธารณะที่ได้มา (นาน, หน่วย TimeUnit) โยนข้อยกเว้น {return internallock (เวลา, หน่วย); } / ** ปล่อยการล็อค* / public void release () โยนข้อยกเว้น {releaselock (ourlockpath); -3. รายละเอียดของการใช้งานล็อคแบบกระจาย
ตรรกะที่สำคัญสำหรับการรับล็อคแบบกระจายคือ asedributedLock ซึ่งใช้รายละเอียดของการใช้งานล็อคแบบกระจายตาม Zookeeper
คลาสสาธารณะที่ใช้งาน DIRSTRIBLEDLOCK {ไคลเอนต์ ZKClientext สุดท้ายส่วนตัว; เส้นทางสตริงสุดท้ายส่วนตัว; Basepath สตริงสุดท้ายส่วนตัว; Lockname สตริงสุดท้ายส่วนตัว; ส่วนตัวคงที่จำนวนเต็มสุดท้าย Max_Retry_Count = 10; Public BasedItributedLock (zkClientext ไคลเอนต์, เส้นทางสตริง, lockname สตริง) {this.client = ไคลเอนต์; this.basepath = เส้นทาง; this.path = path.concat ("/"). concat (lockname); this.lockName = lockname; } โมฆะส่วนตัว deleteourpath (String ourpath) โยนข้อยกเว้น {client.delete (ourpath); } สตริงส่วนตัว createLockNode (zkclient ไคลเอ็นต์, เส้นทางสตริง) พ่นข้อยกเว้น {return client.createephemeralepeartherential (พา ธ , null); } / ** * วิธีการหลักสำหรับการล็อค * @param startmillis * @param millistowait * @param ourpath * @return * @throws ยกเว้น * / บูลีนส่วนตัว waittolock (startmillis ยาว, millistowait ยาว, สตริงของเรา บูลีน Dodelete = False; ลอง {ในขณะที่ (! havethelock) {// วิธีนี้ใช้การได้มาของโหนดลำดับทั้งหมดภายใต้โหนดล็อกเกอร์และเรียงลำดับจากรายการขนาดเล็กไปจนถึงขนาดใหญ่ <String> เด็ก = getSortedChildren (); สตริง sequencenodeName = ourpath.substring (basepath.length ()+1); // คำนวณตำแหน่งการเรียงลำดับของโหนดลำดับที่สร้างโดยไคลเอนต์ตอนนี้ในโหนดเด็กทั้งหมดของล็อกเกอร์ หากการเรียงลำดับเป็น 0 นั่นหมายความว่าได้รับการล็อคแล้ว int urnindex = children.indexof (Sequencenodename); /*หากโหนดคำสั่งซื้อ [ชั่วคราว] ที่ฉันสร้างขึ้นก่อนไม่พบใน getSterstedChildren ซึ่งหมายความว่าโหนดที่เราสร้างอาจถูกลบเนื่องจากการหยุดแฟลชเครือข่าย ข้อยกเว้นจะต้องถูกโยนทิ้ง ให้ระดับก่อนหน้าจัดการ *ระดับก่อนหน้าคือการจับข้อยกเว้นและดำเนินการจำนวนที่ระบุของการลองใหม่ ดูวิธี retchlock ในวิธี retchlock ที่ตามมา*/ if (uryindex <0) {โยน zknonodeException ใหม่ ("ไม่พบ:" + sequencenodename); } // หากโหนดที่สร้างโดยไคลเอนต์ปัจจุบันมีค่ามากกว่า 0 ในรายการโหนดลูกล็อกเกอร์หมายความว่าลูกค้ารายอื่นได้รับล็อค // ในเวลานี้ไคลเอนต์ปัจจุบันต้องรอให้ลูกค้ารายอื่นปล่อยล็อค, บูลีน iSgetThelock = ourIndex == 0; // จะตรวจสอบได้อย่างไรว่าลูกค้ารายอื่นเปิดตัวล็อคหรือไม่? รับโหนดที่เล็กกว่าตัวเองจากรายการโหนดเด็กและตั้งค่าเซสชันการฟังสำหรับสตริง pathToWatch = iSgetTheLock? NULL: เด็ก. get (urerIndex - 1); if (iSgetTheLock) {havethelock = true; } else {// ถ้าโหนดที่เล็กกว่าถูกลบหมายความว่าโหนดของไคลเอนต์ปัจจุบันควรมีขนาดเล็กที่สุดดังนั้นให้ใช้ Countdownlatch เพื่อรับรู้สตริงรอก่อนหน้า = basepath .concat ("/") .concat (pathtowatch); Final Countdownlatch latch = new countdownlatch (1); ขั้นสุดท้าย izkDatalistener ก่อนหน้า liStener = ใหม่ izkDatalistener () {// เมื่อเหตุการณ์การลบโหนดเล็ก ๆ เกิดขึ้นให้ Countdownlatch สิ้นสุดและรอ // ในเวลานี้คุณต้องปล่อยให้โปรแกรมกลับมาอีกครั้งและทำการตัดสินใหม่! โมฆะสาธารณะ HandleDatadeleted (String Datapath) พ่นข้อยกเว้น {latch.countdown (); } โมฆะสาธารณะ HandleDatAchange (สตริง Datapath, ข้อมูลวัตถุ) โยนข้อยกเว้น {// ละเว้น}}; ลอง {// ข้อยกเว้นหากโหนดไม่มีอยู่ client.subsribedatachanges (ก่อนหน้า PHATH, PreviousListener); if (millistowait! = null) {millistowait - = (system.currenttimeMillis () - startmillis); startMillis = system.currentTimeMillis (); if (millistowait <= 0) {dodelete = true; // หมดเวลา - ลบการแบ่งโหนดของเรา; } latch.await (Millistowait, TimeUnit.microseconds); } else {latch.await (); }} catch (zknonodeException e) {// ละเว้น} ในที่สุด {client.unsubscribedatachanges (ก่อนหน้า, precipent, previousListener); }}}}} catch (Exception E) {// ข้อยกเว้นจะต้องถูกลบ Dodelete = true; โยน e; } ในที่สุด {// ถ้าคุณต้องการลบโหนดถ้า (Dodelete) {deleteourpath (ourpath); }} return havethelock; } สตริงส่วนตัว getLockNodEnumber (สตริง str, สตริง lockname) {int index = str.lastindexof (lockname); if (index> = 0) {index += lockname.length (); return index <= str.length ()? str.substring (ดัชนี): ""; } return str; } รายการส่วนตัว <String> getTortEdChildren () โยนข้อยกเว้น {ลอง {รายการ <String> children = client.getChildren (basepath); collections.sort (เด็ก, ตัวเปรียบเทียบใหม่ <string> () {public int Compare (String LHS, String rhs) {return getLockNodenumber (LHS, lockname) .Compareto (getLockNodenumber (rhs, lockname));}}); คืนเด็ก; } catch (zknonodeException e) {client.createPersistent (basepath, true); กลับมา getTortedChildren (); }} void protected releaselock (String lockpath) โยนข้อยกเว้น {deleteourpath (lockpath); } string string retedlock (นาน, unit timeunit) โยนข้อยกเว้น {สุดท้าย startmillis ยาว = system.currentTimeMillis (); Final Long MilliStowait = (UNIT! = NULL)? UNIT.TOMILLIS (เวลา): NULL; String OurPath = NULL; บูลีน hasthelock = false; บูลีน iSdone = false; int retryCount = 0; // net flash break ต้องลองอีกครั้งในขณะที่ (! isdone) {isdone = true; ลอง {// createLockNode ใช้เพื่อสร้างโหนดลำดับ [ชั่วคราว] สำหรับไคลเอนต์เพื่อรับล็อคภายใต้ล็อกเกอร์ (โหนดถาวร Basepath) OurPath = createLockNode (ไคลเอนต์เส้นทาง); / *** วิธีนี้ใช้เพื่อตรวจสอบว่าได้รับการล็อคหรือไม่นั่นคือไม่ว่าโหนดคำสั่งที่สร้างขึ้นด้วยตัวเองนั้นเล็กที่สุดในบรรดาโหนดเด็กทั้งหมดของล็อกเกอร์* หากล็อคไม่ได้รับรอการปล่อยล็อคที่จะปล่อยออกมาอีกครั้ง } catch (zknonodeException e) {if (retryCount ++ <max_retry_count) {isdone = false; } else {โยน e; }}}} ถ้า (hasthelock) {return our path; } return null; -ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น