اذهب إلى المحتوى

سنُكمِل في التمارين القليلة القادمة بناء محرك البحث الذي تحدثنا عنه في مقالة تنفيذ أسلوب البحث بالعمق أولًا تعاوديًّا وتكراريًّا. يتكوَّن أيّ محرك بحثٍ من الوظائف التالية:

  • الزحف crawling: ويُنفّذُ من خلال برنامجٍ بإمكانه تحميلُ صفحة إنترنت وتحليلُها واستخراجُ النص وأيِّ روابط إلى صفحاتٍ أخرى.
  • الفهرسة indexing: وتنفّذُ من خلال هيكل بيانات data structure بإمكانه البحث عن كلمة والعثور على الصفحات التي تحتوي على تلك الكلمة.
  • الاسترجاع retrieval: وهي طريقةٌ لتجميع نتائج المُفهرِس واختيار الصفحات الأكثر صلة بكلمات البحث.

إذا كنت قد أتممت تمرين مقالة استخدام خريطة ومجموعة لبناء مُفهرِّس Indexer، فقد نفَّذت مُفهرسًا بالفعل باستخدام خرائط جافا. سنناقش هذا التمرين هنا وسنُنشِئ نسخةً جديدةً تُخزِّن النتائج في قاعدة بيانات.

وإذا كنت قد أكملت تمرين مقالة تنفيذ أسلوب البحث بالعمق أولًا باستخدام الواجهتين Iterables و Iterators، فقد نفَّذت بالفعل زاحفًا يَتبِع أول رابطٍ يعثرُ عليه. سنُنشِئ في التمرين التالي نسخةً أعمّ تُخزِّن كل رابطٍ تجده في رتل queue، وتتبع تلك الروابط بالترتيب.

في النهاية، ستُكلّف بالعمل على برنامج الاسترجاع.

سنُوفِّر شيفرةً مبدئيّةً أقصر في هذه التمارين، وسنعطيك فرصةً أكبر لاتخاذ القرارات المتعلقة بالتصميم. وتجدر الإشارة إلى أن هذه التمارين ذات نهايات مفتوحة، أي سنطرح عليك فقط بعض الأهداف البسيطة التي يتعين عليك الوصول إليها، ولكنك تستطيع بالطبع المُضِي قدمًا إذا أردت المزيد من التحدي.

والآن، سنبدأ بالنسخة الجديدة من المُفهرِس.

قاعدة بيانات Redis

تُخزِّن النسخة السابقة من المُفهرِس البيانات في هيكلَيْ بياناتٍ: الأول هو كائنٌ من النوع TermCounter يَربُط كل كلمة بحثٍ بعدد المرات التي ظهرت فيها الكلمة في صفحة إنترنت معينةٍ، والثاني كائنٌ من النوع Index يربُط كلمة البحث بمجموعة الصفحات التي ظهرت فيها.

يُخزَّن هيكلا البيانات في ذاكرة التطبيق، ولذا يتلاشيان بمجرد انتهاء البرنامج. توصف البيانات التي تُخزَّن فقط في ذاكرة التطبيق بأنها "متطايرة volatile"؛ لأنها تزول بمجرد انتهاء البرنامج.

في المقابل، تُوصف البيانات التي تظل موجودةً بعد انتهاء البرنامج الذي أنشأها بأنها "مستمرة persistent". مثال ذلك الملفات المُخزَّنة في نظام الملفات فهي مُستمرة في العموم، وكذلك البيانات المُخزَّنة في قاعدة بيانات أيضًا مستمرة.

يُعدّ تخزين البيانات في ملف واحدة من أبسط طرق حفظ البيانات، فيُمكِننا ببساطة أن نحوِّل هياكل البيانات المُتضمِّنة للبيانات إلى صيغة JSON ثم نكتبها إلى ملف قبل انتهاء البرنامج. عندما نُشغِّل البرنامج مرة أخرى، سنتمكَّن من قراءة الملف وإعادة بناء هياكل البيانات.

ولكن هناك مشكلتان في هذا الحل:

  1. عادةً ما تكون عمليتا قراءة هياكل البيانات الضخمة (مثل مفهرس الويب) وكتابتها عمليتين بطيئتين.
  2. قد لا تستوعب مساحة ذاكرة برنامج واحد هيكل البيانات بأكمله.
  3. إذا انتهى برنامجٌ معينٌ على نحوٍ غير متوقع (نتيجة لانقطاع الكهرباء مثلًا)، فسنفقد جميع التغييرات التي أجريناها على البيانات منذ آخر مرة فتحنا فيها البرنامج.

وهناك طريقة أخرى لحفظ البيانات وهي قواعد البيانات. إذ تُعدّ قواعدُ البيانات البديلَ الأفضلَ، فهي تُوفِّر مساحةَ تخزينٍ مستمرةً، كما أنها قادرةٌ على قراءة جزءٍ من قاعدة البيانات أو كتابته دون الحاجة إلى قراءة قاعدة البيانات أو كتابتها بالكامل.

تتوفَّر الكثير من أنواع نظم إدارة قواعد البيانات DBMS، ويتمتع كلٌّ منها بإمكانيات مختلفة. ويُمكِنك قراءة مقارنة بين أنظمة إدارة قواعد البيانات العلاقية والاطلاع على سلسلة تصميم قواعد البيانات.

تُوفِّر قاعدة بيانات Redis -التي سنَستخدِمها في هذا التمرين- هياكل بيانات مستمرة مشابهةً لهياكل البيانات التي تُوفِّرها جافا، فهي تُوفِّر:

  • قائمة سلاسل نصية مشابهة للنوع List.
  • جداول مشابهة للنوع Map.
  • مجموعات من السلاسل النصية مشابهة للنوع Set.

تُعدّ Redis قاعدة بيانات من نوع زوج مفتاح/قيمة، ويَعني ذلك أن هياكل البيانات (القيم) التي تُخزِّنها تكون مُعرَّفةً بواسطة سلاسل نصية ٍفريدةٍ (مفاتيح). تلعب المفاتيح في قاعدة بيانات Redis نفس الدور الذي تلعبه المراجع references في لغة جافا، أي أنّها تُعرِّف هوية الكائنات. سنرى أمثلةً على ذلك لاحقًا.

خوادم وعملاء Redis

عادةً ما تَعمَل قاعدة بيانات Redis كخدمةٍ عن بعد، فكلمة Redis هي في الحقيقة اختصار لعبارة "خادم قاموس عن بعد REmote DIctionary Server"، ولنتمكن من استخدامها، علينا أن نُشغِّل خادم Redis في مكانٍ ما ثم نَتصِل به عبر عميل Redis. من الممكن إعداد ذلك الخادم بأكثرَ من طريقةٍ، كما يُمكِننا الاختيار من بين العديد من برامج العملاء، وسنَستخدِم في هذا التمرين ما يلي:

  1. بدلًا من أن نُثبِّت الخادم ونُشغِّله بأنفسنا، سنَستخدِم خدمةً مثل RedisToGo. تُشغِّل تلك الخدمة قاعدة بيانات Redis على السحابة cloud، وتُقدِّم خطةً مجانيةً بمواردَ تتناسب مع متطلبات هذا التمرين.
  2. بالنسبة للعميل، سنَستخدِم Jedis، وهو عبارةٌ عن مكتبة جافا تحتوي على أصنافٍ وتوابعَ يُمكِنها العمل مع قاعدة بيانات Redis.

انظر إلى التعليمات المُفصَّلة التالية لمساعدتك على البدء:

  • أنشِئ حسابًا على موقع RedisToGo، واختر الخطة التي تريدها (ربما الخطة المجانية).
  • أنشِئ نسخة آلة افتراضية يعمل عليها خادم Redis. إذا نقرت على تبويب "Instances"، ستجد أن النسخة الجديدة مُعرَّفة باسم استضافة ورقم منفذ. كان اسم النسخة الخاصة بنا مثلًا هو dory-10534.
  • انقر على اسم النسخة لكي تفتح صفحة الإعدادات، وسجِّل اسم مُحدّد الموارد الموحد URL الموجود أعلى الصفحة. سيكون مشابهًا لما يلي:
redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534    

يحتوي محدد الموارد الموحد السابق ذكره على اسم الاستضافة الخاص بالخادم dory.redistogo.com، ورقم المنفذ 10534، وكلمة المرور التي سنحتاج إليها للاتصال بالخادم، وهي السلسلة النصية الطويلة المُكوَّنة من أحرف وأعدادٍ في المنتصف. ستحتاج تلك المعلومات في الخطوة التالية.

إنشاء مفهرس يعتمد على Redis

ستجد الملفات التالية الخاصة بالتمرين في مستودع الكتاب:

  • JedisMaker.java: يحتوي على بعض الأمثلة على الاتصال بخادم Redis وتشغيل بعض توابع Jedis.
  • JedisIndex.java: يحتوي على شيفرةٍ مبدئيةٍ لهذا التمرين.
  • JedisIndexTest.java: يحتوي على اختبارات للصنف JedisIndex.
  • WikiFetcher.java: يحتوي على شيفرةٍ تقرأ صفحات إنترنت وتُحلِّلها باستخدام مكتبة jsoup. كتبنا تلك الشيفرة في تمارين المقالات المشار إليها بالأعلى.

ستجد أيضًا الملفات التالية التي كتبناها في نفس تلك التمارين:

  • Index.java: يُنفِّذ مفهرِسًا باستخدام هياكل بيانات تُوفِّرها جافا.
  • TermCounter.java: يُمثِل خريطةً تربُط كلمات البحث بعدد مرات حدوثها.
  • WikiNodeIterable.java: يمرّ عبر عقد شجرة DOM الناتجة من مكتبة jsoup.

إذا تمكَّنت من كتابة نسخك الخاصة من تلك الملفات، يُمكِنك استخدامها لهذا التمرين. إذا لم تكن قد أكملت تلك التمارين أو أكملتها ولكنك غير متأكّد مما إذا كانت تَعمَل على النحو الصحيح، يُمكِنك أن تَنسَخ الحلول من مجلد solutions.

والآن، ستكون خطوتك الأولى هي استخدام عميل Jedis للاتصال بخادم Redis الخاص بك. يُوضِّح الصنف RedisMaker.java طريقة القيام بذلك: عليه أولًا أن يقرأ معلومات الخادم من ملفٍّ، ثم يتصل به، ويُسجِل دخوله باستخدام كلمة المرور، وأخيرًا، يُعيد كائنًا من النوع Jedis الذي يُمكِن استخدامه لتنفيذ عمليات Redis.

ستجد الصنف JedisMaker مُعرَّفًا في الملف JedisMaker.java. يَعمَل ذلك الصنف كصنف مساعد حيث يحتوي على التابع الساكن make الذي يُنشِئ كائنًا من النوع Jedis. يُمكِنك أن تَستخدِم ذلك الكائن بعد التصديق عليه للاتصال بقاعدة بيانات Redis الخاصة بك.

يقرأ الصنف JedisMaker بيانات خادم Redis من ملفٍّ اسمه redis_url.txt موجودٍ في المجلد src/resources:

  • استخدم مُحرّرَ نصوصٍ لإنشاء وتعديل الملف ThinkDataStructures/code/src/resources/redis_url.txt.
  • ضع فيه مُحدّد موارد الخادم الخاص بك. إذا كنت تَستخدِم خدمة RedisToGo، سيكون محدد الموارد مشابهًا لما يلي:
redis://redistogo:1234567feedfacebeefa1e1234567@dory.redistogo.com:10534

لا تضع هذا الملف في مجلدٍ عامٍّ لأنه يحتوي على كلمة مرور خادم Redis. يُمكِنك تجنُّب وقوع ذلك عن طريق الخطأ باستخدام الملف ‎.gitignore الموجود في مستودع الكتاب لمنع وضع الملف فيه.

نفِّذ الأمر ant build لتصريف ملفات الشيفرة والأمر ant JedisMaker لتشغيل المثال التوضيحيّ بالتابع main:

    public static void main(String[] args) {

        Jedis jedis = make();

        // String
        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");
        System.out.println("Got value: " + value);

        // Set
        jedis.sadd("myset", "element1", "element2", "element3");
        System.out.println("element2 is member: " + 
                           jedis.sismember("myset", "element2"));

        // List
        jedis.rpush("mylist", "element1", "element2", "element3");
        System.out.println("element at index 1: " + 
                           jedis.lindex("mylist", 1));

        // Hash
        jedis.hset("myhash", "word1", Integer.toString(2));
        jedis.hincrBy("myhash", "word2", 1);
        System.out.println("frequency of word1: " + 
                           jedis.hget("myhash", "word1"));
        System.out.println("frequency of word1: " + 
                            jedis.hget("myhash", "word2"));

        jedis.close();
    }

يَعرِض المثال أنواع البيانات والتوابع التي ستحتاج إليها غالبًا في هذا التمرين. ينبغي أن تحصل على الخرج التالي عند تشغيله:

Got value: myvalue
element2 is member: true
element at index 1: element2
frequency of word1: 2
frequency of word2: 1

سنشرح طريقة عمل تلك الشيفرة في القسم التالي.

أنواع البيانات في قاعدة بيانات Redis

تَعمَل Redis كخريطةٍ تربط مفاتيحَ (سلاسل نصيّة) بقيم. قد تنتمي تلك القيم إلى مجموعة أنواع مختلفة من البيانات. يُعدّ النوع string واحدًا من أبسط الأنواع التي تُوفِّرها قاعدة بيانات Redis. لاحظ أننا سنكتب أنواع بيانات Redis بخطوط مائلة لنُميزها عن أنواع جافا.

سنَستخدِم التابع jedis.set لإضافة سلسلةٍ نصيّةٍ من النوع string إلى قاعدة البيانات. قد تجد ذلك مألوفًا، فهو يشبه التابع Map.put، حيث تُمثِل المعاملات المُمرَّرة المفتاح الجديد وقيمته المقابلة. في المقابل، يُستخدَم التابع jedis.get للبحث عن مفتاحٍ معينٍ والعثور على قيمته. انظر الشيفرة إلى التالية:

        jedis.set("mykey", "myvalue");
        String value = jedis.get("mykey");

كان المفتاح هو "mykey" والقيمة هي "myvalue" في هذا المثال.

تُوفِّر Redis هيكل البيانات set الذي يَعمَل بشكلٍ مشابهٍ للنوع Set<String>‎ في جافا. إذا أردت أن تضيف عنصرًا جديدًا إلى مجموعةٍ من النوع set، اختر مفتاحًا يُحدّد هوية تلك المجموعة، ثم اِستخدِم التابع jedis.sadd كما يلي:

        jedis.sadd("myset", "element1", "element2", "element3");
        boolean flag = jedis.sismember("myset", "element2");

لاحِظ أنه ليس من الضروري إنشاء المجموعة بخطوةٍ منفصلةٍ، حيث تُنشؤها Redis إن لم تكن موجودةً. تُنشِئ Redis في هذا المثال مجموعةً من النوع set اسمها myset تحتوي على ثلاثة عناصر.

يفحص التابع jedis.sismember ما إذا كان عنصر معين موجودًا في مجموعة من النوع set. تَستغرِق عمليتا إضافة العناصر وفحص عضويّتها زمنًا ثابتًا.

تُوفِّر Redis أيضًا هيكل البيانات list الذي يشبه النوع List<String>‎ في جافا. يضيف التابع jedis.rpush العناصر إلى النهاية اليمنى من القائمة، كما يلي:

        jedis.rpush("mylist", "element1", "element2", "element3");
        String element = jedis.lindex("mylist", 1);

مثلما سبق، لا يُعدّ إنشاء هيكل البيانات قبل إضافة العناصر إليه أمرًا ضروريًا. يُنشِئ هذا المثال قائمةً من النوع list اسمها mylist تحتوي على ثلاثة عناصر.

يَستقبِل التابع jedis.lindex فهرسًا هو عبارةٌ عن عددٍ صحيحٍ، ويعيد عنصرَ القائمةِ المشارَ إليه. تَستغرِق عمليتا إضافة العناصر واسترجاعها زمنًا ثابتًا.

أخيرًا، تُوفِّر Redis الهيكل hash الذي يشبه النوع Map<String, String>‎ في جافا. يضيف التابع jedis.hset مُدخَلًا جديدًا إلى الجدول على النحو التالي:

        jedis.hset("myhash", "word1", Integer.toString(2));
        String value = jedis.hget("myhash", "word1");

يُنشِئ هذا المثال جدولًا جديدًا اسمه myhash يحتوي على مدخلٍ واحدٍ يربُط المفتاح word1 بالقيمة "2".

تنتمي المفاتيح والقيم إلى النوع string، ولذلك، إذا أردنا أن نُخزِّن عددًا صحيحًا من النوع Integer، علينا أن نُحوِّله أولًا إلى النوع String قبل أن نَستدعِي التابع hset. وبالمثل، عندما نبحث عن قيمة باستخدام التابع hget، ستكون النتيجة من النوع String، ولذلك، قد نضطرّ إلى تحويلها مرةً أخرى إلى النوع Integer.

قد يكون العمل مع النوع hash في قاعدة بيانات Redis مربكًا نوعًا ما؛ لأننا نَستخدِم مفتاحين، الأول لتحديد الجدول الذي نريده، والثاني لتحديد القيمة التي نريدها من الجدول. في سياق قاعدة بيانات Redis، يُطلَق على المفتاح الثاني اسم "الحقل field"، أي يشير مفتاح مثل myhash إلى جدولٍ معينٍ من النوع hash بينما يشير حقلٌ مثل word1 إلى قيمةٍ ضمن ذلك الجدول.

عادةً ما تكون القيم المُخزَّنة في جداول من النوع hash أعدادًا صحيحة، ولذلك، يوفِّر نظام إدارة قاعدة بيانات Redis بعض التوابع الخاصة التي تعامل القيم وكأنها أعداد مثل التابع hincrby. انظر إلى المثال التالي:

        jedis.hincrBy("myhash", "word2", 1);

يسترجع هذا التابع المفتاح myhash، ويحصل على القيمة الحالية المرتبطة بالحقل word2 (أو على الصفر إذا لم يكن الحقل موجودًا)، ثم يزيدها بمقدار 1، ويكتبها مرةً أخرى في الجدول.

تستغرق عمليات إضافة المُدْخَلات إلى جدول من النوع hash واسترجاعها وزيادتها زمنًا ثابتًا.

تمرين 11

بوصولك إلى هنا، أصبح لديك كل المعلومات الضرورية لإنشاء مُفهرسٍ قادرٍ على تخزين النتائج في قاعدة بيانات Redis.

والآن، نفِّذ الأمر ant JedisIndexTest. ستفشل بعض الاختبارات لأنه ما يزال أمامنا بعض العمل.

يختبر الصنف JedisIndexTest التوابع التالية:

  • JedisIndex: يستقبل هذا الباني كائنًا من النوع Jedis كمعامل.
  • indexPage: يضيف صفحة إنترنت إلى المفهرس. يَستقبِل سلسلةً نصيّةً من النوع String تُمثِل مُحدّد موارد URL بالإضافة إلى كائن من النوع Elements يحتوي على عناصر الصفحة المطلوب فهرستها.
  • getCounts: يستقبل كلمةَ بحثٍ ويعيد خريطةً من النوع Map<String, Integer>‎ تربُط كل محدد مواردَ يحتوي على تلك الكلمة بعدد مرات ظهورها في تلك الصفحة.

يُوضِح المثال التالي طريقة استخدامِ تلك التوابع:

        WikiFetcher wf = new WikiFetcher();
        String url1 = 
            "http://en.wikipedia.org/wiki/Java_(programming_language)";
        Elements paragraphs = wf.readWikipedia(url1);

        Jedis jedis = JedisMaker.make();
        JedisIndex index = new JedisIndex(jedis);
        index.indexPage(url1, paragraphs);
        Map<String, Integer> map = index.getCounts("the");

إذا بحثنا عن url1 في الخريطة الناتجة map، ينبغي أن نحصل على 339، وهو عدد مرات ظهور كلمة "the" في مقالة ويكيبيديا عن لغة جافا (نسخة المقالة التي خزّناها).

إذا حاولنا فهرسة الصفحة مرةً أخرى، ستحُلّ النتائج الجديدة محل النتائج القديمة.

إذا أردت تحويل هياكل البيانات من جافا إلى Redis، فتذكّر أن كل كائنٍ مُخزّنٍ في قاعدة بيانات Redis مُعرَّفٌ بواسطة مفتاحٍ فريدٍ من النوع string. إذا كان لديك نوعان من الكائنات في نفس قاعدة البيانات، فقد ترغب في إضافة كلمةٍ إلى بداية المفاتيح لتمييزها عن بعضها. على سبيل المثال، لدينا النوعان التاليان من الكائنات:

  • يُمثِل النوع URLSet مجموعةً من النوع set في قاعدة بيانات Redis. تحتوي تلك المجموعة على محددات الموارد الموحدة URL التي تحتوي على كلمةٍ بحثٍ معينة. يبدأ المفتاح الخاص بكل قيمةٍ من النوع URLSet بكلمة":URLSet"، وبالتالي، لكي نحصل على محددات الموارد الموحدة التي تحتوي على كلمة "the"، علينا أن نسترجع المجموعة التي مفتاحها هو"URLSet:the".
  • يُمثِل النوع TermCounter جدولًا من النوع hash في قاعدة بيانات Redis. يربُط هذا الجدول كل كلمة بحث ظهرت في صفحة معيّنة بعدد مرات ظهورها. يبدأ المفتاح الخاصُّ بكل قيمة من النوع TermCounter بكلمة "TermCounter:‎" وينتهي بمحدد الموارد الموحّدِ الخاص بالصفحة التي نبحث فيها.
  • يحتوي التنفيذ الخاص بنا على قيمةٍ من النوع URLSet لكل كلمة بحثٍ، وعلى قيمةٍ من النوع TermCounter لكل صفحة مُفهرسَة. وفَّرنا أيضًا التابعين المساعدين urlSetKey و termCounterKey لإنشاء تلك المفاتيح.

المزيد من الاقتراحات

أصبح لديك الآن كل المعلومات الضرورية لحل التمرين، لذا يُمكِنك أن تبدأ الآن إذا أردت، ولكن ما يزال هناك بعض الاقتراحات القليلة التي ننصحك بقراءتها:

  • سنوفِّر لك مساعدةً أقلّ في هذا التمرين، ونترك لك حريّة أكبر في اتخاذ بعض القرارات المتعلقة بالتصميم. سيكون عليك تحديدًا أن تُفكِّر بالطريقة التي ستُقسِّم بها المشكلة إلى أجزاءَ صغيرةٍ يُمكِن اختبار كلٍّ منها على حدة. بعد ذلك، ستُجمِّع تلك الأجزاء إلى حلٍّ كاملٍ. إذا حاولت كتابة الحل بالكامل على خطوةٍ واحدةٍ بدون اختبار الأجزاء الأصغر، فقد تستغرِق وقتًا طويلًا جدًا لتنقيح الأخطاء.
  • تُمثِّل الاستمرارية واحدةً من تحديات العمل مع البيانات المستمرة، لأن الهياكل المُخزَّنة في قواعد البيانات قد تتغير في كل مرةٍ تُشغِّل فيها البرنامج. فإذا تسبَّبت بخطأٍ في قاعدة البيانات، سيكون عليك إصلاحه أو البدء من جديد. ولكي نُبقِي الأمور تحت السيطرة، وفّرنا لك التوابع deleteURLSets و deleteTermCounters و deleteAllKeys التي تستطيع أن تَستخدِمها لتنظيف قاعدة البيانات والبدء من جديد. يُمكِنك أيضًا استخدام التابع printIndex لطباعة محتويات المُفهرِس.
  • في كلّ مرةٍ تستدعي فيها أيًّا من توابع Jedis، فإنه يُرسِل رسالةً إلى الخادم الذي يُنفِّذ بدوره الأمر المطلوب، ثم يردّ برسالة. إذا نفَّذت الكثير من العمليات الصغيرة، فستحتاج إلى وقت طويل لمعالجتها، ولهذا، من الأفضل تجميع متتالية من العمليات ضمن معاملة واحدة من النوع Transaction لتحسين الأداء.

على سبيل المثال، انظر إلى تلك النسخة البسيطة من التابع deleteAllKeys:

    public void deleteAllKeys() {
        Set<String> keys = jedis.keys("*");
        for (String key: keys) {
            jedis.del(key);
        }
    }

في كل مرةٍ تَستدعِي خلالها التابع del، يضطرّ العميل إلى إجراء اتصالٍ مع الخادم وانتظار الرد. إذا كان المُفهرِس يحتوي على بضع صفحاتٍ، فقد يَستغرِق تنفيذ ذلك التابع وقتًا طويلًا. بدلًا من ذلك، يُمكِنك أن تُسرِّع تلك العملية باستخدام كائنٍ من النوع Transaction على النحو التالي:

    public void deleteAllKeys() {
        Set<String> keys = jedis.keys("*");
        Transaction t = jedis.multi();
        for (String key: keys) {
            t.del(key);
        }
        t.exec();
    }

يعيد التابع jedis.multi كائنًا من النوع Transaction. يُوفِّر هذا الكائن جميع التوابع المتاحة في كائنات النوع Jedis. عندما تستدعي أيًّا من تلك التوابع بكائنٍ من النوع Transaction، فإن العميل لا يُنفِّذها تلقائيًا، ولا يتصل مع الخادم، وإنما يُخزِّن تلك العمليات إلى أن تَستدعِي التابع exec، وعندها، يُرسِل جميعَ العملياتِ المُخزَّنة إلى الخادم في نفس الوقت، وهو ما يكون أسرع عادةً.

تلميحات بسيطة بشأن التصميم

الآن وقد أصبح لديك جميع المعلومات المطلوبة، يمكنك البدء في حل التمرين. إذا لم يكن لديك فكرةٌ عن طريقة البدء، يُمكِنك العودة لقراءة المزيد من التلميحات البسيطة.

لا تتابع القراءة قبل أن تُشغِّل شيفرة الاختبار، وتُجرِّب بعض أوامر Redis البسيطة، وتكتب بعض التوابع الموجودة في الملف JedisIndex.java.

إذا لم تتمكّن من متابعة الحل فعلًا، إليك بعض التوابع التي قد ترغب في العمل عليها:

    /**
     * أضف محدد موارد موحدًا إلى المجموعة الخاصة بكلمة
     */
    public void add(String term, TermCounter tc) {}

    /**
     * ابحث عن كلمة وأعد مجموعة مُحدِّدات الموارد الموحدة التي تحتوي عليها
     */
    public Set<String> getURLs(String term) {}

    /**
     * أعد عدد مرات ظهور كلمة معينة بمحدد موارد موحد
     */
    public Integer getCount(String url, String term) {}

    /**
     * ‫أضف محتويات كائن من النوع `TermCounter` إلى قاعدة بيانات Redis
     */
    public List<Object> pushTermCounterToRedis(TermCounter tc) {}

تلك هي التوابع التي استخدمناها في الحل، ولكنها ليست بالتأكيد الطريقة الوحيدة لتقسيم المشكلة. لذلك، استعن بتلك الاقتراحات فقط إذا وجدتها مفيدة، وتجاهلها تمامًا إذا لم تجدها كذلك.

اكتب بعض الاختبارات لكل تابعٍ منها أولًا، فبمعرفة الطريقة التي ينبغي بها أن تختبر تابعًا معينًا، عادةً ما تتكوَّن لديك فكرةٌ عن طريقة كتابته.

ترجمة -بتصرّف- للفصل Chapter 14: Persistence من كتاب Think Data Structures: Algorithms and Information Retrieval in Java.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...