مرحبًا فيما يلي نسخة من فيديو الخطاب في مسيرة مجتمع Apache Ignite في سانت بطرسبرغ في 20 يونيو. يمكنك تنزيل الشرائح هنا .
هناك فئة كاملة من المشاكل التي يواجهها المستخدمون المبتدئون. لقد قاموا للتو بتنزيل Apache Ignite وتشغيل أول مرتين وثلاث وعشر مرات ، ويأتون إلينا بأسئلة يتم حلها بطريقة مماثلة. لذلك ، أقترح إنشاء قائمة تحقق توفر لك الكثير من الوقت والأعصاب عند إنشاء تطبيقات Apache Ignite الأولى. سنتحدث عن الاستعدادات للإطلاق ؛ كيفية جعل الكتلة تتجمع ؛ كيفية بدء بعض الحسابات في الشبكة الحاسوبية ؛ كيفية إعداد نموذج بيانات ورمز بحيث يمكنك كتابة بياناتك إلى Ignite ثم قراءتها بنجاح. والأهم من ذلك: كيف لا يكسر أي شيء منذ البداية.
التحضير للإطلاق - تكوين التسجيل
نحن بحاجة إلى سجلات. إذا طرحت يومًا سؤالاً على قائمة بريد Apache Ignite أو على StackOverflow ، مثل "لماذا تم تعليق كل شيء" ، فمن المرجح أن أول شيء طُلب منك إرساله هو جميع السجلات من جميع العقد.
وبطبيعة الحال ، يتم تمكين تسجيل Apache Ignite بشكل افتراضي. ولكن هناك فروق دقيقة. أولا ، يكتب أباتشي إغنايت قليلا في stdout
. بشكل افتراضي ، يبدأ تشغيله في ما يسمى الوضع الهادئ. في stdout
، سترى الأخطاء الفظيعة فقط ، وسيتم حفظ كل شيء آخر في ملف ، المسار الذي يعرضه Apache Ignite في البداية (بشكل افتراضي - ${IGNITE_HOME}/work/log
). لا تمسحها وتحتفظ بالسجلات لفترة أطول ، يمكن أن تكون مفيدة جدًا.
stdout
تشعل عند بدء التشغيل الافتراضي

لتسهيل اكتشاف المشكلات دون الدخول في ملفات منفصلة وإعداد مراقبة منفصلة لـ Apache Ignite ، يمكنك تشغيله في وضع مطوّل باستخدام الأمر
ignite.sh -v
ومن ثم سيبدأ النظام في الكتابة عن جميع الأحداث في stdout
مع بقية تسجيل التطبيق.
تحقق من السجلات! في كثير من الأحيان يمكنك العثور على حلول لمشاكلك. إذا انهار نظام المجموعة ، فعادةً ما تظهر في السجل رسائل مثل "زيادة مثل هذه المهلة ومثلها في مثل هذا التكوين. لقد سقطنا بسببه. إنه صغير جدا. الشبكة ليست جيدة بما فيه الكفاية. "
التجمع العنقودي
ضيوف غير مدعوين
المشكلة الأولى التي يواجهها الكثير هي الضيوف غير المدعوين في مجموعتك. أو تتحول بنفسك إلى ضيف غير مدعو: بدء مجموعة جديدة وفجأة ترى أنه في أول لقطة طوبولوجيا بدلاً من عقدة واحدة لديك خادمان من البداية. كيف ذلك؟ لقد أطلقت واحدة فقط.
رسالة تشير إلى أن الكتلة لها عقدتين

الحقيقة هي أنه بشكل افتراضي يستخدم Apache Ignite Multicast ، وعند بدء التشغيل ، سيبحث عن كل Apache Ignite الأخرى الموجودة في نفس الشبكة الفرعية ، في نفس مجموعة Multicast. وإذا حدث ذلك ، فسيحاول الاتصال. وفي حالة الاتصال غير الناجح ، فلن يبدأ على الإطلاق. لذلك ، في المجموعة على جهاز الكمبيوتر المحمول الخاص بالعمل ، تظهر عُقدًا إضافية من المجموعة على الكمبيوتر المحمول الخاص بالزميل بانتظام ، وهو أمر غير مناسب بالطبع.
كيف تحمي نفسك من هذا؟ أسهل طريقة لتكوين IP ثابت. بدلاً من TcpDiscoveryMulticastIpFinder
، والذي يتم استخدامه بشكل افتراضي ، هناك TcpDiscoveryVmIpFinder
. هناك ، اكتب جميع عناوين IP والمنافذ التي تتصل بها. هذا أكثر ملاءمة وسيحميك من عدد كبير من المشاكل ، خاصة في بيئات التطوير والاختبار.
عناوين كثيرة جدًا
المشكلة التالية. قمت بتعطيل الإرسال المتعدد ، ابدأ الكتلة ، في تكوين واحد ، قمت بتعيين كمية لائقة من IP من بيئات مختلفة. ويحدث أن تقوم بتشغيل العقدة الأولى في مجموعة جديدة لمدة 5-10 دقائق ، على الرغم من أن جميع العقد اللاحقة تتصل بها في 5-10 ثوانٍ.
خذ قائمة بثلاثة عناوين IP. لكل منها ، نصف نطاقات من 10 منافذ. في المجموع ، يتم الحصول على 30 عنوان TCP. نظرًا لأن Apache Ignite يجب أن يحاول الاتصال بمجموعة موجودة قبل إنشاء مجموعة جديدة ، فإنه سيتحقق من كل IP بدوره. قد لا يضر ذلك على الكمبيوتر المحمول الخاص بك ، ولكن غالبًا ما يتم تضمين حماية فحص المنفذ في بعض البيئة الغائمة. بمعنى ، عند الوصول إلى منفذ خاص على بعض عناوين IP ، لن تتلقى أي رد حتى انتهاء المهلة. بشكل افتراضي ، تكون 10 ثوانٍ. وإذا كان لديك 3 عناوين تحتوي على 10 منافذ ، فستحصل على 3 * 10 * 10 = 300 ثانية من الانتظار - نفس 5 دقائق للاتصال.
الحل واضح: لا تسجل المنافذ غير الضرورية. إذا كان لديك ثلاثة عناوين IP ، فلا تحتاج حقًا إلى نطاق افتراضي من 10 منافذ. يعد هذا ملائمًا عند اختبار شيء ما على الجهاز المحلي وتشغيل 10 عقد. ولكن في الأنظمة الحقيقية ، عادةً ما يكون المنفذ الواحد كافيًا. أو قم بتعطيل الحماية ضد مسح المنفذ على الشبكة الداخلية ، إذا كانت لديك مثل هذه الفرصة.
المشكلة الشائعة الثالثة هي IPv6. يمكنك أن ترى رسائل خطأ شبكة غريبة: لا يمكن الاتصال ، لا يمكن إرسال رسالة ، عقدة مقسمة. هذا يعني أنك قد سقطت من الكتلة. في كثير من الأحيان ، تحدث هذه المشاكل بسبب البيئات المختلطة من IPv4 و IPv6. هذا لا يعني أن Apache Ignite لا يدعم IPv6 ، ولكن هناك مشاكل معينة في الوقت الحالي.
الحل الأسهل هو تمرير الخيار إلى جهاز Java
-Djava.net.preferIPv4Stack=true
ثم لن تستخدم Java و Apache Ignite IPv6. هذا يحل جزءًا كبيرًا من مشاكل انهيار الكتل.
تحضير قاعدة الكود - نقوم بعمل تسلسل صحيح
تجمعت الكتلة ، من الضروري أن تبدأ شيئًا فيها. أحد أهم العناصر في تفاعل الشفرة مع كود Apache Ignite هو Marshaller أو التسلسل. لكتابة شيء ما للذاكرة ، للإصرار ، للإرسال عبر الشبكة ، يقوم Apache Ignite أولاً بتسلسل كائناتك. يمكنك مشاهدة الرسائل التي تبدأ بالكلمات: "لا يمكن كتابتها بتنسيق ثنائي" أو "لا يمكن إجراء تسلسل لها باستخدام BinaryMarshaller". سيكون هناك تحذير واحد فقط في السجل ، ولكن يمكن ملاحظته. هذا يعني أنك بحاجة إلى تعديل التعليمات البرمجية الخاصة بك أكثر قليلاً لتكوين صداقات مع Apache Ignite.
يستخدم Apache Ignite ثلاث آليات للتسلسل:
JdkMarshaller
- تسلسل Java العادي ؛OptimizedMarshaller
- تسلسل Java محسن قليلاً ، لكن الآليات هي نفسها ؛BinaryMarshaller
هو تسلسل مكتوب خصيصًا لـ Apache Ignite ، ويستخدم في كل مكان تحت غطاء محرك السيارة. لديها عدد من المزايا. في مكان ما يمكننا تجنب التسلسل الإضافي وإلغاء التسلسل ، وفي مكان ما يمكننا حتى الحصول على كائن غير منزوع التسلسل في API ، والعمل معه مباشرة بتنسيق ثنائي كما هو الحال مع شيء مثل JSON.
سيكون BinaryMarshaller
قادرًا على إجراء تسلسل وإلغاء تسلسل POJOs التي لا تحتوي إلا على حقول وأساليب بسيطة. ولكن إذا كان لديك تسلسل مخصص عبر readObject()
و writeObject()
، إذا كنت تستخدم Externalizable
، فلن BinaryMarshaller
. سيرى أنه لا يمكن إجراء تسلسل للكائن الخاص بك عن طريق التسجيل المعتاد للحقول غير المؤقتة وسيستسلم - سيتم إرجاعه إلى OptimizedMarshaller
.
لتكوين صداقات لمثل هذه الكائنات مع Apache Ignite ، تحتاج إلى تنفيذ واجهة Binarylizable
. إنه بسيط للغاية.
على سبيل المثال ، يوجد TreeMap
قياسي من Java. لديها تسلسل مخصص وإلغاء التسلسل عبر كائن القراءة والكتابة. يصف أولاً بعض الحقول ، ثم يكتب الطول والبيانات نفسها إلى OutputStream
.
تنفيذ TreeMap.writeObject()
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
writeBinary()
و readBinary()
من Binarylizable
يعملان بنفس الطريقة تمامًا: يلتف BinaryTreeMap
نفسه في TreeMap
العادية TreeMap
إلى OutputStream
. هذه الطريقة سهلة الكتابة ، وستزيد الإنتاجية بشكل كبير.
تنفيذ BinaryTreeMap.writeBinary()
public void writeBinary(BinaryWriter writer) throws BinaryObjectException { BinaryRawWriter rewriter = writer. rewrite (); rawWriter.writeObject(map.comparator()); int size = map.size(); rawWriter.writeInt(size); for (Map.Entry<Object, Object> entry : ((TreeMap<Object, Object>)map).entrySet()) { rawWriter.writeObject(entry.getKey()); rawWriter.writeObject(entry.getValue()); } }
إطلاق في Compute Grid
لا يسمح لك Ignite بتخزين البيانات فحسب ، بل يتيح لك أيضًا تشغيل الحوسبة الموزعة. كيف ندير نوعًا من لامدا بحيث ينثر جميع الخوادم ويعمل؟
بالنسبة للمبتدئين ، ما هي المشكلة مع أمثلة التعليمات البرمجية هذه؟
ما هي المشكلة؟
Foo foo = …; Bar bar = ...; ignite.compute().broadcast( () -> doStuffWithFooAndBar(foo, bar) );
وإذا كان الأمر كذلك؟
Foo foo = …; Bar bar = ...; ignite.compute().broadcast(new IgniteRunnable() { @Override public void run() { doStuffWithFooAndBar(foo, bar); } });
كما قد تتخيل ، والكثيرون على دراية بمزالق لامداس والفصول المجهولة ، تكمن المشكلة في التقاط المتغيرات من الخارج. على سبيل المثال ، نحن نشحن لامدا. يستخدم زوج من المتغيرات التي يتم الإعلان عنها خارج لامدا. وهذا يعني أن هذه المتغيرات ستسافر معها وتطير عبر الشبكة إلى جميع الخوادم. ثم تطرح نفس الأسئلة: هل هذه الأشياء صديقة مع BinaryMarshaller
؟ ما حجمها؟ هل نريد عمومًا نقلها إلى مكان ما ، أم أن هذه الأشياء كبيرة جدًا لدرجة أنه من الأفضل تمرير نوع من الهوية وإعادة إنشاء الأشياء داخل لامدا بالفعل على الجانب الآخر؟
الطبقة المجهولة أسوأ من ذلك. إذا لم تتمكن لامدا من أخذ هذا معك ، فتخلص منه ، إذا لم يتم استخدامه ، فإن الفصل المجهول سيأخذه بالتأكيد ، وهذا لا يؤدي عادةً إلى أي شيء جيد.
المثال التالي. لامدا مرة أخرى ، ولكن يستخدم Apache Ignite API قليلاً.
استخدام كلمة إشعال داخل إغلاق الحساب خطأ
ignite.compute().broadcast(() -> { IgniteCache foo = ignite.cache("foo"); String sql = "where id = 42"; SqlQuery qry = new SqlQuery("Foo", sql).setLocal(true); return foo.query(qry); });
في النسخة الأصلية ، يأخذ ذاكرة التخزين المؤقت ويقوم محليًا بعمل نوع من استعلام SQL فيه. هذا هو النمط عندما تحتاج إلى إرسال مهمة تعمل فقط مع البيانات المحلية على العقد البعيدة.
ما هي المشكلة هنا؟ تلتقط لامدا مرة أخرى الرابط ، ولكن ليس الآن إلى الكائن ، ولكن إلى Ignite المحلية على العقدة التي نرسلها بها. وهي تعمل أيضًا ، لأن كائن Ignite يحتوي على طريقة readResolve()
، والتي تسمح readResolve()
التسلسل لاستبدال Ignite التي جاءت عبر الشبكة بالشبكة المحلية على العقدة التي أرسلناها إليها. ولكن هذا يؤدي أيضًا في بعض الأحيان إلى عواقب غير مرغوب فيها.
ببساطة ، أنت تقوم ببساطة بنقل المزيد من البيانات عبر الشبكة أكثر مما تريد. إذا كنت بحاجة إلى الحصول على بعض التعليمات البرمجية التي لا تتحكم في الإطلاق إلى Apache Ignite أو بعض Ignintion.localIgnite()
، فإن أبسط طريقة هي استخدام طريقة Ignintion.localIgnite()
. يمكنك تسميته من أي موضوع أنشأه Apache Ignite والحصول على ارتباط إلى كائن محلي. إذا كان لديك lambdas ، والخدمات ، وأي شيء ، وتفهم أنك بحاجة إلى Ignite هنا ، فإنني أوصي بهذه الطريقة.
نستخدم Ignite داخل إغلاق الحساب بشكل صحيح - من خلال localIgnite()
ignite.compute().broadcast(() -> { IgniteCache foo = Ignition.localIgnite().cache("foo"); String sql = "where id = 42"; SqlQuery qry = new SqlQuery("Foo", sql).setLocal(true); return foo.query(qry); });
والمثال الأخير في هذا الجزء. يحتوي Apache Ignite على شبكة خدمة يمكن استخدامها لنشر الخدمات الدقيقة مباشرة في مجموعة ، وسيساعد Apache Ignite في الحفاظ على العدد الصحيح من المثيلات عبر الإنترنت. لنفترض في هذه الخدمة أننا بحاجة أيضًا إلى رابط إلى Apache Ignite. كيف تحصل عليه؟ يمكننا استخدام localIgnite()
، ولكن يجب بعد ذلك حفظ هذا الرابط يدويًا في الحقل.
تخزن الخدمة Ignite في حقل بشكل غير صحيح - تأخذها كحجة للمنشئ
MyService s = new MyService(ignite) ignite.services().deployClusterSingleton("svc", s); ... public class MyService implements Service { private Ignite ignite; public MyService(Ignite ignite) { this.ignite = ignite; } ... }
هناك طريقة أبسط. لا يزال لدينا فصول كاملة ، وليس لامدا ، لذلك يمكننا وضع تعليق توضيحي للحقل على أنه @IgniteInstanceResource
. عندما يتم إنشاء الخدمة ، سوف يضع Apache Ignite نفسه هناك ، ويمكنك استخدامه بأمان. أنصحك بشدة بالقيام بذلك ، وعدم محاولة تمرير Apache Ignite وأولاده إلى المنشئ.
تستخدم الخدمة @IgniteInstanceResource
public class MyService implements Service { @IgniteInstanceResource private Ignite ignite; public MyService() { } ... }
كتابة وقراءة البيانات
يراقب خط الأساس
الآن لدينا مجموعة Apache Ignite وكود معد.
دعونا نتخيل هذا السيناريو:
- ذاكرة تخزين مؤقت واحدة
REPLICATED
- تتوفر نسخ من البيانات على جميع العقد ؛ - المثابرة الأصلية قيد التشغيل - الكتابة إلى القرص.
نبدأ عقدة واحدة. نظرًا لتمكين الإصرار الأصلي ، فإننا بحاجة إلى تنشيط الكتلة قبل العمل معها. تنشيط. ثم نطلق المزيد من العقد.
يبدو أن كل شيء يعمل: الكتابة والقراءة على ما يرام. تحتوي جميع العقد على نسخ من البيانات ؛ يمكنك إيقاف عقدة واحدة بأمان. ولكن إذا أوقفت العقدة الأولى التي بدأت منها الإطلاق ، فحينئذٍ ينقطع كل شيء: تختفي البيانات ، وتتوقف العمليات عن المرور.
والسبب في ذلك هو الطوبولوجيا الأساسية - العقد العديدة التي تخزن بيانات الثبات عليها. لن تحتوي جميع العقد الأخرى على بيانات ثابتة.
يتم تحديد مجموعة العقد هذه لأول مرة في وقت التنشيط. ولم تعد تلك العقد التي أضفتها لاحقًا مدرجة في عدد العقد الأساسية. أي أن الكثير من الطوبولوجيا الأساسية تتكون من واحدة فقط ، العقدة الأولى ، عندما تتوقف ، كل شيء ينكسر. لمنع حدوث ذلك ، ابدأ جميع العقد أولاً ، ثم قم بتنشيط الكتلة. إذا كنت بحاجة إلى إضافة أو إزالة عقدة باستخدام الأمر
control.sh --baseline
يمكنك معرفة العقد المدرجة هناك. يمكن للبرنامج النصي نفسه تحديث الخط الأساسي إلى حالته الحالية.
مثال control.sh

تجميع البيانات
الآن نعلم أن البيانات محفوظة ، حاول قراءتها. لدينا دعم SQL ، يمكنك القيام SELECT
- كما هو الحال في Oracle. ولكن في نفس الوقت ، يمكننا قياس وتشغيل أي عدد من العقد ، يتم تخزين البيانات بطريقة موزعة. دعونا نلقي نظرة على هذا النموذج:
public class Person { @QuerySqlField public Long id; @QuerySqlField public Long orgId; } public class Organization { @QuerySqlField private Long id; }
طلب
SELECT * FROM Person as p JOIN Organization as o ON p.orgId = o.id
لن يعيد جميع البيانات. ما الخطب؟
يشير الشخص ( Person
) إلى المنظمة ( Organization
) حسب الهوية. هذا مفتاح خارجي كلاسيكي. ولكن إذا حاولنا الجمع بين الجدولين وإرسال استعلام SQL ، فلن نتلقى جميع البيانات مع عدة عقد في المجموعة.
والحقيقة هي أن SQL JOIN
يعمل افتراضيًا فقط داخل عقدة واحدة. إذا كانت SQL مستمرة في جميع أنحاء المجموعة لجمع البيانات وإرجاع النتيجة الكاملة ، فستكون بطيئة بشكل لا يصدق. سوف نفقد كل فوائد النظام الموزع. لذا ، بدلاً من ذلك ، يبحث Apache Ignite فقط في البيانات المحلية.
للحصول على النتائج الصحيحة ، نحتاج إلى تجميع البيانات معًا (مملوك). أي أنه بالنسبة للجمع الصحيح بين الشخص والمؤسسة ، يجب تخزين بيانات كلا الجدولين على نفس العقدة.
كيف تفعل ذلك؟ الحل الأسهل هو إعلان مفتاح التقارب. هذه قيمة تحدد أي عقدة ، وفي أي قسم ، وفي أي مجموعة من السجلات سيتم تحديد هذه القيمة أو تلك. إذا أعلنا معرف المؤسسة في Person
كمفتاح تقارب ، فهذا يعني أن الأشخاص الذين لديهم معرف المؤسسة هذا يجب أن يكونوا على نفس العقدة مثل المؤسسة التي لها نفس المعرّف.
إذا لم تتمكن من القيام بذلك لسبب ما ، فهناك حل آخر أقل فعالية - تمكين الصلات الموزعة. يتم ذلك من خلال API ، ويعتمد الإجراء على ما تستخدمه - Java أو JDBC أو أي شيء آخر. ثم سيتم تنفيذ عمليات JOIN
بشكل أبطأ ، ولكن بعد ذلك ستعرض النتائج الصحيحة.
دعونا نفكر في كيفية العمل مع مفاتيح التقارب. كيف نفهم أن هذا المعرّف ومثل هذا المجال ومثل هذا المجال مناسب لتحديد الألفة؟ إذا قلنا أنه سيتم تخزين جميع الأشخاص الذين لديهم نفس orgId
معًا ، فإن orgId
هي مجموعة واحدة غير قابلة للتجزئة. لا يمكننا توزيعه بين عدة عقد. إذا كانت قاعدة البيانات تحتوي على 10 مؤسسات ، فسيكون هناك 10 مجموعات غير قابلة للتجزئة يمكن وضعها على 10 عقد. إذا كان هناك المزيد من العقد في المجموعة ، فستبقى جميع العقد "الإضافية" بدون مجموعات. من الصعب تحديد هذا في وقت التشغيل ، لذا فكر فيه مسبقًا.
إذا كان لديك منظمة كبيرة واحدة و 9 منظمات صغيرة ، فسيختلف حجم المجموعات. لكن Apache Ignite لا ينظر في عدد السجلات في مجموعات التقارب عندما يوزعها عبر العقد. لذلك ، لن يضع مجموعة واحدة على عقدة واحدة ، ولكن 9 مجموعات أخرى على أخرى من أجل تسوية التوزيع بطريقة أو بأخرى. بدلا من ذلك ، سيضعهم 5 و 5 ، (أو 6 و 4 ، أو حتى 7 و 3).
كيف تجعل البيانات موزعة بالتساوي؟ نرجو أن يكون لدينا
- مفاتيح K
- مجموعة متنوعة من مفاتيح التقارب ؛
- أقسام P ، أي مجموعات كبيرة من البيانات التي سيقوم Apache Ignite بتوزيعها بين العقد ؛
- العقد N.
ثم من الضروري أن الشرط
K >> A >> P >> N
حيث >>
"أكثر بكثير" وسيتم توزيع البيانات بالتساوي نسبيًا.
بالمناسبة ، الافتراضي هو P = 1024.
على الأرجح لن تنجح في توزيع موحد. كان هذا هو الحال في Apache Ignite 1.x to 1.9. كان يسمى هذا FairAffinityFunction
ولم يعمل بشكل جيد جدًا - فقد أدى إلى الكثير من حركة المرور بين العقد. الآن تسمى الخوارزمية RendezvousAffinityFunction
. لا يعطي توزيعًا نزيهًا تمامًا ، الخطأ بين العقد سيكون زائدًا أو ناقصًا 5-10 ٪.
قائمة مرجعية لمستخدمي Apache Ignite الجدد
- إعداد وقراءة وتخزين السجلات
- قم بإيقاف تشغيل الإرسال المتعدد ، واكتب فقط تلك العناوين والمنافذ التي تستخدمها
- تعطيل IPv6
- إعداد
BinaryMarshaller
ل BinaryMarshaller
- تتبع خط الأساس الخاص بك
- قم بإعداد ارتصاف التقارب