الغرض من مشاركتي هو التحدث عن واجهة برمجة تطبيقات C ++ لقاعدة بيانات Apache Ignite الموزعة المسماة Ignite C ++ ، بالإضافة إلى ميزاتها.
حول Apache Ignite على لوحة كتب بالفعل أكثر من مرة ، فمن المؤكد أن بعضكم يعرف بالفعل ما هو ولماذا هو ضروري.
باختصار عن Apache Ignite لأولئك الذين لم يكونوا على دراية به بعد
لن أخوض في تفاصيل حول كيفية ظهور Apache Ignite وكيف يختلف عن قواعد البيانات الكلاسيكية. لقد أثيرت كل هذه الأسئلة هنا ، هنا أو هنا .
لذا ، فإن Apache Ignite هو في الأساس قاعدة بيانات سريعة التوزيع محسنة للعمل مع ذاكرة الوصول العشوائي. نشأ Ignite نفسه من شبكة بيانات داخل الذاكرة ، وحتى وقت قريب ، تم وضعه كمخزن مؤقت سريع للغاية وموزع بالكامل في الذاكرة بناءً على جدول تجزئة موزع. لهذا السبب ، إلى جانب تخزين البيانات ، لديها العديد من الميزات الملائمة للمعالجة الموزعة بسرعة: Map-Reduce ، وعمليات البيانات الذرية ، ومعاملات ACID الكاملة ، واستعلامات بيانات SQL ، وما يسمى بـ Query Continues ، والتي تجعل من الممكن مراقبة التغييرات في بيانات معينة و الآخرين.
في الآونة الأخيرة ، ومع ذلك ، أضاف النظام الأساسي دعمًا للتخزين المستمر للبيانات على القرص . بعد ذلك ، حصل Apache Ignite على جميع مزايا قاعدة البيانات الكاملة الموجهة للكائنات ، مع الحفاظ على راحة وثراء الأدوات والمرونة وسرعة تاريخ الشبكة.
جزء من النظرية
جزء مهم لفهم العمل مع Apache Ignite هو أنه مكتوب بلغة Java. سوف تسأل: "ما الفرق الذي يحدثه ما تمت كتابته قاعدة البيانات إذا اتصلت بها على أي حال من خلال SQL؟" هناك بعض الحقيقة في ذلك. إذا كنت ترغب في استخدام Ignite فقط كقاعدة بيانات ، فيمكنك أخذ برنامج تشغيل ODBC أو JDBC الذي يأتي مع Ignite ، ورفع عدد عقد الخادم التي تحتاجها باستخدام البرنامج النصي ignite.sh
الذي تم إنشاؤه خصيصًا ، وتكوينها باستخدام تكوينات مرنة وخاصة يحلق حول اللغة ، والعمل مع Ignite حتى من PHP ، وحتى من Go.
توفر واجهة Ignite الأصلية العديد من الميزات أكثر من SQL فقط. من أبسطها: عمليات ذرية سريعة مع كائنات في قاعدة البيانات ، وكائنات التزامن الموزعة والحوسبة الموزعة في كتلة على البيانات المحلية ، عندما لا تحتاج إلى سحب مئات ميغابايت من البيانات إلى العميل لإجراء العمليات الحسابية. كما تفهم ، لا يعمل هذا الجزء من واجهة برمجة التطبيقات من خلال SQL ، ولكنه مكتوب بلغات برمجة محددة جدًا للأغراض العامة.
بطبيعة الحال ، نظرًا لأن Ignite مكتوب بلغة Java ، يتم تنفيذ واجهة برمجة التطبيقات الأكثر شمولاً في لغة البرمجة هذه. ومع ذلك ، بالإضافة إلى Java ، هناك أيضًا إصدارات API لـ C # .NET و C ++. هؤلاء هم ما يسمى بالعملاء "الكثيرين" - في الواقع ، عقدة Ignite في JVM ، التي تم إطلاقها من C ++ أو C # ، والتي يتم التواصل معها من خلال JNI. هذا النوع من العقدة ضروري ، من بين أمور أخرى ، حتى تتمكن الكتلة من تشغيل الحوسبة الموزعة باللغات المقابلة - C ++ و C #.
بالإضافة إلى ذلك ، هناك بروتوكول مفتوح لما يسمى بالعملاء "الرقيقين". هذه مكتبات خفيفة الوزن بالفعل بلغات برمجة مختلفة تتواصل مع المجموعة عبر TCP / IP. تستهلك مساحة ذاكرة أقل بكثير ، وتبدأ على الفور تقريبًا ، ولا تتطلب JVM على الجهاز ، ولكن لديهم وقت استجابة أسوأ قليلاً وليست واجهات برمجة تطبيقات غنية جدًا مقارنة بالعملاء "الكثيرين". اليوم هناك عملاء ضعفاء في Java و C # و Node.js ، عملاء في C ++ و PHP و Python3 و Go يتم تطويرهم بنشاط.
في إحدى المشاركات ، سوف ألقي نظرة على واجهة برمجة تطبيقات Ignite thick لواجهة برمجة تطبيقات C ++ ، لأنها هي التي توفر حاليًا واجهة برمجة التطبيقات الأكثر شمولاً.
الشروع في العمل
لن أتطرق بالتفصيل إلى تثبيت وتكوين الإطار نفسه - فالعملية روتينية وليست مثيرة للاهتمام للغاية وموصوفة جيدًا ، على سبيل المثال ، في الوثائق الرسمية . دعنا نذهب مباشرة إلى الرمز.
نظرًا لأن Apache Ignite عبارة عن منصة موزعة ، للبدء ، فإن أول شيء عليك القيام به هو تشغيل عقدة واحدة على الأقل. يتم ذلك ببساطة باستخدام فئة ignite::Ignition
:
#include <iostream> #include <ignite/ignition.h> using namespace ignite; int main() { IgniteConfiguration cfg; Ignite node = Ignition::Start(cfg); std::cout << "Node started. Press 'Enter' to stop" << std::endl; std::cin.get(); Ignition::StopAll(false); std::cout << "Node stopped" << std::endl; return 0; }
تهانينا ، لقد أطلقت أول عقدة Cache Apache Ignite بالإعدادات الافتراضية. إن فئة Ignite ، بدورها ، هي نقطة الدخول الرئيسية للوصول إلى واجهة برمجة تطبيقات الكتلة بالكامل.
العمل مع البيانات
المكون الرئيسي لـ Ignite C ++ الذي يوفر API للعمل مع البيانات هو ذاكرة التخزين المؤقت ، ignite::cache::Cache<K,V>
. توفر ذاكرة التخزين المؤقت مجموعة أساسية من الأساليب للعمل مع البيانات. نظرًا لأن Cache
هي في الأساس واجهة لجدول التجزئة الموزع ، فإن الطرق الأساسية للعمل معها تشبه العمل مع الحاويات العادية مثل map
أو map
unordered_map
.
#include <string> #include <cassert> #include <cstdint> #include <ignite/ignition.h> using namespace ignite; struct Person { int32_t age; std::string firstName; std::string lastName; } //... int main() { IgniteConfiguration cfg; Ignite node = Ignition::Start(cfg); cache::Cache<int32_t, Person> personCache = node.CreateCache<int32_t, Person>("PersonCache"); Person p1 = { 35, "John", "Smith" }; personCache.Put(42, p1); Person p2 = personCache.Get(42); std::cout << p2 << std::endl; assert(p1 == p2); return 0; }
تبدو بسيطة جدا ، أليس كذلك؟ في الواقع ، تصبح الأمور معقدة بعض الشيء إذا نظرنا عن كثب إلى قيود C ++.
تحديات التكامل C ++
كما ذكرت ، تمت كتابة Apache Ignite بالكامل بلغة Java - وهي لغة قوية تعتمد على OOP. من المنطقي أن العديد من ميزات هذه اللغة ، المرتبطة ، على سبيل المثال ، مع انعكاس وقت تنفيذ البرنامج ، تم استخدامها بنشاط لتنفيذ مكونات Apache Ignite. على سبيل المثال ، لتسلسل / إلغاء تسلسل الكائنات للتخزين على القرص ونقلها عبر الشبكة.
في C ++ ، على عكس Java ، لا يوجد مثل هذا الانعكاس القوي. بشكل عام ، لا ، ليس بعد ، للأسف. على وجه الخصوص ، لا توجد طرق لمعرفة قائمة ونوع حقول كائن ما ، والتي يمكن أن تسمح تلقائيًا بإنشاء التعليمات البرمجية اللازمة لتسلسل / إلغاء تسلسل الكائنات من أنواع المستخدمين. لذلك ، فإن الخيار الوحيد هنا هو أن تطلب من المستخدم تقديم مجموعة البيانات الوصفية اللازمة حول نوع المستخدم وكيفية التعامل معه بشكل صريح.
في Ignite C ++ ، يتم تنفيذ ذلك من خلال تخصص قالب ignite::binary::BinaryType<T>
. يتم استخدام هذا الأسلوب في العملاء "السميكين" و "الرقيقين". بالنسبة لفئة الشخص الموضحة أعلاه ، قد يبدو التخصص المماثل كما يلي:
namespace ignite { namespace binary { template<> struct BinaryType<Person> { static int32_t GetTypeId() { return GetBinaryStringHashCode("Person"); } static void GetTypeName(std::string& name) { name = "Person"; } static int32_t GetFieldId(const char* name) { return GetBinaryStringHashCode(name); } static bool IsNull(const Person& obj) { return false; } static void GetNull(Person& dst) { dst = Person(); } static void Write(BinaryWriter& writer, const Person& obj) { writer.WriteInt32("age", obj.age; writer.WriteString("firstName", obj.firstName); writer.WriteString("lastName", obj.lastName); } static void Read(BinaryReader& reader, Person& dst) { dst.age = reader.ReadInt32("age"); dst.firstName = reader.ReadString("firstName"); dst.lastName = reader.ReadString("lastName"); } }; }
كما ترى ، بالإضافة إلى BinaryType<Person>::Write
التسلسل / إلغاء التسلسل BinaryType<Person>::Write
، BinaryType<Person>::Read
، هناك العديد من الطرق الأخرى. إنها ضرورية لتشرح للمنصة كيفية العمل مع أنواع C ++ المخصصة بلغات أخرى ، خاصة Java. دعونا نلقي نظرة فاحصة على كل طريقة:
GetTypeName()
- إرجاع اسم النوع. يجب أن يكون اسم النوع هو نفسه على جميع الأنظمة الأساسية التي يتم استخدام النوع عليها. إذا كنت تستخدم النوع فقط في Ignite C ++ ، فيمكن أن يكون الاسم أي شيء.GetTypeId()
- تقوم هذه الطريقة بإرجاع معرف فريد عبر النظام الأساسي للنوع. للعمل الصحيح مع نوع على منصات مختلفة ، من الضروري أن يتم حسابه بنفس الطريقة في كل مكان. ترجع طريقة GetBinaryStringHashCode(TypeName)
نفس معرف النوع كما هو الحال في جميع الأنظمة الأساسية الأخرى بشكل افتراضي ، أي أن تنفيذ هذه الطريقة يسمح لك بالعمل بشكل صحيح مع هذا النوع من الأنظمة الأساسية الأخرى.GetFieldId()
- إرجاع معرف فريد لاسم النوع. مرة أخرى ، بالنسبة للعمل الصحيح عبر الأنظمة الأساسية ، يجدر استخدام طريقة GetBinaryStringHashCode()
؛IsNull()
- للتحقق مما إذا كان مثيل لفئة ما هو كائن من النوع NULL
. تستخدم لتسلسل القيم NULL
بشكل صحيح. ليست مفيدة جدًا مع مثيلات الفئة نفسها ، ولكن يمكن أن تكون مريحة للغاية إذا كان المستخدم يريد العمل مع المؤشرات الذكية وتحديد التخصص ، على سبيل المثال ، لـ BinaryType< std::unique_ptr<Person> >
.GetNull()
- يتم الاتصال به عند محاولة إلغاء تسلسل قيمة NULL
. كل ما يقال عن IsNull
ينطبق أيضًا على GetNull()
.
SQL
إذا قمنا برسم تشابه مع قواعد البيانات الكلاسيكية ، فإن ذاكرة التخزين المؤقت هي مخطط قاعدة بيانات مع اسم فئة يحتوي على جدول واحد - مع اسم النوع. بالإضافة إلى مخططات التخزين المؤقت ، هناك مخطط عام يسمى PUBLIC
يمكنك من خلاله إنشاء / حذف عدد غير محدود من الجداول باستخدام أوامر DDL القياسية ، مثل CREATE TABLE
و DROP TABLE
وما إلى ذلك. بالنسبة للمخطط العام ، يتصلون عادةً عبر ODBC / JDBC إذا كانوا يريدون استخدام Ignite ببساطة كقاعدة بيانات موزعة.
يدعم Ignite استعلامات SQL الكاملة ، بما في ذلك DML و DDL. لا يوجد دعم لمعاملات SQL حتى الآن ، لكن المجتمع يعمل الآن بنشاط على تنفيذ MVCC ، والذي سيضيف المعاملات ، وعلى حد علمي ، تم إدخال التغييرات الرئيسية مؤخرًا في الماجستير.
للعمل مع بيانات ذاكرة التخزين المؤقت عبر SQL ، يجب أن تحدد بشكل صريح في تكوين ذاكرة التخزين المؤقت حقول الكائن التي سيتم استخدامها في استعلامات SQL. تتم كتابة التكوين في ملف XML ، وبعد ذلك يتم تحديد المسار إلى ملف التكوين عند بدء العقدة:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"> <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"> <property name="cacheConfiguration"> <list> <bean class="org.apache.ignite.configuration.CacheConfiguration"> <property name="name" value="PersonCache"/> <property name="queryEntities"> <list> <bean class="org.apache.ignite.cache.QueryEntity"> <property name="keyType" value="java.lang.Integer"/> <property name="valueType" value="Person"/> <property name="fields"> <map> <entry key="age" value="java.lang.Integer"/> <entry key="firstName" value="java.lang.String"/> <entry key="lastName" value="java.lang.String"/> </map> </property> </bean> </list> </property> </bean> </list> </property> </bean> </beans>
يتم تحليل التكوين بواسطة مشغّل Java ، لذا يجب أيضًا تحديد الأنواع الأساسية لـ Java. بعد إنشاء ملف التكوين ، تحتاج إلى بدء العقدة ، والحصول على مثيل ذاكرة التخزين المؤقت ، ويمكنك البدء في استخدام SQL:
//... int main() { IgniteConfiguration cfg; cfg.springCfgPath = "config.xml"; Ignite node = Ignition::Start(cfg); cache::Cache<int32_t, Person> personCache = node.GetCache<int32_t, Person>("PersonCache"); personCache.Put(1, Person(35, "John", "Smith")); personCache.Put(2, Person(31, "Jane", "Doe")); personCache.Put(3, Person(12, "Harry", "Potter")); personCache.Put(4, Person(12, "Ronald", "Weasley")); cache::query::SqlFieldsQuery qry( "select firstName, lastName from Person where age = ?"); qry.AddArgument<int32_t>(12); cache::query::QueryFieldsCursor cursor = cache.Query(qry); while (cursor.HasNext()) { QueryFieldsRow row = cursor.GetNext(); std::cout << row.GetNext<std::string>() << ", "; std::cout << row.GetNext<std::string>() << std::endl; } return 0; }
بنفس الطريقة ، يمكنك استخدام insert
update
create table
والاستعلامات الأخرى. بالطبع ، يتم دعم الطلبات عبر ذاكرة التخزين المؤقت أيضًا. ومع ذلك ، في هذه الحالة ، يجب الإشارة إلى اسم ذاكرة التخزين المؤقت في الطلب بعلامات اقتباس كاسم المخطط. على سبيل المثال ، بدلاً من
select * from Person inner join Profession
يجب أن يكتب
select * from "PersonCache".Person inner join "ProfessionCache".Profession
وهكذا دواليك
هناك بالفعل العديد من الاحتمالات في Apache Ignite ، وبالطبع ، في منشور واحد كان من المستحيل تغطيتها جميعًا. تتطور C ++ API بنشاط الآن ، لذلك سيكون هناك المزيد من الاهتمام قريبًا. من الممكن أن أكتب بعض المنشورات الإضافية حيث سأقوم بتحليل بعض الميزات بمزيد من التفصيل.
ملاحظة: لقد كنت أحد مشغلي Apache Ignite منذ عام 2017 وأعمل بنشاط على تطوير واجهة برمجة تطبيقات C ++ لهذا المنتج. إذا كنت معتادًا إلى حد ما على C ++ أو Java أو .NET وترغب في المشاركة في تطوير منتج مفتوح مع مجتمع نشط وودي ، فسنجد دائمًا بعض المهام الأخرى المثيرة للاهتمام لك.