Komunikasi Layanan Mikro: Tutorial Integrasi Musim Semi dengan Redis
Diterbitkan: 2022-03-11Arsitektur layanan mikro adalah pendekatan yang sangat populer dalam merancang dan mengimplementasikan aplikasi web yang sangat skalabel. Komunikasi dalam aplikasi monolitik antar komponen biasanya didasarkan pada pemanggilan metode atau fungsi dalam proses yang sama. Aplikasi berbasis layanan mikro, di sisi lain, adalah sistem terdistribusi yang berjalan di banyak mesin.
Komunikasi antara layanan mikro ini penting untuk memiliki sistem yang stabil dan skalabel. Ada beberapa cara untuk melakukan ini. Komunikasi berbasis pesan adalah salah satu cara untuk melakukan ini dengan andal.
Saat menggunakan pesan, komponen berinteraksi satu sama lain dengan bertukar pesan secara asinkron. Pesan dipertukarkan melalui saluran.
Ketika Layanan A ingin berkomunikasi dengan Layanan B, alih-alih mengirimkannya secara langsung, A mengirimkannya ke saluran tertentu. Ketika Layanan B ingin membaca pesan, ia mengambil pesan dari saluran pesan tertentu.
Dalam tutorial Integrasi Musim Semi ini, Anda akan mempelajari cara mengimplementasikan perpesanan dalam aplikasi Musim Semi menggunakan Redis. Anda akan diarahkan melalui aplikasi contoh di mana satu layanan mendorong peristiwa dalam antrian dan layanan lain memproses peristiwa ini satu per satu.
Integrasi Musim Semi
Proyek Spring Integration memperluas kerangka kerja Spring untuk menyediakan dukungan untuk pengiriman pesan antara atau di dalam aplikasi berbasis Spring. Komponen dihubungkan bersama melalui paradigma pesan. Komponen individu mungkin tidak menyadari komponen lain dalam aplikasi.
Spring Integration menyediakan berbagai pilihan mekanisme untuk berkomunikasi dengan sistem eksternal. Adaptor saluran adalah salah satu mekanisme yang digunakan untuk integrasi satu arah (kirim atau terima). Dan gateway digunakan untuk skenario permintaan/balasan (masuk atau keluar).
Apache Camel merupakan alternatif yang banyak digunakan. Integrasi pegas biasanya lebih disukai di layanan berbasis Pegas yang ada karena merupakan bagian dari ekosistem Musim Semi.
Redis
Redis adalah penyimpanan data dalam memori yang sangat cepat. Opsional dapat bertahan ke disk juga. Ini mendukung struktur data yang berbeda seperti pasangan nilai kunci sederhana, set, antrian, dll.
Menggunakan Redis sebagai antrian membuat berbagi data antar komponen dan penskalaan horizontal menjadi lebih mudah. Seorang produsen atau beberapa produsen dapat mendorong data ke antrian, dan konsumen atau beberapa konsumen dapat menarik data dan memproses acara tersebut.
Beberapa konsumen tidak dapat menggunakan peristiwa yang sama—ini memastikan bahwa satu peristiwa diproses sekali.
Manfaat menggunakan Redis sebagai antrian pesan:
- Eksekusi paralel dari tugas-tugas diskrit dengan cara yang tidak menghalangi
- Performa luar biasa
- Stabilitas
- Pemantauan dan debugging mudah
- Implementasi dan penggunaan yang mudah
Aturan:
- Menambahkan tugas ke antrian harus lebih cepat daripada memproses tugas itu sendiri.
- Mengkonsumsi tugas harus lebih cepat daripada memproduksinya (dan jika tidak, tambahkan lebih banyak konsumen).
Integrasi Musim Semi dengan Redis
Berikut ini berjalan melalui pembuatan contoh aplikasi untuk menjelaskan cara menggunakan Integrasi Musim Semi dengan Redis.
Katakanlah Anda memiliki aplikasi yang memungkinkan pengguna untuk mempublikasikan posting. Dan Anda ingin membangun fitur follow. Persyaratan lainnya adalah setiap kali seseorang memublikasikan postingan, semua pengikut harus diberi tahu melalui beberapa saluran komunikasi (misalnya, email atau pemberitahuan push).
Salah satu cara untuk menerapkan ini adalah dengan mengirim email ke setiap pengikut setelah pengguna memublikasikan sesuatu. Tetapi apa yang terjadi ketika pengguna memiliki 1.000 pengikut? Dan ketika 1.000 pengguna menerbitkan sesuatu dalam 10 detik, masing-masing dari mereka memiliki 1.000 pengikut? Juga, apakah postingan penerbit akan menunggu sampai semua email terkirim?
Sistem terdistribusi menyelesaikan masalah ini.
Masalah khusus ini dapat diselesaikan dengan menggunakan antrian. Layanan A (produser), yang bertanggung jawab untuk menerbitkan posting, hanya akan melakukan itu. Ini akan menerbitkan posting dan mendorong acara dengan daftar pengguna yang perlu menerima email dan posting itu sendiri. Daftar pengguna dapat diambil di layanan B, tetapi untuk kesederhanaan contoh ini, kami akan mengirimkannya dari layanan A.
Ini adalah operasi asinkron. Ini berarti layanan yang menerbitkan tidak perlu menunggu untuk mengirim email.
Layanan B (konsumen) akan menarik acara dari antrian dan memprosesnya. Dengan cara ini, kami dapat dengan mudah menskalakan layanan kami, dan kami dapat meminta n konsumen mengirim email (memproses acara).
Jadi mari kita mulai dengan implementasi di layanan produsen. Ketergantungan yang diperlukan adalah:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.integration</groupId> <artifactId>spring-integration-redis</artifactId> </dependency>Ketiga dependensi Maven ini diperlukan:
- Jedis adalah klien Redis.
- Ketergantungan Spring Data Redis memudahkan penggunaan Redis di Java. Ini memberikan konsep Spring yang sudah dikenal seperti kelas template untuk penggunaan API inti dan akses data bergaya repositori yang ringan.
- Spring Integration Redis menyediakan perpanjangan model pemrograman Spring untuk mendukung Pola Integrasi Perusahaan yang terkenal.
Selanjutnya, kita perlu mengkonfigurasi klien Jedis:
@Configuration public class RedisConfig { @Value("${redis.host}") private String redisHost; @Value("${redis.port:6379}") private int redisPort; @Bean public JedisPoolConfig poolConfig() { JedisPoolConfig poolConfig = new JedisPoolConfig(); poolConfig.setMaxTotal(128); return poolConfig; } @Bean public RedisConnectionFactory redisConnectionFactory(JedisPoolConfig poolConfig) { final JedisConnectionFactory connectionFactory = new JedisConnectionFactory(); connectionFactory.setHostName(redisHost); connectionFactory.setPort(redisPort); connectionFactory.setPoolConfig(poolConfig); connectionFactory.setUsePool(true); return connectionFactory; } } Anotasi @Value berarti bahwa Spring akan menyuntikkan nilai yang ditentukan dalam properti aplikasi ke dalam bidang. Ini berarti nilai redis.host dan redis.port harus ditentukan dalam properti aplikasi.
Sekarang, kita perlu mendefinisikan pesan yang ingin kita kirim ke antrian. Contoh pesan sederhana dapat terlihat seperti:
@Getter @Setter @Builder public class PostPublishedEvent { private String postUrl; private String postTitle; private List<String> emails; } Catatan: Project Lombok (https://projectlombok.org/) menyediakan @Getter , @Setter , @Builder , dan banyak anotasi lainnya untuk menghindari kode yang berantakan dengan getter, setter, dan hal-hal sepele lainnya. Anda dapat mempelajarinya lebih lanjut dari artikel Toptal ini.
Pesan itu sendiri akan disimpan dalam format JSON dalam antrian. Setiap kali acara dipublikasikan ke antrian, pesan akan diserialkan ke JSON. Dan saat mengkonsumsi dari antrian, pesan akan di-deserialized.
Dengan pesan yang ditentukan, kita perlu mendefinisikan antrian itu sendiri. Di Spring Integration, ini dapat dengan mudah dilakukan melalui konfigurasi .xml . Konfigurasi harus ditempatkan di dalam direktori resources/WEB-INF .
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-redis="http://www.springframework.org/schema/integration/redis" xsi:schemaLocation="http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <int-redis:queue-outbound-channel-adapter channel="eventChannelJson" serializer="serializer" auto-startup="true" connection-factory="redisConnectionFactory" queue="my-event-queue" /> <int:gateway service-interface="org.toptal.queue.RedisChannelGateway" error-channel="errorChannel" default-request-channel="eventChannel"> <int:default-header name="topic" value="queue"/> </int:gateway> <int:channel/> <int:channel/> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <int:object-to-json-transformer input-channel="eventChannel" output-channel="eventChannelJson"/> </beans>Dalam konfigurasi, Anda dapat melihat bagian "int-redis:queue-outbound-channel-adapter." Sifatnya adalah:
- id: Nama kacang komponen.
- channel:
MessageChanneldari mana titik akhir ini menerima pesan. - connection-factory: Referensi ke kacang
RedisConnectionFactory. - antrian: Nama daftar Redis di mana operasi push berbasis antrian dilakukan untuk mengirim pesan Redis. Atribut ini saling eksklusif dengan ekspresi antrian.
- antrian-ekspresi: Ekspresi SpEL untuk menentukan nama daftar Redis menggunakan pesan masuk saat runtime sebagai variabel
#root. Atribut ini saling eksklusif dengan antrian. - serializer: Referensi kacang
RedisSerializer. Secara default, ini adalahJdkSerializationRedisSerializer. Namun, untuk muatanString,StringRedisSerializerdigunakan jika referensi serializer tidak disediakan. - extract-payload: Tentukan apakah endpoint ini hanya mengirim payload ke antrian Redis atau seluruh pesan. Nilai defaultnya adalah
true. - left-push: Tentukan apakah titik akhir ini harus menggunakan push kiri (bila
true) atau push kanan (bilafalse) untuk menulis pesan ke daftar Redis. Jika benar, daftar Redis bertindak sebagai antrean FIFO saat digunakan dengan adaptor saluran masuk antrean Redis default. Atur kefalseuntuk digunakan dengan perangkat lunak yang membaca dari daftar dengan pop kiri atau untuk mencapai urutan pesan seperti tumpukan. Nilai defaultnya adalahtrue.
Langkah selanjutnya adalah menentukan gateway, yang disebutkan dalam konfigurasi .xml . Untuk gateway, kami menggunakan kelas RedisChannelGateway dari paket org.toptal.queue .

StringRedisSerializer digunakan untuk membuat pesan bersambung sebelum disimpan di Redis. Juga dalam konfigurasi .xml , kami mendefinisikan gateway dan mengatur RedisChannelGateway sebagai layanan gateway. Ini berarti bahwa kacang RedisChannelGateway dapat disuntikkan ke kacang lainnya. Kami mendefinisikan default-request-channel properti karena itu juga memungkinkan untuk memberikan referensi saluran per-metode dengan menggunakan anotasi @Gateway . Definisi kelas:
public interface RedisChannelGateway { void enqueue(PostPublishedEvent event); } Untuk menghubungkan konfigurasi ini ke dalam aplikasi kita, kita harus mengimpornya. Ini diimplementasikan di kelas SpringIntegrationConfig .
@ImportResource("classpath:WEB-INF/event-queue-config.xml") @AutoConfigureAfter(RedisConfig.class) @Configuration public class SpringIntegrationConfig { } @ImportResource penjelasan digunakan untuk mengimpor file konfigurasi Spring .xml ke @Configuration . Dan anotasi @AutoConfigureAfter digunakan untuk mengisyaratkan bahwa konfigurasi otomatis harus diterapkan setelah kelas konfigurasi otomatis lain yang ditentukan.
Kami sekarang akan membuat layanan dan mengimplementasikan metode yang akan enqueue acara ke antrian Redis.
public interface QueueService { void enqueue(PostPublishedEvent event); } @Service public class RedisQueueService implements QueueService { private RedisChannelGateway channelGateway; @Autowired public RedisQueueService(RedisChannelGateway channelGateway) { this.channelGateway = channelGateway; } @Override public void enqueue(PostPublishedEvent event) { channelGateway.enqueue(event); } } Dan sekarang, Anda dapat dengan mudah mengirim pesan ke antrian menggunakan metode enqueue dari QueueService .
Antrian redis hanyalah daftar dengan satu atau lebih produsen dan konsumen. Untuk mempublikasikan pesan ke antrian, produsen menggunakan perintah LPUSH Redis. Dan jika Anda memantau Redis (petunjuk: ketik redis-cli monitor ), Anda dapat melihat bahwa pesan ditambahkan ke antrian:
"LPUSH" "my-event-queue" "{\"postUrl\":\"test\",\"postTitle\":\"test\",\"emails\":[\"test\"]}"Sekarang, kita perlu membuat aplikasi konsumen yang akan menarik event ini dari antrian dan memprosesnya. Layanan konsumen membutuhkan ketergantungan yang sama dengan layanan produsen.
Sekarang kita dapat menggunakan kembali kelas PostPublishedEvent untuk membatalkan serialisasi pesan.
Kita perlu membuat konfigurasi antrian dan, sekali lagi, itu harus ditempatkan di dalam direktori resources/WEB-INF . Isi dari konfigurasi antrian adalah:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:int="http://www.springframework.org/schema/integration" xmlns:int-redis="http://www.springframework.org/schema/integration/redis" xsi:schemaLocation="http://www.springframework.org/schema/integration/redis http://www.springframework.org/schema/integration/redis/spring-integration-redis.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <int-redis:queue-inbound-channel-adapter channel="eventChannelJson" queue="my-event-queue" serializer="serializer" auto-startup="true" connection-factory="redisConnectionFactory"/> <int:channel/> <int:channel> <int:queue/> </int:channel> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> <int:json-to-object-transformer input-channel="eventChannelJson" output-channel="eventChannel" type="com.toptal.integration.spring.model.PostPublishedEvent"/> <int:service-activator input-channel="eventChannel" ref="RedisEventProcessingService" method="process"> <int:poller fixed-delay="10" time-unit="SECONDS" max-messages-per-poll="500"/> </int:service-activator> </beans> Dalam konfigurasi .xml , int-redis:queue-inbound-channel-adapter dapat memiliki properti berikut:
- id: Nama kacang komponen.
- channel:
MessageChanneltempat kami mengirim pesan dari titik akhir ini. - auto-startup: Atribut
SmartLifecycleuntuk menentukan apakah titik akhir ini harus dimulai secara otomatis setelah konteks aplikasi dimulai atau tidak. Nilai defaultnya adalahtrue. - phase: Atribut
SmartLifecycleuntuk menentukan fase di mana titik akhir ini akan dimulai. Nilai defaultnya adalah0. - connection-factory: Referensi ke kacang
RedisConnectionFactory. - antrian: Nama daftar Redis di mana operasi pop berbasis antrian dilakukan untuk mendapatkan pesan Redis.
- error-channel:
MessageChannelyang akan kami kirimiErrorMessagesdenganExceptionsdari tugas mendengarkanEndpoint. Secara default,MessagePublishingErrorHandleryang mendasari menggunakanerrorChanneldefault dari konteks aplikasi. - serializer: Referensi kacang
RedisSerializer. Itu bisa berupa string kosong, yang berarti tidak ada serializer. Dalam hal ini,byte[]dari pesan Redis masuk dikirim ke saluran sebagai muatanMessage. Secara default, ini adalahJdkSerializationRedisSerializer. - menerima-waktu habis: Batas waktu dalam milidetik untuk operasi pop untuk menunggu pesan Redis dari antrian. Nilai defaultnya adalah 1 detik.
- interval pemulihan: Waktu dalam milidetik saat tugas pendengar harus tidur setelah pengecualian pada operasi pop sebelum memulai kembali tugas pendengar.
- harapkan-pesan: Tentukan apakah titik akhir ini mengharapkan data dari antrian Redis berisi seluruh pesan. Jika atribut ini disetel ke
true, serializer tidak boleh berupa string kosong karena pesan memerlukan beberapa bentuk deserialisasi (serialisasi JDK secara default). Nilai defaultnya adalahfalse. - task-executor: Referensi ke kacang Spring
TaskExecutor(atau standar JDK 1.5+ Executor). Ini digunakan untuk tugas mendengarkan yang mendasarinya. Secara default,SimpleAsyncTaskExecutordigunakan. - right-pop: Tentukan apakah titik akhir ini harus menggunakan pop kanan (bila
true) atau pop kiri (bilafalse) untuk membaca pesan dari daftar Redis. Jikatrue, daftar Redis bertindak sebagai antrean FIFO saat digunakan dengan adaptor saluran keluar antrean Redis default. Setel kefalseuntuk digunakan dengan perangkat lunak yang menulis ke daftar dengan push kanan atau untuk mencapai urutan pesan seperti tumpukan. Nilai defaultnya adalahtrue.
Bagian yang penting adalah “service activator”, yang menentukan layanan dan metode mana yang harus digunakan untuk memproses event.'
Selain itu, json-to-object-transformer memerlukan atribut type untuk mengubah JSON menjadi objek, setel di atas ke type="com.toptal.integration.spring.model.PostPublishedEvent" .
Sekali lagi, untuk menyambungkan konfigurasi ini, kita memerlukan kelas SpringIntegrationConfig , yang bisa sama seperti sebelumnya. Dan terakhir, kita membutuhkan sebuah layanan yang benar-benar akan memproses acara tersebut.
public interface EventProcessingService { void process(PostPublishedEvent event); } @Service("RedisEventProcessingService") public class RedisEventProcessingService implements EventProcessingService { @Override public void process(PostPublishedEvent event) { // TODO: Send emails here, retry strategy, etc :) } }Setelah Anda menjalankan aplikasi, Anda dapat melihat di Redis:
"BRPOP" "my-event-queue" "1"Kesimpulan
Dengan Spring Integration dan Redis, membangun aplikasi microservices Spring tidak sesulit biasanya. Dengan sedikit konfigurasi dan sedikit kode boilerplate, Anda dapat membangun fondasi arsitektur layanan mikro Anda dalam waktu singkat.
Bahkan jika Anda tidak berencana untuk menghapus proyek Spring Anda saat ini sepenuhnya dan beralih ke arsitektur baru, dengan bantuan Redis, sangat mudah untuk mendapatkan peningkatan kinerja yang besar dengan antrian.
