Halo semuanya. Seperti yang Anda ketahui, saya biasa menulis dan berbicara lebih banyak tentang penyimpanan, Vertica, penyimpanan data besar dan hal-hal analitis lainnya. Sekarang semua database lain telah jatuh ke dalam bidang tanggung jawab saya, tidak hanya analitis, tetapi juga OLTP (PostgreSQL), dan NOSQL (MongoDB, Redis, Tarantool).
Situasi ini memungkinkan saya untuk melihat organisasi yang memiliki beberapa basis data sebagai organisasi yang memiliki satu basis data yang terdistribusi heterogen (heterogen). Sebuah database heterogen terdistribusi tunggal, terdiri dari sekelompok PostgreSQL, Redis dan Mong ... Dan, mungkin, satu atau dua database Vertica.
Pekerjaan dari pangkalan terdistribusi tunggal ini menghasilkan banyak tugas menarik. Pertama-tama, dari sudut pandang bisnis, penting bahwa semuanya normal dengan data bergerak di sepanjang pangkalan tersebut. Saya tidak secara khusus menggunakan istilah integritas, konsistensi, karena istilah ini kompleks, dan dalam nuansa yang berbeda dalam mempertimbangkan DBMS (teorema C ID dan C AP) ia memiliki arti yang berbeda.
Situasi dengan basis terdistribusi diperburuk jika perusahaan mencoba untuk beralih ke arsitektur layanan-mikro. Di bawah kucing, saya berbicara tentang bagaimana memastikan integritas data dalam arsitektur layanan mikro tanpa transaksi terdistribusi dan konektivitas yang ketat. (Dan pada akhirnya saya jelaskan mengapa saya memilih ilustrasi ini untuk artikel).

Menurut Chris Richardson (salah satu penginjil paling terkenal dari arsitektur layanan mikro), arsitektur ini memiliki dua pendekatan untuk bekerja dengan database: database bersama dan basis data per layanan.

Basis data bersama adalah langkah pertama yang baik, solusi yang bagus untuk perusahaan kecil tanpa rencana pertumbuhan yang ambisius. Selain itu, pola ini sendiri merupakan anti-pola dari sudut pandang arsitektur layanan microsoft dua layanan yang berbagi basis umum tidak dapat diuji dan diskalakan secara independen. Yaitu alih-alih, layanan ini adalah salah satu layanan yang cenderung menjadi monolit.
Pola basis data per layanan mengasumsikan bahwa setiap layanan memiliki basis datanya sendiri. Suatu layanan dapat mengakses data layanan lain hanya melalui API (dalam arti luas), tanpa koneksi langsung ke basis datanya.
Pola basis data per layanan memungkinkan tim layanan terkait untuk memilih database yang mereka inginkan. Seseorang dapat di MongoDB, seseorang percaya pada PostgreSQL, seseorang membutuhkan Redis (risiko kehilangan data saat dimatikan dapat diterima untuk layanan ini), dan seseorang umumnya menyimpan data dalam file CSV pada disk (dan mengapa, sebenarnya , dan tidak?).

Bekerja dengan “kebun binatang” basis data meningkatkan tugas memulihkan urutan data ke tingkat kompleksitas yang sama sekali baru.
ACID dan arsitektur microservice
Mari kita lihat tugas mengatur segala sesuatunya melalui prisma dari serangkaian persyaratan ACID berbasis DBMS klasik: kita akan memperluas esensi setiap huruf singkatan dan menggambarkan kesulitan dengan huruf ini dalam arsitektur layanan mikro.
(A) CID - Atomicity. Atomicity - semua atau tidak sama sekali.
Menurut persyaratan Atomicity, sangat penting untuk menyelesaikan semua langkah (dengan kemungkinan pengulangan), jika langkah penting gagal, batalkan langkah yang sudah selesai.
Ilustrasi di atas menunjukkan proses pengujian untuk membeli layanan VIP: uang dicadangkan dalam penagihan (1), layanan bonus (2) diaktifkan untuk pengguna, jenis pengguna diubah menjadi Pro (3), uang pesanan dalam penagihan didebit (4). Keempat langkah harus diselesaikan atau tidak selesai.

Dalam hal ini, Anda tidak dapat menggantung di tengah proses, oleh karena itu, sinkronisasi lebih disukai, dalam kasus ekstrim, sinkronisasi dengan batas waktu bawaan.
A (C) ID - Konsistensi. Konsistensi - setiap langkah tidak boleh bertentangan dengan kondisi batas.
Contoh klasik kondisi untuk, misalnya, mengirim uang dari klien A dalam layanan 1 ke klien B dalam layanan 2: sebagai akibat dari pengiriman uang tersebut tidak boleh kurang (uang tidak boleh hilang selama transfer) atau lebih (tidak dapat diterima untuk mengirim uang yang sama ke dua pengguna pada saat bersamaan). Untuk mematuhi persyaratan ini, Anda perlu kode kondisi di suatu tempat dan memeriksa data untuk kondisi (idealnya, tanpa panggilan tambahan).

ACI (D) - Daya Tahan. Persyaratan Daya Tahan berarti bahwa efek operasi tidak hilang.
Dalam kondisi kegigihan Polyglot, suatu layanan dapat beroperasi pada basis data yang secara teratur dapat "kehilangan" data yang dicatat di dalamnya. Trik serupa dapat diperoleh bahkan dari database padat seperti PostgreSQL, jika replikasi asinkron diaktifkan di sana. Ilustrasi menunjukkan bagaimana perubahan yang direkam dalam Master, tetapi yang tidak mencapai Slave melalui replikasi asinkron, dapat dihancurkan dengan membakar server Master. Untuk memastikan persyaratan Daya Tahan, perlu untuk dapat mendiagnosis dan memulihkan kerugian tersebut dengan benar.

Dan di mana saya, Anda bertanya?
Dan tidak ada tempat. Isolasi dalam lingkungan beberapa layanan asinkron independen adalah persyaratan teknis. Penelitian modern telah menunjukkan bahwa proses bisnis nyata dapat diimplementasikan tanpa isolasi. Isolasi menyederhanakan pemikiran dengan meminimalkan konkurensi (mengembangkan komputasi paralel lebih sulit bagi seorang programmer), tetapi arsitektur layanan mikro secara paralel paralel, isolasi dalam lingkungan seperti itu berlebihan.
Ada banyak pendekatan untuk mencapai kepatuhan dengan persyaratan di atas. Algoritma yang paling dikenal luas dari transaksi terdistribusi yang disediakan oleh apa yang disebut dua fase komitmen (2PC). Sayangnya, menerapkan komitmen dua fase memerlukan penulisan ulang semua layanan yang terlibat. Dan yang paling serius: algoritma ini tidak terlalu produktif. Ilustrasi dari penelitian terbaru menunjukkan bahwa algoritma ini menunjukkan kinerja tertentu pada basis terdistribusi dari dua server, tetapi dengan peningkatan jumlah server, produktivitas tidak tumbuh secara linear ... Atau lebih tepatnya, tidak tumbuh sama sekali.

Salah satu keunggulan utama arsitektur layanan-mikro adalah kemampuan untuk meningkatkan kinerja secara linear dengan hanya menambah lebih banyak server. Ternyata jika kita menggunakan komitmen dua fase untuk memastikan integritas terdistribusi, proses ini akan menjadi hambatan, pembatas pertumbuhan produktivitas, meskipun ada peningkatan jumlah server.
Bagaimana Anda dapat memastikan integritas terdistribusi (persyaratan ACiD) tanpa komitmen dua fase, dengan kemampuan untuk skala secara linear dalam kinerja?
Penelitian modern (misalnya, Evaluasi Kontrol Concurrency Terdistribusi. VLDB 2017 ) berpendapat bahwa apa yang disebut "pendekatan optimistis" dapat membantu. Perbedaan antara komitmen dua fase dan "pendekatan optimis" umum dapat diilustrasikan oleh perbedaan antara toko lama Soviet (dengan counter) dan supermarket modern seperti Auchan. Di toko dengan penghitung, setiap pelanggan dianggap mencurigakan, dan disajikan dengan kontrol maksimal. Karena itu garis dan konflik. Dan di supermarket, pembeli dianggap jujur secara default, mereka memberinya kesempatan untuk mendekati rak dan mengisi gerobak. Tentu saja, ada alat pemantauan untuk menangkap penjahat (kamera, keamanan), tetapi sebagian besar pembeli tidak perlu berurusan dengan mereka.
Oleh karena itu, supermarket dapat ditingkatkan, diperluas, hanya dengan menempatkan lebih banyak meja kas. Mirip dengan arsitektur microservice: jika integritas terdistribusi dipastikan oleh "pendekatan optimistis", ketika hanya proses di mana sesuatu yang salah ditambahkan dengan pemeriksaan. Dan proses normal berjalan tanpa pemeriksaan tambahan.
Itu penting. "Pendekatan optimis" mencakup beberapa algoritma. Saya ingin bercerita tentang saga - algoritma untuk menjaga integritas terdistribusi, direkomendasikan oleh Chris Richardson.
Sagas - elemen algoritma
Algoritma melorot memiliki dua opsi. Oleh karena itu, pada awalnya saya ingin menjelaskan elemen-elemen algoritma yang diperlukan secara universal sehingga deskripsi tersebut cocok untuk kedua opsi.
Elemen 1. Saluran persisten pengiriman acara yang dapat diandalkan antar layanan, yang menjamin "setidaknya satu kali pengiriman". Yaitu jika langkah 2 dari proses telah selesai dengan sukses, maka pemberitahuan (acara) tentang ini harus mencapai langkah 3 setidaknya sekali, pengiriman berulang dapat diterima, tetapi tidak ada yang harus hilang. "Persistent" berarti bahwa saluran harus menyimpan pemberitahuan untuk beberapa waktu (2-3 hari, seminggu) sehingga layanan yang telah kehilangan perubahan terbaru karena hilangnya basis data (lihat contoh Daya Tahan, pada ilustrasi ini adalah langkah 2), dapat memulihkan perubahan ini dengan memutar ulang acara dari saluran.

Elemen 2. Idempotensi panggilan layanan melalui penggunaan kunci idempotensi unik. Bayangkan saya (pengguna) memulai proses pembelian paket VIP (lihat contoh untuk Atomicity). Pada awal proses, saya diberikan kunci unik, kunci idempotensi, misalnya, 42. Selanjutnya, panggilan ke setiap langkah (1 → 2 → 3 → 4) harus dilakukan dengan kunci idempotensi yang ditunjukkan. Dalam paragraf di atas, kemungkinan kedatangan berulang pesan yang sama ke layanan (dalam langkah) disebutkan. Layanan (langkah) harus secara otomatis dapat mengabaikan kedatangan berulang dari acara yang diproses, memeriksa pengulangan dengan kunci idempotensi. Artinya, jika semua layanan (langkah-langkah proses) idempoten, maka untuk memenuhi persyaratan Atomicity dan Daya Tahan, cukup untuk mengarahkan ulang ke langkah-langkah yang terkait dengan acara dari saluran. Langkah-langkah yang melewatkan acara akan mengeksekusi mereka, dan langkah-langkah yang sudah menyelesaikan acara akan mengabaikannya karena idempotensi.

Elemen 3. Pembatalan panggilan layanan (langkah-langkah) oleh kunci idempotency.
Untuk memastikan Atomicity (lihat contoh), jika proses dengan kunci idempotensi 42, misalnya, berhenti / jatuh pada langkah 3, maka perlu untuk membatalkan keberhasilan pelaksanaan langkah 1 dan 2 untuk kunci 42. Untuk ini, setiap langkah proses wajib harus memiliki langkah "kompensasi" , Metode API yang membatalkan eksekusi langkah yang diperlukan untuk kunci idempotensi yang ditentukan (42). Implementasi panggilan kompensasi adalah elemen yang sulit tetapi perlu dalam penyempurnaan layanan sebagai bagian dari implementasi algoritma sag.

Tiga elemen yang tercantum di atas relevan untuk kedua versi implementasi "melorot": diatur dan koreografi.
Sagas yang diatur
Algoritma yang lebih sederhana dan lebih jelas untuk kisah-kisah yang diatur lebih mudah dipahami dan diimplementasikan. Dalam sebuah artikel yang sangat bagus, kevteev menggambarkan algoritma dan proses implementasi dari mekanisme kisah-kisah yang diatur dalam Avito. Algoritma mereka mengasumsikan adanya layanan kontrol, "mengatur" panggilan layanan dalam kerangka proses bisnis yang dilayani. Layanan pemantauan yang sama mungkin memiliki database sendiri (misalnya, PostgreSQL), yang bertindak sebagai saluran pengiriman peristiwa persisten yang dapat diandalkan (elemen 1).
Sagas koreografi
Kisah koreografi lebih rumit. Di sini, bus data yang menerapkan persyaratan berikut ini harus bertindak sebagai saluran persisten yang andal: penerbitan api-dan-lupakan, publikasi-berlangganan pengiriman acara, setidaknya sekali pengiriman. Yaitu setiap langkah dari setiap proses harus menerima perintah untuk beroperasi dari bus, dan memberikan pesan tentang penyelesaian yang berhasil, tentang dimulainya langkah berikutnya, sehingga ia juga membacanya dari bus dan melanjutkan prosesnya. Terlebih lagi, untuk setiap pesan mungkin ada beberapa pelanggan.
Saga koreografi juga harus memiliki layanan pengendali, layanan kisah, tetapi jauh lebih "ringan". Layanan harus tahu tentang proses bisnis yang terdaftar dalam sistem, tentang komposisi langkah-langkah yang termasuk dalam setiap proses. Dia juga harus mendengarkan bus, memantau pelaksanaan setiap proses (setiap kunci idempotensi), dan hanya jika ada yang salah, melempar "pengulangan" langkah-langkah tertentu, atau melempar "membatalkan", "kompensasi" untuk langkah-langkah yang diambil.
Nuansa
Salah satu nuansa terpenting dari kisah-kisah yang membedakan mereka dari transaksi klasik adalah penyimpangan dari linearitas, urutan, dan kewajiban setiap langkah. Sebuah saga tidak harus berupa rangkaian kejadian linier, ini bisa berupa grafik terarah: acara pendaftaran pengguna baru dapat menghasilkan beberapa langkah secara paralel (mengirim SMS, mendaftarkan login, menghasilkan kata sandi, mengirim email), beberapa di antaranya mungkin opsional. Dalam perkiraan pertama, tampaknya dalam saga "bercabang" dengan langkah-langkah opsional, sulit untuk menentukan penyelesaian saga (proses), tetapi, pada kenyataannya, semuanya sederhana: saga (proses) selesai ketika semua langkah yang diperlukan selesai, dalam urutan apa pun.

Nuansa kedua, yang lebih khas untuk kisah-kisah koreografi, tetapi juga mungkin untuk kisah-kisah yang diatur, adalah memilih pendekatan untuk mendaftarkan proses-proses bisnis, jenis-jenis kisah dalam layanan kisah. Contoh Atomicity menggambarkan proses empat langkah yang diperlukan berturut-turut.
Siapa yang mendaftarkan proses ini, mengindikasikan semua langkah, menempatkan dependensi dan langkah-langkah wajib? Jawaban yang jelas, tetapi kuno adalah bahwa proses registrasi harus dilakukan secara terpusat dalam layanan sag. Tetapi jawaban ini tidak terlalu konsisten dengan arsitektur microservice. Dalam arsitektur microservice, lebih menjanjikan, lebih produktif, dan lebih cepat untuk mendaftarkan proses bottom-up. Yaitu bukan untuk menuliskan semua nuansa proses dalam layanan sag, tetapi untuk memungkinkan layanan individual untuk "menyesuaikan" ke dalam proses yang ada sendiri, menunjukkan sifat mengikat / opsional dan pendahulunya wajib.
Yaitu proses mendaftarkan pengguna dalam layanan sag awalnya mungkin terdiri dari tiga langkah, dan kemudian, selama pengembangan sistem, tujuh langkah lagi akan cocok di sana, dan satu langkah akan dituliskan, dan akan ada sembilan dari mereka. Skema "anarkis" dan "terdesentralisasi" seperti itu sulit untuk diuji, untuk menerapkan proses yang ketat dan terkoordinasi, tetapi jauh lebih nyaman bagi tim Agile, untuk evolusi produk multi arah yang berkelanjutan.
Sebenarnya disini. Dengan presentasi yang serius, saya pikir layak untuk menyelesaikannya, jika tidak artikelnya terlalu besar.
Berikut ini tautan ke presentasi materi ini, saya membuat laporan tentang topik ini di Highload Siberia 2018.
UPD - dan video dari konferensi:
Epilog
Pada akhirnya, saya ingin mencoba menjelaskan semua hal di atas dalam bahasa yang lebih kiasan.
Lagipula, apa saga awalnya? Plot ini, petualangan ini dari Abad Pertengahan ... Atau dari Game of Thrones. Sebuah peristiwa terjadi (pertempuran, pernikahan, seseorang meninggal), berita tentang ini terbang di seluruh dunia melalui pembawa pesan, melalui merpati pos, melalui pedagang. Ketika berita sampai pada mereka yang tertarik (dalam seminggu, dalam sebulan, dalam satu tahun), mereka bereaksi: mereka mengirim tentara, menyatakan perang, mereka mengeksekusi seseorang, dan pesan-pesan baru beterbangan.
Tidak ada badan pengawas yang memantau urutan tindakan. Tidak ada transaksi, tidak ada kemunduran, dalam arti membatalkan tindakan, seolah-olah tidak pernah terjadi. Semua dengan cara dewasa, setiap tindakan berlangsung selamanya. Itu bisa dikompensasi, tetapi justru tindakan (pembunuhan) dan kompensasi (membayar untuk kepala, vira), dan bukan penghapusan kematian.
Peristiwa memakan waktu lama, datang dari sumber yang berbeda, tindakan terjadi secara paralel, dan tidak secara berurutan. Dan cukup sering, peserta baru tiba-tiba muncul di plot, yang memutuskan untuk berpartisipasi (naga tiba;)) ... dan beberapa peserta lama tiba-tiba mati.
Hal-hal seperti itu. Sepertinya berantakan dan kacau, tetapi semuanya berfungsi, koordinasi internal dunia tidak dilanggar, plotnya berkembang dan konsisten ... Meskipun terkadang tidak dapat diprediksi.