Percubaan Lebih Baik dengan Backup dan Jitter Eksponensial

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan meneroka bagaimana kita dapat meningkatkan percubaan pelanggan dengan dua strategi yang berbeza: backoff eksponensial dan jitter.

2. Cuba semula

Dalam sistem yang diedarkan, komunikasi rangkaian antara banyak komponen dapat gagal kapan saja. Aplikasi pelanggan menangani kegagalan ini dengan melaksanakan percubaan semula .

Mari kita anggap bahawa kita mempunyai aplikasi pelanggan yang meminta perkhidmatan jarak jauh - PingPongService .

interface PingPongService { String call(String ping) throws PingPongServiceException; }

Aplikasi pelanggan mesti mencuba semula jika PingPongService mengembalikan PingPongServiceException . Pada bahagian berikut, kami akan melihat cara untuk melaksanakan percubaan pelanggan.

3. Ketahanan4j Cuba semula

Sebagai contoh, kami akan menggunakan perpustakaan Resilience4j, terutamanya modulnya semula. Kami perlu menambahkan modul ketahanan4j-retry ke pom.xml kami :

 io.github.resilience4j resilience4j-retry 

Untuk penyegaran menggunakan percubaan semula, jangan lupa untuk melihat Panduan Ketahanan4j kami.

4. Backoff Eksponen

Aplikasi pelanggan mesti melaksanakan percubaan secara bertanggungjawab. Apabila pelanggan mencuba panggilan yang gagal tanpa menunggu, mereka mungkin membanjiri sistem , dan menyumbang kepada penurunan perkhidmatan yang sudah berada dalam keadaan tertekan.

Backoff eksponensial adalah strategi umum untuk menangani percubaan panggilan rangkaian yang gagal. Secara sederhana, pelanggan menunggu selang waktu yang lebih lama secara berterusan antara percubaan berturut-turut :

wait_interval = base * multiplier^n 

di mana,

  • asas adalah selang awal, iaitu, tunggu percubaan pertama
  • n ialah bilangan kegagalan yang telah berlaku
  • pengganda adalah pengganda sewenang-wenang yang boleh diganti dengan nilai yang sesuai

Dengan pendekatan ini, kami memberi ruang pernafasan kepada sistem untuk pulih dari kegagalan berselang, atau masalah yang lebih teruk lagi.

Kita boleh menggunakan algoritma backoff eksponensial dalam percubaan Resilience4j dengan mengkonfigurasi IntervalFunctionnya yang menerimaInterval awal dan pengganda .

The IntervalFunction digunakan oleh mekanisme cuba semula sebagai fungsi tidur:

IntervalFunction intervalFn = IntervalFunction.ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER); RetryConfig retryConfig = RetryConfig.custom() .maxAttempts(MAX_RETRIES) .intervalFunction(intervalFn) .build(); Retry retry = Retry.of("pingpong", retryConfig); Function pingPongFn = Retry .decorateFunction(retry, ping -> service.call(ping)); pingPongFn.apply("Hello"); 

Mari mensimulasikan senario dunia nyata, dan menganggap bahawa kita mempunyai beberapa pelanggan yang memanggil PingPongService serentak:

ExecutorService executors = newFixedThreadPool(NUM_CONCURRENT_CLIENTS); List tasks = nCopies(NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply("Hello")); executors.invokeAll(tasks); 

Mari kita lihat log panggilan jauh untuk NUM_CONCURRENT_CLIENTS sama dengan 4:

[thread-1] At 00:37:42.756 [thread-2] At 00:37:42.756 [thread-3] At 00:37:42.756 [thread-4] At 00:37:42.756 [thread-2] At 00:37:43.802 [thread-4] At 00:37:43.802 [thread-1] At 00:37:43.802 [thread-3] At 00:37:43.802 [thread-2] At 00:37:45.803 [thread-1] At 00:37:45.803 [thread-4] At 00:37:45.803 [thread-3] At 00:37:45.803 [thread-2] At 00:37:49.808 [thread-3] At 00:37:49.808 [thread-4] At 00:37:49.808 [thread-1] At 00:37:49.808 

Kita dapat melihat corak yang jelas di sini - pelanggan menunggu selang masa pertumbuhan yang semakin meningkat, tetapi mereka semua memanggil perkhidmatan jarak jauh pada masa yang sama pada setiap percubaan (perlanggaran).

Kami hanya menangani sebahagian masalah - kami tidak lagi menggunakan perkhidmatan jarak jauh dengan percubaan semula, tetapi daripada menyebarkan beban kerja dari masa ke masa, kami mempunyai masa kerja yang berselang dengan waktu yang lebih lama. Tingkah laku ini serupa dengan Masalah Petir.

5. Memperkenalkan Jitter

Dalam pendekatan kami sebelumnya, penantian pelanggan semakin lama semakin lama tetapi masih diselaraskan. Menambah jitter menyediakan cara untuk memecahkan penyegerakan antara klien dengan itu mengelakkan pertembungan . Dalam pendekatan ini, kami menambahkan rawak pada selang menunggu.

wait_interval = (base * 2^n) +/- (random_interval) 

di mana, interval random_ ditambahkan (atau dikurangkan) untuk memecahkan penyegerakan antara klien.

Kami tidak akan membahas mekanik pengkomputeran selang rawak, tetapi pengacakan mesti memberi ruang kepada pengedaran panggilan pelanggan yang lebih lancar.

Kita boleh menggunakan backoff eksponensial dengan jitter dalam percubaan Resilience4j dengan mengkonfigurasi IntervalFungsi backoff rawak eksponensial yang juga menerima randomizationFactor :

IntervalFunction intervalFn = IntervalFunction.ofExponentialRandomBackoff(INITIAL_INTERVAL, MULTIPLIER, RANDOMIZATION_FACTOR); 

Mari kembali ke senario dunia nyata kita, dan melihat log panggilan jauh dengan jitter:

[thread-2] At 39:21.297 [thread-4] At 39:21.297 [thread-3] At 39:21.297 [thread-1] At 39:21.297 [thread-2] At 39:21.918 [thread-3] At 39:21.868 [thread-4] At 39:22.011 [thread-1] At 39:22.184 [thread-1] At 39:23.086 [thread-5] At 39:23.939 [thread-3] At 39:24.152 [thread-4] At 39:24.977 [thread-3] At 39:26.861 [thread-1] At 39:28.617 [thread-4] At 39:28.942 [thread-2] At 39:31.039

Sekarang kita mempunyai penyebaran yang jauh lebih baik. Kami telah menghilangkan kedua-dua perlanggaran dan masa senggang, dan berakhir dengan kadar panggilan pelanggan yang hampir berterusan , tanpa melambung awal.

Catatan: Kami telah melampaui selang untuk memberi gambaran, dan dalam senario dunia nyata, kita akan mempunyai jurang yang lebih kecil.

6. Kesimpulannya

Dalam tutorial ini, kami telah meneroka bagaimana kami dapat meningkatkan bagaimana aplikasi klien mencuba semula panggilan yang gagal dengan menambahkan backoff eksponensial dengan jitter.

Kod sumber untuk sampel yang digunakan dalam tutorial tersedia di GitHub.