Pengantar Hystrix

1. Ikhtisar

Sistem terdistribusi tipikal terdiri dari banyak layanan yang berkolaborasi bersama.

Layanan ini rentan terhadap kegagalan atau respons yang tertunda. Jika suatu layanan gagal, hal itu dapat berdampak pada layanan lain yang memengaruhi kinerja dan mungkin membuat bagian lain dari aplikasi tidak dapat diakses atau dalam kasus terburuk menurunkan seluruh aplikasi.

Tentu saja, ada solusi yang tersedia yang membantu membuat aplikasi tangguh dan toleran terhadap kesalahan - salah satu kerangka kerja tersebut adalah Hystrix.

Pustaka kerangka kerja Hystrix membantu mengontrol interaksi antara layanan dengan memberikan toleransi kesalahan dan toleransi latensi. Ini meningkatkan ketahanan keseluruhan sistem dengan mengisolasi layanan yang gagal dan menghentikan efek berjenjang dari kegagalan.

Dalam rangkaian posting ini kita akan mulai dengan melihat bagaimana Hystrix datang untuk menyelamatkan ketika layanan atau sistem gagal dan apa yang dapat dicapai Hystrix dalam keadaan ini.

2. Contoh Sederhana

Cara Hystrix memberikan toleransi kesalahan dan latensi adalah dengan mengisolasi dan membungkus panggilan ke layanan jarak jauh.

Dalam contoh sederhana ini kami membungkus panggilan dalam run () metode HystrixCommand:

class CommandHelloWorld extends HystrixCommand { private String name; CommandHelloWorld(String name) { super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup")); this.name = name; } @Override protected String run() { return "Hello " + name + "!"; } }

dan kami menjalankan panggilan sebagai berikut:

@Test public void givenInputBobAndDefaultSettings_whenCommandExecuted_thenReturnHelloBob(){ assertThat(new CommandHelloWorld("Bob").execute(), equalTo("Hello Bob!")); }

3. Pengaturan Maven

Untuk menggunakan Hystrix dalam proyek Maven, kita perlu memiliki ketergantungan hystrix-core dan rxjava-core dari Netflix di proyek pom.xml :

 com.netflix.hystrix hystrix-core 1.5.4  

Versi terbaru selalu dapat ditemukan di sini.

 com.netflix.rxjava rxjava-core 0.20.7 

Versi terbaru pustaka ini selalu dapat ditemukan di sini.

4. Menyiapkan Layanan Jarak Jauh

Mari kita mulai dengan mensimulasikan contoh dunia nyata.

Dalam contoh di bawah ini , kelas RemoteServiceTestSimulator mewakili layanan di server jarak jauh. Ini memiliki metode yang merespons dengan pesan setelah periode waktu tertentu. Kita dapat membayangkan bahwa penantian ini adalah simulasi dari proses yang memakan waktu di sistem jarak jauh yang mengakibatkan respons yang tertunda ke layanan panggilan:

class RemoteServiceTestSimulator { private long wait; RemoteServiceTestSimulator(long wait) throws InterruptedException { this.wait = wait; } String execute() throws InterruptedException { Thread.sleep(wait); return "Success"; } }

Dan berikut adalah contoh klien kami yang memanggil RemoteServiceTestSimulator .

Panggilan ke layanan diisolasi dan digabungkan dalam metode run () dari HystrixCommand. Pembungkus inilah yang memberikan ketahanan yang kita singgung di atas:

class RemoteServiceTestCommand extends HystrixCommand { private RemoteServiceTestSimulator remoteService; RemoteServiceTestCommand(Setter config, RemoteServiceTestSimulator remoteService) { super(config); this.remoteService = remoteService; } @Override protected String run() throws Exception { return remoteService.execute(); } }

Panggilan tersebut dijalankan dengan memanggil metode execute () pada instance objek RemoteServiceTestCommand .

Tes berikut menunjukkan bagaimana ini dilakukan:

@Test public void givenSvcTimeoutOf100AndDefaultSettings_whenRemoteSvcExecuted_thenReturnSuccess() throws InterruptedException { HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroup2")); assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(100)).execute(), equalTo("Success")); }

Sejauh ini kita telah melihat bagaimana membungkus panggilan layanan jarak jauh di objek HystrixCommand . Pada bagian di bawah ini, mari kita lihat bagaimana menangani situasi ketika layanan jarak jauh mulai memburuk.

5. Bekerja Dengan Layanan Jarak Jauh dan Pemrograman Defensif

5.1. Pemrograman Defensif Dengan Batas Waktu

Ini adalah praktik pemrograman umum untuk menyetel waktu tunggu untuk panggilan ke layanan jarak jauh.

Mari kita mulai dengan melihat cara menyetel batas waktu di HystrixCommand dan cara membantu dengan korsleting:

@Test public void givenSvcTimeoutOf5000AndExecTimeoutOf10000_whenRemoteSvcExecuted_thenReturnSuccess() throws InterruptedException { HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest4")); HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter(); commandProperties.withExecutionTimeoutInMilliseconds(10_000); config.andCommandPropertiesDefaults(commandProperties); assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(), equalTo("Success")); }

Dalam pengujian di atas, kami menunda respons layanan dengan menyetel waktu tunggu ke 500 ms. Kami juga menyetel batas waktu eksekusi pada HystrixCommand menjadi 10.000 ms, sehingga memberikan waktu yang cukup bagi layanan jarak jauh untuk merespons.

Sekarang mari kita lihat apa yang terjadi ketika waktu tunggu eksekusi kurang dari panggilan waktu tunggu layanan:

@Test(expected = HystrixRuntimeException.class) public void givenSvcTimeoutOf15000AndExecTimeoutOf5000_whenRemoteSvcExecuted_thenExpectHre() throws InterruptedException { HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupTest5")); HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter(); commandProperties.withExecutionTimeoutInMilliseconds(5_000); config.andCommandPropertiesDefaults(commandProperties); new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(15_000)).execute(); }

Perhatikan bagaimana kami telah menurunkan bilah dan menyetel batas waktu eksekusi menjadi 5.000 ms.

Kami mengharapkan layanan merespons dalam 5.000 md, sedangkan kami telah menyetel layanan untuk merespons setelah 15.000 md. Jika Anda memperhatikan saat Anda menjalankan tes, tes akan keluar setelah 5.000 md daripada menunggu 15.000 md dan akan menampilkan HystrixRuntimeException.

Ini menunjukkan bagaimana Hystrix tidak menunggu lebih lama dari waktu tunggu yang dikonfigurasi untuk sebuah tanggapan. Ini membantu membuat sistem yang dilindungi oleh Hystrix lebih responsif.

Pada bagian di bawah ini kita akan melihat ke dalam pengaturan ukuran kumpulan benang yang mencegah benang habis dan kita akan membahas manfaatnya.

5.2. Pemrograman Defensif Dengan Kumpulan Benang Terbatas

Mengatur batas waktu untuk panggilan layanan tidak menyelesaikan semua masalah yang terkait dengan layanan jarak jauh.

Saat layanan jarak jauh mulai merespons dengan lambat, aplikasi biasa akan terus memanggil layanan jarak jauh tersebut.

Aplikasi tidak tahu apakah layanan jarak jauh itu sehat atau tidak dan utas baru muncul setiap kali ada permintaan masuk. Ini akan menyebabkan utas pada server yang sudah bermasalah untuk digunakan.

We don't want this to happen as we need these threads for other remote calls or processes running on our server and we also want to avoid CPU utilization spiking up.

Let's see how to set the thread pool size in HystrixCommand:

@Test public void givenSvcTimeoutOf500AndExecTimeoutOf10000AndThreadPool_whenRemoteSvcExecuted _thenReturnSuccess() throws InterruptedException { HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupThreadPool")); HystrixCommandProperties.Setter commandProperties = HystrixCommandProperties.Setter(); commandProperties.withExecutionTimeoutInMilliseconds(10_000); config.andCommandPropertiesDefaults(commandProperties); config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() .withMaxQueueSize(10) .withCoreSize(3) .withQueueSizeRejectionThreshold(10)); assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(), equalTo("Success")); }

In the above test, we are setting the maximum queue size, the core queue size and the queue rejection size. Hystrix will start rejecting the requests when the maximum number of threads have reached 10 and the task queue has reached a size of 10.

The core size is the number of threads that always stay alive in the thread pool.

5.3. Defensive Programming With Short Circuit Breaker Pattern

However, there is still an improvement that we can make to remote service calls.

Let's consider the case that the remote service has started failing.

We don't want to keep firing off requests at it and waste resources. We would ideally want to stop making requests for a certain amount of time in order to give the service time to recover before then resuming requests. This is what is called the Short Circuit Breaker pattern.

Let's see how Hystrix implements this pattern:

@Test public void givenCircuitBreakerSetup_whenRemoteSvcCmdExecuted_thenReturnSuccess() throws InterruptedException { HystrixCommand.Setter config = HystrixCommand .Setter .withGroupKey(HystrixCommandGroupKey.Factory.asKey("RemoteServiceGroupCircuitBreaker")); HystrixCommandProperties.Setter properties = HystrixCommandProperties.Setter(); properties.withExecutionTimeoutInMilliseconds(1000); properties.withCircuitBreakerSleepWindowInMilliseconds(4000); properties.withExecutionIsolationStrategy (HystrixCommandProperties.ExecutionIsolationStrategy.THREAD); properties.withCircuitBreakerEnabled(true); properties.withCircuitBreakerRequestVolumeThreshold(1); config.andCommandPropertiesDefaults(properties); config.andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.Setter() .withMaxQueueSize(1) .withCoreSize(1) .withQueueSizeRejectionThreshold(1)); assertThat(this.invokeRemoteService(config, 10_000), equalTo(null)); assertThat(this.invokeRemoteService(config, 10_000), equalTo(null)); assertThat(this.invokeRemoteService(config, 10_000), equalTo(null)); Thread.sleep(5000); assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(), equalTo("Success")); assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(), equalTo("Success")); assertThat(new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(500)).execute(), equalTo("Success")); }
public String invokeRemoteService(HystrixCommand.Setter config, int timeout) throws InterruptedException { String response = null; try { response = new RemoteServiceTestCommand(config, new RemoteServiceTestSimulator(timeout)).execute(); } catch (HystrixRuntimeException ex) { System.out.println("ex = " + ex); } return response; }

In the above test we have set different circuit breaker properties. The most important ones are:

  • The CircuitBreakerSleepWindow which is set to 4,000 ms. This configures the circuit breaker window and defines the time interval after which the request to the remote service will be resumed
  • The CircuitBreakerRequestVolumeThreshold which is set to 1 and defines the minimum number of requests needed before the failure rate will be considered

With the above settings in place, our HystrixCommand will now trip open after two failed request. The third request will not even hit the remote service even though we have set the service delay to be 500 ms, Hystrix will short circuit and our method will return null as the response.

We will subsequently add a Thread.sleep(5000) in order to cross the limit of the sleep window that we have set. This will cause Hystrix to close the circuit and the subsequent requests will flow through successfully.

6. Conclusion

Singkatnya, Hystrix dirancang untuk:

  1. Memberikan perlindungan dan kontrol atas kegagalan dan latensi dari layanan yang biasanya diakses melalui jaringan
  2. Hentikan cascading kegagalan akibat beberapa layanan tidak aktif
  3. Gagal dengan cepat dan pulih dengan cepat
  4. Turunkan dengan anggun jika memungkinkan
  5. Pemantauan waktu nyata dan peringatan dari pusat komando tentang kegagalan

Di posting selanjutnya kita akan melihat bagaimana menggabungkan manfaat Hystrix dengan kerangka Spring.

Kode proyek lengkap dan semua contoh dapat ditemukan di proyek github.