paint-brush
Paristik Berlinera: nola sortu etengailuak Kotlinenarabera@jesperancinha
420 irakurketak
420 irakurketak

Paristik Berlinera: nola sortu etengailuak Kotlinen

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

Luzeegia; Irakurri

Etengailuak erabiltzen dira gaur egun erantzuten ez duten zerbitzuren bati edo besteari gehiegizko eskaerak ekiditeko. Esaterako, zerbitzu bat itzaltzen denean, edozein arrazoirengatik, etengailu batek bere izenak dioen bezala, zirkuitua hautsi beharko luke. Artikulu honetan, AOPn oinarritutako inplementazio bat erabiliz etengailuak ezartzeko programatikoki hiru modu aztertuko ditugu.
featured image - Paristik Berlinera: nola sortu etengailuak Kotlinen
João Esperancinha HackerNoon profile picture
0-item

1. Sarrera

Circuit-breakers erabiltzen dira gaur egun erantzuten ez duten zerbitzuren bati edo besteari gehiegizko eskaerak ekiditeko. Esaterako, zerbitzu bat itzaltzen denean, edozein arrazoirengatik, etengailu batek, bere izenak dioen bezala, zirkuitua hautsi beharko luke. Hau da, zaldi lasterketa baten emaitzak lortzeko milioi bat eskaera aldi berean egiten diren egoeran, eskaera horiek hori kudeatu dezakeen beste zerbitzu batera bideratzea nahi dugu.


Beste zerbitzu hau haren erreplika izan daiteke, edo jatorrizko zerbitzuaren hutsegiteari buruzko beste eragiketa batzuk egiteko soilik erabil daiteke. Azken helburua beti da beharrezkoa ez diren deiak apurtzea eta fluxua beste nonbait egitea. 2017 , Michael Nygard Circuit Breaker diseinu eredua software garapenaren diseinuaren abangoardian jarri zuen. Hau bere argitalpenean egin zen Release It!: Design and Deploy Production-Ready Software (Pragmatic Programers) 1. Edizioa.


circuit breaker diseinuaren eredua benetako zirkuitu elektroniko eta elektrikoetan inspiratuta dago. Hala ere, kontzeptu orokor bati dagokionez, etengailuaren ideia benetan Thomas Edison asmatu zuen 1879 . Garai hartan bezala, gainezka dagoen korronte bat kudeatu behar da. Termino oso-oso sinpleetan, hau da software-arkitekturari aplikatzen dioguna kasu honetan. Helburu nagusia sistema nahikoa erresistentea dela ziurtatzea da. Zein erresistentzia izan behar duen eta zeinen fault-tolerant izan behar duen eredu hori ezartzea errazteaz arduratzen diren ingeniarien ustez. Horren atzean dagoen ideia da baldintza jakin batzuetan eskakizun jakin baten fluxua amaiera endpoint beraren atzean dagoen beste fluxu eskuragarriago batera erraz birbideratu nahi dugula.


Demagun A tik B eskaera bat egin nahi dugula. Noizean behin, B-k huts egiten du eta C beti dago eskuragarri. B ausaz huts egiten badu, C iritsi nahi dugu gure zerbitzua guztiz erabilgarri izan dadin. Hala ere, B ra itzultzeko eskaerak egiteko, ziurtatu nahi dugu B ez duela hainbeste huts egingo berriro. Ondoren, gure sistema konfiguratu dezakegu B-ri ausazko eskaerak egiteko eta B ra guztiz itzultzeko hutsegite-tasa maila jakin bat jaitsi denean.


Agian C eskaera egin nahi dugu errorean baina baita latentzian ere. B oso motela bada, baliteke eskaera guztiak C ra berriro bidali nahi izatea. Beste konfigurazio posible asko daude, hala nola short-circuiting C iristen saiatzea saiakera kopuru zehaztu baten ondoren, eskaera motak, aldibereko hariak eta beste hainbat aukera.

Estatuko Makina


Etengailu bat benetan zer den ezagutzen dugun gehiago ulertzeko, etenaldi batek gure aplikazioan entitate gisa funtzionatzen duela ulertu behar dugu. Hiru egoera nagusi daude etengailu baten kasuan. Itxita, irekia edo erdi irekia izan daiteke. Itxitako egoera batek gure aplikazio-fluxuak normal exekutatzen direla esan nahi du. Segurtasunez egin ditzakegu eskaerak A zerbitzura, jakinik eskaera guztiak B zerbitzura joango direla. Egoera ireki batek B zerbitzurako eskaera guztiek huts egingo dutela esan nahi du. Porrot bat irudikatzeko definitu ditugun arauak gertatu dira eta jada ez gara B zerbitzura iristen. Kasu honetan, salbuespen bat itzultzen da beti. half-open egoera da gure etengailuak B zerbitzuan probak egiteko agintzen dionean berriro martxan dagoen ikusteko.


Eskaera arrakastatsu bakoitza normaltasunez kudeatzen da, baina C-ri eskaerak egiten jarraituko du. B-k ezarri ditugun egiaztapen-arauen arabera espero bezala jokatzen badu, gure etengailua itxita egoerara itzuliko da eta A zerbitzua egiten hasiko da. B zerbitzuari soilik eskatzen dio. Aplikazio gehienetan, etengailu batek dekoratzaileen diseinu-eredua jarraitzen du. Hala ere, eskuz inplementa daiteke, eta etengailuak ezartzeko eta, azkenik, AOPn oinarritutako inplementazio bat erabiltzeko hiru modu programatiko aztertuko ditugu. Kodea GitHub- en dago eskuragarri.

Diagrama

2. Autoen egiaztapenak

Artikulu honen azken puntuan, auto-lasterketen joko bat ikusiko dugu. Hala ere, hara iritsi baino lehen, circuit breaker batekin exekutatzen den aplikazio bat eraikitzeko alderdi batzuk gidatu nahi zaitut.

2.1. Kystrix (parisetik-berlinera-kystrix-runnable-app)

Kystrixs , DSL txiki gisa , Johan Haleby asmatu eta sortutako liburutegi harrigarria da . Liburutegiak aukera asko eskaintzen ditu, Spring eta Spring WebFlux-ekin integratzea barne. Interesgarria da begirada bat ematea eta pixka bat jolastea:

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


Adibide bat sortu dut, eta GitHub -eko from-paris-to-berlin-kystrix-runnable-app moduluan dago. Lehenik eta behin, kodeari begirada bat emango diogu:

 @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() } }

Kode honek adibideko 2. komandoa adierazten du. Egiaztatu komandoaren kodea 1. Hemen gertatzen ari dena da monoCommand nahi dugun komandoa definitzen ari garela. Hemen, deitu behar dugun metodoa definitzen dugu. commandProperties -en, circuit-breaker irekitzeko egoera aldatzen duten arauak definitzen ditugu. Gure deia berariaz atzeratzen dugu segundo 1 irauteko.


Aldi berean, 5000 milisegundoko denbora-muga definitzen dugu. Horrek esan nahi du inoiz ez garela denbora-muga batera iritsiko. Adibide honetan, Id batekin deiak egin ditzakegu. Proba bat besterik ez denez, Id=1 suposatuko dugu, auto baten Id bat dela, circuit-breaker behar ez duen Jaguar bat. Horrek ere esan nahi du inoiz ez dugula Tank1 lortuko fallback metodoan definitutako moduan. Oraindik ohartu ez bazara, begiratu arretaz atzerako metodoari. Metodo honek Observable bat erabiltzen du. WebFlux Observable diseinu ereduaren arabera inplementatzen den arren, Flux ez da zehazki Behagarri bat.


Hala ere, hystrix-ek biak onartzen ditu. Exekutatu aplikazioa eta ireki zure arakatzailea http://localhost:8080/cars/2 helbidean, hau baieztatzeko. Garrantzitsua da ulertzea Spring Boot abiaraztean deiak egiten oso goiz hasten bazara, azkenean Tank1 mezu bat jaso dezakezula. Hau da, abioko atzerapenak 5 segundo gainditu ditzakeelako oso erraz prozesu hau exekutatzen ari zarenaren arabera. Bigarren adibidean, gure adibidea Tank 2-ra zirkuitu laburtuko dugu:

 @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() } }

Adibide honetan, gure circuit-breaker egoera irekiko amaierako itzulera Tank 2 batera sartuko da erantzun gisa. Hau da, hemen ere 1s atzerapena eragiten ari garelako, baina gure zirkuitu etenaldiaren egoera 500 ms-ren ondoren abiarazten dela zehazten dugu. hystrix nola funtzionatzen duen ezagutzen baduzu, aurrerantzean kystrix ez duela ezer desberdina ikusiko duzu. Une honetan Hystrix-ek eman ez zidana, jokoa egiteko behar nuena eskaintzeko modurik gabeko eta esfortzurik gabekoa izan zen. Kystrix bezeroaren arabera lan egiten duela dirudi. Horrek esan nahi du gure kodea deklaratu behar dugula gure zerbitzu nagusiaren atzean dauden zerbitzuei eskaerak egin aurretik.

2.2. Erresilientzia4J

Resilience4J badirudi askok etengailu baten ezarpen oso gisa aipatzen dutela. Nire lehenengo saiakerak etengailuen alderdi garrantzitsu batzuk aztertzen joan ziren. Hots, etengailu bat ikusi nahi nuen denbora-muga eta eskaera arrakastatsuen maiztasunaren arabera funtziona zezakeen. Resilience4J short-circuiting mota desberdinak konfiguratzeko aukera ematen du. Hauek 6 kategoria desberdinetan bereizten dira: CircuitBreaker , Bulkhead , Ratelimiter , Retry eta Timelimiter . Horiek guztiak diseinu ereduen izenak ere badira. CircuitBreaker moduluak eredu honen inplementazio osoa eskaintzen du.


Konfiguratu ditzakegun parametro asko ditugu, baina funtsean, CircuitBreaker moduluak hutsegite gisa antzematen duguna konfiguratzeko aukera ematen digu, zenbat eskaera onartzen ditugun erdi irekian eta leiho irristakorra, denboraren arabera konfigura daitekeena edo. zenbaketa, non egoera itxian gertatzen diren eskaeren zenbaketa mantentzen dugun. Hau garrantzitsua da errore-maiztasuna kalkulatzeko. Funtsean, CircuitBreaker modulu honek eskaeren tasarekin lagunduko digula esan genezake, baina hori ez da zertan egia.


Nola interpretatzen duzunaren araberakoa da. Modu hobea dirudi akatsei aurre egiteko modu bat besterik ez dela pentsatzeko. Denbora-muga batetik edo salbuespen batetik datozen ala ez, hemen tratatzen dira eta eskaerak modu errazean birbideratu daitezke beste nonbait. Bulkhead modulua aldibereko eskaerei aurre egiteko diseinatuta dago. Ez da tasa mugatzailea.


Horren ordez, Bulkhead diseinu eredua inplementatzen du, prozesatzeko gehiegizko puntu bakar batean gerta ez dadin erabiltzen dena. Kasu honetan, Bulkhead gure eskaerak prozesatzeko aukera ematen digu eskuragarri dauden amaiera-puntu guztietan banatzeko moduan. Bulkhead izena itsasontzi handi batek normalean hondoratzea saihestu behar duen konpartimentu itxietatik dator, istripu bat gertatuz gero, eta itsasontzien kasuan bezala, hari-igerilekuan zenbat hari egongo diren eta alokairu-denbora zehaztu behar dugu. .


RateLimiter modulua eskaeren tasa kudeatzeko diseinatuta dago. Honen eta Bulkhead moduluaren arteko aldea ezinbestekoa da puntu jakin batera arte tasekin tolerantea izan nahi dugula. Horrek esan nahi du ez dugula porrotik eragin behar horretarako. Diseinuan esaten dugu ez dugula balio jakin batetik gorako tasarik onartzen. Horrez gain, eskaera bat birbideratu edo atxikita eduki dezakegu eskaera egiteko baimena eman arte. Retry modulua da ulertzeko errazena, ez baitu beste moduluekin zerikusi handirik.


Funtsean, amaiera-puntu jakin baterako errepikapenen kopurua esplizituki adierazten dugu, gure definitutako atalasea iritsi arte. Timelimiter modulua CircuitBreaker moduluaren sinplifikazio gisa ikus daiteke, biek denbora-muga konfiguratzeko aukera partekatzen baitute. Hala ere, Timelimiter ez dago beste parametro batzuen menpekoa, adibidez, leiho irristagarriak, eta ez du eraikuntza barneko hutsegite atalasearen kalkulurik.


Beraz, zerbitzu jakin bati deitzean denbora-muga batzuk kudeatzea interesatzen bazaigu eta beste akats posibleak kontuan hartzen ez baditugu, seguruenik hobeto egongo gara Timelimiter ekin.

2.2.1. Resilience4J Kotlin eta No Spring esparruarekin (from-paris-to-berlin-resilience4j-runnable-app)

Modulu honetan, resilience4j kotlin liburutegia soilik erabiltzea erabaki dut:

 <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>


Inplementazio hau GitHub-en repo-n dago eskuragarri. Lehenik eta behin TimeLimiter ereduari begiratuko diogu:

 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") }

Kasu honetan, getPrivateCar function TimeLimiter funtzioarekin apaintzen ari gara decorateSuspendFunction funtzioa erabiliz. Honek egingo duena denbora-muga eragitea da, deitzen dugun funtzioak denbora gehiegi hartzen badu Opel Corsa bat lortuko dugu Lancya baten ordez. Hau probatzeko, aplikazioa exekutatu eta http://localhost:8080/cars/timelimiter/normal/1 ireki besterik ez dugu egin.


Inplementazioari begira, ikusten dugu inoiz ezin dugula Lancya bat lortu. Eta hori nahita 10s itxaroten dugulako itzuli aurretik. Gure TimeLimiter denbora-muga askoz txikiagoa du, beraz, honek ez du inoiz funtzionatuko. TimeLimiter nahiko erraza da ulertzeko. CircuitBreaker A , berriz, beste istorio bat izan daiteke. Hau nola egin daitekeen adibide bat da:

 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") }

Kasu honetan, esaten ari gara gure etengailuak zirkuitua ixtea nahi dugula, jabetzarekin hutsegite-tasa 20% baino txikiagoa denean. Dei geldoek ere atalasea izango dute, baina kasu honetan, %50etik beherakoa izango da. Dei geldo batek 1s baino gehiago iraun behar duela esaten dugu bat hartzeko. Gainera, egoera erdi ireki baten iraupena 1s izan behar dela zehazten ari gara. Horrek esan nahi du, praktikan, egoera irekia, erdi irekia edo itxia izango dugula.


Era berean, esaten dugu gehienez ere 500 estatu erdi irekiko eskaera onartzen ditugula. Erroreak kalkulatzeko, etengailuak jakin behar du zein markatan egingo duen hori. Hau garrantzitsua da zirkuitua noiz itxi behar den zehazteko. Kalkulu honetarako gutxieneko 2 eskaera beharrezkoak izango direla esaten dugu, minimumNumberOfCalls propietatearekin. Gogoratzen al duzu erdi irekia dela zirkuitua ixten saiatzen ari garela eskaerak hutsegite seguruaren atalasea iristen badira?


Konfigurazio honetan, esan nahi du gutxienez 2 eskaera egin behar ditugula, leiho lerragarriaren barruan, errore-maiztasuna kalkulatzeko eta egoera itxi batera itzuli edo ez zehazteko. Hau da konfiguratu ditugun aldagai guztien irakurketa zehatza. Oro har, horrek esan nahi duena da gure aplikazioak seguruenik hainbat dei egingo dituela zerbitzu alternatibora, halakorik egonez gero, ez da oso erraz pasako egoera irekitik itxiera, izan ere, erdibidean horretarako arrakasta-tasa % 80koa izan behar da. -open egoerak eta irekitako egoerarako denbora-muga gertatu behar da.


Modu asko daude denbora-muga horiek zehazteko. Gure adibidean, maxDurationInHalfOpenState 1s dela esaten dugu. Horrek esan nahi du gure CircuitBreaker egoera irekita mantenduko duela, gure egiaztapenak itxitako egoera baldintza betetzen ez badu edo denbora-muga hori oraindik gertatu ez bada. CircuitBreaker honetan definitutako portaera zaila izan daiteke jarraitzea eta aurreikustea, eskaeren geldialdi-denbora, tasa eta bestelako ezaugarri zehatzak ezin direlako zehatz-mehatz errepikatu, baina amaierako puntu honetara hainbat eskaera egiten baditugu, ikusiko dugu. goian azaldutako jokabidea gure esperientziarekin bat dator.


Beraz, saia gaitezen amaierako puntuei hainbat eskaera egiten: http://localhost:8080/cars/circuit/1 eta http://localhost:8080/cars/circuit/2 . 1ean amaitzen den autoa arrakastaz berreskuratzearen amaiera da, eta 2z amaitzea zehaztutako autoa eskuratzean huts egin izanaren amaiera da. Kodea ikusita, 2 bat ez den edozerk esan nahi du Lancya bat jasotzen dugula erantzun gisa. A 2 , esan nahi du berehala botatzen dugula exekuzio-denboraren salbuespena, hau da, erantzun gisa Opel Corsa bat lortzen dugula esan nahi du.


1 amaierako puntuari eskaerak egiten baditugu, Lancya erantzun gisa ikusten jarraituko dugu. Sistema huts egiten hasten bada, orduan 2-ri eskaerak egiten dizkiozu,\; ikusiko duzu Lancya itzultzea ez dela etengabea izango pixka bat igaro ondoren. System Ireki egoeran dagoela jakinaraziko du eta ez dela eskaera gehiago onartzen.

 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


Orduan, gure etengailua erdi irekiko egoerara joango da eskaera arrakastatsu baten ondoren, eta horrek esan nahi du eskaera batzuk 1era itzuli beharko ditugula normalizatu aurretik. Lancya Opel Corsa aldatuko gara pare bat aldiz Lancya berriro eskuratu baino lehen. Zenbaki hau 2 dela definitu dugu. Hau da erroreen kalkulurako minimoa. Huts bakarra eragiten badugu eta huts egiten ez duen amaierako puntuari deitzen jarraitzen badugu, gertatzen ari denaren irudi argiago bat lor dezakegu:

 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

Egoera irekiko mezu hau, egia izan arren, huts egin ez duen amaierako 2 eskaera egin ondoren gertatu da. Horregatik estatua erdi irekia omen da.

2.2.2. Resilience4J Spring Boot-ekin eta AOPrik gabe (from-paris-to-berlin-resilience4j-spring-app)

Aurreko segmentuan, oso modu programatikoan nola inplementatu ikusi genuen, Spring teknologiarik erabili gabe. Spring erabili genuen baina WebFlux MVC zerbitzu mota bat eskaintzeko soilik. Gainera, ez dugu ezer aldatu zerbitzuei buruz. Hurrengo aplikazioan, honako liburutegi hauek aztertuko ditugu:

 <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>


Kodea nola egiten den aztertzean, alde handia ikus dezakegu:

 @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")) } }

Adibide honetan eta hurrengoan, denbora-muga propietateei begiratuko diegu gehienbat. CircuitBreaker -en konplexutasunak ez dira hain garrantzitsuak hau sarrerako artikulua delako. Garrantzitsua dena konturatzea da Resilience4J -k Udaberrirako eskaintzen dituen dekoratzaileekin hori zein erraz ezar dezakegun. Modu programmatical oraindik ere, gure hasierako argitaletxea erraz apain dezakegu, carService.getCar() -tik lortzen duguna, nahi ditugun short-circuit motekin.


Adibide honetan, TimeLiter , BulkHead eta CircuitBreaker bat erregistratzen ditugu. Azkenik, TimeoutException bat gertatutakoan abiarazteko funtzioa definitzen dugu. Ikusi behar duguna da hau guztia nola konfiguratuta dagoen. Udaberrian Resilience4J konfiguratzen dugu beste edozein modulu konfiguragarri bezala. application.yml erabiltzen dugu:

 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

Fitxategi hau beren errepositoriotik hartutako adibide-fitxategi bat da eta horren arabera nire adibidera aldatu da. Lehen ikusi dugunez, limiters/short-circuit mota ezberdinen instantziek izen bat dute. Izena oso garrantzitsua da hainbat erregistro eta mugatzaile desberdinak badituzu. Gure adibiderako, eta lehen aipatu bezala, denbora-mugatzailea interesatzen zaigu. 2setara mugatuta dagoela ikus dezakegu. Zerbitzua inplementatu dugun moduari erreparatzen badiogu, ikusten dugu denbora-muga bat gertatzera behartzen ari garela:

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

Hasi gaitezen aplikazioa, eta arakatzailean, joan hona: http://localhost:8080/cars/test/2 . Fiat bat eskuratu beharrean, Rolls Royce bat lortuko dugu. Horrela definitu dugu denbora-muga. Modu berean, CircuitBreaker bat erraz sor dezakegu.

3. Kasua

Orain arte, CircuitBreakers eta erlazionatutako mugatzaileak ezartzeko hiru modu ezinbesteko ikusi ditugu. Gainera, etengailuak ezartzeko dudan modurik gogokoena aztertuko dugu, nik egindako aplikazio batetik pasatuz, hau da, oso joko sinple bat, non karratuetan klik egiten dugun Paristik Berlinera joateko. Jokoa nola inplementatu ulertzeko egiten da. Ez du asko esaten hau non gauzatu. Zuekin ezagutzak partekatzeko diseinatu dudan kasu bat besterik ez da.


Jakina noiz uzten dizut gero erabakitzeko. Funtsean, hainbat kotxe sortu eta Berlinera bide bat ezarri nahi dugu. Ibilbide honetako toki ezberdinetan, ausaz arazoak sortuko ditugun hirietara iritsiko gara. Gure circuit breakers erabakiko dute zenbat denbora itxaron beharko dugun aurrera jarraitu ahal izateko. Beste autoek ez dute arazorik, eta bide egokia aukeratu besterik ez dugu egin behar.


Ordutegi bat egiaztatzeko baimena dugu non erregistratuta dagoen hiri batean arazo jakin bat minutu jakin batean gertatuko den. Minutu marka baliozkoa da bere 0 posizio indexatuetan. Horrek esan nahi du 2k esan nahi du erlojuaren 2, 12, 22, 32, 42, 52 minutuko marka every balio izango duela arazo hau sortzeko. Arazoak 2 motakoak izango dira: ERROR eta TIMEOUT . Errore-huts batek 20 segundoko atzerapena emango dizu.


Denbora-muga batek 50 segundoko atzerapena emango dizu. Hiri aldaketa bakoitzeko, denek 10 segundo itxaron behar dute. Itxaron baino lehen, ordea, autoa hurrengo hiriko sarreran dago jada hori atzerako metodoetan egiten denean. Kasu honetan, hurrengo hiria ausaz aukeratzen da.

Joko orria

4. Ezarpena

Lehenago ikusi dugu nola konfiguratu gure resilience4j erregistroa application.yml erabiliz. Hau eginda, ikus ditzagun gure funtzioak apaintzeko adibide batzuk:

 @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) }


Ikus dezakegunez, jatorrizko zerbitzu-deiak zuzenean apaintzen dira oharrak erabiliz! Hau paketean AOP modulua egoteagatik bakarrik egin daiteke:

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

AOP edo Aspect Oriented Programming OOP oinarritutako beste programazio paradigma bat da. OOP ren osagarritzat hartzen da, eta hain zuzen ere zenbat oharrak funtzionatzen duten. Honek jatorrizko metodoaren inguruan, aurretik edo ondoren beste funtzio batzuk abiarazteko aukera ematen du ebaketa-puntu zehatzetan. Adibidean ikus dezakezun bezala, denbora-muga edo erroreak sortzen ari gara. BlockageException , era berean, ordezko metodoaren barruan sortzen da. Horrek ez du arazo bat adierazten. Erantzuna izan ezik.


Hala ere, aplikazioa WebSockets, eta, beraz, errore hau ez da aplikazioan ikusiko. Orain arte, hau zen jokoa. Hau inplementatu nuen, oharrak erabiltzeak gure bizitza askoz errazagoa izan dezakeen erakusteko helburuarekin, aplikazio erresiliente bat inplementatzen denean. CircuitBreakers ez ezik, beste teknologia batzuk ere baditugu, hala nola WebSockets , Spring WebFlux , Docker , NGINX , typescript eta beste hainbat. Hau guztia CircuitBreakers aplikazio batean nola jokatuko lukeen ikusteko egin da. Aplikazio honekin jolastu nahi baduzu, joan proiektuaren errora eta exekutatu:

 make docker-clean-build-start


Ondoren, exekutatu komando hau:

 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":[]}'


Eskaera honen karga from-paris-to-berlin-city-generator modulua erabiliz sortzen da. Modulu hau aztertzen baduzu, ulertu nahiko erraza dela ikusiko duzu eta jokorako zure mapa sortu dezakezula! Azkenik, joan http://localhost:9000 helbidera eta zure aplikazioa martxan egon beharko litzateke! Orain, eskuineko laukietan klik egin beharko zenuke jokoan aritzeko. Ez egin klik gorrietan irabazi nahi baduzu. Zirkuitu-disektorea martxan ikusi nahi baduzu, mesedez, exekutatu aplikazioen erregistroak:

 docker logs from_paris_to_berlin_web -f

Eta esplizituki egin klik lauki gorrietan hutsegite bat eragiteko.

5. Nola desberdintzen diren Kystrix eta Resilience4J

Kystrix aproposa da zure aplikazioa txikia den kasuetan, eta DSLaren erabilera oso baxua mantentzen dela ziurtatu nahi duzu. Badirudi alde txar bakarra ez duela etengailu batek eraginda eragiteko metodoak apaintzeko modu errazik eskaintzen. Resilience4J aukera paregabea dirudi etengailuekin enpresa-lanerako. Oharpenetan oinarritutako programazioa eskaintzen du, AOPren abantaila guztiak erabiltzen ditu eta bere moduluak bereizita daude. Nolabait esateko, aplikazioko puntu kritikoetarako ere estrategikoki erabil daiteke. Aplikazio baten alderdi asko estaltzeko esparru oso gisa ere erabil daiteke.

6. Ondorioa

Aukeratzen dugun marka edozein dela ere, helburua beti da aplikazio erresiliente bat izatea. Artikulu honetan, etengailuak ikertzen eta nire aurkikuntzak maila oso altuan bizi izan ditudan adibide batzuk erakutsi ditut. Horrek esan nahi du artikulu hau etengailuak zer diren eta mugatzaileek zer egin dezaketen jakin nahi duten pertsonentzat idatzita dagoela.


Aukerak amaigabeak dira gure aplikazioak circuit breakers bezalako erresilientzia mekanismoekin hobetzea pentsatzen denean. Eredu honek aplikazio bat doitzeko aukera ematen du, ditugun baliabideak hobeto erabiltzeko. Gehienbat hodeian, oraindik oso garrantzitsua da gure kostuak optimizatzea eta zenbat baliabide esleitu behar ditugun benetan.


CircuitBreakers konfiguratzea ez da zeregin hutsala Mugatzaileentzat bezala, eta benetan konfigurazio aukera guztiak ulertu behar ditugu errendimendu eta erresilientzia maila ezin hobeak lortzeko. Hau da etengailuei buruzko sarrerako artikulu honetan xehetasunetan sartu nahi izan ez dudan arrazoia.


Circuit-breakers aplikazio mota askotara aplika daitezke. Mezularitza eta streaming aplikazio mota gehienek hau beharko dute. Oso eskuragarri egon behar duten datu-bolumen handiak kudeatzen dituzten aplikazioetarako, etengailuaren formaren bat inplementatu dezakegu eta behar dugu. Lineako txikizkako denda handiek datu kopuru handiak kudeatu behar dituzte egunero eta iraganean, Hystrix oso erabilia zen. Gaur egun, badirudi hau baino askoz gehiago biltzen duen Resilience4J norabidean goazela.

6. Erreferentziak

Eskerrik asko!

Espero dut artikulu hau egitea nik bezainbeste gustatu izana! Mesedez, utzi iritzia, iruzkinak edo edozein sare sozialetan eman nahi duzun iritzia beheko esteketan. Oso eskertuta nago artikulu hau hobetzen lagundu nahi badidazu. Aplikazio honen iturburu-kode guztia GitHub-en jarri dut. Eskerrik asko irakurtzeagatik!