paint-brush
Dari Paris ke Berlin: Cara Membuat Pemutus Litar di Kotlinoleh@jesperancinha
428 bacaan
428 bacaan

Dari Paris ke Berlin: Cara Membuat Pemutus Litar di Kotlin

oleh João Esperancinha26m2025/01/23
Read on Terminal Reader

Terlalu panjang; Untuk membaca

Pemutus litar digunakan pada masa kini untuk mengelakkan jumlah permintaan yang berlebihan dilakukan kepada satu atau perkhidmatan tidak responsif yang lain. Apabila contohnya perkhidmatan dimatikan, atas sebab apa pun, pemutus litar harus seperti namanya, memutuskan litar. Dalam artikel ini, kita akan melihat tiga cara pemrograman untuk melaksanakan pemutus litar menggunakan pelaksanaan berasaskan AOP.
featured image - Dari Paris ke Berlin: Cara Membuat Pemutus Litar di Kotlin
João Esperancinha HackerNoon profile picture
0-item

1. Pengenalan

Circuit-breakers digunakan pada masa kini untuk mengelakkan jumlah permintaan yang berlebihan dilakukan kepada satu atau perkhidmatan tidak responsif yang lain. Apabila, sebagai contoh, perkhidmatan dimatikan, atas apa jua sebab, pemutus litar harus, seperti yang dinyatakan dalam namanya, memutuskan litar. Dalam erti kata lain, dalam keadaan di mana 1 juta permintaan sedang dilakukan pada masa yang sama untuk mendapatkan keputusan perlumbaan kuda, kami mahu permintaan ini dialihkan ke perkhidmatan lain yang boleh menangani perkara ini.


Perkhidmatan lain ini boleh menjadi replikanya, atau ia boleh digunakan semata-mata untuk melaksanakan operasi lain mengenai kegagalan perkhidmatan asal. Matlamat akhir adalah sentiasa untuk memecahkan panggilan yang tidak perlu dan menjalankan aliran di tempat lain. Pada 2017 , Michael Nygard membawa corak reka bentuk Pemutus Litar ke hadapan dalam reka bentuk pembangunan perisian. Ini dilakukan dalam penerbitannya Release It!: Design and Deploy Production-Ready Software (Pragmatic Programmers) 1st Edition.


Corak reka bentuk circuit breaker diilhamkan oleh litar elektronik dan elektrik sebenar. Walau bagaimanapun, dari segi konsep umum, idea pemutus litar sebenarnya telah dicipta pada 1879 oleh Thomas Edison . Sama seperti pada masa itu, arus yang melimpah perlu dikendalikan. Dalam istilah yang sangat, sangat mudah, inilah yang kami gunakan untuk seni bina perisian dalam kes ini. Matlamat utama adalah untuk memastikan sistem itu cukup berdaya tahan. Sejauh mana ia mesti berdaya tahan dan betapa fault-tolerant itu sebenarnya di mata jurutera yang bertanggungjawab untuk memudahkan pelaksanaan corak ini. Idea di sebaliknya ialah dalam keadaan tertentu kita mungkin ingin mengubah hala aliran permintaan tertentu kepada aliran lain yang lebih tersedia di belakang endpoint yang sama.


Katakan kita mahu melaksanakan permintaan daripada A kepada B . Dari semasa ke semasa, B gagal dan C sentiasa tersedia. Jika B gagal secara rawak, kami ingin mencapai C untuk menyediakan perkhidmatan kami sepenuhnya. Walau bagaimanapun, untuk membuat permintaan kembali kepada B , kami ingin memastikan bahawa B tidak gagal lagi. Kami kemudiannya boleh mengkonfigurasi sistem kami untuk membuat permintaan rawak kepada B dan hanya kembali sepenuhnya ke B apabila kadar kegagalan telah menurun pada tahap tertentu.


Kami mungkin ingin membuat permintaan C atas kesilapan tetapi juga pada kependaman. Jika B sangat lambat, kami mungkin mahu menghantar semula semua permintaan kepada C . Terdapat banyak konfigurasi lain yang mungkin, seperti cuba mencapai C jika selepas beberapa percubaan tertentu, jenis permintaan, utas serentak dan banyak pilihan lain Ini juga dikenali sebagai short-circuiting , dan kebanyakannya adalah langkah sementara.

Mesin Negeri


Untuk memahami lebih lanjut pengetahuan kita tentang apa sebenarnya pemutus litar, kita perlu memahami bahawa pemutus litar berfungsi sebagai entiti dalam aplikasi kita. Terdapat tiga status utama untuk pemutus litar. Ia boleh ditutup, terbuka atau separuh terbuka. Status ditutup bermakna aliran aplikasi kami berjalan seperti biasa. Kami boleh membuat permintaan kepada perkhidmatan A dengan selamat, dengan mengetahui bahawa semua permintaan akan pergi ke perkhidmatan B. Keadaan terbuka bermakna semua permintaan untuk perkhidmatan B akan gagal. Peraturan yang telah kami tentukan untuk mewakili kegagalan telah berlaku dan kami tidak lagi mencapai perkhidmatan B. Dalam kes ini, pengecualian sentiasa dikembalikan. Keadaan half-open ialah apabila pemutus litar kami diarahkan untuk melakukan ujian pada perkhidmatan B untuk melihat sama ada ia beroperasi semula.


Setiap permintaan yang berjaya dikendalikan seperti biasa, tetapi ia akan terus membuat permintaan kepada C. Jika B berkelakuan seperti yang diharapkan mengikut peraturan pengesahan yang telah kami tetapkan, pemutus litar kami akan kembali kepada keadaan tertutup dan perkhidmatan A akan mula membuat permintaan secara eksklusif kepada perkhidmatan B. Dalam kebanyakan aplikasi, pemutus litar mengikut corak reka bentuk penghias. Walau bagaimanapun, ia boleh dilaksanakan secara manual, dan kami akan melihat tiga cara program untuk melaksanakan pemutus litar dan akhirnya menggunakan pelaksanaan berasaskan AOP. Kod ini tersedia di GitHub .

Gambar rajah

2. Pemeriksaan Kereta

Dalam titik terakhir artikel ini, kita akan melihat permainan perlumbaan kereta. Walau bagaimanapun, sebelum kita sampai ke sana, saya ingin membimbing anda melalui beberapa aspek membina aplikasi yang berjalan dengan circuit breaker .

2.1. Kystrix (dari-paris-ke-berlin-kystrix-runnable-app)

Kystrixs , sebagai DSL kecil , adalah perpustakaan yang menakjubkan yang dicipta dan dicipta oleh Johan Haleby . Perpustakaan menyediakan banyak kemungkinan, termasuk penyepaduan dengan Spring dan Spring WebFlux. Adalah menarik untuk melihatnya dan bermain-main sedikit:

 <dependency> <groupId>se.haleby.kystrix</groupId> <artifactId>kystrix-core</artifactId> </dependency> <dependency> <groupId>se.haleby.kystrix</groupId> <artifactId>kystrix-spring</artifactId> </dependency>


Saya telah mencipta contoh, dan ia terletak pada modul from-paris-to-berlin-kystrix-runnable-app pada GitHub . Pertama, kita lihat pada kod:

 @GetMapping("/{id}") private fun getCars(@PathVariable id: Int): Mono<Car> { return if (id == 1) Mono.just(Car("Jaguar")) else { hystrixObservableCommand<Car> { groupKey("Test2") commandKey("Test-Command2") monoCommand { webClient.get().uri("/cars/carros/1").retrieve().bodyToMono<Car>() .delayElement(Duration.ofSeconds(1)) } commandProperties { withRequestLogEnabled(true) withExecutionTimeoutInMilliseconds(5000) withExecutionTimeoutEnabled(true) withFallbackEnabled(true) withCircuitBreakerEnabled(false) withCircuitBreakerForceClosed(true) } fallback { Observable.just(Car("Tank1")) } }.toMono() } }

Kod ini mewakili arahan 2 contoh. Semak kod untuk arahan 1. Apa yang berlaku di sini ialah kita mentakrifkan arahan yang kita mahu dengan monoCommand . Di sini, kami menentukan kaedah yang perlu kami panggil. Dalam commandProperties , kami mentakrifkan peraturan yang menjadikan keadaan perubahan circuit-breaker dibuka. Kami menangguhkan panggilan kami secara eksplisit agar panggilan itu bertahan tepat 1 saat.


Pada masa yang sama, kami menentukan tamat masa 5000 milisaat. Ini bermakna kita tidak akan mencapai tamat masa. Dalam contoh ini, kita boleh membuat panggilan dengan Id . Oleh kerana ini hanyalah ujian, kami menganggap Id=1 , sebagai Id kereta, Jaguar tanpa memerlukan circuit-breaker . Ini juga bermakna bahawa kita tidak akan mendapat Tank1 seperti yang ditakrifkan dalam kaedah sandaran. Jika anda masih belum perasan, lihat dengan teliti kaedah sandaran. Kaedah ini menggunakan Observable . Walaupun WebFlux dilaksanakan mengikut corak reka bentuk Observable , Flux bukanlah Observable.


Walau bagaimanapun, hystrix menyokong kedua-duanya. Sila jalankan aplikasi dan buka penyemak imbas anda di http://localhost:8080/cars/2 , untuk mengesahkan ini. Adalah penting untuk memahami bahawa jika anda mula membuat panggilan sangat awal dalam permulaan Spring Boot, anda akhirnya mungkin mendapat mesej Tank1. Ini kerana kelewatan permulaan boleh melepasi 5 saat dengan sangat mudah bergantung pada cara anda menjalankan proses ini. Dalam contoh kedua, kita akan membuat litar pintas contoh kita ke Tangki 2:

 @GetMapping("/timeout/{id}") private fun getCarsTimeout(@PathVariable id: Int): Mono<Car> { return if (id == 1) Mono.just(Car("Jaguar")) else { hystrixObservableCommand<Car> { groupKey("Test3") commandKey("Test-Command3") monoCommand { webClient.get().uri("/cars/carros/1").retrieve().bodyToMono<Car>() .delayElement(Duration.ofSeconds(1)) } commandProperties { withRequestLogEnabled(true) withExecutionIsolationThreadInterruptOnTimeout(true) withExecutionTimeoutInMilliseconds(500) withExecutionTimeoutEnabled(true) withFallbackEnabled(true) withCircuitBreakerEnabled(false) withCircuitBreakerForceClosed(true) } fallback { Observable.just(Car("Tank2")) } }.toMono() } }

Dalam contoh ini, circuit-breaker kami akan masuk ke Tangki pengembalian hujung keadaan terbuka 2 sebagai tindak balas. Ini kerana kami juga menyebabkan kelewatan 1s di sini, tetapi kami menyatakan bahawa keadaan pemutus litar kami tercetus selepas tanda 500ms. Jika anda tahu cara hystrix berfungsi, anda akan dapati bahawa kystrix bukanlah sesuatu yang berbeza untuk bergerak ke hadapan. Apa yang tidak disediakan oleh hystrix untuk saya pada ketika ini, ialah cara yang lancar dan mudah untuk menyediakan apa yang saya perlukan untuk membuat permainan. Kystrix nampaknya berfungsi berdasarkan pelanggan. Ini bermakna kami perlu mengisytiharkan kod kami sebelum membuat permintaan kepada perkhidmatan di belakang perkhidmatan utama kami.

2.2. Ketahanan4J

Resilience4J nampaknya dirujuk oleh ramai orang sebagai pelaksanaan pemutus litar yang sangat lengkap. Percubaan pertama saya meneroka beberapa aspek penting pemutus litar. Iaitu, saya ingin melihat pemutus litar yang boleh berfungsi berdasarkan tamat masa dan kekerapan permintaan yang berjaya. Resilience4J membolehkan pelbagai jenis modul short-circuiting dikonfigurasikan. Ini dipisahkan kepada 6 kategori berbeza: CircuitBreaker , Bulkhead , Ratelimiter , Retry dan Timelimiter . Semua ini juga nama corak reka bentuk. Modul CircuitBreaker menyediakan pelaksanaan lengkap corak ini.


Kami mempunyai banyak parameter yang boleh kami konfigurasikan, tetapi pada asasnya, modul CircuitBreaker membolehkan kami mengkonfigurasi perkara yang kami kenali sebagai gagal, berapa banyak permintaan yang kami benarkan dalam keadaan separuh terbuka dan tetingkap gelongsor, yang boleh dikonfigurasikan mengikut masa atau count, di mana kami menyimpan kiraan permintaan yang berlaku dalam keadaan tertutup. Ini penting untuk mengira kekerapan ralat. Pada asasnya, kami boleh mengatakan bahawa modul CircuitBreaker ini akan membantu kami dengan kadar permintaan, tetapi itu tidak semestinya benar.


Ia bergantung kepada cara anda mentafsirnya. Nampaknya cara yang lebih baik untuk menganggapnya sebagai cara untuk menangani kesilapan. Sama ada ia datang daripada tamat masa atau pengecualian, di sinilah ia ditangani dan cara permintaan boleh diubah hala dengan lancar ke tempat lain Modul Bulkhead direka bentuk untuk menangani permintaan serentak. Ia bukan penghad kadar.


Sebaliknya, ia melaksanakan corak reka bentuk Bulkhead , yang digunakan untuk menghalang terlalu banyak pemprosesan daripada berlaku dalam satu titik akhir tunggal. Dalam kes ini, Bulkhead membenarkan kami memproses permintaan kami dengan cara ia dapat diedarkan merentas semua titik akhir yang tersedia. Nama Bulkhead berasal dari petak tertutup yang berbeza yang biasanya perlu dielakkan oleh kapal besar, sekiranya berlaku kemalangan, dan seperti dalam kes kapal, kita perlu menentukan bilangan benang yang akan tersedia dalam kumpulan benang dan masa pajakannya. .


Modul RateLimiter direka untuk mengendalikan kadar permintaan. Perbezaan antara ini dan modul Bulkhead adalah penting supaya kita mahu bertolak ansur dengan kadar sehingga satu tahap tertentu. Ini bermakna kita tidak perlu menyebabkan kegagalan untuk itu. Kami hanya katakan, dalam reka bentuk bahawa kami tidak bertolak ansur dengan kadar di atas nilai tertentu. Selain itu, kami boleh sama ada mengubah hala permintaan atau menahannya sehingga kebenaran untuk melaksanakan permintaan itu diberikan. Modul Retry mungkin adalah yang paling mudah untuk difahami kerana ia tidak mempunyai banyak persamaan dengan modul lain.


Kami pada asasnya mengisytiharkan secara jelas bilangan percubaan semula ke titik akhir tertentu, sehingga kami mencapai ambang yang ditetapkan kami. Modul Timelimiter boleh dilihat sebagai penyederhanaan modul CircuitBreaker kerana kedua-duanya berkongsi kemungkinan untuk mengkonfigurasi tamat masa. Walau bagaimanapun, Timelimiter tidak bergantung pada parameter lain seperti tetingkap gelongsor, dan ia juga tidak mempunyai pengiraan ambang kegagalan dalam binaan.


Jadi, jika kami benar-benar berminat untuk mengendalikan tamat masa semasa memanggil perkhidmatan tertentu, dan kami tidak mengambil kira kemungkinan kerosakan lain, maka kami mungkin lebih baik menggunakan Timelimiter .

2.2.1. Resilience4J dengan rangka kerja Kotlin dan No Spring (from-paris-to-berlin-resilience4j-runnable-app)

Dalam modul ini, saya telah memutuskan untuk menggunakan hanya perpustakaan resilience4j kotlin:

 <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-kotlin</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-retry</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-ratelimiter</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-timelimiter</artifactId> </dependency>


Pelaksanaan ini tersedia pada repo di GitHub. Mula-mula kita akan melihat corak TimeLimiter :

 var timeLimiterConfig: TimeLimiterConfig = TimeLimiterConfig.custom() .timeoutDuration(Duration.ofMillis(100)) .build() var timeLimiter: TimeLimiter = TimeLimiter.of("backendName", timeLimiterConfig) private suspend fun getPublicCar(): Car { return timeLimiter.decorateSuspendFunction { getPrivateCar() }.let { suspendFunction -> try { suspendFunction() } catch (exception: Exception) { Car("Opel Corsa") } } } private suspend fun getPrivateCar(): Car { delay(10000) return Car("Lancya") }

Dalam kes ini, kami menghiasi function getPrivateCar kami dengan fungsi TimeLimiter menggunakan fungsi decorateSuspendFunction . Perkara ini akan menyebabkan tamat masa, jika fungsi yang kita panggil mengambil masa terlalu lama, kita akan mendapat Opel Corsa dan bukannya Lancya. Untuk mencuba ini, kami hanya boleh menjalankan aplikasi dan membuka http://localhost:8080/cars/timelimiter/normal/1 .


Melihat ke dalam pelaksanaan, kami melihat bahawa kami tidak boleh mendapatkan Lancya . Dan ini kerana kami sengaja menunggu 10s sebelum kami mengembalikannya semula. TimeLimiter kami mempunyai tamat masa yang jauh lebih rendah jadi ini tidak akan berjaya. TimeLimiter agak mudah difahami. A CircuitBreaker , sebaliknya, boleh menjadi cerita yang berbeza. Ini adalah contoh bagaimana ia boleh dilakukan:

 val circuitBreakerConfig = CircuitBreakerConfig.custom() .failureRateThreshold(20f) .slowCallRateThreshold(50f) .slowCallDurationThreshold(Duration.ofMillis(1000)) .waitDurationInOpenState(Duration.ofMillis(1000)) .maxWaitDurationInHalfOpenState(Duration.ofMillis(1000)) .permittedNumberOfCallsInHalfOpenState(500) .minimumNumberOfCalls(2) .slidingWindowSize(2) .slidingWindowType(COUNT_BASED) .build() val circuitBreaker = CircuitBreakerRegistry.of(circuitBreakerConfig).circuitBreaker("TEST") private suspend fun getPublicCar(id: Long): Car { return circuitBreaker.decorateSuspendFunction { getPrivateCar(id) }.let { suspendFunction -> try { suspendFunction() } catch (exception: Exception) { Car("Opel Corsa") } } } private fun getPrivateCar(id: Long): Car { if (id == 2L) { throw RuntimeException() } return Car("Lancya") }

Dalam kes ini, kami mengatakan bahawa kami mahu pemutus litar kami menutup litar apabila kadar kegagalan lebih rendah daripada 20% dengan harta itu. Panggilan perlahan juga akan mempunyai ambang, tetapi dalam kes ini, ia akan menjadi kurang daripada 50%. Kami mengatakan bahawa panggilan perlahan mesti bertahan lebih lama daripada 1 untuk dianggap sebagai satu. Kami juga menyatakan bahawa tempoh keadaan separuh terbuka hendaklah 1s. Ini bermakna, dalam amalan, kita sama ada akan mempunyai keadaan terbuka, keadaan separuh terbuka, atau keadaan tertutup.


Kami juga mengatakan bahawa kami membenarkan maksimum 500 permintaan keadaan separuh terbuka. Untuk pengiraan ralat, pemutus litar perlu tahu pada tanda mana ia akan melakukannya. Ini penting untuk menentukan masa untuk menutup litar. Kami mengatakan bahawa 2 permintaan akan diperlukan untuk pengiraan ini, dengan sifat minimumNumberOfCalls . Ingat bahawa separuh terbuka ialah apabila kita akan terus mencuba untuk menutup litar jika permintaan mencapai ambang selamat gagal?


Dalam konfigurasi ini, ini bermakna kita perlu membuat sekurang-kurangnya 2 permintaan, dalam tetingkap gelongsor, untuk mengira kekerapan ralat dan menentukan sama ada untuk kembali kepada keadaan tertutup atau tidak. Ini ialah bacaan tepat bagi semua pembolehubah yang telah kami konfigurasikan. Secara umum, maksud ini ialah aplikasi kami mungkin akan membuat beberapa panggilan ke perkhidmatan alternatif, sekiranya ada, ia tidak akan bertukar dengan mudah daripada keadaan terbuka kepada tertutup memandangkan kadar kejayaan untuk melakukannya mestilah 80% dalam tempoh separuh -keadaan terbuka dan tamat masa untuk keadaan terbuka mesti telah berlaku.


Terdapat banyak cara untuk menentukan tamat masa sedemikian. Dalam contoh kami, kami mengatakan bahawa maxDurationInHalfOpenState ialah 1s. Ini bermakna bahawa CircuitBreaker kami akan mengekalkan status terbuka, hanya jika semakan kami tidak memenuhi syarat keadaan tertutup atau jika tamat masa ini belum berlaku. Tingkah laku yang ditakrifkan dalam CircuitBreaker ini mungkin sukar untuk diikuti dan untuk diramalkan, semata-mata kerana masa henti tertentu, kadar dan ciri permintaan lain tidak mungkin untuk direplikasi dengan tepat, tetapi jika kami melakukan beberapa permintaan ke titik akhir ini, kami akan melihat bahawa tingkah laku yang diterangkan di atas sepadan dengan pengalaman kami.


Jadi, mari cuba dan lakukan beberapa permintaan ke titik akhir: http://localhost:8080/cars/circuit/1 dan http://localhost:8080/cars/circuit/2 . Berakhir dengan 1 ialah titik akhir untuk mendapatkan semula kereta yang berjaya, dan berakhir dengan 2 ialah titik akhir kegagalan mendapatkan kereta tertentu. Melihat pada kod, kita melihat bahawa apa-apa selain daripada 2 bermakna kita mendapat Lancya sebagai respons. A 2 , bermakna kami segera membuang pengecualian masa jalan, yang bermakna kami akhirnya mendapat Opel Corsa sebagai respons.


Jika kami hanya membuat permintaan untuk titik akhir 1 , kami akan terus melihat Lancya sebagai respons. Jika sistem mula gagal, iaitu apabila anda membuat permintaan untuk 2,\; anda akan melihat bahawa kembali ke Lancya tidak akan menjadi berterusan selepas beberapa ketika. System akan memaklumkan bahawa ia berada dalam keadaan Terbuka dan tiada lagi permintaan dibenarkan.

 2021-10-20 09:56:50.492 ERROR 34064 --- [ctor-http-nio-2] .fcbrrcCarControllerCircuitBreaker : io.github.resilience4j.circuitbreaker.CallNotPermittedException: CircuitBreaker 'TEST' is OPEN and does not permit further calls


Pemutus litar kami kemudiannya akan pergi ke keadaan separuh terbuka selepas permintaan berjaya, dan ini bermakna kami perlu melakukan beberapa permintaan kembali kepada 1 sebelum ia menjadi normal. Kami akan bertukar daripada Lancya kepada Opel Corsa beberapa kali sebelum kami mendapatkan Lancya semula. Kami mentakrifkan nombor ini sebagai 2. Ini adalah minimum untuk pengiraan ralat. Jika kita hanya menyebabkan satu gagal dan terus memanggil titik akhir tidak gagal, kita boleh mendapatkan gambaran yang lebih jelas tentang perkara yang berlaku:

 2021-10-20 11:53:29.058 ERROR 34090 --- [ctor-http-nio-4] .fcbrrcCarControllerCircuitBreaker : java.lang.RuntimeException 2021-10-20 11:53:41.102 ERROR 34090 --- [ctor-http-nio-4] .fcbrrcCarControllerCircuitBreaker : io.github.resilience4j.circuitbreaker.CallNotPermittedException: CircuitBreaker 'TEST' is OPEN and does not permit further calls

Mesej status terbuka ini, walaupun benar, berlaku selepas saya membuat 2 permintaan ke titik akhir tidak gagal. Inilah sebabnya negeri itu dikatakan separuh terbuka.

2.2.2. Resilience4J dengan Spring Boot dan No AOP (from-paris-to-berlin-resilience4j-spring-app)

Dalam segmen sebelumnya, kami melihat cara melaksanakannya dengan cara yang sangat terprogram, tanpa menggunakan sebarang teknologi Spring. Kami memang menggunakan Spring tetapi hanya untuk menyediakan jenis perkhidmatan WebFlux MVC . Selanjutnya, kami tidak mengubah apa-apa tentang perkhidmatan itu sendiri. Dalam aplikasi berikut, kami akan meneroka perpustakaan berikut:

 <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-spring-boot2</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-all</artifactId> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-reactor</artifactId> </dependency>


Apabila melihat bagaimana kod itu dilakukan, kita dapat melihat perbezaan yang agak besar:

 @RestController @RequestMapping("/cars") class CarController( private val carService: CarService, timeLimiterRegistry: TimeLimiterRegistry, circuitBreakerRegistry: CircuitBreakerRegistry, bulkheadRegistry: BulkheadRegistry ) { private var timeLimiter: TimeLimiter = timeLimiterRegistry.timeLimiter(CARS) private var circuitBreaker = circuitBreakerRegistry.circuitBreaker(CARS) private var bulkhead = bulkheadRegistry.bulkhead(CARS) @GetMapping("/{id}") private fun getCars(@PathVariable id: Int): Mono<Car> { return carService.getCar() .transform(TimeLimiterOperator.of(timeLimiter)) .transform(CircuitBreakerOperator.of(circuitBreaker)) .transform(BulkheadOperator.of(bulkhead)) .onErrorResume(TimeoutException::class.java, ::fallback) } @GetMapping("/test/{id}") private fun getCarsTest(@PathVariable id: Int): Mono<Car> { return carService.getCar() .transform(TimeLimiterOperator.of(timeLimiter)) .transform(CircuitBreakerOperator.of(circuitBreaker)) .transform(BulkheadOperator.of(bulkhead)) .onErrorResume(TimeoutException::class.java, ::fallback) } @GetMapping("/carros/{id}") private fun getCarros(@PathVariable id: Long): Mono<Car> { return Mono.just(Car("Laborghini")) } private fun fallback(ex: Throwable): Mono<Car> { return Mono.just(Car("Rolls Royce")) } }

Dalam contoh ini dan yang berikut, kita akan melihat kebanyakannya pada sifat tamat masa. Selok-belok CircuitBreaker sendiri kurang relevan kerana ini adalah artikel pengenalan. Apa yang penting untuk disedari di sini ialah betapa mudahnya kita boleh melaksanakan perkara ini dengan penghias yang disediakan untuk Spring oleh Resilience4J . Walaupun masih dalam fesyen programmatical , kami boleh menghiasi penerbit awal kami dengan mudah, yang kami dapat daripada carService.getCar() , dengan jenis short-circuit yang kami mahukan.


Dalam contoh ini, kami mendaftarkan TimeLiter , BulkHead dan CircuitBreaker . Akhir sekali, kami mentakrifkan fungsi sandaran untuk dicetuskan sebaik sahaja TimeoutException telah berlaku. Apa yang masih perlu kita lihat ialah bagaimana semua ini dikonfigurasikan. Kami mengkonfigurasi Resilience4J dalam Spring sama seperti mana-mana modul boleh dikonfigurasikan lain. Kami menggunakan application.yml :

 resilience4j.circuitbreaker: configs: default: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 5 permittedNumberOfCallsInHalfOpenState: 3 automaticTransitionFromOpenToHalfOpenEnabled: true waitDurationInOpenState: 5s failureRateThreshold: 50 eventConsumerBufferSize: 10 recordExceptions: - org.springframework.web.client.HttpServerErrorException - java.util.concurrent.TimeoutException - java.io.IOException ignoreExceptions: # - io.github.robwin.exception.BusinessException shared: slidingWindowSize: 100 permittedNumberOfCallsInHalfOpenState: 30 waitDurationInOpenState: 1s failureRateThreshold: 50 eventConsumerBufferSize: 10 ignoreExceptions: # - io.github.robwin.exception.BusinessException instances: cars: baseConfig: default roads: registerHealthIndicator: true slidingWindowSize: 10 minimumNumberOfCalls: 10 permittedNumberOfCallsInHalfOpenState: 3 waitDurationInOpenState: 5s failureRateThreshold: 50 eventConsumerBufferSize: 10 # recordFailurePredicate: io.github.robwin.exception.RecordFailurePredicate resilience4j.retry: configs: default: maxAttempts: 3 waitDuration: 100 retryExceptions: - org.springframework.web.client.HttpServerErrorException - java.util.concurrent.TimeoutException - java.io.IOException ignoreExceptions: # - io.github.robwin.exception.BusinessException instances: cars: baseConfig: default roads: baseConfig: default resilience4j.bulkhead: configs: default: maxConcurrentCalls: 100 instances: cars: maxConcurrentCalls: 10 roads: maxWaitDuration: 10ms maxConcurrentCalls: 20 resilience4j.thread-pool-bulkhead: configs: default: maxThreadPoolSize: 4 coreThreadPoolSize: 2 queueCapacity: 2 instances: cars: baseConfig: default roads: maxThreadPoolSize: 1 coreThreadPoolSize: 1 queueCapacity: 1 resilience4j.ratelimiter: configs: default: registerHealthIndicator: false limitForPeriod: 10 limitRefreshPeriod: 1s timeoutDuration: 0 eventConsumerBufferSize: 100 instances: cars: baseConfig: default roads: limitForPeriod: 6 limitRefreshPeriod: 500ms timeoutDuration: 3s resilience4j.timelimiter: configs: default: cancelRunningFuture: false timeoutDuration: 2s instances: cars: baseConfig: default roads: baseConfig: default

Fail ini ialah fail contoh yang diambil dari repo mereka dan diubah suai kepada contoh saya dengan sewajarnya. Seperti yang telah kita lihat sebelum ini, contoh pelbagai jenis limiters/short-circuit , mempunyai nama. Nama itu sangat penting jika anda mempunyai banyak pendaftaran yang berbeza dan pengehad yang berbeza. Untuk contoh kami, dan seperti yang dinyatakan sebelum ini, kami berminat dengan pembatas masa. Kita dapat lihat bahawa ia terhad kepada 2s. Jika kami melihat cara kami melaksanakan perkhidmatan, kami melihat bahawa kami memaksa tamat masa berlaku:

 @Component open class CarService { open fun getCar(): Mono<Car> { return Mono.just(Car("Fiat")).delayElement(Duration.ofSeconds(10)); } }

Mari mulakan aplikasi, dan dalam penyemak imbas, pergi ke: http://localhost:8080/cars/test/2 . Daripada mendapatkan Fiat , kami akan mendapat Rolls Royce . Beginilah cara kami menentukan tamat masa. Dengan cara yang sama, kita boleh membuat CircuitBreaker dengan mudah.

3. Kes

Sehingga kini, kami telah melihat tiga cara penting untuk melaksanakan CircuitBreakers dan pengehad yang berkaitan. Selanjutnya, kita akan melihat cara kegemaran saya untuk melaksanakan pemutus litar dengan melalui aplikasi yang saya buat, yang merupakan permainan yang sangat mudah di mana kita hanya mengklik pada petak untuk pergi dari Paris ke Berlin. Permainan dibuat untuk memahami bagaimana untuk melaksanakan. Ia tidak mengatakan banyak tentang di mana untuk melaksanakan ini. Ia hanyalah satu kes yang saya reka bentuk untuk berkongsi dengan anda pengetahuan.


Yang tahu-bila saya serahkan kepada anda untuk membuat keputusan kemudian. Pada asasnya, kami ingin mencipta beberapa kereta dan mewujudkan laluan ke Berlin. Di lokasi yang berbeza dalam laluan ini, kami akan sampai ke bandar di mana secara rawak, kami akan mencipta masalah. circuit breakers kami akan menentukan berapa lama kami perlu menunggu sebelum kami dibenarkan untuk meneruskan. Kereta lain tiada masalah, dan kami hanya perlu memilih laluan yang betul.


Kami dibenarkan menyemak jadual waktu di mana ia didaftarkan apabila masalah tertentu akan berlaku di bandar pada tanda minit tertentu. Tanda minit adalah sah dalam 0 kedudukan terindeksnya. Ini bermakna 2 bermakna every tanda 2, 12, 22, 32, 42, 52 minit pada jam akan sah untuk mencipta masalah ini. Masalah akan terdiri daripada 2 jenis: ERROR dan TIMEOUT . Kegagalan ralat akan memberi anda kelewatan selama 20 saat.


Tamat masa akan memberi anda kelewatan selama 50 saat. Untuk setiap perubahan bandar, semua orang perlu menunggu 10 saat. Sebelum menunggu, bagaimanapun, kereta itu sudah berada di pintu masuk bandar berikut apabila ini dilakukan dalam kaedah sandaran. Dalam kes ini, bandar seterusnya dipilih secara rawak.

Halaman Permainan

4. Pelaksanaan

Kami telah melihat sebelum ini, bagaimana untuk mengkonfigurasi pendaftaran resilience4j kami menggunakan application.yml . Setelah melakukan ini, mari kita lihat beberapa contoh cara menghiasi fungsi kita:

 @TimeLimiter(name = CarService.CARS, fallbackMethod = "reportTimeout") @CircuitBreaker(name = CarService.CARS, fallbackMethod = "reportError") @Bulkhead(name = CarService.CARS) open fun moveToCity(id: Long): Mono<RoadRace> { val myCar = roadRace.getMyCar() if (!myCar.isWaiting()) { val destination = myCar.location.forward.find { it.id == id } val blockage = destination?.blockageTimeTable?.find { it.minute.toString().last() == LocalDateTime.now().minute.toString().last() } blockage?.let { roadBlockTime -> when (roadBlockTime.blockageType) { BlockageType.TIMEOUT -> return Mono.just(roadRace).delayElement(Duration.ofSeconds(10)) BlockageType.ERROR -> return Mono.create { it.error(BlockageException()) } BlockageType.UNKNOWN -> return listOf(Mono.create { it.error(BlockageException()) }, Mono.just(roadRace).delayElement(Duration.ofSeconds(10))).random() else -> print("Nothing to do here!") } } destination?.let { myCar.delay(10) myCar.location = it myCar.formerLocations.add(myCar.location) } } return Mono.just(roadRace) } private fun reportError(exception: Exception): Mono<RoadRace> { logger.info("---- **** error reported") roadRace.getMyCar().delay(20L) roadRace.getMyCar().randomFw() roadRace.errorReports.add("Error reported! at ${LocalDateTime.now()}") return Mono.create { it.error(BlockageException()) } } private fun reportTimeout(exception: TimeoutException): Mono<RoadRace> { logger.info("---- **** timeout reported!") roadRace.getMyCar().delay(50L) roadRace.getMyCar().randomFw() roadRace.errorReports.add("Timeout reported! at ${LocalDateTime.now()}") return Mono.just(roadRace) }


Seperti yang kita dapat lihat, panggilan perkhidmatan asal dihiasi terus menggunakan anotasi! Ini hanya boleh dilakukan kerana kehadiran modul AOP dalam pakej:

 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>

AOP , atau Aspect Oriented Programming ialah satu lagi paradigma pengaturcaraan berdasarkan OOP . Ia dianggap sebagai pelengkap kepada OOP , dan ia adalah tepat berapa banyak anotasi yang berfungsi. Ini membolehkan untuk mencetuskan fungsi lain di sekeliling, sebelum, atau selepas kaedah asal dalam titik potong yang tepat. Seperti yang anda boleh lihat daripada contoh, kami sama ada menjana tamat masa atau ralat. BlockageException , dijana juga dalam kaedah sandaran. Ini tidak mewakili masalah. Kecuali respons.


Walau bagaimanapun, aplikasi berjalan pada WebSockets, dan oleh itu, ralat ini tidak akan dilihat dalam aplikasi. Setakat ini, ini adalah permainan. Saya melaksanakan ini dengan fokus untuk menunjukkan cara menggunakan anotasi boleh menjadikan hidup kita lebih mudah apabila melaksanakan aplikasi yang berdaya tahan. Kami bukan sahaja melaksanakan CircuitBreakers, tetapi juga teknologi lain, seperti WebSockets , Spring WebFlux , Docker , NGINX , typescript , dan beberapa yang lain. Ini semua telah dibuat untuk melihat bagaimana CircuitBreakers akan dimainkan dalam aplikasi. Jika anda ingin bermain dengan aplikasi ini, sila pergi ke akar projek dan jalankan:

 make docker-clean-build-start


Kemudian jalankan arahan ini:

 curl -X POST http://localhost:8080/api/fptb/blockage -H "Content-Type: application/json" --data '{"id":1,"name":"Paris","forward":[{"id":2,"name":"Soissons","forward":[{"id":5,"name":"Aken","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":6,"name":"Heerlen","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":7,"name":"Düren","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":3,"name":"Compiègne","forward":[{"id":5,"name":"Aken","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":6,"name":"Heerlen","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":7,"name":"Düren","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":4,"name":"Reims","forward":[{"id":5,"name":"Aken","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":6,"name":"Heerlen","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]},{"id":7,"name":"Düren","forward":[{"id":8,"name":"Berlin","forward":[],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]}],"blockageTimeTable":[]}'


Muatan permintaan ini dijana menggunakan modul from-paris-to-berlin-city-generator . Jika anda melihat ke dalam modul ini, anda akan melihat bahawa ia agak mudah untuk difahami dan anda boleh menjana peta anda sendiri untuk permainan! Akhir sekali, pergi ke http://localhost:9000 dan aplikasi anda sepatutnya berjalan! Sekarang, anda hanya perlu klik pada petak yang betul untuk bermain permainan. Cuma jangan klik pada yang merah jika anda mahu menang. Jika anda ingin melihat pemutus litar sedang beraksi, sila jalankan log aplikasi:

 docker logs from_paris_to_berlin_web -f

Dan secara eksplisit klik pada petak merah untuk menyebabkan kegagalan.

5. Bagaimana Kystrix dan Resilience4J Berbeza

Kystrix sesuai dalam kes di mana aplikasi anda kecil, dan anda ingin memastikan penggunaan DSL benar-benar rendah. Satu-satunya kelemahan nampaknya ialah ia tidak menawarkan cara mudah untuk menghiasi kaedah yang dipengaruhi oleh pemutus litar. Resilience4J nampaknya merupakan pilihan yang bagus untuk kerja perusahaan dengan pemutus litar. Ia menyediakan pengaturcaraan berasaskan anotasi, menggunakan semua faedah van AOP, dan modulnya diasingkan. Dari satu segi, ia juga boleh digunakan secara strategik untuk mata kritikal dalam aplikasi. Ia juga boleh digunakan sebagai rangka kerja lengkap untuk merangkumi banyak aspek aplikasi.

6. Kesimpulan

Tidak kira jenama yang kami pilih, matlamatnya adalah untuk sentiasa mempunyai aplikasi yang berdaya tahan. Dalam artikel ini, saya menunjukkan beberapa contoh bagaimana saya secara peribadi mengalami penyiasatan pemutus litar dan penemuan saya pada tahap yang sangat tinggi. Ini bermakna bahawa artikel ini benar-benar ditulis untuk orang yang ingin mengetahui apa itu pemutus litar dan apa yang boleh dilakukan oleh Limiters.


Kemungkinannya adalah tidak berkesudahan apabila memikirkan tentang menambah baik aplikasi kami dengan mekanisme ketahanan seperti circuit breakers . Corak ini membenarkan penalaan halus aplikasi untuk menggunakan lebih baik sumber yang ada yang kami ada. Kebanyakannya dalam awan, masih sangat penting untuk mengoptimumkan kos kami dan berapa banyak sumber yang sebenarnya perlu kami peruntukkan.


Mengkonfigurasi CircuitBreakers bukanlah tugas remeh kerana ia adalah untuk Limiters, dan kami benar-benar perlu memahami semua kemungkinan konfigurasi untuk mencapai tahap prestasi dan daya tahan yang optimum. Inilah sebabnya saya tidak mahu pergi secara terperinci dalam artikel pengenalan tentang pemutus litar ini.


Circuit-breakers boleh digunakan pada pelbagai jenis aplikasi. Kebanyakan aplikasi pemesejan dan penstriman memerlukan ini. Untuk aplikasi yang mengendalikan volum data yang besar yang juga perlu sangat tersedia, kami boleh dan harus melaksanakan beberapa bentuk pemutus litar. Kedai runcit dalam talian yang besar perlu mengendalikan sejumlah besar data setiap hari dan pada masa lalu, Hystrix digunakan secara meluas. Pada masa ini, kami nampaknya bergerak ke arah Resilience4J yang merangkumi lebih banyak daripada ini.

6. Rujukan

Terima kasih!

Saya harap anda menikmati artikel ini sama seperti saya membuatnya! Sila tinggalkan ulasan, ulasan atau sebarang maklum balas yang anda ingin berikan pada mana-mana sosial dalam pautan di bawah. Saya sangat berterima kasih jika anda ingin membantu saya membuat artikel ini lebih baik. Saya telah meletakkan semua kod sumber aplikasi ini pada GitHub. Terima kasih kerana membaca!

L O A D I N G
. . . comments & more!

About Author

João Esperancinha HackerNoon profile picture
João Esperancinha@jesperancinha
Software Engineer for 10+ Years, OCP11, Spring Professional 2020 and a Kong Champion

GANTUNG TANDA

ARTIKEL INI DIBENTANGKAN DALAM...