Apa strategi transaksi yang paling diterima untuk layanan microser

80

Salah satu masalah utama yang saya lihat terjadi dalam sistem dengan layanan microser adalah cara transaksi bekerja ketika mereka menjangkau berbagai layanan. Dalam arsitektur kami sendiri, kami telah menggunakan transaksi terdistribusi untuk menyelesaikan ini, tetapi mereka datang dengan masalah mereka sendiri. Terutama kebuntuan telah menjadi rasa sakit sejauh ini.

Pilihan lain tampaknya adalah semacam manajer transaksi yang dibuat khusus, yang mengetahui aliran dalam sistem Anda, dan akan menangani pengembalian untuk Anda sebagai proses latar belakang yang mencakup seluruh sistem Anda (sehingga akan memberi tahu layanan lain untuk mengembalikan dan jika mereka down, beri tahu mereka nanti).

Apakah ada opsi lain yang diterima? Keduanya tampaknya memiliki kekurangan. Yang pertama dapat menyebabkan kebuntuan dan banyak masalah lainnya, yang kedua dapat menyebabkan inkonsistensi data. Apakah ada opsi yang lebih baik?

Kristof
sumber
Hanya untuk memastikan, apakah layanan-layanan microser tersebut digunakan oleh banyak klien pada saat yang sama, atau hanya satu per satu?
Marcel
2
Saya mencoba menanyakan pertanyaan ini agnostik untuk itu, Marcel. Tapi mari kita asumsikan kita menggunakan sistem yang cukup besar untuk melakukan keduanya, dan kita ingin memiliki arsitektur yang mendukung keduanya.
Kristof
4
Ini adalah pertanyaan dengan kata-kata yang buruk, tetapi sangat penting. Ketika kebanyakan orang berpikir tentang "transaksi" mereka berpikir hampir secara eksklusif tentang konsistensi dan bukan aspek atomisitas, isolasi atau daya tahan transaksi. Pertanyaannya sebenarnya harus dinyatakan, "Bagaimana Anda membuat sistem ACID-compliant diberikan arsitektur microservices. Hanya menerapkan konsistensi dan bukan sisa ACID tidak benar-benar berguna. Kecuali Anda suka data buruk.
Jeff Fischer
10
Anda selalu dapat mencoba mengubah pertanyaan saya untuk membuatnya kurang "kata-kata buruk", Jeff Fischer.
Kristof
Pertanyaan dan diskusi ini terkait erat dengan pertanyaan lain ini di SO .
Paulo Merson

Jawaban:

42

Pendekatan yang biasa adalah mengisolasi layanan-layanan mikro itu sebanyak mungkin - memperlakukan mereka sebagai satu unit. Kemudian transaksi dapat dikembangkan dalam konteks layanan secara keseluruhan (yaitu bukan bagian dari transaksi DB biasa, meskipun Anda masih dapat memiliki transaksi DB internal ke layanan).

Pikirkan bagaimana transaksi terjadi dan jenis apa yang masuk akal untuk layanan Anda kemudian, Anda dapat menerapkan mekanisme rollback yang membatalkan operasi aslinya, atau sistem komitmen 2 fase yang menyimpan operasi asli hingga diminta untuk melakukan komit secara nyata. Tentu saja kedua sistem ini berarti Anda menerapkannya sendiri, tetapi kemudian Anda sudah menerapkan layanan-mikro Anda.

Jasa keuangan melakukan hal semacam ini sepanjang waktu - jika saya ingin memindahkan uang dari bank saya ke bank Anda, tidak ada transaksi tunggal seperti yang Anda miliki dalam DB. Anda tidak tahu sistem apa yang dijalankan bank mana pun, jadi harus secara efektif memperlakukan masing-masing seperti layanan mikro Anda. Dalam hal ini, bank saya akan memindahkan uang saya dari akun saya ke rekening holding dan kemudian memberi tahu bank Anda bahwa mereka punya uang, jika pengiriman gagal, bank saya akan mengembalikan akun saya dengan uang yang mereka coba kirim.

gbjbaanb
sumber
5
Anda mengasumsikan bahwa pengembalian uang tidak akan pernah gagal.
Sanjeev Kumar Dangi
9
@SanjeevKumarDangi lebih kecil kemungkinannya dan bahkan jika itu gagal, ia dapat dengan mudah ditangani karena rekening induk dan akun yang sebenarnya ada di dalam kendali bank.
gldraphael
30

Saya pikir kebijaksanaan standar adalah tidak pernah melakukan transaksi lintas batas layanan-mikro. Jika set data tertentu benar-benar perlu konsisten secara atom dengan yang lain, kedua hal itu menjadi satu.

Ini adalah salah satu alasan mengapa sangat sulit untuk membagi sistem menjadi layanan sampai Anda telah mendesainnya sepenuhnya. Yang di dunia modern mungkin berarti menuliskannya ...

soru
sumber
37
Pendekatan seperti itu bisa mengarah pada menggabungkan semua layanan microser ke aplikasi monolitik tunggal pada akhirnya.
Slava Fomin II
4
Ini adalah pendekatan yang benar. Ini sebenarnya sederhana: jika Anda memerlukan transaksi lintas layanan, layanan Anda salah: mendesain ulang mereka! @SlavaFominII apa yang Anda katakan hanya benar jika Anda tidak tahu bagaimana merancang sistem layanan-mikro. Jika Anda menemukan diri Anda menentang pendekatan layanan mikro, jangan lakukan itu, monolith Anda akan lebih baik daripada desain layanan mikro yang buruk. Hanya ketika Anda menemukan batas layanan yang tepat adalah saat Anda harus membagi monolit dalam layanan. Kalau tidak menggunakan microservices bukanlah pilihan arsitektur yang tepat, itu hanya mengikuti hype.
Francesc Castells
@FrancescCastells Bagaimana jika layanan kami benar-benar membutuhkan transaksi di antara layanan lain, apakah maksud Anda bahwa kami harus mengabaikan konteks yang dibatasi dan memodelkan layanan kami sedemikian rupa sehingga hanya berakhir sebagai satu transaksi? Saya seorang pemula di microservices, masih membaca jadi maafkan pertanyaan saya jika kedengarannya naif ....: D: D
Bilbo Baggins
@ BilboBaggins Maksud saya kebalikan dari mengabaikan konteks terbatas. Menurut definisi transaksi terjadi dalam konteks terbatas dan tidak di beberapa dari mereka. Oleh karena itu, jika Anda merancang dengan benar layanan microser Anda bersama dengan konteks terbatas Anda, maka transaksi Anda tidak boleh menjangkau beberapa layanan. Perhatikan bahwa, kadang-kadang, yang Anda butuhkan bukanlah transaksi, tetapi penanganan yang lebih baik atas konsistensi akhirnya dan tindakan kompensasi yang tepat ketika segala sesuatu tidak berjalan dengan benar.
Francesc Castells
Saya tidak mengerti maksud Anda, apakah ada kemungkinan kita bisa mendiskusikan ini dalam obrolan?
Bilbo Baggins
17

Saya pikir jika konsistensi adalah persyaratan kuat dalam aplikasi Anda, Anda harus bertanya pada diri sendiri apakah layanan mikro adalah pendekatan yang lebih baik. Seperti yang dikatakan Martin Fowler :

Microservices memperkenalkan masalah konsistensi akhirnya karena desakan mereka yang terpuji pada manajemen data yang terdesentralisasi. Dengan monolith, Anda dapat memperbarui banyak hal bersama dalam satu transaksi. Layanan Microsoft memerlukan banyak sumber daya untuk memperbarui, dan transaksi yang didistribusikan disukai (untuk alasan yang baik). Jadi sekarang, pengembang perlu menyadari masalah konsistensi, dan mencari tahu cara mendeteksi ketika ada yang tidak sinkron sebelum melakukan apa pun yang akan disesali oleh kode.

Tetapi mungkin dalam kasus Anda, Anda dapat mengorbankan Konsistensi dalam posisi Ketersediaan

Proses bisnis seringkali lebih toleran terhadap ketidakkonsistenan daripada yang Anda pikirkan karena bisnis seringkali menghargai ketersediaan yang lebih banyak.

Namun saya juga bertanya pada diri sendiri apakah ada strategi untuk transaksi terdistribusi di layanan mikro, tetapi mungkin biayanya terlalu tinggi. Saya ingin memberi Anda dua sen saya dengan artikel Martin Fowler dan teorema CAP yang selalu luar biasa .

gabrielgiussi
sumber
1
Dalam transaksi bisnis terdistribusi, konsistensi kadang-kadang ditangani dengan menambahkan lapisan orkestrasi seperti mesin alur kerja atau antrian yang dirancang dengan cermat. Lapisan itu menangani semua dua fase yang melakukan dan bergulir kembali, dan memungkinkan layanan microser fokus pada logika bisnis tertentu. Tetapi kembali ke CAP, berinvestasi dalam ketersediaan dan konsistensi membuat kinerja menjadi korban. Bagaimana layanan microser dibandingkan dengan banyak kelas terpisah yang terdiri dari bisnis Anda di OOP?
Greg
Anda dapat menggunakan RAFT melalui layanan microser untuk mengatasi konsistensi FWIW
f0ster
1
+1. Saya sering bertanya-tanya mengapa dalam konteks microservices transaksi diinginkan sama sekali, jika semua pandangan 'tarik' data dapat diimplementasikan sebagai pandangan terwujud. Misalnya, jika satu layanan mikro menerapkan debet dari satu akun dan kredit lain ke akun lain, pandangan saldo hanya akan mempertimbangkan pasangan kredit dan debet, di mana kredit dan debet yang tak tertandingi masih akan berada di buffer untuk pandangan terwujud.
Sentinel
16

Seperti yang disarankan dalam setidaknya satu jawaban di sini tetapi juga di tempat lain di web, dimungkinkan untuk merancang satu layanan mikro yang bertahan entitas bersama dalam transaksi normal jika Anda memerlukan konsistensi antara kedua entitas.

Tetapi pada saat yang sama, Anda mungkin memiliki situasi di mana entitas benar-benar tidak termasuk dalam layanan mikro yang sama, misalnya, catatan penjualan dan catatan pemesanan (ketika Anda memesan sesuatu untuk memenuhi penjualan). Dalam kasus seperti itu, Anda mungkin memerlukan cara untuk memastikan konsistensi antara kedua layanan mikro.

Transaksi yang didistribusikan secara tradisional telah digunakan dan dalam pengalaman saya, mereka bekerja dengan baik sampai skala ke ukuran di mana penguncian menjadi masalah. Anda dapat mengendurkan penguncian sehingga benar-benar hanya sumber daya yang relevan (mis. Barang yang dijual) "dikunci" menggunakan perubahan negara, tetapi ini adalah di mana ia mulai menjadi rumit karena Anda memasuki wilayah di mana Anda perlu membangun semua logika untuk melakukan ini, sendiri, daripada memiliki, katakanlah database yang menanganinya untuk Anda.

Saya telah bekerja dengan perusahaan-perusahaan yang telah kehilangan arah untuk membangun kerangka kerja transaksi mereka sendiri untuk menangani masalah yang kompleks ini, tetapi saya tidak merekomendasikannya karena mahal dan membutuhkan waktu untuk matang.

Ada beberapa produk di luar sana yang dapat dibaut ke sistem Anda yang menjaga konsistensi. Mesin proses bisnis adalah contoh yang baik dan mereka biasanya menangani konsistensi pada akhirnya dan dengan menggunakan kompensasi. Produk lain bekerja dengan cara yang sama. Anda biasanya berakhir dengan lapisan perangkat lunak di dekat klien, yang berkaitan dengan konsistensi dan transaksi serta panggilan (mikro) layanan untuk melakukan pemrosesan bisnis yang sebenarnya . Salah satu produk tersebut adalah konektor JCA generik yang dapat digunakan dengan solusi Java EE (untuk transparansi: Saya penulisnya). Lihat http://blog.maxant.co.uk/pebble/2015/08/04/1438716480000.html untuk perincian lebih lanjut dan diskusi lebih mendalam tentang masalah yang diangkat di sini.

Cara lain untuk menangani transaksi dan konsistensi adalah dengan membungkus panggilan ke layanan mikro menjadi panggilan ke sesuatu yang transaksional seperti antrian pesan. Ambil contoh catatan penjualan / catatan pesanan dari atas - Anda bisa membiarkan layanan penjualan mikro mengirim pesan ke sistem pesanan, yang dilakukan dalam transaksi yang sama yang menulis penjualan ke database. Hasilnya adalah solusi asinkron yang berskala sangat baik. Menggunakan teknologi seperti soket web, Anda bahkan dapat mengatasi masalah pemblokiran yang sering dikaitkan dengan meningkatkan solusi asinkron. Untuk gagasan lebih lanjut tentang pola seperti ini, lihat artikel saya yang lain: http://blog.maxant.co.uk/pebble/2015/08/11/1439322480000.html .

Solusi mana pun yang akhirnya Anda pilih, penting untuk mengenali bahwa hanya sebagian kecil dari sistem Anda yang akan menulis hal-hal yang perlu konsisten - sebagian besar akses cenderung hanya-baca. Karena alasan itu, bangun manajemen transaksi hanya menjadi bagian-bagian yang relevan dari sistem, sehingga masih dapat berkembang dengan baik.

Ant Kutschera
sumber
Sementara itu, saya akan mengatakan bahwa seseorang harus secara serius mempertimbangkan untuk mengubah proses menjadi asinkron, di mana setiap langkah dalam proses sepenuhnya transaksional. Lihat di sini untuk perincian: blog.maxant.co.uk/pebble/2018/02/18/1518974314273.html
Ant Kutschera
"Buat contoh penjualan / catatan pesanan dari atas - Anda bisa membiarkan layanan penjualan mikro mengirim pesan ke sistem pesanan, yang dilakukan dalam transaksi yang sama yang menulis penjualan ke database." Tidak yakin apakah yang Anda sarankan pada dasarnya adalah transaksi terdistribusi, yang mengatakan, bagaimana Anda menangani skenario rollback dalam kasus ini? Misal, pesan dikomit ke antrian pesan tetapi dibatalkan di sisi DB.
sactiw
@sactiw tidak yakin jika saya mungkin memiliki dua fase komit dalam pikiran, tapi saya akan menghindarinya sekarang dan sebagai gantinya menulis data bisnis saya, dan fakta bahwa pesan perlu ditambahkan ke antrian, dalam satu transaksi ke layanan mikro DB saya . "Fakta" alias "perintah" kemudian diproses secara async setelah transaksi dilakukan, menggunakan mekanisme coba ulang otomatis. Lihat artikel blog dari 2018-03-10 untuk contoh. Hindari kemunduran atau kompensasi demi strategi ke depan jika memungkinkan karena lebih mudah untuk diterapkan.
Ant Kutschera
1

Ada banyak solusi yang lebih mudah dikompromikan daripada yang nyaman bagi saya. Memang, jika use case Anda rumit, seperti memindahkan uang antar bank yang berbeda, alternatif yang lebih menyenangkan mungkin tidak mungkin. Tapi mari kita lihat apa yang bisa kita lakukan dalam skenario umum, di mana penggunaan layanan microser mengganggu transaksi basis data calon kami.

Opsi 1: Hindari kebutuhan transaksi jika semuanya memungkinkan

Jelas dan disebutkan sebelumnya, tetapi ideal jika kita bisa mengelolanya. Apakah komponen-komponen tersebut sebenarnya termasuk dalam microservice yang sama? Atau bisakah kita mendesain ulang sistem sehingga transaksi menjadi tidak perlu? Mungkin menerima non-transaksionalitas adalah pengorbanan yang paling terjangkau.

Opsi 2: Gunakan antrian

Jika ada cukup kepastian bahwa layanan lain akan berhasil pada apa pun yang kita inginkan, kita dapat memanggilnya melalui beberapa bentuk antrian. Item yang antri tidak akan diambil hingga nanti, tetapi kami dapat memastikan bahwa item tersebut sudah antri .

Misalnya, katakan bahwa kami ingin memasukkan entitas dan mengirim email, sebagai satu transaksi. Alih-alih memanggil server surat, kami mengantri e-mail dalam sebuah tabel.

Begin transaction
Insert entity
Insert e-mail
Commit transaction

Kelemahan yang jelas adalah bahwa beberapa layanan microser akan membutuhkan akses ke tabel yang sama.

Opsi 3: Lakukan pekerjaan eksternal yang terakhir, sesaat sebelum menyelesaikan transaksi

Pendekatan ini bertumpu pada asumsi bahwa melakukan transaksi sangat tidak mungkin gagal.

Begin transaction
Insert entity
Insert another entity
Make external call
Commit transaction

Jika kueri gagal, panggilan eksternal belum terjadi. Jika panggilan eksternal gagal, transaksi tidak pernah dilakukan.

Pendekatan ini hadir dengan keterbatasan yang kita hanya bisa membuat satu panggilan eksternal, dan itu harus dilakukan terakhir (yaitu kita tidak dapat menggunakan hasilnya dalam permintaan kita).

Opsi 4: Buat sesuatu dalam status tertunda

Seperti yang diposting di sini , kami dapat meminta beberapa layanan microser membuat komponen yang berbeda, masing-masing dalam status tertunda, non-transaksi.

Validasi apa pun dilakukan, tetapi tidak ada yang dibuat dalam keadaan definitif. Setelah semuanya berhasil dibuat, masing-masing komponen diaktifkan. Biasanya, operasi ini sangat sederhana dan kemungkinan kesalahan terjadi sangat kecil, sehingga kita bahkan dapat memilih untuk melakukan aktivasi secara non-transaksi.

Kelemahan terbesarnya adalah kita harus memperhitungkan keberadaan barang-barang yang tertunda. Permintaan pilih apa pun perlu mempertimbangkan apakah akan memasukkan data yang tertunda. Sebagian besar harus mengabaikannya. Dan pembaruan adalah cerita lain sama sekali.

Opsi 5: Biarkan microservice membagikan kuerinya

Tidak ada opsi lain yang melakukannya untuk Anda? Kalau begitu mari kita menjadi ortodoks .

Tergantung pada perusahaan, yang ini mungkin tidak dapat diterima. Aku sadar. Ini tidak lazim. Jika tidak dapat diterima, pilih rute lain. Tetapi jika ini cocok dengan situasi Anda, itu menyelesaikan masalah dengan sederhana dan kuat. Mungkin saja kompromi yang paling dapat diterima.

Ada cara untuk mengubah kueri dari beberapa layanan mikro menjadi transaksi basis data tunggal yang sederhana.

Kembalikan kueri, alih-alih menjalankannya.

Begin transaction
Execute our own query
Make external call, receiving a query
Execute received query
Commit transaction

Dari sisi jaringan, setiap layanan mikro harus dapat mengakses setiap basis data. Ingatlah hal ini, juga dalam hal penskalaan di masa depan.

Jika database yang terlibat dalam transaksi berada di server yang sama, ini akan menjadi transaksi reguler. Jika mereka berada di server yang berbeda, itu akan menjadi transaksi terdistribusi. Kode tetap sama.

Kami menerima permintaan, termasuk jenis koneksinya, parameternya, dan string koneksinya. Kita dapat membungkusnya dalam kelas Command yang dapat dieksekusi dengan rapi, menjaga alirannya tetap terbaca: Panggilan layanan microser di dalam Command, yang kita jalankan, sebagai bagian dari transaksi kami.

String koneksi adalah apa yang diberikan microservice asal, sehingga untuk semua maksud dan tujuan, kueri masih dianggap dijalankan oleh microservice itu. Kami hanya merutekannya secara fisik melalui microservice klien. Apakah itu membuat perbedaan? Yah, itu memungkinkan kita memasukkannya ke dalam transaksi yang sama dengan permintaan lain.

Jika kompromi dapat diterima, pendekatan ini memberi kita transaksionalitas langsung dari aplikasi monolith, dalam arsitektur layanan-mikro.

Timo
sumber
0

Saya akan mulai dengan menguraikan ruang masalah - mengidentifikasi batas layanan Anda . Ketika dilakukan dengan benar, Anda tidak perlu melakukan transaksi lintas layanan.

Layanan yang berbeda memiliki data mereka sendiri, perilaku, kekuatan motivasi, pemerintah, aturan bisnis, dll. Awal yang baik adalah membuat daftar kemampuan tingkat tinggi yang dimiliki perusahaan Anda. Misalnya, pemasaran, penjualan, akuntansi, dukungan. Titik awal lainnya adalah struktur organisasi, tetapi perlu diperhatikan bahwa ada peringatan - untuk beberapa alasan (politik, misalnya) mungkin bukan skema dekomposisi bisnis yang optimal. Pendekatan yang lebih ketat adalah analisis rantai nilai . Ingat, layanan Anda dapat menyertakan orang juga, itu bukan perangkat lunak semata. Layanan harus berkomunikasi satu sama lain melalui acara .

Langkah selanjutnya adalah mengukir layanan ini. Akibatnya, Anda mendapatkan agregat yang relatif independen . Mereka mewakili unit konsistensi. Dengan kata lain, internal mereka harus konsisten dan ACID. Agregat berkomunikasi satu sama lain melalui acara baik.

Jika Anda berpikir bahwa domain Anda menuntut konsistensi terlebih dahulu, pikirkan lagi. Tak satu pun dari sistem besar dan misi-kritis dibangun dengan pemikiran ini. Mereka semua didistribusikan dan pada akhirnya konsisten. Periksa kertas klasik Pat Helland .

Berikut adalah beberapa kiat praktis tentang cara membangun sistem terdistribusi.

Zapadlo
sumber