Katakanlah kita memiliki microservices Pengguna, Wallet REST, dan gateway API yang merekatkan semuanya. Ketika Bob mendaftar di situs web kami, gateway API kami perlu membuat pengguna melalui layanan pengguna mikro dan dompet melalui layanan mikro Dompet.
Sekarang, inilah beberapa skenario di mana segala sesuatunya bisa salah:
Pembuatan Bob pengguna gagal: tidak apa-apa, kami hanya mengembalikan pesan kesalahan ke Bob. Kami menggunakan transaksi SQL sehingga tidak ada yang melihat Bob di sistem. Semuanya baik :)
Pengguna Bob dibuat tetapi sebelum Dompet kami dapat dibuat, API gateway kami mengalami kerusakan. Kami sekarang memiliki Pengguna tanpa dompet (data tidak konsisten).
Pengguna Bob dibuat dan saat kami membuat Wallet, koneksi HTTP turun. Penciptaan dompet mungkin telah berhasil atau mungkin tidak.
Solusi apa yang tersedia untuk mencegah terjadinya ketidakkonsistenan data seperti ini? Apakah ada pola yang memungkinkan transaksi untuk menjangkau beberapa permintaan REST? Saya telah membaca halaman Wikipedia tentang komitmen dua fase yang sepertinya menyentuh masalah ini, tetapi saya tidak yakin bagaimana menerapkannya dalam praktik. Transaksi Terdistribusi Atom ini : makalah desain yang tenang juga tampak menarik walaupun saya belum membacanya.
Atau, saya tahu REST mungkin tidak cocok untuk kasus penggunaan ini. Apakah mungkin cara yang tepat untuk menangani situasi ini untuk menghentikan REST sepenuhnya dan menggunakan protokol komunikasi yang berbeda seperti sistem antrian pesan? Atau haruskah saya menegakkan konsistensi dalam kode aplikasi saya (misalnya, dengan memiliki pekerjaan latar belakang yang mendeteksi ketidakkonsistenan dan memperbaikinya atau dengan memiliki atribut "status" pada model Pengguna saya dengan nilai "menciptakan", "menciptakan", dll.)?
sumber
Jawaban:
Apa yang tidak masuk akal:
Apa yang akan membuat Anda sakit kepala:
Apa yang mungkin merupakan alternatif terbaik:
Tetapi bagaimana jika Anda membutuhkan respons sinkron?
sumber
Ini adalah pertanyaan klasik yang saya tanyakan saat wawancara baru-baru ini. Bagaimana cara memanggil beberapa layanan web dan masih mempertahankan beberapa jenis penanganan kesalahan di tengah tugas. Saat ini, dalam komputasi kinerja tinggi, kami menghindari komitmen dua fase. Saya membaca makalah bertahun-tahun yang lalu tentang apa yang disebut "model Starbuck" untuk transaksi: Pikirkan tentang proses pemesanan, pembayaran, persiapan dan penerimaan kopi yang Anda pesan di Starbuck ... Saya terlalu menyederhanakan berbagai hal tetapi model dua fase komit akan sarankan bahwa seluruh proses akan menjadi transaksi pembungkus tunggal untuk semua langkah yang terlibat sampai Anda menerima kopi Anda. Namun, dengan model ini, semua karyawan akan menunggu dan berhenti bekerja sampai Anda mendapatkan kopi Anda. Anda melihat gambarnya?
Sebagai gantinya, "model Starbuck" lebih produktif dengan mengikuti model "usaha terbaik" dan mengkompensasi kesalahan dalam proses. Pertama, mereka memastikan bahwa Anda membayar! Lalu, ada antrian pesan dengan pesanan Anda terlampir pada cangkir. Jika ada yang salah dalam proses, seperti Anda tidak mendapatkan kopi Anda, bukan itu yang Anda pesan, dll, kami masuk ke dalam proses kompensasi dan kami memastikan Anda mendapatkan apa yang Anda inginkan atau mengembalikan uang Anda, Ini adalah model yang paling efisien untuk peningkatan produktivitas.
Terkadang, starbuck membuang-buang kopi tetapi proses keseluruhannya efisien. Ada trik lain untuk dipikirkan ketika Anda membangun layanan web Anda seperti mendesainnya sedemikian rupa sehingga mereka dapat dipanggil berapa kali dan masih memberikan hasil akhir yang sama. Jadi, rekomendasi saya adalah:
Jangan terlalu baik ketika mendefinisikan layanan web Anda (saya tidak yakin tentang hype layanan mikro yang terjadi hari ini: terlalu banyak risiko terlalu jauh);
Async meningkatkan kinerja jadi lebih suka menjadi async, kirim pemberitahuan melalui email kapan pun memungkinkan.
Bangun layanan yang lebih cerdas untuk menjadikannya "dapat diingat" beberapa kali, diproses dengan uid atau taskid yang akan mengikuti urutan bottom-top hingga akhir, memvalidasi aturan bisnis di setiap langkah;
Gunakan antrian pesan (JMS atau yang lain) dan alihkan ke prosesor penanganan kesalahan yang akan menerapkan operasi ke "kembalikan" dengan menerapkan operasi yang berlawanan, omong-omong, bekerja dengan urutan async akan memerlukan semacam antrian untuk memvalidasi keadaan proses saat ini, jadi pertimbangkan itu;
Dalam upaya terakhir, (karena mungkin tidak sering terjadi), masukkan ke dalam antrian untuk pemrosesan kesalahan secara manual.
Mari kita kembali dengan masalah awal yang diposting. Buat akun dan buat dompet dan pastikan semuanya sudah selesai.
Katakanlah layanan web dipanggil untuk mengatur seluruh operasi.
Kode semu layanan web akan terlihat seperti ini:
Panggil layanan pembuatan akun mikro, berikan informasi dan id tugas unik 1.1 Layanan pembuatan akun akan terlebih dahulu memeriksa apakah akun itu sudah dibuat. Id tugas dikaitkan dengan catatan akun. Layanan microsoft mendeteksi bahwa akun tidak ada sehingga membuatnya dan menyimpan id tugas. CATATAN: layanan ini dapat dipanggil 2000 kali, ia akan selalu melakukan hasil yang sama. Layanan menjawab dengan "tanda terima yang berisi informasi minimal untuk melakukan operasi pembatalan jika diperlukan".
Panggil pembuatan Dompet, berikan ID akun dan id tugas. Katakanlah suatu kondisi tidak valid dan pembuatan dompet tidak dapat dilakukan. Panggilan kembali dengan kesalahan tetapi tidak ada yang dibuat.
Orkestra diberitahu tentang kesalahan tersebut. Ia tahu ia harus membatalkan pembuatan Akun tetapi tidak akan melakukannya sendiri. Ia akan meminta layanan dompet untuk melakukannya dengan mengirimkan "tanda terima minimal yang dibatalkan" yang diterima pada akhir langkah 1.
Layanan Akun membaca tanda terima undo dan tahu cara membatalkan operasi; tanda terima yang dibatalkan bahkan dapat menyertakan informasi tentang layanan microsoft lain yang dapat disebut sebagai bagian dari pekerjaan tersebut. Dalam situasi ini, tanda terima yang dibatalkan dapat berisi ID Akun dan mungkin beberapa informasi tambahan yang diperlukan untuk melakukan operasi yang berlawanan. Dalam kasus kami, untuk mempermudah, katakanlah cukup hapus akun menggunakan id akunnya.
Sekarang, katakanlah layanan web tidak pernah menerima keberhasilan atau kegagalan (dalam hal ini) bahwa pembatalan pembuatan Akun dilakukan. Itu hanya akan memanggil layanan membatalkan Akun lagi. Dan layanan ini seharusnya tidak pernah gagal karena tujuannya adalah agar akun tidak ada lagi. Jadi ia memeriksa apakah ada dan melihat tidak ada yang bisa dilakukan untuk membatalkannya. Jadi kembalinya operasi itu sukses.
Layanan web kembali ke pengguna bahwa akun tidak dapat dibuat.
Ini adalah contoh sinkron. Kami bisa mengelolanya dengan cara yang berbeda dan memasukkan kasus ke antrian pesan yang ditargetkan ke help desk jika kami tidak ingin sistem menyelesaikan kesalahan ". Saya telah melihat ini dilakukan di perusahaan di mana tidak cukup kait dapat diberikan ke sistem ujung belakang untuk memperbaiki situasi. Meja bantuan menerima pesan yang berisi apa yang dilakukan dengan sukses dan memiliki informasi yang cukup untuk memperbaiki hal-hal seperti tanda terima undo kami dapat digunakan dengan cara yang sepenuhnya otomatis.
Saya telah melakukan pencarian dan situs web microsoft memiliki deskripsi pola untuk pendekatan ini. Ini disebut pola transaksi kompensasi:
Mengkompensasi pola transaksi
sumber
Semua sistem terdistribusi memiliki masalah dengan konsistensi transaksional. Cara terbaik untuk melakukan ini adalah seperti yang Anda katakan, memiliki komitmen dua fase. Buat dompet dan pengguna dibuat dalam status tertunda. Setelah dibuat, buat panggilan terpisah untuk mengaktifkan pengguna.
Panggilan terakhir ini harus diulang dengan aman (jika koneksi Anda turun).
Ini akan mengharuskan bahwa panggilan terakhir tahu tentang kedua tabel (sehingga dapat dilakukan dalam satu transaksi JDBC).
Atau, Anda mungkin ingin memikirkan mengapa Anda begitu khawatir tentang pengguna tanpa dompet. Apakah Anda yakin ini akan menimbulkan masalah? Jika demikian, mungkin memiliki mereka sebagai panggilan istirahat terpisah adalah ide yang buruk. Jika seorang pengguna tidak boleh ada tanpa dompet, maka Anda mungkin harus menambahkan dompet ke pengguna (dalam panggilan POST asli untuk membuat pengguna).
sumber
IMHO salah satu aspek kunci dari arsitektur layanan microsoft adalah bahwa transaksi terbatas pada layanan microser individual (Prinsip tanggung jawab tunggal).
Dalam contoh saat ini, pembuatan Pengguna akan menjadi transaksi sendiri. Pembuatan pengguna akan mendorong acara USER_CREATED ke dalam antrian acara. Layanan Wallet akan berlangganan acara USER_CREATED dan melakukan pembuatan Wallet.
sumber
Jika dompet saya hanyalah kumpulan catatan lain dalam basis data sql yang sama dengan pengguna, maka saya mungkin akan menempatkan kode pembuatan pengguna dan dompet di layanan yang sama dan mengatasinya dengan menggunakan fasilitas transaksi basis data normal.
Kedengarannya bagi saya Anda bertanya tentang apa yang terjadi ketika kode pembuatan dompet mengharuskan Anda menyentuh sistem atau sistem lain? Id mengatakan itu semua tergantung pada seberapa kompleks dan atau berisiko proses pembuatannya.
Jika itu hanya masalah menyentuh datastore lain yang dapat diandalkan (katakanlah yang tidak dapat berpartisipasi dalam transaksi sql Anda), maka tergantung pada parameter sistem secara keseluruhan, saya mungkin bersedia mengambil risiko dengan peluang kecil yang semakin menghilang bahwa penulisan kedua tidak akan terjadi. Saya mungkin tidak melakukan apa pun, tetapi mengajukan pengecualian dan menangani data yang tidak konsisten melalui transaksi kompensasi atau bahkan beberapa metode ad-hoc. Seperti yang selalu saya katakan kepada pengembang saya: "jika hal semacam ini terjadi di aplikasi, itu tidak akan luput dari perhatian".
Seiring meningkatnya kompleksitas dan risiko pembuatan dompet, Anda harus mengambil langkah-langkah untuk memperbaiki risiko yang terlibat. Katakanlah beberapa langkah mengharuskan memanggil beberapa mitra apis.
Pada titik ini Anda dapat memperkenalkan antrian pesan bersama dengan gagasan tentang pengguna yang dibangun sebagian dan / atau dompet.
Strategi yang sederhana dan efektif untuk memastikan entitas Anda pada akhirnya akan dibangun dengan benar adalah memiliki pekerjaan coba lagi sampai mereka berhasil, tetapi banyak tergantung pada kasus penggunaan untuk aplikasi Anda.
Saya juga akan berpikir panjang dan keras tentang mengapa saya memiliki langkah yang cenderung gagal dalam proses penyediaan saya.
sumber
Satu Solusi sederhana adalah Anda membuat pengguna menggunakan Layanan Pengguna dan menggunakan bus perpesanan tempat layanan pengguna memancarkan acara-acaranya, dan Layanan Wallet mendaftar pada bus perpesanan, mendengarkan acara yang Dibuat Pengguna dan membuat Dompet untuk Pengguna. Sementara itu, jika pengguna menggunakan Wallet UI untuk melihat Dompetnya, periksa apakah pengguna baru saja dibuat dan tunjukkan pembuatan dompet Anda sedang berlangsung, silakan periksa beberapa saat lagi
sumber
Secara tradisional, manajer transaksi terdistribusi digunakan. Beberapa tahun yang lalu di dunia Java EE Anda mungkin telah membuat layanan ini sebagai EJB yang digunakan untuk node yang berbeda dan gateway API Anda akan membuat panggilan jarak jauh ke EJB tersebut. Server aplikasi (jika dikonfigurasi dengan benar) secara otomatis memastikan, menggunakan dua fase komit, bahwa transaksi dilakukan atau dibatalkan pada setiap node, sehingga konsistensi dijamin. Tetapi itu mengharuskan semua layanan dikerahkan pada jenis server aplikasi yang sama (sehingga mereka kompatibel) dan pada kenyataannya hanya pernah bekerja dengan layanan yang dikerahkan oleh satu perusahaan.
Untuk SOAP (ok, bukan REST), ada spesifikasi WS-AT tetapi tidak ada layanan yang pernah saya integrasikan yang mendukungnya. Untuk REST, JBoss memiliki sesuatu dalam pipeline . Kalau tidak, "pola" adalah untuk menemukan produk yang dapat Anda pasang ke arsitektur Anda, atau membangun solusi Anda sendiri (tidak disarankan).
Saya telah menerbitkan produk semacam itu untuk Java EE: https://github.com/maxant/genericconnector
Menurut kertas yang Anda referensi, ada juga pola Coba-Batalkan / Konfirmasi dan Produk terkait dari Atomikos.
Mesin BPEL menangani konsistensi antara layanan yang disebarkan dari jarak jauh menggunakan kompensasi.
Ada banyak cara untuk "mengikat" sumber daya non-transaksional ke dalam suatu transaksi:
Bermain sebagai pendukung setan: mengapa membangun sesuatu seperti itu, ketika ada produk yang melakukannya untuk Anda (lihat di atas), dan mungkin melakukannya lebih baik daripada yang Anda bisa, karena mereka dicoba dan diuji?
sumber
Secara pribadi saya suka ide Layanan Mikro, modul didefinisikan oleh kasus penggunaan, tetapi sebagai pertanyaan Anda menyebutkan, mereka memiliki masalah adaptasi untuk bisnis klasik seperti bank, asuransi, telekomunikasi, dll ...
Transaksi terdistribusi, seperti yang banyak disebutkan, bukanlah pilihan yang baik, orang-orang sekarang pergi lebih untuk sistem yang pada akhirnya konsisten tetapi saya tidak yakin ini akan bekerja untuk bank, asuransi, dll ....
Saya menulis blog tentang solusi yang saya usulkan, mungkin ini dapat membantu Anda ....
https://mehmetsalgar.wordpress.com/2016/11/05/micro-services-fan-out-transaction-problems-and-solutions-with-spring-bootjboss-and-netflix-eureka/
sumber
Konsistensi akhirnya adalah kuncinya di sini.
Komandan bertanggung jawab atas transaksi yang didistribusikan dan mengambil kendali. Ia tahu instruksi yang akan dieksekusi dan akan mengoordinasikan mengeksekusi mereka. Dalam kebanyakan skenario hanya akan ada dua instruksi, tetapi dapat menangani banyak instruksi.
Komandan bertanggung jawab untuk menjamin pelaksanaan semua instruksi, dan itu berarti pensiun. Ketika komandan mencoba untuk melakukan pembaruan jarak jauh dan tidak mendapat respons, ia tidak perlu mencoba lagi. Dengan cara ini sistem dapat dikonfigurasi agar lebih rentan terhadap kegagalan dan menyembuhkan dirinya sendiri.
Karena kami telah mencoba ulang, kami memiliki idempotensi. Idempotence adalah hak milik untuk dapat melakukan sesuatu dua kali sedemikian rupa sehingga hasil akhirnya sama seperti jika dilakukan sekali saja. Kita perlu idempotensi di layanan jarak jauh atau sumber data sehingga, dalam kasus di mana ia menerima instruksi lebih dari sekali, ia hanya memprosesnya sekali.
Konsistensi akhirnya Ini memecahkan sebagian besar tantangan transaksi terdistribusi, namun kami perlu mempertimbangkan beberapa poin di sini. Setiap transaksi yang gagal akan diikuti oleh percobaan ulang, jumlah percobaan ulang tergantung pada konteksnya.
Konsistensi adalah akhirnya yaitu, ketika sistem berada di luar kondisi yang konsisten selama coba lagi, misalnya jika pelanggan telah memesan buku, dan melakukan pembayaran dan kemudian memperbarui jumlah stok. Jika operasi pembaruan stok gagal dan dengan asumsi bahwa itu adalah stok terakhir yang tersedia, buku akan tetap tersedia sampai operasi coba lagi untuk pembaruan stok berhasil. Setelah coba lagi berhasil, sistem Anda akan konsisten.
sumber
Mengapa tidak menggunakan platform API Management (APIM) yang mendukung scripting / pemrograman? Jadi, Anda akan dapat membangun layanan komposit di APIM tanpa mengganggu layanan mikro. Saya telah merancang menggunakan APIGEE untuk tujuan ini.
sumber