Saga yang diatur atau cara membangun transaksi bisnis dalam layanan dengan database per pola layanan

Hai Nama saya Konstantin Evteev, saya bekerja di Avito sebagai pemimpin unit DBA. Tim kami mengembangkan sistem penyimpanan Avito, membantu dalam pemilihan atau penerbitan basis data dan infrastruktur terkait, mendukung Tujuan Tingkat Layanan untuk server basis data, dan kami juga bertanggung jawab untuk efisiensi dan pemantauan sumber daya, memberi nasihat tentang desain, dan mungkin mengembangkan layanan mikro, terkait dengan sistem penyimpanan, atau layanan untuk pengembangan platform dalam konteks penyimpanan.


Saya ingin memberi tahu Anda bagaimana kami memecahkan salah satu tantangan arsitektur layanan mikro - melakukan transaksi bisnis dalam infrastruktur layanan yang dibangun menggunakan Basis Data per pola layanan. Saya membuat presentasi tentang topik ini di konferensi Highload ++ Siberia 2018 .


gambar



Teori Sesingkat mungkin


Saya tidak akan menjelaskan secara rinci teori kisah-kisah. Saya hanya akan memberi Anda pengantar singkat agar Anda memahami konteksnya.


Seperti sebelumnya (dari awal Avito hingga 2015 - 2016): kami tinggal di monolith, dengan basis monolitik dan aplikasi monolitik. Pada titik tertentu, kondisi ini mulai mencegah kami tumbuh. Di satu sisi, kami mengalami kinerja server dengan database utama, tetapi ini bukan alasan utama, karena masalah kinerja dapat diselesaikan, misalnya menggunakan sharding. Di sisi lain, monolith memiliki logika yang sangat kompleks, dan pada tahap pertumbuhan tertentu, penyampaian perubahan (rilis) menjadi sangat panjang dan tidak dapat diprediksi: ada banyak dependensi yang tidak jelas dan kompleks (semuanya terhubung erat), juga sulit untuk diuji, secara umum ada banyak masalah. Solusinya adalah beralih ke arsitektur microservice. Pada tahap ini, kami memiliki pertanyaan dengan transaksi bisnis yang sangat terkait dengan ACID yang disediakan oleh basis monolitik: tidak jelas bagaimana cara melakukan migrasi logika bisnis ini. Ketika bekerja dengan Avito, ada banyak skenario berbeda yang diterapkan oleh beberapa layanan, ketika integritas dan konsistensi data sangat penting, misalnya, membeli langganan premium, mendebit uang, menerapkan layanan kepada pengguna, membeli paket VAS - jika terjadi keadaan atau kecelakaan yang tidak terduga, semuanya mungkin tidak berjalan secara tak terduga sesuai rencana. Kami menemukan solusinya dalam kisah-kisah.


Saya suka deskripsi teknis kisah - kisah pada tahun 1987 oleh Kenneth Salem dan Hector Garcia-Molina, salah satu anggota dewan direksi Oracle saat ini. Bagaimana masalah dirumuskan: ada sejumlah kecil transaksi jangka panjang yang untuk waktu yang lama mencegah pelaksanaan operasi kecil, lebih sedikit sumber daya, dan lebih sering. Sebagai hasil yang diinginkan, Anda dapat memberikan contoh dari kehidupan: yang pasti, banyak dari Anda mengantri untuk menyalin dokumen, dan operator mesin fotokopi, jika ia memiliki tugas menyalin seluruh buku atau hanya banyak salinan, membuat salinan anggota antrian lain dari waktu ke waktu. Tetapi membuang sumber daya hanyalah sebagian dari masalah. Situasi ini diperburuk oleh kunci jangka panjang ketika melakukan tugas-tugas yang intensif sumber daya, kaskade yang akan dibangun di DBMS Anda. Selain itu, kesalahan dapat terjadi selama transaksi panjang: transaksi tidak akan selesai dan rollback akan dimulai. Jika transaksi itu panjang, maka rollback juga akan memakan waktu lama, dan mungkin akan ada coba lagi dari aplikasi. Secara umum, "semuanya cukup menarik." Solusi yang diusulkan dalam deskripsi teknis SAGAS adalah dengan membagi transaksi panjang menjadi beberapa bagian.


Sepertinya saya banyak yang mendekati ini tanpa membaca dokumen ini. Kami telah berulang kali berbicara tentang defproc kami (prosedur ditangguhkan diimplementasikan menggunakan pgq). Misalnya, ketika memblokir pengguna untuk penipuan, kami dengan cepat melakukan transaksi singkat dan menanggapi klien. Dalam transaksi singkat ini, termasuk, kami menempatkan tugas dalam antrian transaksional, dan kemudian secara serempak, dalam batch kecil, misalnya, sepuluh iklan memblokir iklannya. Kami melakukan ini dengan menerapkan antrian transaksional dari Skype .


Tetapi kisah kita hari ini sedikit berbeda. Kita perlu melihat masalah-masalah ini dari sisi lain: menggergaji monolith ke dalam layanan-layanan mikro yang dibangun menggunakan basis data per pola layanan.


Salah satu parameter terpenting bagi kami adalah mencapai kecepatan potong maksimum. Oleh karena itu, kami memutuskan untuk mentransfer fungsionalitas lama dan semua logikanya ke microservices, tanpa mengubah apa pun. Persyaratan tambahan yang harus kami penuhi:


  • Berikan perubahan data dependen untuk data penting bisnis
  • dapat mengatur pesanan yang ketat;
  • mengamati konsistensi seratus persen - mengoordinasikan data bahkan dalam kasus kecelakaan;
  • menjamin operasi transaksi di semua tingkatan.

Di bawah persyaratan di atas, solusi dalam bentuk saga orkestrasi paling cocok.


Implementasi saga yang diatur sebagai layanan PG Saga


Seperti inilah layanan PG Saga.


gambar


PG dalam namanya, karena PostgreSQL sinkron digunakan sebagai repositori layanan. Apa lagi yang ada di dalam:


  • API
  • pelaksana;
  • pemeriksa;
  • pemeriksa kesehatan;
  • kompensator.

Diagram juga menunjukkan pemilik layanan dari kisah-kisah, dan di bawah ini adalah layanan yang akan melakukan langkah-langkah kisah tersebut. Mereka mungkin memiliki repositori yang berbeda.


Bagaimana cara kerjanya


Pertimbangkan contoh membeli paket VAS. VAS (Layanan yang ditambahkan nilai) - layanan berbayar untuk promosi iklan.


Pertama, pemilik layanan saga harus mendaftarkan pembuatan saga di layanan saga


gambar


Setelah itu, ia menghasilkan kelas saga yang sudah dengan Payload.


gambar


Selanjutnya, sudah dalam layanan sag, pelaksana mengambil panggilan saga yang sebelumnya dibuat dari toko dan mulai mengeksekusi dalam langkah-langkah. Langkah pertama dalam kasus kami adalah membeli langganan premium. Pada saat ini, uang dicadangkan dalam layanan penagihan.


gambar


Kemudian dalam layanan pengguna operasi VAS diterapkan.


gambar


Kemudian layanan VAS sudah ada, dan paket Anda dibuat. Langkah-langkah lain mungkin lebih jauh, tetapi itu tidak begitu penting bagi kita.


gambar


Kecelakaan


Kecelakaan dapat terjadi dalam layanan apa pun, tetapi ada trik terkenal tentang cara mempersiapkannya. Dalam sistem terdistribusi, penting untuk mengetahui teknik ini. Sebagai contoh, salah satu batasan terpenting adalah bahwa jaringan tidak selalu dapat diandalkan. Pendekatan yang akan menyelesaikan masalah interaksi dalam sistem terdistribusi:


  1. Kami coba lagi.
  2. Kami menandai setiap operasi dengan kunci idempoten. Ini diperlukan untuk menghindari duplikasi operasi. Lebih lanjut tentang kunci idempoten dapat ditemukan di artikel ini .
  3. Kami mengkompensasi transaksi - karakteristik aksi dari kisah-kisah.


Kompensasi Transaksi: Cara Kerjanya


Untuk setiap transaksi positif, kita harus menggambarkan tindakan sebaliknya: skenario bisnis dari langkah jika terjadi kesalahan.


Dalam implementasi kami, kami menawarkan skenario kompensasi berikut:


Jika beberapa langkah dari saga itu tidak berhasil, dan kami melakukan banyak percobaan ulang, maka ada kemungkinan bahwa pengulangan terakhir dari operasi itu berhasil, tetapi kami hanya tidak mendapatkan jawaban. Kami akan mencoba untuk mengkompensasi transaksi, meskipun langkah ini tidak diperlukan jika pelaksana layanan dari langkah masalah benar-benar rusak dan benar-benar tidak dapat diakses.


Dalam contoh kita, akan terlihat seperti ini:


  1. Matikan paket VAS.

gambar


  1. Batalkan operasi pengguna.

gambar


  1. Kami membatalkan reservasi dana.

gambar


Apa yang harus dilakukan jika kompensasi tidak berhasil


Jelas, kita harus menindaklanjuti skenario yang sama. Sekali lagi, terapkan coba lagi, kunci idempoten untuk mengkompensasi transaksi, tetapi jika tidak ada yang keluar saat ini, misalnya, layanan tidak tersedia, Anda perlu menghubungi pemilik layanan saga, memberi tahu Anda bahwa saga telah gagal. Selanjutnya, tindakan yang lebih serius: meningkatkan masalah, misalnya, untuk uji coba manual atau meluncurkan otomatisasi untuk menyelesaikan masalah tersebut.


Yang lebih penting: bayangkan bahwa beberapa langkah layanan saga tidak tersedia. Tentunya pemrakarsa tindakan ini akan mencoba lagi. Dan pada akhirnya, layanan kisah Anda mengambil langkah pertama, langkah kedua, dan pelaksananya tidak tersedia, Anda membatalkan langkah kedua, membatalkan langkah pertama, dan mungkin juga ada anomali terkait dengan kurangnya isolasi. Secara umum, layanan saga dalam situasi ini terlibat dalam pekerjaan yang tidak berguna, yang masih menghasilkan beban dan kesalahan.


Bagaimana cara melakukannya? Healthchecker harus mewawancarai layanan yang menyelesaikan langkah-langkah melorot dan melihat apakah mereka berfungsi. Jika layanan menjadi tidak tersedia, maka ada dua cara: untuk mengimbangi kisah-kisah yang sedang beroperasi, dan untuk mencegah kisah-kisah baru dari membuat instance baru (panggilan), atau untuk membuat tanpa membawa mereka untuk bekerja sebagai pelaksana, sehingga layanan tidak tindakan yang tidak perlu.


Skenario kecelakaan lain


Bayangkan kita melakukan langganan premium yang sama lagi.


  1. Kami membeli paket VAS dan memesan uang.

gambar


  1. Kami menerapkan layanan kepada pengguna.

gambar


  1. Kami membuat paket VAS.

gambar


Sepertinya bagus. Tapi tiba-tiba, ketika transaksi selesai, ternyata replikasi asinkron digunakan dalam layanan pengguna dan kecelakaan telah terjadi pada basis master. Mungkin ada beberapa alasan untuk lagging replika: beban tertentu pada replika yang memperlambat kecepatan pemutaran replikasi atau memblokir pemutaran replikasi. Selain itu, sumber (master) dapat kelebihan beban, dan kelambatan pengiriman perubahan muncul di sisi sumber. Secara umum, untuk beberapa alasan, replika itu tertinggal, dan perubahan langkah yang berhasil diselesaikan setelah kecelakaan tiba-tiba menghilang (hasil / keadaan).


gambar


Untuk melakukan ini, kami menerapkan komponen lain dalam sistem - kami menggunakan pemeriksa. Pemeriksa memeriksa semua langkah kisah sukses melalui waktu yang diketahui lebih besar dari semua kelambatan yang mungkin (misalnya, setelah 12 jam), dan memeriksa apakah semuanya masih berhasil diselesaikan. Jika langkah itu tiba-tiba gagal, kisahnya kembali.


gambar


gambar


gambar


gambar


Mungkin juga ada situasi di mana setelah 12 jam sudah tidak ada yang dibatalkan - semuanya berubah dan bergerak. Dalam hal ini, alih-alih skenario pembatalan, solusinya mungkin dengan memberi sinyal layanan pemilik saga bahwa operasi ini belum selesai. Jika operasi pembatalan tidak memungkinkan, katakanlah, Anda perlu melakukan pembatalan setelah menagih pengguna, dan saldonya sudah nol, dan uang tidak dapat didebit. Kami memiliki skenario seperti itu selalu dipecahkan ke arah pengguna. Anda mungkin memiliki prinsip yang berbeda, ini konsisten dengan perwakilan produk.


Akibatnya, seperti yang mungkin Anda perhatikan, di tempat yang berbeda untuk integrasi dengan layanan sag Anda perlu menerapkan banyak logika yang berbeda. Oleh karena itu, ketika tim klien ingin membuat hikayat, mereka akan memiliki serangkaian tugas yang sangat tidak jelas. Pertama-tama, kami membuat saga sehingga duplikasi tidak berhasil, untuk ini kami bekerja dengan beberapa operasi idempoten untuk membuat saga dan pelacakannya. Juga, dalam layanan, diwajibkan untuk menyadari kemampuan untuk melacak setiap langkah dari setiap kisah, agar tidak melakukan dua kali di satu sisi, dan, di sisi lain, untuk dapat menjawab apakah itu benar-benar selesai. Dan semua mekanisme ini perlu dilayani entah bagaimana sehingga repositori layanan tidak meluap. Selain itu, ada banyak bahasa tempat layanan dapat ditulis, dan banyak pilihan repositori. Pada setiap tahap, Anda perlu memahami teori dan menerapkan semua logika ini di bagian yang berbeda. Jika tidak, Anda bisa membuat banyak kesalahan.


Ada banyak cara yang benar, tetapi ada situasi yang tidak kalah ketika Anda dapat "menembak diri sendiri". Agar kisah-kisah itu bekerja dengan benar, Anda perlu merangkum semua mekanisme di atas di pustaka klien yang secara transparan akan mengimplementasikannya untuk klien Anda.


Contoh logika generasi saga yang bisa disembunyikan di perpustakaan klien


Ini dapat dilakukan secara berbeda, tetapi saya mengusulkan pendekatan berikut.


  1. Kami mendapatkan ID permintaan yang dengannya kami harus membuat saga.
  2. Kami pergi ke layanan melorot, mendapatkan pengenal unik, menyimpannya di penyimpanan lokal bersama dengan ID permintaan dari titik 1.
  3. Jalankan saga dengan payload dalam layanan sag. Nuansa penting: Saya mengusulkan operasi layanan lokal yang menciptakan hikayat, untuk mendesain, sebagai langkah pertama hikayat.
  4. Ada perlombaan tertentu ketika layanan saga dapat melakukan langkah ini (poin 3), dan backend kami, yang memulai penciptaan saga, juga akan melakukan itu. Untuk melakukan ini, kami melakukan operasi idempoten di mana-mana: satu orang melakukan itu, dan panggilan kedua hanya menerima "OK".
  5. Kami memanggil langkah pertama (poin 4) dan hanya setelah itu kami merespons klien yang memulai tindakan ini.

Dalam contoh ini, kami bekerja dengan saga sebagai database. Anda dapat mengirim permintaan, dan kemudian koneksi dapat terputus, tetapi tindakan akan dilakukan. Ini tentang pendekatan yang sama.


Bagaimana cara mengecek semuanya


Penting untuk mencakup seluruh layanan tes sag. Kemungkinan besar, Anda akan membuat perubahan, dan tes yang ditulis di awal akan membantu untuk menghindari kejutan yang tidak terduga. Selain itu, perlu untuk memeriksa kisah-kisah itu sendiri. Sebagai contoh, bagaimana kita mengatur pengujian layanan sag dan menguji urutan sag dalam satu transaksi. Ada beberapa blok uji yang berbeda. Jika kita berbicara tentang layanan jeblok, dia tahu bagaimana melakukan transaksi positif dan kompensasi, jika kompensasi tidak bekerja, dia memberi tahu pemilik jebol layanan. Kami menulis tes secara umum untuk bekerja dengan kisah abstrak.


Di sisi lain, transaksi positif dan transaksi kompensasi pada layanan yang melakukan langkah sag adalah API sederhana, dan pengujian bagian ini menjadi tanggung jawab tim yang memiliki layanan ini.


Dan kemudian tim pemilik saga menulis tes end-to-end, di mana ia memeriksa bahwa semua logika bisnis berfungsi dengan benar ketika kisah tersebut dieksekusi. Tes end-to-end berjalan pada lingkungan dev penuh, semua instance layanan dinaikkan, termasuk layanan sag, dan skenario bisnis sudah diuji di sana.


gambar
Total:


  • tulis lebih banyak unit test;
  • tulis tes integrasi;
  • tulis tes ujung ke ujung.

    Langkah selanjutnya adalah CDC. Arsitektur microservice memengaruhi spesifikasi tes. Di Avito, kami mengambil pendekatan berikut untuk menguji arsitektur layanan-mikro: Kontrak yang Didorong Konsumen. Pendekatan ini membantu, pertama-tama, untuk menyoroti masalah yang dapat diidentifikasi dalam tes end-to-end, tetapi tes end-to-end "sangat mahal".

Apa esensi dari CDC? Ada layanan yang menyediakan kontrak. Dia memiliki API - ini adalah penyedia. Dan ada layanan lain yang memanggil API, yaitu menggunakan kontrak - konsumen.


Layanan konsumen menulis tes untuk kontrak penyedia, dan tes yang hanya akan diperiksa oleh kontrak bukanlah tes fungsional. Penting bagi kami untuk memastikan bahwa ketika mengubah API, langkah-langkah dalam konteks ini tidak akan rusak. Setelah kami menulis tes, elemen lain dari broker layanan muncul - informasi tentang tes CDC dicatat di dalamnya. Setiap kali layanan penyedia diubah, itu akan meningkatkan lingkungan yang terisolasi dan menjalankan tes yang ditulis konsumen. Apa intinya: tim yang menghasilkan kisah menulis tes untuk semua langkah dari saga dan mendaftarkannya.


gambar
Tentang bagaimana Avito menerapkan pendekatan CDC untuk menguji layanan microser, Frol Kryuchkov berbicara di RIT ++. Abstrak dapat ditemukan di situs web Backend.conf - Saya sarankan Anda membiasakan diri.


Jenis-jenis Sagas


Dalam urutan panggilan fungsi


a) tidak teratur - fungsi hikayat dipanggil dalam urutan apa pun dan jangan menunggu satu sama lain selesai;
b) dipesan - fungsi-fungsi saga dipanggil dalam urutan yang diberikan, satu demi satu, berikutnya tidak dipanggil sampai sebelumnya selesai;
c) campur - untuk sebagian fungsi urutan diatur, tetapi untuk bagian tidak, tetapi diatur sebelum atau setelah tahap apa untuk melaksanakannya.


Pertimbangkan skenario tertentu. Dalam skenario yang sama membeli langganan premium, langkah pertama adalah memesan uang. Sekarang kita dapat membuat perubahan pada pengguna dan membuat paket premium secara paralel, dan kami akan memberi tahu pengguna hanya ketika kedua langkah ini selesai.
gambar


Dengan mendapatkan hasil dari pemanggilan fungsi


a) sinkron - hasil fungsi diketahui segera;
b) asinkron - fungsi mengembalikan "OK" dengan segera, dan hasilnya dikembalikan kemudian, melalui panggilan balik ke API layanan sag dari layanan klien.


Saya ingin memperingatkan Anda tentang kesalahan: lebih baik tidak melakukan langkah-langkah sinkron dari kisah-kisah, terutama ketika menerapkan hikayat yang diatur. Jika Anda melakukan langkah sag sinkron, layanan sag akan menunggu langkah ini selesai. Ini adalah beban tambahan, masalah ekstra dalam pelayanan kisah-kisah, karena itu adalah satu, dan ada banyak peserta dalam kisah-kisah.


Penskalaan sag


Penskalaan tergantung pada ukuran sistem yang Anda rencanakan. Pertimbangkan opsi dengan instance penyimpanan tunggal:


  • one saga step handler, proseskan langkah-langkah dengan batch;
  • Di penangan, kami menerapkan "sisir" - kami mengambil langkah-langkah untuk sisa divisi: ketika setiap pelaksana mendapatkan langkahnya sendiri.
  • n penangan dan lompat terkunci - akan menjadi lebih efisien dan lebih fleksibel.

Dan hanya kemudian, jika Anda tahu sebelumnya bahwa Anda akan mengalami kinerja satu server dalam DBMS, Anda perlu melakukan sharding - contoh database yang akan bekerja dengan kumpulan data mereka. Sharding dapat disembunyikan di balik API layanan sag.


Lebih banyak fleksibilitas


Selain itu, dalam pola ini, setidaknya secara teori, layanan klien (melakukan langkah saga) dapat mengakses dan masuk ke layanan pelemahan, dan partisipasi dalam kisah dapat opsional juga. Mungkin juga ada skenario lain: jika Anda sudah mengirim email, tidak mungkin untuk mengkompensasi tindakan - Anda tidak dapat mengembalikan surat itu kembali. Tetapi Anda dapat mengirim surat baru bahwa yang sebelumnya salah, dan sepertinya begitu-begitu. Lebih baik menggunakan skenario di mana saga akan dimainkan hanya maju, tanpa kompensasi apa pun. Jika tidak bermain maju, maka perlu untuk menginformasikan layanan pemilik saga tentang masalah tersebut.


Kapan Anda membutuhkan kunci?


Sebuah penyimpangan kecil tentang kisah-kisah pada umumnya: jika Anda dapat membuat logika Anda tanpa saga, maka lakukanlah. Sagas itu sulit. Dengan kunci, ini hampir sama: lebih baik untuk selalu menghindari kunci.


Ketika saya datang ke tim penagihan untuk berbicara tentang kisah-kisah, mereka berkata bahwa mereka membutuhkan kunci. Saya berhasil menjelaskan kepada mereka mengapa lebih baik melakukannya tanpanya dan bagaimana melakukannya. Tetapi jika Anda masih membutuhkan kunci, maka ini harus diramalkan sebelumnya. Sebelum layanan pelemahan, kami telah menerapkan kunci dalam kerangka kerja satu DBMS. Contoh dengan defproc dan skrip untuk memblokir iklan secara tidak sinkron dan memblokir akun secara sinkron, ketika kami pertama kali melakukan bagian dari operasi secara sinkron dan mengatur kunci, dan kemudian kami menyelesaikan sisa pekerjaan dengan batch secara tidak sinkron.


Bagaimana cara melakukannya? , , , , , - , . . . : , , .


-, , . , , . , , . . โ€” , , .


ACID โ€”


, , . . โ€” durability. . . , . - , - - ,


Tireads=>otherrtransactionwrites=>Tj(orCi)writes


โ€” - , - , , - , . , - , - .


Tiwrites=>othertransactionreads=>Ciwrites


โ€” .


Tireads=>othertransactionwrites=>Tjreads


:


  1. , , , , .
  2. , . , , , , , .
  3. .
  4. payload . eventual consistency โ€” , , , . , , , -.


Pemantauan


. , . . checker. . , .


gambar


gambar


(50%, 75%, 95%, 99%), , - .


, โ€” , . . , - . , โ€” .


. , - ( ) . healthchecker endpoint' info (keep-alive) .


. -. -, - , - . , , , end-to-end. - . , , โ€” .
. .


:



, healthchecker, - , . , . .



, . , , . . choreography โ€” - . , choreography- , . choreography , . , . , , , .



. , , . , + .


API


, - - ( API ), , API. API . โ€” . API , , 100% .


, , , , . โ€” , , . .



, , , . ( ) .



, , , , .



. , , .


saga call ID


. API , .


โ€”


- legacy . , ( ยซยป ). ยซ ยป? - , , , , - , . , , , . , ยซ ยป, , -. . โ€” . , .


, , . , , , , . , , . .


, . .

Source: https://habr.com/ru/post/id418235/


All Articles