paint-brush
Pengiriman Pesan yang Andal dalam Sistem Terdistribusioleh@fairday
37,380 bacaan
37,380 bacaan

Pengiriman Pesan yang Andal dalam Sistem Terdistribusi

oleh Aleksei8m2024/03/18
Read on Terminal Reader
Read this story w/o Javascript

Terlalu panjang; Untuk membaca

Membangun sistem terdistribusi yang andal, sangat tersedia, dan dapat diskalakan memerlukan kepatuhan terhadap teknik, prinsip, dan pola tertentu.
featured image - Pengiriman Pesan yang Andal dalam Sistem Terdistribusi
Aleksei HackerNoon profile picture

Masalah penulisan ganda

Membangun sistem terdistribusi yang andal, sangat tersedia, dan dapat diskalakan memerlukan kepatuhan terhadap teknik, prinsip, dan pola tertentu. Desain sistem tersebut melibatkan penanganan berbagai tantangan. Di antara masalah yang paling umum dan mendasar adalah masalah penulisan ganda .


"Masalah penulisan ganda" merupakan tantangan yang muncul dalam sistem terdistribusi, terutama saat menangani beberapa sumber data atau basis data yang perlu disinkronkan. Masalah ini merujuk pada kesulitan memastikan bahwa perubahan data ditulis secara konsisten ke berbagai penyimpanan data, seperti basis data atau cache, tanpa menimbulkan masalah seperti ketidakkonsistenan data, konflik, atau hambatan kinerja.


Arsitektur layanan mikro dan basis data pola per layanan memberi Anda banyak manfaat, seperti penerapan dan penskalaan independen, kegagalan terisolasi, dan potensi peningkatan kecepatan pengembangan. Namun, operasi memerlukan perubahan di antara beberapa layanan mikro, yang memaksa Anda untuk memikirkan solusi andal guna mengatasi masalah ini.

Hampir contoh nyata

Mari kita pertimbangkan skenario di mana domain kita melibatkan penerimaan aplikasi pinjaman, menilainya, dan kemudian mengirimkan pemberitahuan kepada nasabah.


Dalam semangat prinsip tanggung jawab tunggal, hukum Conway, dan pendekatan desain berbasis domain, setelah beberapa sesi peninjauan peristiwa, seluruh domain dibagi menjadi tiga subdomain dengan konteks terikat yang ditentukan yang memiliki batasan yang jelas, model domain, dan bahasa yang ada di mana-mana.


Sistem pertama bertugas untuk meng-onboarding dan menyusun aplikasi pinjaman baru. Sistem kedua mengevaluasi aplikasi-aplikasi ini dan membuat keputusan berdasarkan data yang diberikan. Proses penilaian ini, termasuk KYC/KYB, antipenipuan, dan pemeriksaan risiko kredit, dapat memakan waktu, sehingga memerlukan kemampuan untuk menangani ribuan aplikasi secara bersamaan. Akibatnya, fungsionalitas ini telah didelegasikan ke layanan mikro khusus dengan basis datanya sendiri, yang memungkinkan penskalaan independen.

Lebih jauh lagi, subsistem ini dikelola oleh dua tim berbeda, masing-masing dengan siklus rilis, perjanjian tingkat layanan (SLA), dan persyaratan skalabilitasnya sendiri.


Terakhir , layanan notifikasi khusus tersedia untuk mengirimkan peringatan kepada pelanggan.



Berikut ini adalah deskripsi singkat tentang kasus penggunaan utama sistem ini:

  1. Seorang nasabah mengajukan aplikasi pinjaman.
  2. Layanan Aplikasi Pinjaman mencatat aplikasi baru dengan status "Tertunda" dan memulai proses penilaian dengan meneruskan aplikasi ke Layanan Penilaian.
  3. Layanan Penilaian mengevaluasi aplikasi pinjaman yang masuk dan kemudian menginformasikan keputusan tersebut kepada Layanan Aplikasi Pinjaman.
  4. Setelah menerima keputusan, Layanan Aplikasi Pinjaman memperbarui status aplikasi pinjaman sesuai dengan itu dan memicu Layanan Pemberitahuan untuk memberi tahu nasabah tentang hasilnya.
  5. Layanan Notifikasi memproses permintaan ini dan mengirimkan notifikasi ke pelanggan melalui email, SMS, atau metode komunikasi pilihan lainnya, sesuai dengan pengaturan pelanggan.


Sekilas ini adalah sistem yang cukup sederhana dan primitif, tetapi mari kita bahas bagaimana layanan aplikasi pinjaman memproses perintah kirim aplikasi pinjaman.


Kita dapat mempertimbangkan dua pendekatan untuk interaksi layanan:

  1. Komit-Lokal-Pertama-Lalu-Terbitkan: Dalam pendekatan ini, layanan memperbarui basis data lokalnya (komit) dan kemudian menerbitkan peristiwa atau pesan ke layanan lain.

  2. Terbitkan-Dahulu-Kemudian-Komit-Lokal: Sebaliknya, metode ini melibatkan penerbitan peristiwa atau pesan sebelum melakukan komitmen perubahan ke basis data lokal.


Kedua metode ini memiliki kekurangan dan hanya sebagian aman untuk komunikasi dalam sistem terdistribusi.


Ini adalah diagram urutan penerapan pendekatan pertama.


Komitmen-Lokal-Pertama-Kemudian-Publikasikan


Dalam skenario ini, Layanan Aplikasi Pinjaman menggunakan pendekatan First-Local-Commit-Then-Publish , di mana ia terlebih dahulu melakukan transaksi dan kemudian mencoba mengirim pemberitahuan ke sistem lain. Namun, proses ini rentan terhadap kegagalan jika, misalnya, ada masalah jaringan, Layanan Penilaian tidak tersedia, atau Layanan Aplikasi Pinjaman mengalami kesalahan Out of Memory (OOM) dan mogok. Dalam kasus seperti itu, pesan akan hilang, sehingga Penilaian tidak mengetahui aplikasi pinjaman baru, kecuali jika tindakan tambahan diterapkan.


Dan yang kedua.

Pertama-tama-Terbitkan-Kemudian-Komit-Lokal
Dalam skenario First-Publish-Then-Local-Commit , Layanan Aplikasi Pinjaman menghadapi risiko yang lebih signifikan. Layanan ini mungkin memberi tahu Layanan Penilaian tentang aplikasi baru tetapi gagal menyimpan pembaruan ini secara lokal karena masalah seperti masalah basis data, kesalahan memori, atau bug kode. Pendekatan ini dapat menyebabkan inkonsistensi yang signifikan dalam data, yang dapat menyebabkan masalah serius, tergantung pada bagaimana Layanan Tinjauan Pinjaman menangani aplikasi yang masuk.


Oleh karena itu, kita harus mengidentifikasi solusi yang menawarkan mekanisme yang kuat untuk menerbitkan peristiwa kepada konsumen eksternal. Namun, sebelum membahas solusi potensial, pertama-tama kita harus mengklarifikasi jenis jaminan pengiriman pesan yang dapat dicapai dalam sistem terdistribusi.

Jaminan pengiriman pesan

Ada empat jenis jaminan yang dapat kita capai.

  1. Tidak ada jaminan
    Tidak ada jaminan bahwa pesan akan terkirim ke tujuan. Pendekatan First-Local-Commit-Then-Publish adalah tentang hal ini. Konsumen dapat menerima pesan sekali, beberapa kali, atau tidak pernah sama sekali.

  2. Paling banyak satu kali pengiriman
    Pengiriman paling banyak satu kali berarti pesan akan dikirim ke tujuan paling banyak 1 kali. Pendekatan First-Local-Commit-Then-Publish dapat diimplementasikan dengan cara ini juga dengan kebijakan percobaan ulang dengan nilai satu.

  3. Setidaknya satu kali pengiriman_Konsumen akan menerima dan memproses setiap pesan tetapi mungkin menerima pesan yang sama lebih dari satu kali.

  4. Pengiriman tepat satu kali\Pengiriman tepat satu kali berarti konsumen akan menerima pesan secara efektif satu kali.
    Secara teknis, hal itu mungkin dicapai dengan transaksi Kafka dan implementasi idempoten spesifik dari produsen dan konsumen.


Dalam kebanyakan kasus, jaminan pengiriman 'minimal satu kali' mengatasi banyak masalah dengan memastikan pesan terkirim minimal satu kali, tetapi konsumen harus idempoten. Namun, mengingat kegagalan jaringan yang tidak dapat dihindari, semua logika konsumen harus idempoten untuk menghindari pemrosesan pesan duplikat, terlepas dari jaminan produsen. Oleh karena itu, persyaratan ini tidak terlalu merugikan karena mencerminkan kenyataan.

Solusi

Ada banyak solusi untuk masalah ini, yang memiliki kelebihan dan kekurangannya masing-masing.

Komitmen dua fase

Menurut Wikipedia, Two-Phase Commit (2PC) adalah protokol transaksi terdistribusi yang digunakan dalam ilmu komputer dan sistem manajemen basis data untuk memastikan konsistensi dan keandalan transaksi terdistribusi. Protokol ini dirancang untuk situasi di mana beberapa sumber daya (misalnya, basis data) perlu berpartisipasi dalam satu transaksi, dan memastikan bahwa semuanya melakukan commit transaksi atau semuanya membatalkannya, sehingga menjaga konsistensi data. Kedengarannya persis seperti yang kita butuhkan, tetapi Two-Phase Commit memiliki beberapa kekurangan:

  • Jika salah satu sumber daya yang berpartisipasi menjadi tidak responsif atau mengalami kegagalan, seluruh proses dapat diblokir hingga masalah teratasi. Hal ini dapat menyebabkan potensi masalah kinerja dan ketersediaan.
  • Two-Phase Commit tidak menyediakan mekanisme toleransi kesalahan bawaan. Ia bergantung pada mekanisme eksternal atau intervensi manual untuk menangani kegagalan.
  • Tidak semua pangkalan data modern mendukung Komitmen Dua Fase.

Basis data bersama

Solusi yang paling jelas untuk arsitektur layanan mikro adalah menerapkan pola (atau bahkan terkadang antipola) — basis data bersama. Pendekatan ini sangat intuitif jika Anda memerlukan konsistensi transaksional di beberapa tabel dalam basis data yang berbeda, cukup gunakan satu basis data bersama untuk layanan mikro ini.


Kelemahan pendekatan ini meliputi pengenalan satu titik kegagalan, menghambat penskalaan basis data independen, dan membatasi kemampuan untuk menggunakan berbagai solusi basis data yang paling sesuai untuk persyaratan dan kasus penggunaan tertentu. Selain itu, modifikasi pada basis kode layanan mikro akan diperlukan untuk mendukung bentuk transaksi terdistribusi tersebut.

Kotak keluar transaksional

' Transactional outbox ' adalah pola desain yang digunakan dalam sistem terdistribusi untuk memastikan penyebaran pesan yang andal, bahkan dalam menghadapi sistem pengiriman pesan yang tidak andal. Pola ini melibatkan penyimpanan peristiwa dalam tabel 'OutboxEvents' yang ditentukan dalam transaksi yang sama dengan operasi itu sendiri. Pendekatan ini selaras dengan properti ACID dari basis data relasional. Sebaliknya, banyak basis data No-SQL tidak sepenuhnya mendukung properti ACID, melainkan memilih prinsip teorema CAP dan filosofi BASE, yang memprioritaskan ketersediaan dan konsistensi akhir daripada konsistensi yang ketat.


Kotak keluar transaksional menyediakan setidaknya satu jaminan dan dapat diimplementasikan dengan beberapa pendekatan:

  1. Pelacakan log transaksi

  2. Penerbit jajak pendapat


Pendekatan pelacakan log transaksi menyiratkan penggunaan solusi khusus basis data seperti CDC (Change Data Capture). Kelemahan utama pendekatan tersebut adalah:

  • Solusi khusus basis data

  • Peningkatan latensi karena spesifikasi implementasi CDC


Metode lain adalah Polling Publisher , yang memfasilitasi pemindahan data keluar dengan melakukan polling pada tabel data keluar. Kelemahan utama pendekatan ini adalah potensi peningkatan beban basis data, yang dapat menyebabkan biaya yang lebih tinggi. Lebih jauh lagi, tidak semua basis data No-SQL mendukung kueri yang efisien untuk segmen dokumen tertentu. Oleh karena itu, mengekstrak seluruh dokumen dapat mengakibatkan penurunan kinerja.


Berikut adalah diagram sekuens kecil yang menjelaskan cara kerjanya.


Dengarkan dirimu sendiri

Tantangan utama dengan pola Transactional Outbox terletak pada ketergantungannya pada properti ACID basis data. Pola ini mungkin mudah digunakan dalam basis data OLTP biasa, tetapi menimbulkan tantangan dalam ranah NoSQL. Untuk mengatasi hal ini, solusi yang mungkin adalah memanfaatkan log penambahan (misalnya, Kafka) langsung dari inisiasi pemrosesan permintaan.


Alih-alih memproses perintah 'submit loan application' secara langsung, kami langsung mengirimkannya ke topik internal Kafka lalu mengembalikan hasil 'accepted' ke klien. Akan tetapi, karena kemungkinan besar perintah tersebut masih perlu diproses, kami tidak dapat langsung memberi tahu klien tentang hasilnya. Untuk mengelola konsistensi akhir ini, kami dapat menggunakan teknik seperti long polling, polling yang diinisiasi klien, pembaruan UI optimis, atau menggunakan WebSockets atau Server-Sent Events untuk notifikasi. Akan tetapi, ini adalah topik yang berbeda sama sekali, jadi mari kita kembali ke pokok bahasan awal kita.


Kami mengirim pesan pada topik Kafka internal. Layanan Aplikasi Pinjaman kemudian menggunakan pesan ini — perintah yang sama yang diterimanya dari klien — dan mulai memproses. Pertama, ia menjalankan beberapa logika bisnis; hanya setelah logika ini berhasil dijalankan dan hasilnya disimpan, ia menerbitkan pesan baru pada topik Kafka publik.


Mari kita lihat sedikit kode semu.


 public async Task HandleAsync(SubmitLoanApplicationCommand command, ...) { //First, process business logic var loanApplication = await _loanApplicationService.HandleCommandAsync(command, ...); //Then, send new events to public Kafka topic producer.Send(new LoanApplicationSubmittedEvent(loanApplication.Id)); //Then, commit offset consumer.Commit(); }


Bagaimana jika pemrosesan logika bisnis gagal? Jangan khawatir, karena offset belum dikomit, pesan akan dicoba lagi.


Bagaimana jika pengiriman peristiwa baru ke Kafka gagal? Jangan khawatir, karena logika bisnis bersifat idempoten, maka aplikasi pinjaman duplikat tidak akan dibuat. Sebaliknya, aplikasi akan mencoba mengirim ulang pesan ke topik Kafka publik.


Bagaimana jika pesan dikirim ke Kafka, tetapi komitmen offset gagal? Jangan khawatir, karena logika bisnis bersifat idempoten, maka aplikasi pinjaman duplikat tidak akan dibuat. Sebaliknya, pesan akan dikirim ulang ke topik Kafka publik dan berharap komitmen offset berhasil kali ini.


Kelemahan utama pendekatan ini meliputi kompleksitas tambahan yang terkait dengan gaya pemrograman baru, konsistensi akhir (karena klien tidak akan langsung mengetahui hasilnya), dan persyaratan semua logika bisnis bersifat idempoten.

Sumber acara

Apa itu event sourcing, dan bagaimana cara penerapannya di sini? Event sourcing adalah pola arsitektur perangkat lunak yang digunakan untuk memodelkan status sistem dengan menangkap semua perubahan pada datanya sebagai serangkaian peristiwa yang tidak dapat diubah. Peristiwa ini merepresentasikan fakta atau transisi status dan berfungsi sebagai satu-satunya sumber kebenaran untuk status sistem saat ini. Jadi, secara teknis, dengan menerapkan sistem event-sourcing, kita sudah memiliki semua peristiwa di EventStore, dan EventStore ini dapat digunakan oleh konsumen sebagai satu-satunya sumber kebenaran tentang apa yang terjadi. Tidak diperlukan solusi basis data khusus untuk melacak semua perubahan atau masalah tentang pemesanan, satu-satunya masalah ada di sisi baca karena untuk bisa mendapatkan status entitas yang sebenarnya diperlukan pemutaran ulang semua peristiwa.

Kesimpulan

Dalam artikel ini, kami mengulas beberapa pendekatan untuk membangun pesan yang andal dalam sistem terdistribusi. Ada beberapa rekomendasi yang dapat kami pertimbangkan saat membangun sistem dengan karakteristik ini

  1. Selalu kembangkan konsumen idempoten karena kegagalan jaringan tidak dapat dihindari.
  2. Gunakan First-Local-Commit-Then-Publish dengan hati-hati dengan pemahaman yang jelas tentang persyaratan jaminan.
  3. Jangan sekali-kali menggunakan pendekatan Terbitkan-Dahulu-Komit-Lokal karena dapat mengakibatkan ketidakkonsistenan data yang parah dalam sistem Anda.
  4. Jika keputusan pilihan basis data yang ada sangat mungkin berubah atau strategi teknis mengharuskan pemilihan solusi penyimpanan terbaik untuk masalah tersebut — jangan membangun pustaka bersama dengan mengikat solusi basis data seperti CDC .
  5. Gunakan pendekatan Kotak Keluar Transaksional sebagai solusi standar untuk mencapai setidaknya satu jaminan.
  6. Pertimbangkan untuk menggunakan pendekatan Dengarkan diri Anda sendiri saat memanfaatkan basis data No-SQL.


Lain kali, kita akan melihat contoh yang lebih praktis tentang penerapan Kotak Keluar Transaksional. Lihat

Anda!