Tujuan posting saya adalah untuk berbicara tentang C ++ API dari database terdistribusi Apache Ignite yang disebut Ignite C ++, serta fitur-fiturnya.
Tentang Apache Ignite on habr sudah menulis lebih dari sekali, jadi pasti beberapa dari Anda sudah tahu apa itu dan mengapa itu perlu.
Secara singkat tentang Apache Ignite untuk mereka yang belum terbiasa dengannya
Saya tidak akan masuk ke rincian tentang bagaimana Apache Ignite muncul dan bagaimana hal itu berbeda dari database klasik. Semua pertanyaan ini telah diajukan di sini , di sini atau di sini .
Jadi, Apache Ignite pada dasarnya adalah database terdistribusi cepat yang dioptimalkan untuk bekerja dengan RAM. Ignite tumbuh dari In-memory Data Grid dan, hingga saat ini, diposisikan sebagai cache terdistribusi yang sangat cepat dan sepenuhnya dalam memori berdasarkan tabel hash yang didistribusikan. Itulah sebabnya, selain menyimpan data, ia memiliki banyak fitur yang nyaman untuk pemrosesan terdistribusi cepat: Peta-Mengurangi, operasi data atom, transaksi ACID lengkap, kueri data SQL, yang disebut Continues Queries, yang memungkinkan untuk memantau perubahan pada data tertentu dan lainnya.
Namun, baru-baru ini, platform tersebut telah menambahkan dukungan untuk penyimpanan data yang terus-menerus pada disk . Setelah itu, Apache Ignite mendapatkan semua manfaat dari database berorientasi objek penuh, sambil menjaga kenyamanan, kekayaan alat, fleksibilitas dan kecepatan tanggal grid.
Sedikit teori
Bagian penting untuk memahami bekerja dengan Apache Ignite adalah bahwa ia ditulis dalam Java. Anda akan bertanya: "Apa bedanya dengan apa yang ditulis database jika saya tetap berkomunikasi dengannya melalui SQL?" Ada beberapa kebenaran dalam hal ini. Jika Anda ingin menggunakan Ignite hanya sebagai database, Anda dapat mengambil ODBC atau JDBC driver yang datang dengan Ignite, meningkatkan jumlah node server yang Anda butuhkan menggunakan skrip ignite.sh
dibuat khusus, konfigurasikan menggunakan konfigurasi fleksibel dan terutama melambung tentang bahasa, bekerja dengan Ignite bahkan dari PHP, bahkan dari Go.
Antarmuka Ignite asli menyediakan banyak fitur lebih dari sekadar SQL. Dari yang paling sederhana: operasi atom cepat dengan objek dalam database, objek sinkronisasi terdistribusi, dan komputasi terdistribusi dalam sebuah cluster pada data lokal, ketika Anda tidak perlu menarik ratusan megabyte data ke klien untuk perhitungan. Seperti yang Anda pahami, bagian API ini tidak berfungsi melalui SQL, tetapi ditulis dalam bahasa pemrograman tujuan umum yang sangat spesifik.
Tentu saja, karena Ignite ditulis dalam Java, API paling komprehensif diimplementasikan dalam bahasa pemrograman ini. Namun, selain Java, ada juga versi API untuk C # .NET dan C ++. Ini adalah apa yang disebut "tebal" klien - pada kenyataannya, simpul Ignite di JVM, diluncurkan dari C ++ atau C #, komunikasi dengan yang terjadi melalui JNI. Node jenis ini diperlukan, antara lain, agar cluster dapat menjalankan komputasi terdistribusi dalam bahasa yang sesuai - C ++ dan C #.
Selain itu, ada protokol terbuka untuk apa yang disebut klien "tipis". Ini sudah perpustakaan ringan dalam berbagai bahasa pemrograman yang berkomunikasi dengan cluster melalui TCP / IP. Mereka mengambil lebih sedikit ruang memori, memulai hampir secara instan, tidak memerlukan JVM pada mesin, tetapi mereka memiliki latensi yang sedikit lebih buruk dan API yang tidak terlalu kaya dibandingkan dengan klien "tebal". Saat ini ada klien tipis di Java, C #, dan Node.js, klien di C ++, PHP, Python3, Go secara aktif dikembangkan.
Dalam sebuah posting, saya akan melihat API tebal Ignite untuk C ++ API, karena itu yang saat ini menyediakan API paling komprehensif.
Memulai
Saya tidak akan membahas secara terperinci tentang pemasangan dan konfigurasi kerangka itu sendiri - prosesnya rutin, tidak terlalu menarik dan dijelaskan dengan baik, misalnya, dalam dokumentasi resmi . Ayo langsung ke kode.
Karena Apache Ignite adalah platform terdistribusi, untuk memulai, hal pertama yang perlu Anda lakukan adalah menjalankan setidaknya satu node. Ini dilakukan dengan sangat sederhana menggunakan kelas 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; }
Selamat, Anda meluncurkan simpul Cache Apache Ignite pertama Anda dengan pengaturan default. Kelas Ignite, pada gilirannya, adalah titik masuk utama untuk mengakses seluruh API cluster.
Bekerja dengan data
Komponen utama Ignite C ++ yang menyediakan API untuk bekerja dengan data adalah cache, ignite::cache::Cache<K,V>
. Cache menyediakan serangkaian metode dasar untuk bekerja dengan data. Karena Cache
pada dasarnya adalah antarmuka ke tabel hash terdistribusi, metode dasar untuk bekerja dengannya mirip dengan bekerja dengan wadah biasa seperti map
atau 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; }
Terlihat sangat sederhana, bukan? Bahkan, hal-hal menjadi sedikit rumit jika kita melihat lebih dekat pada batasan C ++.
C ++ Tantangan Integrasi
Seperti yang saya sebutkan, Apache Ignite sepenuhnya ditulis di Jawa - bahasa berbasis OOP yang kuat. Adalah logis bahwa banyak fitur bahasa ini, yang terkait, misalnya, dengan refleksi waktu pelaksanaan program, secara aktif digunakan untuk mengimplementasikan komponen Apache Ignite. Misalnya, untuk serialisasi / deserialisasi objek untuk penyimpanan pada disk dan transmisi melalui jaringan.
Di C ++, tidak seperti Java, tidak ada refleksi yang kuat. Secara umum, tidak, belum, sayangnya. Secara khusus, tidak ada cara untuk mengetahui daftar dan jenis bidang suatu objek, yang dapat memungkinkan secara otomatis menghasilkan kode yang diperlukan untuk membuat serial / deserializing objek jenis pengguna. Oleh karena itu, satu-satunya pilihan di sini adalah meminta pengguna untuk secara eksplisit menyediakan set metadata yang diperlukan tentang jenis pengguna dan bagaimana cara menggunakannya.
Di Ignite C ++, ini diterapkan melalui spesialisasi template ignite::binary::BinaryType<T>
. Pendekatan ini digunakan baik pada klien "tebal" dan "tipis". Untuk kelas Person yang disajikan di atas, spesialisasi serupa mungkin terlihat seperti ini:
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"); } }; }
Seperti yang Anda lihat, selain metode serialisasi / deserialisasi BinaryType<Person>::Write
, BinaryType<Person>::Read
, ada beberapa metode lain. Mereka diperlukan untuk menjelaskan kepada platform cara bekerja dengan tipe C ++ khusus dalam bahasa lain, khususnya Java. Mari kita perhatikan lebih dekat setiap metode:
GetTypeName()
- Mengembalikan nama tipe. Nama tipe harus sama pada semua platform tempat tipe tersebut digunakan. Jika Anda hanya menggunakan tipe di Ignite C ++, namanya bisa apa saja.GetTypeId()
- Metode ini mengembalikan pengidentifikasi unik lintas platform untuk jenisnya. Untuk pekerjaan yang benar dengan tipe pada platform yang berbeda, perlu bahwa itu dihitung sama di mana-mana. Metode GetBinaryStringHashCode(TypeName)
mengembalikan Tipe ID yang sama seperti pada semua platform lainnya secara default, yaitu, penerapan metode ini memungkinkan Anda untuk bekerja dengan benar dengan tipe ini dari platform lain.GetFieldId()
- Mengembalikan pengidentifikasi unik untuk nama jenis. Sekali lagi, untuk pekerjaan lintas platform yang benar perlu menggunakan metode GetBinaryStringHashCode()
;IsNull()
- Memeriksa apakah instance kelas adalah objek bertipe NULL
. Digunakan untuk membuat serialisasi nilai NULL
dengan benar. Tidak terlalu berguna dengan instance kelas itu sendiri, tetapi bisa sangat nyaman jika pengguna ingin bekerja dengan pointer pintar dan mendefinisikan spesialisasi, misalnya, untuk BinaryType< std::unique_ptr<Person> >
.GetNull()
- Dipanggil ketika mencoba deserialize nilai NULL
. Semua yang dikatakan tentang IsNull
juga berlaku untuk GetNull()
.
SQL
Jika kita menggambar analogi dengan database klasik, maka cache adalah skema basis data dengan nama kelas yang berisi satu tabel - dengan nama tipe. Selain skema cache, ada skema umum yang disebut PUBLIC
di mana Anda dapat membuat / menghapus jumlah tabel yang tidak terbatas menggunakan perintah DDL standar, seperti CREATE TABLE
, DROP TABLE
dan sebagainya. Ke skema PUBLIC bahwa mereka biasanya terhubung melalui ODBC / JDBC jika mereka ingin menggunakan Ignite hanya sebagai database terdistribusi.
Ignite mendukung kueri SQL lengkap, termasuk DML dan DDL. Belum ada dukungan untuk transaksi SQL, tetapi komunitas sekarang aktif bekerja pada implementasi MVCC, yang akan menambah transaksi, dan, sejauh yang saya tahu, perubahan utama baru-baru ini diperkenalkan pada master.
Untuk bekerja dengan data cache melalui SQL, Anda harus secara eksplisit menentukan dalam konfigurasi cache yang bidang objek akan digunakan dalam query SQL. Konfigurasi ditulis dalam file XML, setelah itu path ke file konfigurasi ditentukan ketika node mulai:
<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>
Konfigurasi diurai oleh mesin Java, jadi tipe dasar juga harus ditentukan untuk Java. Setelah file konfigurasi dibuat, Anda perlu memulai node, dapatkan instance cache dan Anda dapat mulai menggunakan 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; }
Dengan cara yang sama, Anda bisa menggunakan insert
, update
, create table
dan kueri lainnya. Tentu saja, permintaan lintas-cache juga didukung. Namun, dalam hal ini, nama cache harus ditunjukkan dalam permintaan dengan tanda kutip sebagai nama skema. Misalnya, alih-alih
select * from Person inner join Profession
harus menulis
select * from "PersonCache".Person inner join "ProfessionCache".Profession
Dan sebagainya
Ada banyak kemungkinan di Apache Ignite dan, tentu saja, dalam satu posting tidak mungkin untuk membahas semuanya. C ++ API sedang aktif dikembangkan sekarang, jadi akan segera ada yang lebih menarik. Mungkin saja saya akan menulis beberapa posting lagi di mana saya akan menganalisis beberapa fitur secara lebih rinci.
PS Saya telah menjadi committer Apache Ignite sejak 2017 dan saya aktif mengembangkan C ++ API untuk produk ini. Jika Anda cukup terbiasa dengan C ++, Java, atau .NET dan ingin berpartisipasi dalam pengembangan produk terbuka dengan komunitas yang ramah dan aktif, kami akan selalu menemukan beberapa tugas menarik lainnya untuk Anda.