Transaksi terdistribusi di Spring, dengan dan tanpa XA

Meskipun umum menggunakan Java Transaction API dan protokol XA untuk transaksi terdistribusi di Spring, Anda memiliki opsi lain. Penerapan yang optimal bergantung pada jenis sumber daya yang digunakan aplikasi Anda dan kompromi yang ingin Anda lakukan antara kinerja, keamanan, keandalan, dan integritas data. Dalam fitur JavaWorld ini, David Syer dari SpringSource memandu Anda melalui tujuh pola untuk transaksi terdistribusi dalam aplikasi Spring, tiga di antaranya dengan XA dan empat tanpa. Level: Menengah

Dukungan Spring Framework untuk Java Transaction API (JTA) memungkinkan aplikasi menggunakan transaksi terdistribusi dan protokol XA tanpa berjalan di container Java EE. Bahkan dengan dukungan ini, bagaimanapun, XA mahal dan dapat menjadi tidak dapat diandalkan atau tidak praktis untuk dikelola. Ini mungkin mengejutkan, bahwa kelas aplikasi tertentu dapat menghindari penggunaan XA sama sekali.

Untuk membantu Anda memahami pertimbangan yang terlibat dalam berbagai pendekatan untuk transaksi terdistribusi, saya akan menganalisis tujuh pola pemrosesan transaksi, memberikan contoh kode untuk membuatnya konkret. Saya akan menyajikan pola dalam urutan terbalik dari keamanan atau keandalan, dimulai dengan yang memiliki jaminan integritas data dan atomisitas tertinggi dalam keadaan paling umum. Saat Anda berpindah ke bawah daftar, lebih banyak peringatan dan batasan akan berlaku. Polanya juga kira-kira dalam urutan terbalik dari biaya runtime (dimulai dengan yang paling mahal). Polanya semuanya arsitektural, atau teknis, sebagai lawan dari pola bisnis, jadi saya tidak fokus pada kasus penggunaan bisnis, hanya pada jumlah kode minimal untuk melihat setiap pola bekerja.

Perhatikan bahwa hanya tiga pola pertama yang melibatkan XA, dan itu mungkin tidak tersedia atau dapat diterima berdasarkan kinerja. Saya tidak membahas pola XA seluas yang lain karena pola tersebut tercakup di tempat lain, meskipun saya memberikan demonstrasi sederhana tentang yang pertama. Dengan membaca artikel ini, Anda akan mempelajari apa yang dapat dan tidak dapat Anda lakukan dengan transaksi terdistribusi dan bagaimana serta kapan harus menghindari penggunaan XA - dan kapan tidak melakukannya.

Transaksi dan atomisitas terdistribusi

Sebuah transaksi terdistribusi adalah salah satu yang melibatkan lebih dari satu sumber daya transaksional. Contoh sumber daya transaksional adalah konektor untuk berkomunikasi dengan database relasional dan middleware perpesanan. Seringkali sumber daya seperti memiliki API yang terlihat sesuatu seperti begin(), rollback(), commit(). Di dunia Java, sumber daya transaksional biasanya muncul sebagai produk pabrik yang disediakan oleh platform yang mendasarinya: untuk database, itu Connection(diproduksi oleh DataSource) atau Java Persistence API (JPA) EntityManager; untuk Java Message Service (JMS), ini adalah Session.

Dalam contoh umum, pesan JMS memicu update database. Dipecah menjadi timeline, interaksi yang sukses berjalan seperti ini:

  1. Mulai transaksi pengiriman pesan
  2. Terima pesan
  3. Mulai transaksi database
  4. Perbarui database
  5. Lakukan transaksi database
  6. Lakukan transaksi perpesanan

Jika kesalahan database seperti pelanggaran batasan terjadi pada pembaruan, urutan yang diinginkan akan terlihat seperti ini:

  1. Mulai transaksi pengiriman pesan
  2. Terima pesan
  3. Mulai transaksi database
  4. Perbarui database, gagal!
  5. Roll back database transaksi
  6. Kembalikan transaksi pesan

Dalam kasus ini, pesan kembali ke middleware setelah rollback terakhir dan kembali di beberapa titik untuk diterima di transaksi lain. Ini biasanya merupakan hal yang baik, karena jika tidak, Anda mungkin tidak memiliki catatan bahwa terjadi kegagalan. (Mekanisme untuk menangani percobaan ulang otomatis dan menangani pengecualian berada di luar cakupan artikel ini.)

Fitur penting dari kedua garis waktu ini adalah bahwa keduanya atom , membentuk satu transaksi logis yang berhasil sepenuhnya atau gagal sepenuhnya.

Tapi apa yang menjamin bahwa garis waktu terlihat seperti salah satu dari urutan ini? Beberapa sinkronisasi antara sumber daya transaksional harus terjadi, sehingga jika seseorang melakukan keduanya, dan sebaliknya. Jika tidak, seluruh transaksi tidak bersifat atomik. Transaksi didistribusikan karena melibatkan banyak sumber daya, dan tanpa sinkronisasi, transaksi tidak akan bersifat atomik. Kesulitan teknis dan konseptual dengan transaksi terdistribusi semuanya terkait dengan sinkronisasi sumber daya (atau kekurangannya).

Tiga pola pertama yang dibahas di bawah ini didasarkan pada protokol XA. Karena pola ini telah dibahas secara luas, saya tidak akan membahasnya secara mendetail di sini. Mereka yang terbiasa dengan pola XA mungkin ingin langsung beralih ke pola Sumber Daya Transaksi Bersama.

XA penuh dengan 2PC

Jika Anda memerlukan jaminan antipeluru bahwa transaksi aplikasi Anda akan pulih setelah pemadaman, termasuk server mogok, maka XA Penuh adalah satu-satunya pilihan Anda. Sumber daya bersama yang digunakan untuk menyinkronkan transaksi dalam hal ini adalah manajer transaksi khusus yang mengoordinasikan informasi tentang proses menggunakan protokol XA. Di Java, dari sudut pandang pengembang, protokol diekspos melalui JTA UserTransaction.

Menjadi antarmuka sistem, XA adalah teknologi yang memungkinkan yang tidak pernah dilihat oleh sebagian besar pengembang. Yang perlu mereka ketahui adalah bahwa itu ada, apa yang memungkinkan, berapa biayanya, dan implikasinya terhadap bagaimana mereka menggunakan sumber daya transaksional. Biaya berasal dari protokol dua fase (2PC) yang digunakan manajer transaksi untuk memastikan bahwa semua sumber daya menyetujui hasil transaksi sebelum berakhir.

Jika aplikasi mengaktifkan Spring, ia menggunakan JtaTransactionManagermanajemen transaksi deklaratif Spring dan Spring untuk menyembunyikan detail sinkronisasi yang mendasarinya. Perbedaan bagi pengembang antara menggunakan XA dan tidak menggunakan XA adalah tentang mengonfigurasi sumber daya pabrik: DataSourceinstans, dan manajer transaksi untuk aplikasi tersebut. Artikel ini menyertakan contoh aplikasi ( atomikos-dbproyek) yang menggambarkan konfigurasi ini. The DataSourcecontoh dan manajer transaksi adalah satu-satunya XA- atau JTA-spesifik elemen dari aplikasi.

Untuk melihat sampel bekerja, jalankan pengujian unit di bawah com.springsource.open.db. Kelas sederhana MulipleDataSourceTestshanya memasukkan data ke dalam dua sumber data dan kemudian menggunakan fitur dukungan integrasi Spring untuk memutar kembali transaksi, seperti yang ditunjukkan pada Daftar 1:

Daftar 1. Transaksi rollback

@Transactional @Test public void testInsertIntoTwoDataSources() throws Exception { int count = getJdbcTemplate().update( "INSERT into T_FOOS (id,name,foo_date) values (?,?,null)", 0, "foo"); assertEquals(1, count); count = getOtherJdbcTemplate() .update( "INSERT into T_AUDITS (id,operation,name,audit_date) values (?,?,?,?)", 0, "INSERT", "foo", new Date()); assertEquals(1, count); // Changes will roll back after this method exits }

Kemudian MulipleDataSourceTestsverifikasi bahwa kedua operasi tersebut dibatalkan, seperti yang ditunjukkan pada Listing 2:

Kode 2. Memverifikasi rollback

@AfterTransaction public void checkPostConditions() { int count = getJdbcTemplate().queryForInt("select count(*) from T_FOOS"); // This change was rolled back by the test framework assertEquals(0, count); count = getOtherJdbcTemplate().queryForInt("select count(*) from T_AUDITS"); // This rolled back as well because of the XA assertEquals(0, count); }

Untuk pemahaman yang lebih baik tentang cara kerja manajemen transaksi Spring dan cara mengonfigurasinya secara umum, lihat Panduan Referensi Spring.

XA dengan Optimasi 1PC

Pola ini adalah pengoptimalan yang digunakan banyak manajer transaksi untuk menghindari overhead 2PC jika transaksi menyertakan satu sumber daya. Anda akan mengharapkan server aplikasi Anda untuk mengetahui hal ini.

XA dan Gambit Sumber Daya Terakhir

Fitur lain dari banyak manajer transaksi XA adalah bahwa mereka masih dapat memberikan jaminan pemulihan yang sama ketika semua kecuali satu sumber daya berkemampuan XA seperti yang mereka bisa ketika semuanya ada. Mereka melakukan ini dengan memesan sumber daya dan menggunakan sumber daya non-XA sebagai pemungutan suara. Jika gagal dilakukan, maka semua sumber daya lainnya dapat dibatalkan. Ini hampir 100 persen antipeluru - tetapi tidak cukup. Dan jika gagal, gagal tanpa meninggalkan banyak jejak kecuali diambil langkah ekstra (seperti yang dilakukan di beberapa implementasi top-end).

Pola Sumber Daya Transaksi Bersama

Pola yang bagus untuk mengurangi kompleksitas dan meningkatkan throughput di beberapa sistem adalah menghilangkan kebutuhan XA sama sekali dengan memastikan bahwa semua sumber daya transaksional dalam sistem sebenarnya didukung oleh sumber daya yang sama. Ini jelas tidak mungkin dalam semua kasus penggunaan pemrosesan, tetapi ini sama solidnya dengan XA dan biasanya jauh lebih cepat. Pola Sumber Daya Transaksi Bersama bersifat antipeluru tetapi khusus untuk platform dan skenario pemrosesan tertentu.

Contoh sederhana dan familiar (untuk banyak) dari pola ini adalah berbagi database Connectionantara komponen yang menggunakan pemetaan relasional objek (ORM) dengan komponen yang menggunakan JDBC. Inilah yang terjadi Anda menggunakan manajer transaksi musim semi yang mendukung alat ORM seperti Hibernate, EclipseLink, dan Java Persistence API (JPA). Transaksi yang sama dapat digunakan dengan aman di seluruh komponen ORM dan JDBC, biasanya didorong dari atas oleh eksekusi metode tingkat layanan tempat transaksi dikontrol.

Penggunaan efektif lainnya dari pola ini adalah kasus pembaruan berbasis pesan dari satu database (seperti dalam contoh sederhana dalam pengantar artikel ini). Sistem middleware-perpesanan perlu menyimpan datanya di suatu tempat, seringkali dalam database relasional. Untuk menerapkan pola ini, yang diperlukan hanyalah mengarahkan sistem pesan ke database yang sama dengan data bisnis yang akan digunakan. Pola ini bergantung pada vendor messaging-middleware yang mengekspos detail strategi penyimpanannya sehingga dapat dikonfigurasi untuk menunjuk ke database yang sama dan menghubungkan ke transaksi yang sama.

Not all vendors make this easy. An alternative, which works for almost any database, is to use Apache ActiveMQ for messaging and plug a storage strategy into the message broker. This is fairly easy to configure once you know the trick. It's demonstrated in this article's shared-jms-db samples project. The application code (unit tests in this case) does not need to be aware that this pattern is in use, because it is all enabled declaratively in Spring configuration.

A unit test in the sample called SynchronousMessageTriggerAndRollbackTests verifies that everything is working with synchronous message reception. The testReceiveMessageUpdateDatabase method receives two messages and uses them to insert two records in the database. When this method exits, the test framework rolls back the transaction, so you can verify that the messages and the database updates are both rolled back, as shown in Listing 3:

Listing 3. Verifying rollback of messages and database updates

@AfterTransaction public void checkPostConditions() { assertEquals(0, SimpleJdbcTestUtils.countRowsInTable(jdbcTemplate, "T_FOOS")); List list = getMessages(); assertEquals(2, list.size()); }

The most important features of the configuration are the ActiveMQ persistence strategy, linking the messaging system to the same DataSource as the business data, and the flag on the Spring JmsTemplate used to receive the messages. Listing 4 shows how to configure the ActiveMQ persistence strategy:

Listing 4. Configuring ActiveMQ persistence

    ...             

Kode 5 menunjukkan bendera musim semi JmsTemplateyang digunakan untuk menerima pesan:

Kode 5. Menyiapkan JmsTemplateuntuk penggunaan transaksional

 ...   

Tanpa sessionTransacted=true, panggilan API transaksi sesi JMS tidak akan pernah dibuat dan penerimaan pesan tidak dapat dibatalkan. Bahan penting di sini adalah broker tertanam dengan async=falseparameter khusus dan pembungkus untuk DataSourceitu bersama-sama memastikan bahwa ActiveMQ menggunakan JDBC transaksional yang sama Connectiondengan Spring.