تدفقات Redis كهيكل بيانات نظيف

أثارت بنية بيانات Redis 5 الجديدة ، التي يطلق عليها تدفقات ، اهتمامًا كبيرًا بالمجتمع. بطريقة ما سأتحدث مع أولئك الذين يستخدمون الجداول في الإنتاج والكتابة عنها. لكن الآن أريد أن أفكر في موضوع مختلف قليلاً. يبدو لي أن الكثير من الناس يفكرون في التدفقات كنوع من أداة سريالية لحل المهام الصعبة بشكل رهيب. في الواقع ، توفر بنية البيانات * هذه أيضًا المراسلة ، ولكن سيكون تبسيطًا لا يصدق افتراض أن وظيفة Redis Streams محدودة فقط بهذا.

التدفقات هي قالب رائع و "نموذج عقلي" يمكن استخدامه بنجاح كبير في تصميم النظام ، ولكن التدفقات الواقعية ، مثل معظم هياكل بيانات Redis ، هي بنية أكثر عمومية ويمكن استخدامها لمجموعة من المهام الأخرى. في هذه المقالة ، سنقدم التدفقات كهيكل بيانات خالص ، متجاهلاً تمامًا عمليات الحظر ، ومجموعات المستلمين ، وجميع وظائف المراسلة الأخرى.

تدفقات - وهذا هو CSV على المنشطات


إذا كنت تريد تسجيل عدد من عناصر البيانات المهيكلة وتعتقد أن قاعدة البيانات ستكون فائضة هنا ، يمكنك ببساطة فتح الملف في وضع append only وكتابة كل سطر كـ CSV (القيمة المفصولة بفواصل):

 (open data.csv in append only) time=1553096724033,cpu_temp=23.4,load=2.3 time=1553096725029,cpu_temp=23.2,load=2.1 

يبدو بسيطا. لقد فعل الناس ذلك منذ وقت طويل وما زالوا يفعلون ذلك: إنه قالب موثوق به ، إذا كنت تعرف ما هو. ولكن ماذا سيكون المعادل في الذاكرة؟ في الذاكرة ، تصبح معالجة البيانات الأكثر تقدمًا ممكنة ، وتتم إزالة العديد من قيود ملفات CSV تلقائيًا ، مثل:

  1. من الصعب (غير فعال) تلبية طلبات النطاق.
  2. الكثير من المعلومات الزائدة: يحتوي كل سجل على نفس الوقت تقريبًا ، ويتم تكرار الحقول. في الوقت نفسه ، سيؤدي حذف البيانات إلى جعل التنسيق أقل مرونة إذا كنت أريد التبديل إلى مجموعة مختلفة من الحقول.
  3. إزاحة العناصر هي ببساطة إزاحة بايت في الملف: إذا قمنا بتغيير بنية الملف ، فسوف تصبح الإزاحات خاطئة ، لذلك لا يوجد مفهوم حقيقي لمعرف أساسي. لا يمكن تقديم الإدخالات في جوهرها بشكل لا لبس فيه.
  4. بدون القدرة على تجميع البيانات المهملة وبدون إعادة كتابة السجل ، لا يمكنك حذف الإدخالات ، ولكن يمكنك تمييزها على أنها غير صالحة. عادةً ما تمتص سجلات إعادة الكتابة لعدة أسباب ، من المستحسن تجنبها.

في الوقت نفسه ، يعد سجل CSV هذا جيدًا بطريقته الخاصة: لا يوجد هيكل ثابت ، يمكن أن تتغير الحقول ، ومن السهل إنشاءه ، وهو مضغوط تمامًا. كانت الفكرة مع تيارات Redis هي الحفاظ على الفضائل ، ولكن للتغلب على القيود. والنتيجة هي بنية بيانات هجينة تشبه إلى حد بعيد مجموعات فرز Redis: فهي تشبه * بنية البيانات الأساسية ، ولكنها تستخدم عدة تمثيلات داخلية للحصول على هذا التأثير.

مقدمة إلى سلاسل الرسائل (يمكنك تخطي إذا كنت على دراية بالأساسيات)


يتم تمثيل تدفقات Redis كعقد ماكرو مضغوطة دلتا متصلة بواسطة شجرة قاعدة. نتيجة لذلك ، يمكنك البحث بسرعة عن سجلات عشوائية ، والحصول على النطاقات ، وحذف العناصر القديمة ، وما إلى ذلك. في الوقت نفسه ، تشبه واجهة المبرمج إلى حد كبير ملف CSV:

 > XADD mystream * cpu-temp 23.4 load 2.3 "1553097561402-0" > XADD mystream * cpu-temp 23.2 load 2.1 "1553097568315-0" 

كما ترون من المثال ، يقوم أمر XADD تلقائيًا بإنشاء معرف السجل وإرجاعه ، والذي يزيد بشكل رتيب ويتكون من جزأين: <time> - <counter>. الوقت بالميلي ثانية ، ويتم زيادة العداد للسجلات في نفس الوقت.

لذلك ، أول تجريد جديد لفكرة ملف CSV في وضع append only هو استخدام العلامة النجمية كوسيطة ID لـ XADD: هذه هي الطريقة التي نحصل بها على معرف السجل من الخادم مجانًا. هذا المعرف مفيد ليس فقط للإشارة إلى عنصر معين في الدفق ، بل يرتبط أيضًا بالوقت الذي تم فيه إضافة السجل إلى الدفق. في الواقع ، مع XRANGE ، يمكنك تنفيذ استعلامات النطاق أو استرداد العناصر الفردية:

 > XRANGE mystream 1553097561402-0 1553097561402-0 1) 1) "1553097561402-0" 2) 1) "cpu-temp" 2) "23.4" 3) "load" 4) "2.3" 

في هذه الحالة ، استخدمت نفس المعرّف لبدء النطاق وإنهائه لتحديد عنصر واحد. ومع ذلك ، يمكنني استخدام أي نطاق وسيطة COUNT للحد من عدد النتائج. وبالمثل ، ليست هناك حاجة لتحديد معرفات كاملة لنطاق ما ، يمكنني ببساطة استخدام وقت unix فقط للحصول على عناصر في نطاق زمني محدد:

 > XRANGE mystream 1553097560000 1553097570000 1) 1) "1553097561402-0" 2) 1) "cpu-temp" 2) "23.4" 3) "load" 4) "2.3" 2) 1) "1553097568315-0" 2) 1) "cpu-temp" 2) "23.2" 3) "load" 4) "2.1" 

في الوقت الحالي ، ليست هناك حاجة لإظهار ميزات واجهة برمجة التطبيقات الأخرى ، وهناك وثائق لذلك. في الوقت الحالي ، دعنا نركز فقط على نمط الاستخدام هذا: XADD للإضافة ، XRANGE (وأيضًا XREAD) لاستخراج النطاقات (اعتمادًا على ما تريد القيام به) ، ودعونا نرى لماذا تعد التدفقات قوية جدًا بحيث تسميها هياكل البيانات.

إذا كنت تريد معرفة المزيد عن التدفقات وواجهات برمجة التطبيقات ، فتأكد من قراءة البرنامج التعليمي .

لاعبي التنس


قبل بضعة أيام ، قام صديق لي الذي بدأ دراسة Redis وأنا بمحاكاة طلب لتتبع ملاعب التنس واللاعبين والمباريات المحلية. طريقة تصميم اللاعبين واضحة ، اللاعب هو كائن صغير ، لذلك نحن بحاجة فقط إلى التجزئة مع مفاتيح مثل player:<id> . بعد ذلك ستدرك على الفور أنك بحاجة إلى وسيلة لتتبع الألعاب في أندية التنس المحددة. إذا كان player:1 player:2 لعبوا فيما بينهم player:1 فاز ، يمكننا إرسال السجل التالي إلى ساحة المشاركات:

 > XADD club:1234.matches * player-a 1 player-b 2 winner 1 "1553254144387-0" 

هذه العملية البسيطة تعطينا:

  1. معرف المطابقة الفريدة: معرف في الدفق.
  2. ليست هناك حاجة لإنشاء كائن لتحديد المطابقة.
  3. طلبات النطاق المجاني لمطابقات الترحيل أو مشاهدة المباريات لتاريخ ووقت محددين.

قبل ظهور التدفقات ، يتعين علينا إنشاء مجموعة مرتبة حسب الوقت: ستكون عناصر المجموعة المصنفة معرفات مطابقة ، والتي يتم تخزينها في مفتاح مختلف كقيمة تجزئة. ليس فقط مزيد من العمل ، ولكن أيضا المزيد من الذاكرة. ذاكرة أكثر من ذلك بكثير (انظر أدناه).

هدفنا الآن هو إظهار أن تدفقات Redis هي نوع من مجموعة مرتبة في وضع append only ، مع مفاتيح حسب الوقت ، حيث يكون كل عنصر عبارة عن علامة صغيرة. وفي بساطته ، هذه ثورة حقيقية في سياق النمذجة.

الذاكرة


حالة الاستخدام أعلاه ليست مجرد نمط برمجة أكثر تماسكًا. يختلف استهلاك الذاكرة في مؤشرات الترابط عن النهج القديم مع مجموعة مفرزة + علامة تجزئة لكل كائن بحيث بدأت بعض الأشياء الآن في العمل والتي كان من المستحيل تنفيذها من قبل على الإطلاق.

فيما يلي إحصائيات حول مقدار الذاكرة لتخزين مليون تطابق في التكوين المقدم مسبقًا:

   +  = 220  (242 RSS)  = 16,8  (18.11 RSS) 

الفرق هو أكثر من ترتيب الحجم (أي ، 13 مرة). هذا يعني أن تكون قادرًا على العمل مع المهام التي كانت مكلفة جدًا في السابق لأداء في الذاكرة. الآن هم قابلة للحياة تماما. يتمثل السحر في تقديم تدفقات Redis: يمكن أن تحتوي عقد الماكرو على العديد من العناصر التي يتم ترميزها بشكل مضغوط في بنية بيانات تسمى listpack. ستهتم هذه البنية ، على سبيل المثال ، بترميز الأعداد الصحيحة في شكل ثنائي ، حتى لو كانت سلاسل نصية. بالإضافة إلى ذلك ، نحن نطبق ضغط دلتا وضغط نفس الحقول. ومع ذلك ، يظل من الممكن البحث حسب المعرف أو الوقت ، لأن مثل هذه العقد المرتبطة في شجرة أساسية ، والتي تم تصميمها أيضًا مع تحسين الذاكرة. يوضح ذلك معًا الاستخدام الاقتصادي للذاكرة ، لكن الجزء المثير للاهتمام هو أن المستخدم لا يرى أي تفاصيل تنفيذ تجعل مؤشرات الترابط فعالة للغاية.

الآن دعنا نعول. إذا كان بإمكاني تخزين مليون سجل في حوالي 18 ميغابايت من الذاكرة ، فيمكنني تخزين 10 مليون في 180 ميغابايت و 100 مليون في 1.8 جيجابايت. مع 18 غيغابايت فقط من الذاكرة ، يمكنني الحصول على مليار عنصر.

سلسلة زمنية


من المهم الإشارة إلى أن المثال أعلاه مع مباريات التنس يختلف اختلافًا جوهريًا * تمامًا عن استخدام تدفقات Redis في السلاسل الزمنية. نعم ، من المنطقي أننا لا نزال نسجل نوعًا من الأحداث ، لكن هناك فرقًا أساسيًا. في الحالة الأولى ، نقوم بتسجيل وإنشاء سجلات لتقديم الكائنات. وفي السلسلة الزمنية ، نقيس ببساطة شيء يحدث خارجًا لا يمثل الكائن فعليًا. يمكنك القول أن هذا التمييز تافه ، لكنه ليس كذلك. من المهم أن نفهم فكرة أن خيوط Redis يمكن استخدامها لإنشاء كائنات صغيرة بترتيب مشترك ولتخصيص معرفات لهذه الكائنات.

لكن حتى أبسط طريقة لاستخدام السلاسل الزمنية هي طفرة هائلة ، لأنه قبل ظهور الخيوط ، كان Redis غير قادر عملياً على فعل أي شيء هنا. خصائص الذاكرة ومرونة المواضيع ، وكذلك القدرة على الحد من التدفقات المغطاة (انظر معلمات XADD) هي أدوات مهمة للغاية في أيدي المطور.

النتائج


تعتبر التدفقات مرنة وتوفر العديد من حالات الاستخدام ، لكنني أردت أن أكتب مقالة قصيرة جدًا لإظهار الأمثلة واستهلاك الذاكرة بوضوح. ربما كان استخدام التدفقات واضحًا بالنسبة للعديد من القراء. ومع ذلك ، فإن المحادثات مع المطورين في الأشهر الأخيرة قد تركت لدي انطباعًا بأن العديد منهم لديهم ارتباط قوي بين التدفقات وبيانات التدفق ، كما لو كانت بنية البيانات جيدة هناك فقط. هذا ليس كذلك. :-)

Source: https://habr.com/ru/post/ar444996/


All Articles