هناك عمومًا ثلاث طرق لتنفيذ الأقفال الموزعة: 1. قفل قاعدة البيانات المتفائل ؛ 2. القفل الموزع على أساس redis. 3. قفل موزع على أساس zookeeper. ستقدم هذه المدونة الطريقة الثانية ، والتي تتمثل في تطبيق القفل الموزع على أساس Redis. على الرغم من وجود العديد من المدونات على الإنترنت التي تقدم تنفيذ الأقفال الموزعة Redis ، فإن تنفيذها لديه مشاكل مختلفة. من أجل تجنب الأطفال المضللين ، ستقدم هذه المدونة بالتفصيل كيفية تنفيذ الأقفال الموزعة Redis بشكل صحيح.
مصداقية
أولاً ، من أجل ضمان توفر الأقفال الموزعة ، يجب علينا على الأقل التأكد من أن تنفيذ القفل يفي بالشروط الأربعة التالية في نفس الوقت:
استبعاد متبادل. في أي وقت ، يمكن لعميل واحد فقط الاحتفاظ بالقفل.
لا يحدث أي مسدود. حتى إذا تعطل العميل أثناء فترة القفل دون فتحه بنشاط ، فيمكنه التأكد من أن العملاء الآخرين يمكنهم إضافة أقفال.
تحمل خطأ. طالما أن معظم عقد redis تعمل بشكل طبيعي ، يمكن للعميل قفل وفتح.
يجب أن يكون الشخص الذي ربط الجرس غير مرتبط. يجب أن يكون القفل وفتح العميل نفسه ، ولا يمكن للعميل فك الأقفال التي يضاف إليها الآخرين.
تنفيذ الكود
تبعيات المكون
أولاً ، نحتاج إلى تقديم مكونات Jedis Open Source عبر Maven وإضافة الكود التالي إلى ملف Pom.xml:
<Rependency> <roupiD> redis.clients </groupId> <StifactId> jedis </stifactid> <الإصدار> 2.9.0 </version> </preminent>
رمز القفل
الموقف الصحيح
الحديث رخيص ، أرني الرمز. عرض الرمز أولاً ثم اشرح سبب تنفيذ ذلك:
الفئة العامة redistool {private static final string lock_success = "ok" ؛ سلسلة نهائية ثابتة خاصة set_if_not_exist = "nx" يتم الحصول عليها بنجاح*/عام trygetDistributedLock (Jedis Jedis ، String Lockkey ، String requestId ، int Expiretime) {String result = jedis.set (lockkey ، requestid ، set_if_not_exist ، set_with_expire_time ، expiretime)كما ترون ، نقوم فقط بإضافة سطر واحد من التعليمات البرمجية: jedis.set (stringkey ، stringValue ، StringNxxx ، StringExpx ، inttime). تحتوي طريقة Set () على خمسة معلمات رسمية في المجموع:
الأول هو مفتاح ، نستخدم المفتاح كقفل ، لأن المفتاح فريد من نوعه.
والثاني هو القيمة. ما نمره هو طلب. قد لا يفهم العديد من أحذية الأطفال. أليس يكفي أن يكون لديك مفتاح كقفل؟ لماذا ما زلنا بحاجة إلى استخدام القيمة؟ والسبب هو أنه عندما تحدثنا عن الموثوقية أعلاه ، يجب أن يفي القفل الموزع بالشرط الرابع لفك ضغط الجرس. من خلال تعيين القيمة إلى requestID ، سنعرف الطلب الذي تمت إضافته إلى القفل ، وسيكون هناك أساس لإلغاء القفل. يمكن إنشاء requestID باستخدام طريقة uuid.randomuuid (). toString ().
الثالث هو NXXX. نحن نملأ هذه المعلمة NX ، مما يعني Setifnotexist ، أي عندما لا يكون المفتاح موجودًا ، فإننا نقوم بإجراء عملية المجموعة ؛ إذا كان المفتاح موجودًا بالفعل ، فلن يتم إجراء أي عملية ؛
الرابع هو expx. نقوم بتمرير هذه المعلمة PX ، مما يعني أننا نريد إضافة إعداد منتهي الصلاحية إلى هذا المفتاح. يتم تحديد الوقت المحدد بواسطة المعلمة الخامسة.
الخامس هو الوقت ، الذي يردد المعلمة الرابعة ويمثل وقت انتهاء المفتاح.
بشكل عام ، لن يؤدي تنفيذ طريقة set () أعلاه إلى نتيجتين فقط: 1. لا يوجد قفل في الوقت الحالي (المفتاح غير موجود) ، ثم يتم تنفيذ عملية القفل ويتم تعيين القفل على قفل القفل ، وتمثل القيمة العميل المقفل. 2. هناك قفل موجود بالفعل ولا يتم إجراء عملية.
إذا كنت حريصًا ، فستجد أن رمز القفل الخاص بنا يفي بالشروط الثلاثة الموضحة في موثوقيتنا. بادئ ذي بدء ، يضيف Set () معلمات NX ، والتي يمكن أن تضمن أنه في حالة وجود مفتاح بالفعل ، لن يتم استدعاء الوظيفة بنجاح ، أي أن عميلًا واحدًا فقط يمكنه الاحتفاظ بالقفل لتلبية Mutex. ثانياً ، نظرًا لأننا نضع وقت انتهاء الصلاحية للقفل ، حتى لو تعطل حامل القفل في الحادث اللاحق ولم يفتح ، سيتم إلغاء قفل القفل تلقائيًا لأنه وصل إلى وقت انتهاء الصلاحية (أي ، يتم حذف المفتاح) ، ولن يكون هناك أي مسدود. أخيرًا ، نظرًا لأننا نخصص قيمة للطلب ، والذي يمثل هوية طلب العميل المقفلة ، يمكن للعميل التحقق مما إذا كان هو نفس العميل عند فتحه. نظرًا لأننا نعتبر فقط سيناريوهات النشر المستقلة Redis ، فلن نعتبر تحمل الأعطال في الوقت الحالي.
مثال خطأ 1
مثال على الخطأ الشائع هو استخدام مزيج من jedis.setnx () و jedis.expire () لتحقيق القفل. الرمز كما يلي:
public static void errorgetlock1 (jedis jedis ، string lockkey ، requestid ، int expiretime) {long result = jedis.setnx (lockkey ، requestId) ؛ if (result == 1) {// expirtime)وظيفة طريقة setNx () هي setifnotexist ، وطريقة Expire () هي إضافة وقت انتهاء الصلاحية إلى القفل. للوهلة الأولى ، يبدو أنها هي نفس طريقة المجموعة السابقة (). ومع ذلك ، نظرًا لأن هذه أوامر redis ، فهي ليست ذرية. إذا تعطل البرنامج فجأة بعد تنفيذ SETNX () ، فإن القفل لا يحدد وقت انتهاء الصلاحية. ثم سيحدث طريق مسدود. السبب وراء تنفيذ بعض الأشخاص على الإنترنت هو أن النسخة السفلية من Jedis لا تدعم طريقة Set () متعددة المعلمات.
مثال الخطأ 2
من الصعب العثور على مثال الخطأ هذا ، والتنفيذ أكثر تعقيدًا أيضًا. فكرة التنفيذ: استخدم الأمر jedis.setnx () لتنفيذ القفل ، حيث يكون المفتاح هو القفل والقيمة هو وقت انتهاء القفل. عملية التنفيذ: 1. حاول إضافة قفل من خلال طريقة setNx (). إذا لم يكن القفل الحالي موجودًا ، فسيتم إرجاع القفل بنجاح. 2. إذا كان القفل موجودًا بالفعل ، فاحصل على وقت انتهاء القفل وقارنه بالوقت الحالي. إذا انتهت صلاحية القفل ، فقم بتعيين وقت انتهاء صلاحية جديد وإرجاع الإرجاع يتم إضافة قفل بنجاح. الرمز كما يلي:
BOOLEAN static kringgetlock2 (jedis jedis ، string lockkey ، int expiretime) {long Expires = System.CurrentTimeMillis () + Expiretime ؛ يوجد قفل ، يتم الحصول على وقت انتهاء صلاحية القفل CurrentValuestr = jedis.get (lockkey) ؛ if (currentValuestr! = null && long.parselong (currentValuestr) <system.currenttimeMillis () {// قد انتهت القفل ، الحصول على وقت انتهاء القفل السابق ، وتعيين وقت القفل ( expiresstr) ؛ if (oldvaluester! = null && oldvaluester.equals (currentValuester)) {// بالنظر إلى حالة التزامن متعدد الخيوط ، فقط في حالات الإعداد لخيط واحد هي نفس القيمة الحالية ؛إذن ما هي مشكلة هذا الرمز؟ 1. بما أن العميل يولد وقت انتهاء الصلاحية نفسه ، فمن الضروري إجبار كل عميل على مزامنة بموجب النهج الموزع. 2. عندما ينتهي القفل ، إذا قام العديد من العملاء بتنفيذ طريقة jedis.getset () في نفس الوقت ، على الرغم من أن عميل واحد فقط يمكنه قفل في النهاية ، فقد يتم كتابة وقت انتهاء صلاحية قفل هذا العميل من قبل عملاء آخرين. 3. لا يحتوي القفل على شعار المالك ، أي أن أي عميل يمكنه فتحه.
فتح رمز
الموقف الصحيح
دعونا نعرض الرمز أولاً ، ثم نشرح ببطء سبب تنفيذ ذلك:
الفئة العامة redistool {private static static final release_success = 1l ؛/*** قم بإطلاق القفل الموزع* @param jedis client* @param lockkey lock* @param request request request* rereturn ما إذا كان الإصدار ناجحًا*/public static boolean prefistributedlock (jedis jedis ، lockkey المفاتيح [1]) == argv [1] ثم إعادة redis.call ('del' ، keys [1]) else return 0 end "؛ object result = jedis.eval (script ، collections.singletonlist (lockkey) ، collections.singletonlist (requestId)) ؛ if (release_success.كما ترون ، نحتاج فقط إلى سطرين من التعليمات البرمجية لفتحه! في السطر الأول من التعليمات البرمجية ، كتبنا رمز نص LUA بسيط. آخر مرة رأينا فيها لغة البرمجة هذه كانت في "المتسلل والرسام" ، لكننا لم نتوقع استخدامها هذه المرة. في السطر الثاني من التعليمات البرمجية ، نقوم بتمرير رمز LUA إلى طريقة jedis.eval () ، وتعيين مفاتيح المعلمات [1] إلى Lockkey و Argv [1] لطلب. طريقة eval () هي تسليم رمز LUA إلى خادم Redis للتنفيذ.
إذن ما هي وظيفة رمز لوا؟ في الواقع ، الأمر بسيط للغاية. أولاً ، احصل على القيمة المقابلة للقفل ، تحقق مما إذا كان مساوياً للطلب ، وإذا كان متساويًا ، فقم بحذف القفل (إلغاء القفل). فلماذا تستخدم لغة لوا لتنفيذها؟ لأنه من الضروري التأكد من أن العمليات أعلاه ذرية. للمشاكل التي ستجلبها Atomicity ، يمكنك قراءة [فتح رمز - مثال خطأ 2]. فلماذا يمكنني تنفيذ طريقة eval () ضمان الذرة ، والتي تنشأ من خصائص redis. فيما يلي شرح جزئي لأمر تقييم على الموقع الرسمي:
ببساطة ، عندما ينفذ الأمر eval Code ، سيتم تنفيذ رمز LUA كأمر ، ولن يقوم Redis بتنفيذ أوامر أخرى حتى يتم تنفيذ أمر EVER.
مثال خطأ 1
رمز فتح أكثر شيوعًا هو استخدام طريقة jedis.del () بشكل مباشر لحذف القفل. ستؤدي طريقة فتح مباشرة دون الحكم أولاً على مالك القفل إلى فتح أي عميل في أي وقت ، حتى لو لم يكن القفل.
public static void errorreleaselock1 (Jedis Jedis ، String Lockkey) {Jedis.del (lockkey) ؛ }مثال الخطأ 2
للوهلة الأولى ، رمز فتح هذا جيد. لقد قمت بتنفيذها تقريبًا مثل هذا من قبل ، وهو ما يشبه الموقف الصحيح. الفرق الوحيد هو أنه مقسمة إلى أوامر لتنفيذها. الرمز كما يلي:
public static static void erractReleAselock2 (Jedis Jedis ، string lockkey ، string requestId) {// حدد ما إذا كان القفل وإلغاء القفل هما نفس العميل إذا (requestId.equals (jedis.get (lockkey))))بالنسبة لتعليقات التعليمات البرمجية ، فإن المشكلة هي أنه إذا تم استدعاء طريقة jedis.del () ، فسيتم إلغاء قفل القفل عندما لم يعد ينتمي إلى العميل الحالي. فهل هناك حقا مثل هذا السيناريو؟ الجواب نعم. على سبيل المثال ، العميل A Locks ، وبعد فترة من الزمن ، العميل A فتح. قبل تنفيذ jedis.del () ، ينتهي القفل فجأة. في هذا الوقت ، يحاول العميل B القفل بنجاح ، ثم يقوم العميل A بتنفيذ طريقة del () ، ثم يتم فتح قفل العميل B.
لخص
تقدم هذه المقالة بشكل أساسي كيفية تنفيذ القفل الموزع بشكل صحيح باستخدام رمز Java. يتم إعطاء أمثلة خطأ كلاسيكية للقفل وفتح. في الواقع ، ليس من الصعب تنفيذ الأقفال الموزعة من خلال Redis ، طالما أنه من الضمان تلبية الشروط الأربعة في الموثوقية.
في أي سيناريو يتم توزيع الأقفال الموزعة بشكل أساسي؟ عندما يكون التزامن مطلوبًا ، على سبيل المثال ، يتطلب إدراج جزء من البيانات التحقق مسبقًا ما إذا كانت قاعدة البيانات تحتوي على بيانات مماثلة. عند إدراج طلبات متعددة في نفس الوقت ، قد يتم تحديد أن قاعدة البيانات لا تحتوي على بيانات مماثلة ، ويمكن إضافة جميعها. في هذا الوقت ، مطلوب معالجة متزامنة ، لكن جدول قفل قاعدة البيانات المباشر يستغرق وقتًا طويلاً للغاية ، لذلك يتم استخدام القفل الموزع. في الوقت نفسه ، يمكن لخيط واحد فقط إجراء تشغيل بيانات إدخال البيانات ، وينتظر مؤشرات الترابط الأخرى.
ما سبق هو كل محتوى هذه المقالة حول لغة Java التي تصف التنفيذ الصحيح للأقفال الموزعة Redis. آمل أن يكون ذلك مفيدًا للجميع. يمكن للأصدقاء المهتمين الاستمرار في الرجوع إلى الموضوعات الأخرى ذات الصلة على هذا الموقع. إذا كانت هناك أي أوجه قصور ، فيرجى ترك رسالة لإشارةها. شكرا لك يا أصدقائك لدعمكم لهذا الموقع!