1. Gambaran keseluruhan
Sistem diedarkan khas terdiri daripada banyak perkhidmatan yang bekerjasama.
Perkhidmatan ini terdedah kepada kegagalan atau tindak balas yang tertangguh. Sekiranya perkhidmatan gagal, ia mungkin memberi kesan kepada perkhidmatan lain yang mempengaruhi prestasi dan mungkin membuat bahagian aplikasi yang lain tidak dapat diakses atau dalam keadaan terburuk menurunkan keseluruhan aplikasi.
Sudah tentu, ada penyelesaian yang dapat membantu menjadikan aplikasi tahan dan tahan terhadap kesalahan - satu kerangka seperti Hystrix.
Perpustakaan rangka Hystrix membantu mengawal interaksi antara perkhidmatan dengan memberikan toleransi kesalahan dan toleransi laten. Ini meningkatkan daya tahan keseluruhan sistem dengan mengasingkan perkhidmatan yang gagal dan menghentikan kesan kegagalan yang berlanjutan.
Dalam siri catatan ini kita akan mulakan dengan melihat bagaimana Hystrix menyelamatkan diri apabila perkhidmatan atau sistem gagal dan apa yang dapat dicapai oleh Hystrix dalam keadaan seperti ini.
2. Contoh Ringkas
Cara Hystrix memberikan toleransi kesalahan dan kependaman adalah mengasingkan dan membungkus panggilan ke perkhidmatan jarak jauh.
Dalam contoh ini mudah kita balut panggilan dalam jangka () kaedah yang 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 melaksanakan panggilan seperti berikut:
@Test public void givenInputBobAndDefaultSettings_whenCommandExecuted_thenReturnHelloBob(){ assertThat(new CommandHelloWorld("Bob").execute(), equalTo("Hello Bob!")); }
3. Persediaan Maven
Untuk menggunakan Hystrix dalam projek Maven, kita perlu mempunyai pergantungan inti hystrix-core dan rxjava dari Netflix dalam projek pom.xml :
com.netflix.hystrix hystrix-core 1.5.4
Versi terbaru boleh didapati di sini.
com.netflix.rxjava rxjava-core 0.20.7
Versi terbaru perpustakaan ini selalu terdapat di sini.
4. Menyiapkan Perkhidmatan Jauh
Mari mulakan dengan meniru contoh dunia nyata.
Dalam contoh di bawah , kelas RemoteServiceTestSimulator mewakili perkhidmatan pada pelayan jauh. Ini mempunyai kaedah yang bertindak balas dengan mesej setelah jangka waktu tertentu. Kita dapat membayangkan bahawa penantian ini adalah simulasi proses yang memakan masa di sistem jauh yang mengakibatkan tindak balas yang tertunda terhadap perkhidmatan panggilan:
class RemoteServiceTestSimulator { private long wait; RemoteServiceTestSimulator(long wait) throws InterruptedException { this.wait = wait; } String execute() throws InterruptedException { Thread.sleep(wait); return "Success"; } }
Dan inilah klien contoh kami yang memanggil RemoteServiceTestSimulator .
Panggilan kepada perkhidmatan adalah terpencil dan dibungkus dalam jangka () kaedah yang HystrixCommand. Ini adalah pembungkus yang memberikan ketahanan yang kita sentuh 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 dijalankan dengan memanggil kaedah eksekusi () pada contoh objek RemoteServiceTestCommand .
Ujian 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 cara membungkus panggilan perkhidmatan jarak jauh di objek HystrixCommand . Pada bahagian di bawah ini mari kita lihat bagaimana menangani situasi ketika perkhidmatan jarak jauh mulai merosot.
5. Bekerja Dengan Perkhidmatan Jauh dan Pengaturcaraan Defensif
5.1. Pengaturcaraan Defensif Dengan Timeout
Ini adalah amalan pengaturcaraan umum untuk menetapkan waktu tunggu untuk panggilan ke perkhidmatan jauh.
Mari mulakan dengan melihat bagaimana menetapkan waktu tamat di HystrixCommand dan bagaimana ia membantu dengan litar pintas:
@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 ujian di atas, kami menunda tindak balas perkhidmatan dengan menetapkan batas waktu hingga 500 ms. Kami juga menetapkan batas waktu pelaksanaan pada HystrixCommand menjadi 10,000 ms, sehingga memberikan masa yang cukup untuk perkhidmatan jarak jauh untuk bertindak balas.
Sekarang mari kita lihat apa yang berlaku apabila tamat waktu pelaksanaan kurang daripada panggilan tamat perkhidmatan:
@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 kita menurunkan bar dan menetapkan batas waktu pelaksanaan hingga 5,000 ms.
Kami menjangkakan perkhidmatan akan bertindak balas dalam masa 5,000 ms, sedangkan perkhidmatan telah menetapkan untuk bertindak balas setelah 15,000 ms. Sekiranya anda menyedari ketika anda menjalankan ujian, ujian akan keluar setelah 5,000 ms dan bukannya menunggu 15,000 ms dan akan membuang HystrixRuntimeException.
Ini menunjukkan bagaimana Hystrix tidak menunggu lebih lama daripada masa tamat yang dikonfigurasi untuk tindak balas. Ini membantu menjadikan sistem yang dilindungi oleh Hystrix lebih responsif.
Pada bahagian di bawah ini, kita akan melihat bagaimana menetapkan ukuran kolam benang yang mencegah benang habis dan kita akan membincangkan faedahnya.
5.2. Pengaturcaraan Defensif Dengan Kolam Thread Terhad
Menetapkan waktu tamat untuk panggilan perkhidmatan tidak menyelesaikan semua masalah yang berkaitan dengan perkhidmatan jarak jauh.
Apabila perkhidmatan jarak jauh mula bertindak balas dengan perlahan, aplikasi khas akan terus memanggil perkhidmatan jarak jauh itu.
Aplikasi ini tidak tahu sama ada perkhidmatan jarak jauh sihat atau tidak dan benang baru dilancarkan setiap kali permintaan masuk. Ini akan menyebabkan utas pada pelayan yang sudah sukar 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
Ringkasnya Hystrix dirancang untuk:
- Memberi perlindungan dan kawalan terhadap kegagalan dan kependaman dari perkhidmatan yang biasanya diakses melalui rangkaian
- Berhenti merangkumi kegagalan akibat beberapa perkhidmatan tergendala
- Gagal cepat pulih dan cepat
- Turunkan dengan anggun di mana mungkin
- Pemantauan masa nyata dan amaran pusat arahan mengenai kegagalan
Dalam posting seterusnya kita akan melihat bagaimana menggabungkan faedah Hystrix dengan rangka Spring.
Kod projek lengkap dan semua contoh boleh didapati di projek github.