مرحبا بالجميع. اسمي ألكساندر ، أنا مطور جافا في مجموعة شركات Tinkoff.
أريد في هذه المقالة مشاركة تجربتي في حل المشكلات المرتبطة بمزامنة حالة ذاكرات التخزين المؤقت في الأنظمة الموزعة. التقينا بهم ، وكسروا تطبيقنا المترابط إلى خدمات
صغيرة . من الواضح أننا سنتحدث عن التخزين المؤقت للبيانات على مستوى JVM ، لأنه مع التخزين المؤقت الخارجي ، يتم حل مشاكل المزامنة خارج سياق التطبيق.
في هذه المقالة ، سأتحدث عن تجربتنا في التحول إلى بنية موجهة نحو الخدمات ، مصحوبة بانتقال إلى Kubernetes ، وحول حل المشكلات ذات الصلة. سننظر في النهج المتبع في تنظيم نظام التخزين المؤقت لشبكة البيانات في الذاكرة (IMDG) ومزاياه وعيوبه ، ولهذا قررنا أن نكتب حلنا الخاص.
يناقش هذا المقالة مشروع له الخلفية مكتوب بلغة جافا. لذلك ، سوف نتحدث أيضًا عن المعايير في مجال التخزين المؤقت المؤقت في الذاكرة. نناقش مواصفات JSR-107 ومواصفات JSR-347 الفاشلة وميزات التخزين المؤقت في Spring. مرحبا بكم في القط!
ودعونا نقطع التطبيق إلى خدمات ...
سوف ننتقل إلى الهندسة المعمارية الموجهة نحو الخدمة والانتقال إلى Kubernetes - هذا ما قررناه منذ ما يزيد قليلاً عن 6 أشهر. لفترة طويلة ، كان مشروعنا متراصة ، حيث تراكمت العديد من المشكلات المتعلقة بالديون الفنية ، وكتبنا وحدات تطبيق جديدة على الإطلاق كخدمات منفصلة. نتيجة لذلك ، كان الانتقال إلى بنية موجهة نحو الخدمة وخفض مترابط أمرًا لا مفر منه.
يتم تحميل تطبيقنا ، ويأتي في المتوسط 500 rps لخدمات الويب (في ذروة تصل إلى 900 rps). من أجل جمع نموذج البيانات بأكمله استجابةً لكل طلب ، يتعين عليك الانتقال إلى ذاكرة التخزين المؤقت المختلفة عدة مئات من المرات.
نحن نحاول الانتقال إلى ذاكرة التخزين المؤقت عن بعد لا يزيد عن ثلاث مرات لكل طلب ، وهذا يتوقف على مجموعة البيانات المطلوبة ، وعلى ذاكرة التخزين المؤقت JVM الداخلية يصل الحمل إلى 90000 دورة في الثانية لكل ذاكرة تخزين مؤقت. لدينا حوالي 30 من هذه المخابئ لمجموعة متنوعة من الكيانات و DTO-shki. في بعض ذاكرات التخزين المؤقت المحملة ، لا يمكننا حتى حذف القيمة ، لأن ذلك قد يؤدي إلى زيادة وقت استجابة خدمات الويب وإلى تعطل التطبيق.
هذه هي الطريقة التي تبدو بها مراقبة الحمل ، ويتم إزالتها من ذاكرات التخزين المؤقت الداخلية على كل عقدة خلال اليوم. وفقًا لملف تعريف التحميل ، من السهل معرفة أن معظم الطلبات هي بيانات للقراءة. التحميل الموحد للكتابة يرجع إلى تحديث القيم في ذاكرات التخزين المؤقت بتردد معين.
التعطل غير صالح لتطبيقنا. لذلك ، لغرض النشر السلس ، قمنا دائمًا بموازنة جميع حركة المرور الواردة إلى عقدتين ونشرنا التطبيق باستخدام طريقة التحديث المتداول. أصبحت Kubernetes حلاً مثاليًا للبنية التحتية عند التبديل إلى الخدمات. وبالتالي ، حلنا العديد من المشاكل في وقت واحد.
مشكلة الطلب المستمر وإنشاء البنية التحتية للخدمات الجديدة
لقد حصلنا على مساحة اسم في المجموعة لكل دائرة ، والتي لدينا ثلاثة: dev - للمطورين ، qa - للمختبرين ، prod - للعملاء.
مع تمييز مساحة الاسم ، تضاف إضافة خدمة أو تطبيق جديد لكتابة أربعة عروض: النشر ، الخدمة ، الدخول ، و ConfigMap.
ارتفاع الحمولة التسامح
يتوسع العمل وينمو باستمرار - قبل عام كان متوسط الحمل أقل مرتين من الحالي.
يسمح لك القياس الأفقي في Kubernetes بتحقيق وفورات الحجم مع زيادة عبء العمل في المشروع المطوَّر.
الصيانة وجمع السجلات والرصد
تصبح الحياة أسهل كثيرًا عندما لا تكون هناك حاجة لإضافة سجلات إلى نظام التسجيل عند إضافة كل عقدة ، وتكوين سياج المقاييس (ما لم يكن لديك نظام لمراقبة الدفع) ، وأداء إعدادات الشبكة ، وتثبيت البرنامج الضروري ببساطة للتشغيل.
بالطبع ، يمكن أتمتة كل هذا باستخدام Ansible أو Terraform ، لكن في النهاية ، تعد كتابة العديد من البيانات لكل خدمة أسهل كثيرًا.
موثوقية عالية
تتيح لك آلية k8s المدمجة لعينات Liveness و Readiness عدم القلق من أن التطبيق بدأ في التباطؤ أو توقف عن الاستجابة تمامًا.
يتحكم Kubernetes الآن في دورة حياة قرون الموقد التي تحتوي على حاويات التطبيقات وحركة المرور الموجهة إليهم.
إلى جانب وسائل الراحة الموصوفة ، نحتاج إلى حل عدد من المشكلات من أجل جعل الخدمات مناسبة للتحجيم الأفقي واستخدام نموذج بيانات شائع للعديد من الخدمات. كان من الضروري حل مشكلتين:
- حالة التطبيق. عندما يتم نشر المشروع في مجموعة k8s ، تبدأ عملية إنشاء القرون التي تحتوي على حاويات من الإصدار الجديد من التطبيق والتي لا تتعلق بحالة قرون الإصدار السابق. يمكن رفع قرون التطبيق الجديدة على خوادم الكتلة التعسفية التي تفي بالقيود المحددة. وأيضًا ، يمكن الآن تدمير كل حاوية تطبيقات تعمل داخل منصة Kubernetes في أي وقت إذا كان مسبار Liveness يقول إنه يحتاج إلى إعادة التشغيل.
- اتساق البيانات. من الضروري الحفاظ على التناسق وسلامة البيانات مع بعضها في جميع العقد. هذا صحيح بشكل خاص إذا كانت العقد متعددة تعمل ضمن نموذج بيانات واحد. من غير المقبول أنه عند تقديم طلبات إلى العقد المختلفة للتطبيق في الاستجابة ، تأتي البيانات غير المتسقة إلى العميل.
في التطور الحديث للأنظمة القابلة للتطوير ، فإن بنية Stateless هي الحل للمشاكل المذكورة أعلاه. لقد تخلصنا من المشكلة الأولى عن طريق نقل جميع الإحصائيات إلى التخزين السحابي S3.
ومع ذلك ، نظرًا للحاجة إلى تجميع نموذج بيانات معقد وتوفير وقت استجابة خدمات الويب الخاصة بنا ، لا يمكننا رفض تخزين البيانات في ذاكرة التخزين المؤقت في الذاكرة. لحل المشكلة الثانية ، كتبوا مكتبة لمزامنة حالة التخزين المؤقت الداخلي للعقد الفردية.
نقوم بمزامنة ذاكرات التخزين المؤقت على عقد منفصلة
كما البيانات الأولية لدينا نظام موزعة تتكون من العقد N. تحتوي كل عقدة على حوالي 20 ذاكرة تخزين مؤقت ، يتم تحديث البيانات فيها عدة مرات في الساعة.
تحتوي معظم ذاكرات التخزين المؤقت على سياسة تحديث بيانات TTL (وقت للعيش) ، ويتم تحديث بعض البيانات مع عملية CRON كل 20 دقيقة بسبب الحمل العالي. يتراوح عبء العمل على المخابئ من عدة آلاف من طلبات تقديم العروض في الليل إلى عشرات الآلاف من الأشخاص خلال اليوم. الحمولة القصوى ، كقاعدة عامة ، لا تتجاوز 100000 دورة في الثانية. لا يتجاوز عدد السجلات في التخزين المؤقت عدة مئات من الآلاف ويتم وضعه في كومة الذاكرة المؤقتة لعقدة واحدة.
مهمتنا هي تحقيق تناسق البيانات بين ذاكرة التخزين المؤقت نفسها على العقد المختلفة ، وكذلك أقصر وقت استجابة ممكن. النظر في ما هناك عموما طرق لحل هذه المشكلة.
الحل الأول والأكثر بساطة الذي يتبادر إلى الذهن هو وضع جميع المعلومات في ذاكرة التخزين المؤقت عن بعد. في هذه الحالة ، يمكنك التخلص تمامًا من حالة التطبيق ، وليس التفكير في مشاكل تحقيق الاتساق والحصول على نقطة وصول واحدة إلى مستودع بيانات مؤقت.
طريقة تخزين البيانات المؤقتة هذه بسيطة للغاية ، ونحن نستخدمها. نقوم بتخزين جزء من البيانات في
Redis ، وهو تخزين بيانات NoSQL في RAM. في Redis ، نقوم عادةً بتسجيل إطار استجابة لخدمة الويب ، ولكل طلب نحتاج إلى إثراء هذه البيانات بالمعلومات ذات الصلة ، والتي يتعين علينا إرسال عدة مئات من الطلبات إلى ذاكرة التخزين المؤقت المحلية.
من الواضح أننا لا نستطيع إخراج بيانات ذاكرات التخزين المؤقت الداخلية للتخزين عن بُعد ، لأن تكلفة نقل هذا الحجم من حركة المرور عبر الشبكة لن تسمح لنا بتلبية وقت الاستجابة المطلوب.
الخيار الثاني هو استخدام
شبكة بيانات في الذاكرة (IMDG) ، وهي ذاكرة تخزين مؤقت موزعة في الذاكرة. مخطط مثل هذا الحل كما يلي:
تعتمد بنية IMDG على مبدأ تقسيم البيانات من ذاكرات التخزين المؤقت للعقد الفردية. في الواقع ، يمكن أن يسمى هذا جدول التجزئة الموزعة على مجموعة من العقد. تعتبر IMDG واحدة من أسرع تطبيقات التخزين الموزع المؤقت.
هناك العديد من تطبيقات IMDG ، وأكثرها شعبية هي
Hazelcast . تسمح لك ذاكرة التخزين المؤقت الموزعة بتخزين البيانات في ذاكرة الوصول العشوائي (RAM) على العديد من عقد التطبيق بمستوى مقبول من الموثوقية والحفاظ على التناسق ، والذي يتحقق عن طريق تكرار البيانات.
إن مهمة إنشاء ذاكرة التخزين المؤقت الموزعة هذه ليست سهلة ، ومع ذلك ، فإن استخدام حل IMDG جاهز بالنسبة لنا قد يكون بديلاً جيدًا لذاكرة التخزين المؤقت JVM ويزيل مشاكل النسخ المتماثل والاتساق وتوزيع البيانات بين جميع عقد التطبيق.
يقوم معظم موردي IMDG لتطبيقات Java بتطبيق
JSR-107 ، واجهة برمجة تطبيقات Java القياسية للعمل مع ذاكرات التخزين المؤقت الداخلية. بشكل عام ، يحتوي هذا المعيار على قصة كبيرة إلى حد ما ، والتي سأناقشها بمزيد من التفصيل أدناه.
ذات مرة ، كانت هناك أفكار لتطبيق الواجهة الخاصة بك للتفاعل مع IMDG -
JSR 347 . لكن تنفيذ مثل هذا API لم يتلق دعمًا كافيًا من مجتمع Java ، والآن لدينا واجهة واحدة للتفاعل مع ذاكرات التخزين المؤقت في الذاكرة ، بغض النظر عن بنية تطبيقنا. جيد أو سيئ سؤال آخر ، ولكنه يسمح لنا بتجاهل كل الصعوبات التي تواجه تطبيق ذاكرة التخزين المؤقت في الذاكرة الموزعة والعمل معها كذاكرة التخزين المؤقت للتطبيق متجانسة.
على الرغم من المزايا الواضحة لاستخدام IMDG ، إلا أن هذا الحل لا يزال أبطأ من ذاكرة التخزين المؤقت القياسية JVM ، نظرًا للحمولة العامة لضمان النسخ المتماثل المستمر للبيانات الموزعة بين العديد من عقد JVM ، وكذلك النسخ الاحتياطي لهذه البيانات. في حالتنا ، لم تكن كمية البيانات الخاصة بالتخزين المؤقت كبيرة جدًا ، حيث كانت البيانات ذات الهامش ملائمة في ذاكرة تطبيق واحد ، لذا بدا تخصيصها للعديد من JVM حلاً غير ضروري. ويمكن لحركة مرور الشبكة الإضافية بين عقد التطبيق تحت الأحمال الثقيلة أن تؤثر بشكل كبير على الأداء وتزيد من زمن الاستجابة لخدمات الويب. في النهاية ، قررنا أن نكتب حلنا الخاص لهذه المشكلة.
تركنا ذاكرات التخزين المؤقت في الذاكرة كتخزين مؤقت للبيانات ، وللمحافظة على الاتساق استخدمنا مدير قائمة انتظار RabbitMQ. اعتمدنا نمط التصميم السلوكي
للناشر والمشترك ، وحافظنا على أهمية البيانات من خلال حذف الإدخال المعدل من ذاكرة التخزين المؤقت لكل عقدة. مخطط الحل كما يلي:
يعرض الرسم التخطيطي مجموعة من العقد N ، كل منها يحتوي على ذاكرة تخزين مؤقت قياسية في الذاكرة. تستخدم جميع العقد نموذج بيانات مشترك ويجب أن تكون متسقة. عند أول وصول إلى ذاكرة التخزين المؤقت بواسطة مفتاح اعتباطي ، تكون القيمة الموجودة في ذاكرة التخزين المؤقت غائبة ، ونضع القيمة الفعلية من قاعدة البيانات فيه. مع أي تغيير - حذف السجل.
يتم توفير المعلومات الفعلية في استجابة ذاكرة التخزين المؤقت هنا عن طريق مزامنة حذف إدخال عند تغييره على أي من العقد. تحتوي كل عقدة في النظام على قائمة انتظار في مدير قائمة انتظار RabbitMQ. يتم التسجيل إلى جميع قوائم الانتظار من خلال نقطة وصول من نوع الموضوع شائعة. هذا يعني أن الرسائل المرسلة إلى Topic تندرج في جميع قوائم الانتظار المرتبطة بها. لذلك ، عند تغيير القيمة على أي عقدة من النظام ، سيتم حذف هذه القيمة من التخزين المؤقت لكل عقدة ، وسيبدأ الوصول اللاحق في كتابة القيمة الحالية إلى ذاكرة التخزين المؤقت من قاعدة البيانات.
بالمناسبة ، توجد آلية PUB / SUB مماثلة في Redis. ولكن ، في رأيي ، لا يزال من الأفضل استخدام مدير قائمة الانتظار للعمل مع قوائم الانتظار ، وكان RabbitMQ مثالي لمهمتنا.
JSR 107 معيار وتنفيذه
يحتوي Java Cache API القياسي للتخزين المؤقت للبيانات في الذاكرة (مواصفات
JSR-107 ) على تاريخ طويل إلى حد ما ؛ لقد تم تطويره لمدة 12 عامًا.
على مدار هذا الوقت الطويل ، تغيرت أساليب تطوير البرمجيات ، وتم استبدال المتجانسات بهندسة الخدمات الصغيرة. نظرًا لوجود مثل هذا الافتقار الطويل للمواصفات الخاصة بـ Cache API ، فقد كانت هناك طلبات لتطوير ذاكرة التخزين المؤقت لواجهة برمجة التطبيقات للأنظمة الموزعة
JSR-347 (شبكات البيانات لمنصة Java). ولكن بعد الإصدار الذي طال انتظاره من JSR-107 وإصدار JCache ، تم سحب طلب إنشاء مواصفات منفصلة للأنظمة الموزعة.
على مدار 12 عامًا في السوق ، تغير مكان التخزين المؤقت للبيانات من HashMap إلى ConcurrentHashMap مع إصدار Java 1.5 ، وبعد ذلك ظهر الكثير من تطبيقات المصادر المفتوحة الجاهزة للتخزين المؤقت في الذاكرة.
بعد إصدار JSR-107 ، بدأت حلول البائعين في تنفيذ المواصفات الجديدة تدريجياً. بالنسبة إلى JCache ، يوجد حتى مزودون متخصصون في التخزين المؤقت الموزع - شبكات البيانات ذاتها ، والتي لم يتم تطبيق المواصفات عليها.
النظر في ما
تتكون منه حزمة
javax.cache ، وكيفية الحصول على نسخة من ذاكرة التخزين المؤقت للتطبيق لدينا:
CachingProvider provider = Caching.getCachingProvider("org.cache2k.jcache.provider.JCacheProvider"); CacheManager cacheManager = provider.getCacheManager(); CacheConfiguration<Integer, String> config = new MutableConfiguration<Integer, String>() .setTypes(Integer.class, String.class) .setReadThrough(true) . . .; Cache<Integer, String> cache = cacheManager.createCache(cacheName, config);
هنا Caching هو محمل الإقلاع ل CachingProvider.
في حالتنا ، سيتم تحميل JCacheProvider ، وهو تطبيق cache2k لموفر JSR-107
SPI ، من ClassLoader. بالنسبة إلى المُحمل ، قد لا تضطر إلى تحديد تطبيق الموفر ، ولكن بعد ذلك سيحاول تحميل التطبيق الموجود
META-INF / services / javax.cache.spi.CachingProvider
في أي حال ، في ClassLoader يجب أن يكون هناك تطبيق واحد من CachingProvider.
إذا كنت تستخدم مكتبة javax.cache دون أي تطبيق ، فسيتم طرح استثناء عند محاولة إنشاء JCache. الغرض من الموفر هو إنشاء وإدارة دورة حياة CacheManager ، والتي بدورها مسؤولة عن إدارة وتكوين ذاكرات التخزين المؤقت. وبالتالي ، لإنشاء ذاكرة التخزين المؤقت ، يجب أن تذهب بالطريقة التالية:
يجب أن تحتوي ذاكرات التخزين المؤقت القياسية التي تم إنشاؤها باستخدام CacheManager على تكوين متوافق مع التطبيق. يمكن تمديد CacheConfiguration المعياري الذي توفره javax.cache إلى تطبيق CacheProvider محدد.
اليوم ، هناك العشرات من التطبيقات المختلفة لمواصفات JSR-107:
Ehcache ،
Guava ،
caffeine ،
cache2k . العديد من التطبيقات عبارة عن شبكة بيانات في الذاكرة في الأنظمة الموزعة -
Hazelcast و
Oracle Coherence .
هناك أيضًا العديد من تطبيقات التخزين المؤقت التي لا تدعم واجهة برمجة التطبيقات القياسية. استخدمنا Ehcache 2 لفترة طويلة في مشروعنا ، وهو غير متوافق مع JCache (ظهر تطبيق المواصفات مع Ehcache 3). ظهرت الحاجة إلى الانتقال إلى تطبيق متوافق مع JCache مع الحاجة إلى مراقبة حالة ذاكرة التخزين المؤقت في الذاكرة. باستخدام MetricRegistry القياسية ، كان من الممكن ربط المراقبة فقط بمساعدة تطبيق JCacheGaugeSet ، الذي يجمع المقاييس من JCache القياسي.
كيف تختار تطبيق ذاكرة التخزين المؤقت في الذاكرة المناسب لمشروعك؟ ربما يجب عليك الانتباه إلى ما يلي:
- هل تحتاج إلى دعم لمواصفات JSR-107.
- ومن الجدير أيضًا الانتباه إلى سرعة التنفيذ المحدد. تحت الأحمال الثقيلة ، يمكن أن يكون لأداء ذاكرات التخزين المؤقت الداخلية تأثير كبير على وقت استجابة نظامك.
- الدعم في الربيع. إذا كنت تستخدم إطار العمل المشهور في مشروعك ، فمن الجدير أن تفكر في حقيقة أن ليس كل تطبيق ذاكرة التخزين المؤقت JVM به CacheManager متوافق في Spring.
إذا كنت تستخدم Spring بنشاط في مشروعك ، مثلنا تمامًا ، فعندئذٍ من أجل البيانات المخبأة مؤقتًا ، من المرجح أنك تتبع النهج الموجه نحو الجانب (AOP) واستخدم التعليق التوضيحيCacheable. Spring يستخدم CacheManager SPI الخاص به من أجل جوانب للعمل. الفاصوليا التالية مطلوبة لتخزين ذاكرات الربيع:
@Bean public org.springframework.cache.CacheManager cacheManager() { CachingProvider provider = Caching.getCachingProvider(); CacheManager cacheManager = provider.getCacheManager(); return new JCacheCacheManager(cacheManager); }
للعمل مع ذاكرة التخزين المؤقت في نموذج AOP ، يجب أيضًا مراعاة اعتبارات المعاملات. يجب أن تدعم ذاكرة التخزين المؤقتة الربيع بالضرورة إدارة المعاملات. تحقيقًا لهذه الغاية ، يرث spring CacheManager خصائص AbstractTransactionSupportingCacheManager ، والتي يمكن استخدامها لمزامنة عمليات البيع / الإخلاء التي تتم داخل معاملة وتنفيذها فقط بعد الالتزام بالمعاملة الناجحة.
يوضح المثال أعلاه استخدام برنامج JCacheCacheManager لبرنامج إدارة مواصفات التخزين المؤقت. هذا يعني أن أي تطبيق JSR-107 لديه أيضًا توافق مع Spring CacheManager. هذا سبب آخر لاختيار ذاكرة التخزين المؤقت في الذاكرة مع دعم لمواصفات JSR لمشروعك. ولكن إذا كنت لا تزال لا تحتاج إلى هذا الدعم ، لكنني أريد حقًا استخدامCacheable ، فأنت لديك دعم لحلين داخليين آخرين للذاكرة المؤقتة: EhCacheCacheManager و CaffeineCacheManager.
عند اختيار تطبيق ذاكرة التخزين المؤقت في الذاكرة ، لم نأخذ بعين الاعتبار دعم IMDG للأنظمة الموزعة ، كما ذكر سابقًا. للحفاظ على أداء مخابئ JVM على نظامنا ، كتبنا الحل الخاص بنا.
مسح ذاكرة التخزين المؤقت في النظام الموزع
تتيح لك IMDGs الحديثة المستخدمة في المشاريع ذات بنية microservice توزيع البيانات في الذاكرة بين جميع العقد العاملة في النظام باستخدام تقسيم البيانات القابلة للتطوير مع المستوى المطلوب من التكرار.
في هذه الحالة ، هناك العديد من المشكلات المرتبطة بالمزامنة وتناسق البيانات وما إلى ذلك ، ناهيك عن الزيادة في وقت الوصول إلى التخزين المؤقت. يعد هذا المخطط ضروريًا إذا كان حجم البيانات المستخدمة يناسب ذاكرة الوصول العشوائي لعقدة واحدة ، وللحفاظ على تناسق البيانات ، يكفي حذف هذا الإدخال على جميع العقد لأي تغيير في قيمة ذاكرة التخزين المؤقت.
عند تنفيذ هذا الحل ، فإن الفكرة الأولى التي تتبادر إلى الذهن هي استخدام بعض EventListener ، في JCache يوجد CacheEntryRemovedListener لحدث حذف إدخال من ذاكرة التخزين المؤقت. يبدو أنه يكفي إضافة تطبيق المستمع الخاص بك ، والذي سيرسل رسائل إلى الموضوع عند حذف السجل ، وتكون ذاكرة التخزين المؤقت في جميع العقد جاهزة - شريطة أن تستمع كل عقدة للأحداث من قائمة الانتظار المرتبطة بالموضوع العام ، كما هو موضح في الرسم التخطيطي. أعلاه.
عند استخدام هذا الحل ، ستكون البيانات الموجودة على العقد المختلفة غير متسقة بسبب حقيقة أن قوائم الأحداث في أي عملية تنفيذ JCache بعد حدوث الحدث. أي إذا لم يكن هناك سجل في ذاكرة التخزين المؤقت المحلية للمفتاح المحدد ، وكان هناك سجل لنفس المفتاح في أي عقدة أخرى ، فلن يتم إرسال الحدث إلى الموضوع.
فكر في الطرق الأخرى المتاحة لجذب حدث القيمة التي يتم حذفها من ذاكرة التخزين المؤقت المحلية.
في الحزمة javax.cache.event ، بجانب EventListeners ، هناك أيضًا CacheEntryEventFilter ، والتي ، وفقًا لجافا دوك ، تستخدم للتحقق من أي حدث CacheEntryEvent قبل إرسال هذا الحدث إلى CacheEntryListener ، سواء كان ذلك سجلاً أو حذفًا أو تحديثًا أو حدثًا في ذاكرة التخزين المؤقت. عند استخدام المرشح ، ستبقى مشكلتنا ، لأنه سيتم تنفيذ المنطق بعد تسجيل الحدث CacheEntryEvent وبعد تنفيذ عملية CRUD في ذاكرة التخزين المؤقت.
ومع ذلك ، من الممكن التقاط بدء حدث لحذف سجل من ذاكرة التخزين المؤقت. للقيام بذلك ، استخدم الأداة المدمجة في JCache التي تسمح لك باستخدام مواصفات API لكتابة البيانات وتحميلها من مصدر خارجي ، إذا لم تكن موجودة في ذاكرة التخزين المؤقت. هناك واجهات اثنين لهذا في حزمة javax.cache.integration:
- CacheLoader - لتحميل البيانات المطلوبة بواسطة المفتاح ، في حالة عدم وجود إدخالات في ذاكرة التخزين المؤقت.
- CacheWriter - لاستخدام كتابة وحذف وتحديث البيانات على مورد خارجي عند استدعاء عمليات التخزين المؤقت المقابلة.
لضمان الاتساق ، تكون أساليب CacheWriter ذرية فيما يتعلق بعملية التخزين المؤقت المقابلة. يبدو أننا وجدنا حلاً لمشكلتنا.
الآن يمكننا الحفاظ على تناسق استجابة ذاكرة التخزين المؤقت في الذاكرة على العقد عند استخدام تطبيق CacheWriter الذي يرسل الأحداث إلى موضوع RabbitMQ كلما كان هناك أي تغيير في السجل في ذاكرة التخزين المؤقت المحلية.
استنتاج
عند تطوير أي مشروع ، عند البحث عن حل مناسب للمشاكل الناشئة ، يتعين على المرء أن يأخذ بعين الاعتبار خصوصياته. في حالتنا ، لم تسمح الميزات المميزة لنموذج بيانات المشروع ، والكود القديم الموروث ، وطبيعة التحميل باستخدام أي من الحلول الحالية لمشكلة التخزين المؤقت الموزعة.
من الصعب جدًا تطبيق تطبيق عالمي على أي نظام مطور. لكل تطبيق من هذا القبيل ، هناك شروط مثالية للاستخدام. في حالتنا ، أدت تفاصيل المشروع إلى الحل الموضح في هذه المقالة. إذا كان لدى شخص ما مشكلة مماثلة ، فسيسعدنا مشاركة حلنا ونشره على GitHub.