Circuit-breakers
ត្រូវបានប្រើប្រាស់នាពេលបច្ចុប្បន្ននេះ ដើម្បីជៀសវាងការស្នើសុំច្រើនពេកដែលត្រូវបានធ្វើឡើងចំពោះសេវាកម្មមួយ ឬផ្សេងទៀតដែលមិនឆ្លើយតប។ ជាឧទាហរណ៍ នៅពេលដែលសេវាមួយបិទ ទោះជាមូលហេតុអ្វីក៏ដោយ ឧបករណ៍បំបែកសៀគ្វីគួរតែបំបែកសៀគ្វី ដូចដែលឈ្មោះរបស់វាបានបញ្ជាក់។ ម្យ៉ាងវិញទៀត នៅក្នុងស្ថានភាពដែលសំណើ 1 លានកំពុងត្រូវបានធ្វើឡើងក្នុងពេលតែមួយ ដើម្បីទទួលបានលទ្ធផលនៃការប្រណាំងសេះ យើងចង់ឱ្យសំណើទាំងនេះត្រូវបានបញ្ជូនបន្តទៅសេវាកម្មមួយផ្សេងទៀតដែលអាចដោះស្រាយវាបាន។
សេវាកម្មផ្សេងទៀតនេះអាចជាការចម្លងរបស់វា ឬវាអាចប្រើសុទ្ធសាធដើម្បីអនុវត្តប្រតិបត្តិការផ្សេងទៀតទាក់ទងនឹងការបរាជ័យនៃសេវាកម្មដើម។ គោលដៅចុងក្រោយគឺតែងតែបំបែកការហៅទូរសព្ទដែលមិនចាំបាច់ ហើយដំណើរការលំហូរនៅកន្លែងផ្សេង។ នៅ 2017
Michael Nygard
បាននាំយកគំរូការរចនាសៀគ្វីបំបែកទៅជាជួរមុខនៃការរចនាអភិវឌ្ឍន៍កម្មវិធី។ នេះត្រូវបានធ្វើនៅក្នុងការបោះពុម្ពផ្សាយរបស់គាត់ Release It!: Design and Deploy Production-Ready Software (Pragmatic Programmers) 1st Edition។
គំរូនៃការរចនា circuit breaker
ត្រូវបានបំផុសគំនិតដោយសៀគ្វីអេឡិចត្រូនិច និងអគ្គិសនីពិតប្រាកដ។ ទោះយ៉ាងណាក៏ដោយ បើនិយាយពីគំនិតទូទៅ គំនិតបំបែកសៀគ្វីពិតជាត្រូវបានបង្កើតឡើងក្នុង 1879
ដោយ Thomas Edison
។ ដូចជានៅពេលនោះដែរ ចរន្តហូរហៀរត្រូវតែដោះស្រាយ។ នៅក្នុងពាក្យសាមញ្ញបំផុត នេះគឺជាអ្វីដែលយើងកំពុងអនុវត្តចំពោះស្ថាបត្យកម្មកម្មវិធីក្នុងករណីនេះ។ គោលដៅចម្បងគឺធ្វើឱ្យប្រាកដថាប្រព័ន្ធមានភាពធន់គ្រប់គ្រាន់។ តើវាត្រូវតែធន់ប៉ុណ្ណា និងថាតើវា fault-tolerant
ប៉ុណ្ណា គឺពិតជាស្ថិតនៅក្នុងក្រសែភ្នែករបស់វិស្វករដែលទទួលខុសត្រូវក្នុងការសម្របសម្រួលការអនុវត្តគំរូនេះ។ គំនិតនៅពីក្រោយវាគឺថានៅក្រោមលក្ខខណ្ឌជាក់លាក់មួយ យើងប្រហែលជាចង់ប្តូរទិសលំហូរនៃសំណើជាក់លាក់មួយទៅកាន់លំហូរដែលមានបន្ថែមទៀតផ្សេងទៀតនៅពីក្រោយ endpoint
ដូចគ្នានេះ។
ឧបមាថាយើងចង់អនុវត្តសំណើពី A
ដល់ B
។ ពីពេលមួយទៅពេលមួយ B បរាជ័យ ហើយ C
តែងតែមាន។ ប្រសិនបើ B
បរាជ័យដោយចៃដន្យ យើងចង់ទៅដល់ C
ដើម្បីធ្វើឱ្យសេវាកម្មរបស់យើងមានពេញលេញ។ ទោះយ៉ាងណាក៏ដោយ ដើម្បីធ្វើសំណើត្រឡប់ទៅ B
យើងចង់ធ្វើឱ្យប្រាកដថា B
មិនបរាជ័យច្រើនម្តងទៀតទេ។ បន្ទាប់មកយើងអាចកំណត់រចនាសម្ព័ន្ធប្រព័ន្ធរបស់យើងដើម្បីធ្វើសំណើដោយចៃដន្យទៅ B ហើយបានតែត្រលប់ទៅ B
យ៉ាងពេញលេញនៅពេលដែលអត្រានៃការបរាជ័យបានធ្លាក់ចុះដល់កម្រិតជាក់លាក់មួយ។
យើងប្រហែលជាចង់ធ្វើសំណើ C ដោយមានកំហុស ប៉ុន្តែក៏មានការពន្យារពេលផងដែរ។ ប្រសិនបើ B
យឺតខ្លាំង យើងប្រហែលជាចង់ផ្ញើសំណើទាំងអស់ឡើងវិញទៅ C
។ មានការកំណត់រចនាសម្ព័ន្ធជាច្រើនទៀតដែលអាចធ្វើបាន ដូចជាការព្យាយាមឈានដល់ C
ប្រសិនបើបន្ទាប់ពីចំនួនដែលបានកំណត់ ប្រភេទនៃសំណើ ខ្សែភ្ជាប់ស្របគ្នា និងជម្រើសជាច្រើនទៀត នេះត្រូវបានគេស្គាល់ថាជា short-circuiting
ហើយវាភាគច្រើនជាការផ្លាស់ប្តូរបណ្តោះអាសន្ន។
ដើម្បីស្វែងយល់បន្ថែមអំពីចំណេះដឹងរបស់យើងអំពីអ្វីដែលឧបករណ៍បំប្លែងសៀគ្វីពិតប្រាកដ យើងត្រូវយល់ថាការបំបែកសៀគ្វីដំណើរការជាអង្គភាពនៅក្នុងកម្មវិធីរបស់យើង។ មានស្ថានភាពសំខាន់បីសម្រាប់ឧបករណ៍បំលែងសៀគ្វី។ វាអាចត្រូវបានបិទ បើក ឬបើកពាក់កណ្តាល។ ស្ថានភាពបិទមានន័យថាកម្មវិធីរបស់យើងដំណើរការជាធម្មតា។ យើងអាចធ្វើសំណើរទៅកាន់សេវាកម្ម A ដោយសុវត្ថិភាព ដោយដឹងថារាល់សំណើទាំងអស់នឹងទៅកាន់សេវា B ។ ស្ថានភាពបើកចំហមានន័យថាសំណើទាំងអស់ចំពោះសេវាកម្ម B នឹងបរាជ័យ។ ច្បាប់ដែលយើងបានកំណត់ដើម្បីតំណាងឱ្យការបរាជ័យបានកើតឡើង ហើយយើងលែងឈានដល់សេវា B ទៀតហើយ។ ក្នុងករណីនេះ ករណីលើកលែងមួយតែងតែត្រលប់មកវិញ។ ស្ថានភាព half-open
គឺនៅពេលដែលឧបករណ៍បំប្លែងសៀគ្វីរបស់យើងត្រូវបានណែនាំឱ្យធ្វើការធ្វើតេស្តលើសេវាកម្ម B ដើម្បីមើលថាតើវាដំណើរការម្តងទៀតឬអត់។
រាល់សំណើដែលជោគជ័យត្រូវបានដោះស្រាយជាធម្មតា ប៉ុន្តែវានឹងបន្តធ្វើសំណើទៅ C។ ប្រសិនបើ B ប្រព្រឹត្តដូចការរំពឹងទុកដោយយោងតាមច្បាប់ផ្ទៀងផ្ទាត់ដែលយើងបានកំណត់នៅនឹងកន្លែងនោះ ឧបករណ៍បំបែកសៀគ្វីរបស់យើងនឹងត្រឡប់ទៅស្ថានភាពបិទ ហើយសេវាកម្ម A នឹងចាប់ផ្តើមបង្កើត សំណើទាំងស្រុងចំពោះសេវាកម្ម B. នៅក្នុងកម្មវិធីភាគច្រើន ឧបករណ៍បំបែកសៀគ្វីធ្វើតាមលំនាំរចនាអ្នកតុបតែង។ ទោះជាយ៉ាងណាក៏ដោយ វាអាចត្រូវបានអនុវត្តដោយដៃ ហើយយើងនឹងពិនិត្យមើលវិធីបីយ៉ាងនៃការអនុវត្តឧបករណ៍បំលែងសៀគ្វី និងចុងក្រោយដោយប្រើការអនុវត្តដែលមានមូលដ្ឋានលើ AOP ។ លេខកូដមាននៅលើ GitHub ។
នៅក្នុងចំណុចចុងក្រោយនៃអត្ថបទនេះ យើងនឹងពិនិត្យមើលហ្គេមប្រណាំងឡាន។ ទោះយ៉ាងណាក៏ដោយ មុននឹងយើងទៅដល់ទីនោះ ខ្ញុំចង់ណែនាំអ្នកអំពីទិដ្ឋភាពមួយចំនួននៃការបង្កើតកម្មវិធីដែលដំណើរការជាមួយ circuit breaker
។
Kystrixs
ជា DSL
តូចមួយ គឺជាបណ្ណាល័យដ៏អស្ចារ្យមួយដែលត្រូវបានបង្កើត និងបង្កើតឡើងដោយ Johan Haleby
។ បណ្ណាល័យផ្តល់នូវលទ្ធភាពជាច្រើន រួមទាំងការរួមបញ្ចូលជាមួយ Spring និង Spring WebFlux ។ ចាប់អារម្មណ៍មើលវាហើយលេងបន្តិចមើល៖
<dependency> <groupId>se.haleby.kystrix</groupId> <artifactId>kystrix-core</artifactId> </dependency> <dependency> <groupId>se.haleby.kystrix</groupId> <artifactId>kystrix-spring</artifactId> </dependency>
ខ្ញុំបានបង្កើតឧទាហរណ៍មួយ ហើយវាមានទីតាំងនៅលើ module from-paris-to-berlin-kystrix-runnable-app នៅលើ GitHub ។ ដំបូងយើងមើលកូដ៖
@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() } }
កូដនេះតំណាងឱ្យពាក្យបញ្ជា 2 នៃឧទាហរណ៍។ ពិនិត្យកូដសម្រាប់ពាក្យបញ្ជា 1. អ្វីដែលកំពុងកើតឡើងនៅទីនេះគឺថាយើងកំពុងកំណត់ពាក្យបញ្ជាដែលយើងចង់បានជាមួយ monoCommand
។ នៅទីនេះយើងកំណត់វិធីសាស្រ្តដែលយើងត្រូវហៅ។ នៅក្នុង commandProperties
យើងកំណត់ច្បាប់ដែលធ្វើឱ្យ circuit-breaker
ផ្លាស់ប្តូរស្ថានភាពដើម្បីបើក។ យើងពន្យារពេលការហៅរបស់យើងយ៉ាងច្បាស់ដើម្បីឱ្យវាមានរយៈពេលយ៉ាងជាក់លាក់ 1 វិនាទី។
ក្នុងពេលជាមួយគ្នានេះ យើងកំណត់ពេលវេលាអស់ 5000
មិល្លីវិនាទី។ នេះមានន័យថាយើងនឹងមិនដល់ពេលវេលាទេ។ ក្នុងឧទាហរណ៍នេះ យើងអាចហៅទូរសព្ទដោយប្រើ Id
។ ដោយសារនេះគ្រាន់តែជាការសាកល្បង យើងសន្មត់ថា Id=1
ជា Id
នៃឡាន ឡាន Jaguar ដែលមិនត្រូវការ circuit-breaker
។ នេះក៏មានន័យផងដែរថា យើងនឹងមិនទទួលបាន Tank1 ដូចដែលបានកំណត់ក្នុងវិធីសាស្ត្រ fallback នោះទេ។ ប្រសិនបើអ្នកមិនទាន់បានកត់សម្គាល់នៅឡើយ សូមក្រឡេកមើលវិធីសាស្ត្រជំនួសវិញ។ វិធីសាស្រ្តនេះប្រើ Observable
។ ទោះបីជា WebFlux
ត្រូវបានអនុវត្តតាមលំនាំនៃការរចនា Observable
ក៏ដោយ Flux
មិនមែនជា Observable ពិតប្រាកដនោះទេ។
ទោះយ៉ាងណាក៏ដោយ hystrix គាំទ្រទាំងពីរ។ សូមដំណើរការកម្មវិធី ហើយបើកកម្មវិធីរុករកតាមអ៊ីនធឺណិតរបស់អ្នកនៅលើ http://localhost:8080/cars/2 ដើម្បីបញ្ជាក់រឿងនេះ។ វាជាការសំខាន់ណាស់ដែលត្រូវយល់ថា ប្រសិនបើអ្នកចាប់ផ្តើមធ្វើការហៅទូរសព្ទនៅដើមដំបូងនៃការចាប់ផ្តើម Spring Boot នោះ អ្នកប្រហែលជាទទួលបានសារ Tank1 ជាយថាហេតុ។ នេះគឺដោយសារតែការពន្យាពេលចាប់ផ្តើមអាចលើសពី 5 វិនាទីយ៉ាងងាយស្រួល អាស្រ័យលើរបៀបដែលអ្នកកំពុងដំណើរការដំណើរការនេះ។ នៅក្នុងឧទាហរណ៍ទី 2 យើងនឹងសៀគ្វីខ្លីឧទាហរណ៍របស់យើងទៅធុង 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() } }
ក្នុងឧទាហរណ៍នេះ circuit-breaker
របស់យើងនឹងចូលទៅក្នុងស្ថានភាពបើកចំហរបញ្ចប់ ធុង 2 ជាការឆ្លើយតប។ នេះគឺដោយសារតែយើងក៏កំពុងបណ្តាលឱ្យមានការពន្យារពេល 1s នៅទីនេះផងដែរ ប៉ុន្តែយើងបញ្ជាក់ថាលក្ខខណ្ឌនៃការដាច់សៀគ្វីរបស់យើងចាប់ផ្តើមបន្ទាប់ពីសញ្ញា 500ms ។ ប្រសិនបើអ្នកដឹងពីរបៀបដែល hystrix
ដំណើរការ អ្នកនឹងឃើញថា kystrix
មិនមានអ្វីប្លែកទេដែលឆ្ពោះទៅមុខ។ អ្វីដែល hystrix មិនបានផ្តល់ឱ្យខ្ញុំនៅចំណុចនេះគឺជាវិធីគ្មានថ្នេរ និងគ្មានការខិតខំប្រឹងប្រែងដើម្បីផ្តល់នូវអ្វីដែលខ្ញុំត្រូវការដើម្បីបង្កើតហ្គេម។ Kystrix
ហាក់ដូចជាដំណើរការលើមូលដ្ឋានអតិថិជន។ នេះមានន័យថា យើងត្រូវប្រកាសកូដរបស់យើង មុនពេលធ្វើការស្នើសុំទៅកាន់សេវាកម្មដែលនៅពីក្រោយសេវាកម្មចម្បងរបស់យើង។
Resilience4J
ហាក់ដូចជាត្រូវបានយោងដោយមនុស្សជាច្រើនថាជាការអនុវត្តពេញលេញនៃសៀគ្វីបំបែក។ ការសាកល្បងដំបូងរបស់ខ្ញុំគឺអំពីការស្វែងរកទិដ្ឋភាពសំខាន់ៗមួយចំនួននៃសៀគ្វីបំបែកសៀគ្វី។ ពោលគឺខ្ញុំចង់ឃើញឧបករណ៍បំលែងសៀគ្វីដែលអាចដំណើរការលើមូលដ្ឋាននៃការអស់ពេល និងភាពញឹកញាប់នៃសំណើជោគជ័យ។ Resilience4J
អនុញ្ញាតឱ្យមានប្រភេទផ្សេងគ្នានៃម៉ូឌុល short-circuiting
ត្រូវបានកំណត់រចនាសម្ព័ន្ធ។ ទាំងនេះត្រូវបានបំបែកជា 6
ប្រភេទផ្សេងៗគ្នា៖ CircuitBreaker
, Bulkhead
, Ratelimiter
, Retry
និង Timelimiter
។ ទាំងអស់នេះក៏ជាឈ្មោះនៃគំរូរចនាផងដែរ។ ម៉ូឌុល CircuitBreaker
ផ្តល់នូវការអនុវត្តពេញលេញនៃគំរូនេះ។
យើងមានប៉ារ៉ាម៉ែត្រជាច្រើនដែលយើងអាចកំណត់រចនាសម្ព័ន្ធបាន ប៉ុន្តែសំខាន់ ម៉ូឌុល CircuitBreaker
អនុញ្ញាតឱ្យយើងកំណត់រចនាសម្ព័ន្ធអ្វីដែលយើងទទួលស្គាល់ថាបរាជ័យ តើសំណើប៉ុន្មានដែលយើងអនុញ្ញាតក្នុងស្ថានភាពពាក់កណ្តាលបើក និងបង្អួចរអិល ដែលអាចកំណត់រចនាសម្ព័ន្ធតាមពេលវេលា ឬ រាប់ ដែលយើងរក្សាចំនួនសំណើដែលកើតឡើងក្នុងស្ថានភាពបិទ។ នេះមានសារៈសំខាន់ក្នុងការគណនាប្រេកង់កំហុស។ ជាសំខាន់ យើងអាចនិយាយបានថា ម៉ូឌុល CircuitBreaker
នេះនឹងជួយយើងជាមួយនឹងអត្រានៃសំណើ ប៉ុន្តែវាមិនពិតនោះទេ។
វាអាស្រ័យលើរបៀបដែលអ្នកបកស្រាយវា។ វាហាក់ដូចជាវិធីល្អជាងក្នុងការគិតថាវាជាវិធីសាមញ្ញមួយដើម្បីដោះស្រាយកំហុស។ មិនថាពួកគេមកពីការអស់ពេល ឬករណីលើកលែងនោះទេ នេះគឺជាកន្លែងដែលពួកគេត្រូវបានដោះស្រាយ និងរបៀបដែលសំណើអាចត្រូវបានបញ្ជូនបន្តទៅកន្លែងផ្សេង ម៉ូឌុល Bulkhead
ត្រូវបានរចនាឡើងដើម្បីដោះស្រាយជាមួយនឹងសំណើស្របគ្នា។ វាមិនមែនជាការកំណត់អត្រាការប្រាក់ទេ។
ផ្ទុយទៅវិញ វាអនុវត្តលំនាំរចនា Bulkhead
ដែលត្រូវបានប្រើដើម្បីការពារដំណើរការច្រើនពេកពីការកើតឡើងនៅក្នុងចំណុចបញ្ចប់តែមួយ។ ក្នុងករណីនេះ Bulkhead
អនុញ្ញាតឱ្យយើងដំណើរការសំណើរបស់យើងតាមរបៀបដែលពួកគេត្រូវបានចែកចាយនៅទូទាំងចំណុចបញ្ចប់ដែលមានទាំងអស់។ ឈ្មោះ Bulkhead
មកពីផ្នែកបិទជិតផ្សេងៗគ្នា ដែលកប៉ាល់ធំជាធម្មតាត្រូវជៀសវាងការលិច ប្រសិនបើឧបទ្ទវហេតុកើតឡើង ហើយដូចនៅក្នុងករណីនៃកប៉ាល់ យើងត្រូវកំណត់ថាតើខ្សែប៉ុន្មាននឹងមាននៅក្នុងបណ្តុំខ្សែស្រឡាយ និងពេលវេលាជួលរបស់ពួកគេ .
ម៉ូឌុល RateLimiter
ត្រូវបានរចនាឡើងដើម្បីគ្រប់គ្រងអត្រានៃសំណើ។ ភាពខុសគ្នារវាងនេះ និងម៉ូឌុល Bulkhead
គឺចាំបាច់ដែលយើងចង់មានការអត់ឱនចំពោះអត្រារហូតដល់ចំណុចជាក់លាក់មួយ។ នេះមានន័យថា យើងមិនចាំបាច់ធ្វើឱ្យបរាជ័យសម្រាប់រឿងនោះទេ។ យើងគ្រាន់តែនិយាយថានៅក្នុងការរចនាដែលយើងមិនអត់ធ្មត់លើអត្រាខ្ពស់ជាងតម្លៃជាក់លាក់មួយ។ លើសពីនេះ យើងអាចប្តូរទិសសំណើ ឬរក្សាវាទុករហូតទាល់តែមានការអនុញ្ញាតក្នុងការអនុវត្តសំណើ។ ម៉ូឌុល Retry
គឺប្រហែលជាងាយស្រួលបំផុតក្នុងការយល់ព្រោះវាមិនមានច្រើនដូចគ្នាជាមួយម៉ូឌុលផ្សេងទៀតទេ។
យើងប្រកាសយ៉ាងច្បាស់អំពីចំនួននៃការព្យាយាមម្តងទៀតទៅកាន់ចំណុចបញ្ចប់ជាក់លាក់មួយ រហូតដល់យើងឈានដល់កម្រិតដែលបានកំណត់របស់យើង។ ម៉ូឌុល Timelimiter
អាចត្រូវបានគេមើលឃើញថាជាការធ្វើឱ្យសាមញ្ញនៃម៉ូឌុល CircuitBreaker
ដែលពួកគេទាំងពីរចែករំលែកលទ្ធភាពក្នុងការកំណត់ការអស់ពេល។ ទោះជាយ៉ាងណាក៏ដោយ Timelimiter
មិនអាស្រ័យលើប៉ារ៉ាម៉ែត្រផ្សេងទៀតដូចជាបង្អួចរអិល ហើយវាក៏មិនមានការគណនាកម្រិតនៃការបរាជ័យក្នុងការសាងសង់ផងដែរ។
ដូច្នេះ ប្រសិនបើយើងចាប់អារម្មណ៍ទាំងស្រុងក្នុងការដោះស្រាយការអស់ពេលនៅពេលហៅទៅសេវាជាក់លាក់មួយ ហើយយើងមិនគិតពីកំហុសដែលអាចកើតមានផ្សេងទៀតទេនោះ យើងប្រហែលជាប្រសើរជាងជាមួយ Timelimiter
។
នៅក្នុងម៉ូឌុលនេះ ខ្ញុំបានសម្រេចចិត្តប្រើបណ្ណាល័យ 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>
ការអនុវត្តនេះមាននៅលើ repo នៅលើ GitHub ។ ដំបូងយើងនឹងមើលគំរូ 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") }
ក្នុងករណីនេះ យើងកំពុងតុបតែង function
getPrivateCar
របស់យើងជាមួយនឹងមុខងារ TimeLimiter
ដោយប្រើមុខងារ decorateSuspendFunction
។ អ្វីដែលវានឹងធ្វើគឺធ្វើឱ្យអស់ពេល ប្រសិនបើមុខងារដែលយើងហៅប្រើពេលយូរពេក យើងនឹងទទួលបាន Opel Corsa ជំនួសឱ្យ Lancya ។ ដើម្បីសាកល្បងវា យើងគ្រាន់តែអាចដំណើរការកម្មវិធី ហើយបើក http://localhost:8080/cars/timelimiter/normal/1 ។
ក្រឡេកទៅមើលការអនុវត្ត យើងឃើញថាយើងមិនអាចទទួលបាន Lancya
ទេ។ ហើយនេះគឺដោយសារតែយើងមានចេតនារង់ចាំ 10s
មុនពេលយើងប្រគល់វាមកវិញ។ TimeLimiter
របស់យើងមានការអស់ពេលតិចជាងច្រើន ដូច្នេះវាមិនដំណើរការទេ។ TimeLimiter
គឺសាមញ្ញណាស់ក្នុងការយល់។ ម្យ៉ាងវិញទៀត CircuitBreaker
អាចជារឿងផ្សេង។ នេះជាឧទាហរណ៍នៃវិធីដែលអាចធ្វើបាន៖
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") }
ក្នុងករណីនេះយើងកំពុងនិយាយថាយើងចង់ឱ្យឧបករណ៍បំលែងសៀគ្វីរបស់យើងបិទសៀគ្វីនៅពេលដែលអត្រាបរាជ័យទាបជាង 20%
ជាមួយនឹងទ្រព្យសម្បត្តិ។ ការហៅទូរសព្ទយឺតក៏នឹងមានកម្រិតដែរ ប៉ុន្តែក្នុងករណីនេះវានឹងមានតិចជាង 50%។ យើងនិយាយថាការហៅយឺតត្រូវតែមានរយៈពេលយូរជាង 1s ដើម្បីចាត់ទុកថាជាការមួយ។ យើងក៏កំពុងបញ្ជាក់ផងដែរថារយៈពេលនៃរដ្ឋបើកចំហពាក់កណ្តាលគួរតែមាន 1s ។ នេះមានន័យថា នៅក្នុងការអនុវត្ត យើងនឹងមានរដ្ឋបើកចំហ រដ្ឋពាក់កណ្តាលបើកចំហ ឬរដ្ឋបិទ។
យើងក៏និយាយផងដែរថា យើងអនុញ្ញាតឱ្យមានសំណើរដ្ឋពាក់កណ្តាលបើកចំហអតិបរមាចំនួន 500 ។ សម្រាប់ការគណនាកំហុស អ្នកបំបែកសៀគ្វីត្រូវដឹងថាសញ្ញាមួយណាដែលវានឹងធ្វើបែបនោះ។ នេះជាការសំខាន់ដើម្បីកំណត់ថាពេលណាត្រូវបិទសៀគ្វី។ យើងនិយាយថាសំណើចំនួន 2 នឹងចាំបាច់តិចតួចបំផុតសម្រាប់ការគណនានេះ ជាមួយនឹងលក្ខណៈសម្បត្តិ minimumNumberOfCalls
។ សូមចាំថាការបើកពាក់កណ្តាលគឺជាពេលដែលយើងនឹងបន្តព្យាយាមធ្វើឱ្យសៀគ្វីបិទ ប្រសិនបើសំណើឈានដល់កម្រិតសុវត្ថិភាពបរាជ័យ?
នៅក្នុងការកំណត់រចនាសម្ព័ន្ធនេះ វាមានន័យថាយើងត្រូវធ្វើសំណើយ៉ាងហោចណាស់ 2
នៅក្នុងបង្អួចរអិល ដើម្បីគណនាប្រេកង់កំហុស និងកំណត់ថាតើត្រូវត្រលប់ទៅស្ថានភាពបិទឬអត់។ នេះគឺជាការអានត្រឹមត្រូវនៃអថេរទាំងអស់ដែលយើងបានកំណត់រចនាសម្ព័ន្ធ។ ជាទូទៅ អ្វីដែលមានន័យថាកម្មវិធីរបស់យើងប្រហែលជានឹងធ្វើការហៅទូរសព្ទជាច្រើនទៅកាន់សេវាជំនួស ប្រសិនបើវាមានមួយ វានឹងមិនផ្លាស់ប្តូរយ៉ាងងាយស្រួលពីរដ្ឋបើកចំហទៅរដ្ឋបិទទេ ដោយសារអត្រាជោគជ័យក្នុងការធ្វើវាត្រូវតែមាន 80% ក្នុងអំឡុងពេលពាក់កណ្តាល។ -open states និងការអស់ពេលសម្រាប់ open state ត្រូវតែកើតឡើង។
មានវិធីជាច្រើនដើម្បីបញ្ជាក់ការអស់ពេលបែបនេះ។ ក្នុងឧទាហរណ៍របស់យើង យើងនិយាយថា maxDurationInHalfOpenState
គឺ 1s ។ នេះមានន័យថា CircuitBreaker
របស់យើងនឹងរក្សាស្ថានភាពបើកចំហ លុះត្រាតែការត្រួតពិនិត្យរបស់យើងមិនបំពេញលក្ខខណ្ឌនៃស្ថានភាពបិទ ឬប្រសិនបើការអស់ពេលនេះមិនទាន់កើតឡើង។ ឥរិយាបទដែលបានកំណត់នៅក្នុង CircuitBreaker
នេះប្រហែលជាពិបាកក្នុងការតាមដាន និងព្យាករណ៍ ព្រោះពេលវេលារងចាំជាក់លាក់ អត្រា និងលក្ខណៈពិសេសផ្សេងទៀតនៃសំណើគឺគ្រាន់តែមិនអាចចម្លងបានពិតប្រាកដ ប៉ុន្តែប្រសិនបើយើងអនុវត្តសំណើជាច្រើនដល់ចំណុចបញ្ចប់នេះ យើងនឹងឃើញថា អាកប្បកិរិយាដែលបានពិពណ៌នាខាងលើត្រូវនឹងបទពិសោធន៍របស់យើង។
ដូច្នេះ សូមព្យាយាម និងអនុវត្តសំណើជាច្រើនទៅកាន់ចំណុចបញ្ចប់៖ http://localhost:8080/cars/circuit/1 និង http://localhost:8080/cars/circuit/2 ។ ការបញ្ចប់ក្នុង 1 គឺជាចំណុចបញ្ចប់នៃការទាញយករថយន្តដែលទទួលបានជោគជ័យ ហើយការបញ្ចប់ដោយ 2 គឺជាចំណុចបញ្ចប់នៃការបរាជ័យក្នុងការទទួលបានរថយន្តដែលបានបញ្ជាក់។ ការក្រឡេកមើលលេខកូដ យើងឃើញថាអ្វីផ្សេងទៀតក្រៅពីលេខ 2 មានន័យថាយើងទទួលបាន Lancya
ជាការឆ្លើយតប។ A 2
មានន័យថាយើងបោះចោលការលើកលែងពេលដំណើរការភ្លាមៗ ដែលមានន័យថាយើងទទួលបាន Opel Corsa
ជាការឆ្លើយតប។
ប្រសិនបើយើងគ្រាន់តែធ្វើការស្នើសុំទៅកាន់ចំណុចបញ្ចប់ទី 1
យើងនឹងបន្តឃើញ Lancya
ជាការឆ្លើយតប។ ប្រសិនបើប្រព័ន្ធចាប់ផ្តើមបរាជ័យ នោះគឺជាពេលដែលអ្នកធ្វើសំណើទៅ 2,\; អ្នកនឹងឃើញថាការត្រឡប់ទៅ Lancya
វិញនឹងមិនស្ថិតនៅថេរទេបន្ទាប់ពីមួយរយៈក្រោយមក។ System
នឹងជូនដំណឹងថាវាស្ថិតក្នុងស្ថានភាពបើកចំហ ហើយមិនអនុញ្ញាតឱ្យមានការស្នើសុំទៀតទេ។
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
ឧបករណ៍បំបែកសៀគ្វីរបស់យើងនឹងទៅស្ថានភាពពាក់កណ្តាលបើកចំហបន្ទាប់ពីការស្នើសុំជោគជ័យ ហើយនេះមានន័យថាយើងនឹងត្រូវអនុវត្តសំណើមួយចំនួនត្រឡប់ទៅលេខ 1 មុនពេលវាដំណើរការធម្មតា។ យើងនឹងប្តូរពី Lancya
ទៅ Opel Corsa
ពីរបីដង មុនពេលយើងទទួលបាន Lancya
ម្តងទៀត។ យើងកំណត់លេខនេះថាជា 2។ នេះគឺតិចតួចបំផុតសម្រាប់ការគណនាកំហុស។ ប្រសិនបើយើងធ្វើឱ្យបរាជ័យមួយហើយបន្តហៅចំណុចបញ្ចប់ដែលមិនបរាជ័យ នោះយើងអាចទទួលបានរូបភាពកាន់តែច្បាស់អំពីអ្វីដែលកំពុងកើតឡើង៖
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
សារស្ថានភាពបើកចំហនេះ ខណៈពេលដែលជាការពិត បានកើតឡើងបន្ទាប់ពីខ្ញុំបានស្នើសុំ 2 ទៅកាន់ចំណុចបញ្ចប់ដែលមិនបរាជ័យ។ នេះជាមូលហេតុដែលរដ្ឋត្រូវបានគេនិយាយថាបើកចំហពាក់កណ្តាល។
នៅក្នុងផ្នែកមុន យើងបានឃើញពីរបៀបអនុវត្តវាតាមវិធីដែលមានលក្ខណៈជាកម្មវិធី ដោយមិនប្រើបច្ចេកវិទ្យា Spring ណាមួយឡើយ។ យើងបានប្រើ Spring ប៉ុន្តែដើម្បីផ្តល់សេវាប្រភេទ WebFlux
MVC
ប៉ុណ្ណោះ។ លើសពីនេះ យើងមិនបានផ្លាស់ប្តូរអ្វីទាំងអស់អំពីសេវាកម្មខ្លួនឯងទេ។ នៅក្នុងកម្មវិធីខាងក្រោម យើងនឹងរុករកបណ្ណាល័យខាងក្រោម៖
<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>
នៅពេលក្រឡេកមើលពីរបៀបដែលកូដត្រូវបានធ្វើរួច យើងអាចឃើញភាពខុសគ្នាខ្លាំង៖
@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")) } }
ក្នុងឧទាហរណ៍នេះ និងខាងក្រោម យើងនឹងមើលភាគច្រើនអំពីលក្ខណៈសម្បត្តិអស់ពេល។ ភាពស្មុគ្រស្មាញរបស់ CircuitBreaker
ខ្លួនឯងមិនសូវពាក់ព័ន្ធទេ ព្រោះនេះជាអត្ថបទណែនាំ។ អ្វីដែលសំខាន់នៅទីនេះដើម្បីដឹងគឺថាតើយើងអាចអនុវត្តវាយ៉ាងងាយស្រួលជាមួយអ្នកតុបតែងដែលបានផ្តល់ឱ្យសម្រាប់ Spring ដោយ Resilience4J
។ ទោះបីជានៅតែស្ថិតក្នុងទម្រង់ programmatical
ក៏ដោយ យើងអាចតុបតែងអ្នកបោះពុម្ពផ្សាយដំបូងរបស់យើងបានយ៉ាងងាយស្រួល ដែលជាកម្មវិធីដែលយើងទទួលបានពី carService.getCar()
ជាមួយនឹងប្រភេទ short-circuit
ដែលយើងចង់បាន។
ក្នុងឧទាហរណ៍នេះ យើងចុះឈ្មោះ TimeLiter
មួយ BulkHead
និង CircuitBreaker
។ ជាចុងក្រោយ យើងកំណត់មុខងារ fallback ដែលនឹងត្រូវបានកេះ នៅពេលដែល TimeoutException បានកើតឡើង។ អ្វីដែលយើងនៅតែត្រូវមើលគឺតើវាត្រូវបានកំណត់ដោយរបៀបណា។ យើងកំណត់រចនាសម្ព័ន្ធ Resilience4J នៅនិទាឃរដូវដូចគ្នានឹងម៉ូឌុលដែលអាចកំណត់រចនាសម្ព័ន្ធផ្សេងទៀតដែរ។ យើងប្រើ 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
ឯកសារនេះជាឯកសារឧទាហរណ៍មួយដែលយកចេញពី repo របស់ពួកគេ ហើយបានកែប្រែទៅជាឧទាហរណ៍របស់ខ្ញុំស្របតាម។ ដូចដែលយើងបានឃើញពីមុន ករណីនៃប្រភេទផ្សេងគ្នានៃ limiters/short-circuit
មានឈ្មោះ។ ឈ្មោះមានសារៈសំខាន់ណាស់ ប្រសិនបើអ្នកមានបញ្ជីឈ្មោះផ្សេងៗគ្នា និងការកំណត់ផ្សេងៗគ្នា។ ជាឧទាហរណ៍របស់យើង ហើយដូចដែលបានរៀបរាប់ពីមុន យើងចាប់អារម្មណ៍លើការកំណត់ពេលវេលា។ យើងអាចមើលឃើញថាវាត្រូវបានកំណត់ត្រឹម 2s ។ ប្រសិនបើយើងក្រឡេកមើលវិធីដែលយើងបានអនុវត្តសេវាកម្មនេះ យើងឃើញថាយើងកំពុងបង្ខំឱ្យពេលវេលាមួយកើតឡើង៖
@Component open class CarService { open fun getCar(): Mono<Car> { return Mono.just(Car("Fiat")).delayElement(Duration.ofSeconds(10)); } }
តោះចាប់ផ្តើមកម្មវិធី ហើយនៅក្នុង browser សូមចូលទៅកាន់៖ http://localhost:8080/cars/test/2 ។ ជំនួសឱ្យការទទួលបាន Fiat
យើងនឹងទទួលបានរថយន្ត Rolls Royce
។ នេះជារបៀបដែលយើងកំណត់ពេលវេលា។ ដូចគ្នាដែរ យើងអាចបង្កើត CircuitBreaker
យ៉ាងងាយស្រួល។
រហូតមកដល់ពេលនេះ យើងបានឃើញវិធីសំខាន់ៗចំនួនបីក្នុងការអនុវត្ត CircuitBreakers
និងឧបករណ៍កំណត់ដែលពាក់ព័ន្ធ។ លើសពីនេះ យើងនឹងពិនិត្យមើលវិធីដែលខ្ញុំចូលចិត្តបំផុតក្នុងការអនុវត្តឧបករណ៍បំបែកសៀគ្វីដោយឆ្លងកាត់កម្មវិធីដែលខ្ញុំបានបង្កើត ដែលជាហ្គេមសាមញ្ញបំផុតដែលយើងគ្រាន់តែចុចលើការ៉េដើម្បីចេញពីទីក្រុងប៉ារីសទៅកាន់ទីក្រុងប៊ែរឡាំង។ ហ្គេមនេះត្រូវបានបង្កើតឡើងដើម្បីយល់ពីរបៀបអនុវត្ត។ វាមិននិយាយច្រើនអំពីកន្លែងដែលត្រូវអនុវត្តនេះទេ។ វាគ្រាន់តែជាករណីដែលខ្ញុំបានបង្កើតឡើងដើម្បីចែករំលែកជាមួយអ្នកនូវចំណេះដឹង។
ចំណេះដឹងពេលខ្ញុំទុកអោយអ្នកសម្រេចចិត្តពេលក្រោយ។ សំខាន់យើងចង់បង្កើតរថយន្តមួយចំនួន និងបង្កើតផ្លូវទៅកាន់ទីក្រុងប៊ែរឡាំង។ នៅក្នុងទីតាំងផ្សេងៗគ្នានៅក្នុងផ្លូវនេះ យើងនឹងទៅដល់ទីក្រុងនានាដែលចៃដន្យ យើងនឹងបង្កើតបញ្ហា។ circuit breakers
របស់យើងនឹងសម្រេចថាតើយើងត្រូវរង់ចាំរយៈពេលប៉ុន្មាន មុនពេលដែលយើងត្រូវបានអនុញ្ញាតឱ្យបន្តដំណើរទៅមុខទៀត។ ចំណែករថយន្តផ្សេងទៀតមិនមានបញ្ហាទេ ហើយយើងគ្រាន់តែជ្រើសរើសផ្លូវត្រូវប៉ុណ្ណោះ។
យើងត្រូវបានអនុញ្ញាតឱ្យពិនិត្យមើលតារាងពេលវេលាដែលវាត្រូវបានចុះឈ្មោះនៅពេលដែលបញ្ហាជាក់លាក់មួយនឹងកើតឡើងក្នុងទីក្រុងនៅលើសញ្ញានាទីជាក់លាក់។ សញ្ញានាទីមានសុពលភាពក្នុង 0 ទីតាំងដែលបានធ្វើលិបិក្រមរបស់វា។ នេះមានន័យថា 2 មានន័យថា every
2, 12, 22, 32, 42, 52 នាទីនៅលើនាឡិកានឹងមានសុពលភាពដើម្បីបង្កើតបញ្ហានេះ។ បញ្ហានឹងមានពីរប្រភេទ៖ ERROR
និង TIMEOUT
។ ការបរាជ័យកំហុសនឹងផ្តល់ឱ្យអ្នកនូវការពន្យារពេល 20 វិនាទី។
ការអស់ពេលនឹងផ្តល់ឱ្យអ្នកនូវការពន្យារពេល 50 វិនាទី។ សម្រាប់ការផ្លាស់ប្តូរទីក្រុង អ្នកគ្រប់គ្នាត្រូវរង់ចាំ 10 វិនាទី។ ទោះយ៉ាងណាក៏ដោយ មុននឹងរង់ចាំ រថយន្តគឺរួចហើយនៅច្រកចូលទីក្រុងខាងក្រោម នៅពេលដែលវាត្រូវបានធ្វើនៅក្នុងវិធីសាស្រ្តធ្លាក់ចុះ។ ក្នុងករណីនេះទីក្រុងបន្ទាប់ត្រូវបានជ្រើសរើសដោយចៃដន្យ។
យើងបានឃើញពីមុនមក របៀបកំណត់រចនាសម្ព័ន្ធបញ្ជីឈ្មោះ resilience4j
របស់យើងដោយប្រើ application.yml
។ ដោយបានធ្វើដូចនេះ សូមមើលឧទាហរណ៍មួយចំនួនអំពីរបៀបតុបតែងមុខងាររបស់យើង៖
@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) }
ដូចដែលយើងអាចមើលឃើញ ការហៅទូរសព្ទសេវាដើមត្រូវបានតុបតែងដោយផ្ទាល់ដោយប្រើចំណារពន្យល់! នេះអាចត្រូវបានធ្វើបានតែដោយសារតែវត្តមានរបស់ម៉ូឌុល AOP
នៅក្នុងកញ្ចប់៖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
AOP
ឬ Aspect Oriented Programming
គឺជាគំរូនៃការសរសេរកម្មវិធីមួយផ្សេងទៀតដោយផ្អែកលើ OOP
។ វាត្រូវបានគេចាត់ទុកថាជាការបំពេញបន្ថែមទៅនឹង OOP
ហើយវាច្បាស់ណាស់ចំនួនចំណារពន្យល់ដំណើរការ។ នេះអនុញ្ញាតឱ្យមានការកេះមុខងារផ្សេងទៀតជុំវិញ មុន ឬក្រោយវិធីសាស្ត្រដើមនៅក្នុងចំណុចកាត់ច្បាស់លាស់។ ដូចដែលអ្នកអាចឃើញពីឧទាហរណ៍ យើងកំពុងបង្កើតការអស់ពេល ឬកំហុស។ BlockageException
ក៏ត្រូវបានបង្កើតនៅខាងក្នុងវិធីសាស្ត្រ fallback ផងដែរ។ នេះមិនតំណាងឱ្យបញ្ហាទេ។ លើកលែងតែការឆ្លើយតប។
ទោះយ៉ាងណាក៏ដោយ កម្មវិធីកំពុងដំណើរការលើ WebSockets,
ដូច្នេះហើយ កំហុសនេះនឹងមិនត្រូវបានមើលឃើញនៅក្នុងកម្មវិធីនោះទេ។ រហូតមកដល់ពេលនេះនេះគឺជាហ្គេម។ ខ្ញុំបានអនុវត្តវាដោយផ្តោតលើការបង្ហាញពីរបៀបដែលការប្រើចំណារពន្យល់អាចធ្វើឱ្យជីវិតរបស់យើងកាន់តែងាយស្រួលនៅពេលអនុវត្តកម្មវិធីធន់។ យើងមិនត្រឹមតែមាន CircuitBreakers អនុវត្តប៉ុណ្ណោះទេ ប៉ុន្តែក៏មានបច្ចេកវិទ្យាផ្សេងទៀតផងដែរ ដូចជា WebSockets
, Spring WebFlux
, Docker
, NGINX
, typescript
និងជាច្រើនទៀត។ ទាំងអស់នេះត្រូវបានធ្វើឡើងដើម្បីមើលពីរបៀបដែល CircuitBreakers នឹងដំណើរការនៅក្នុងកម្មវិធីមួយ។ ប្រសិនបើអ្នកចង់លេងជាមួយកម្មវិធីនេះ សូមចូលទៅកាន់ root នៃគម្រោង ហើយដំណើរការ៖
make docker-clean-build-start
បន្ទាប់មកដំណើរការពាក្យបញ្ជានេះ៖
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":[]}'
បន្ទុកនៃសំណើនេះត្រូវបានបង្កើតដោយប្រើម៉ូឌុល from-paris-to-berlin-city-generator
។ ប្រសិនបើអ្នកក្រឡេកមើលម៉ូឌុលនេះ អ្នកនឹងឃើញថាវាសាមញ្ញណាស់ក្នុងការយល់ ហើយអ្នកអាចបង្កើតផែនទីផ្ទាល់ខ្លួនរបស់អ្នកសម្រាប់ហ្គេម! ជាចុងក្រោយ សូមចូលទៅកាន់ http://localhost:9000 ហើយកម្មវិធីរបស់អ្នកគួរតែដំណើរការ! ឥឡូវនេះ អ្នកគ្រាន់តែចុចលើការ៉េខាងស្ដាំដើម្បីលេងហ្គេម។ កុំចុចលើសញ្ញាក្រហមប្រសិនបើអ្នកចង់ឈ្នះ។ ប្រសិនបើអ្នកចង់ឃើញឧបករណ៍បំបែកសៀគ្វីនៅក្នុងសកម្មភាពនោះ សូមដំណើរការកំណត់ហេតុកម្មវិធី៖
docker logs from_paris_to_berlin_web -f
ហើយចុចលើការ៉េក្រហមឱ្យច្បាស់ដើម្បីធ្វើឱ្យបរាជ័យ។
Kystrix
គឺល្អក្នុងករណីដែលកម្មវិធីរបស់អ្នកតូច ហើយអ្នកចង់ធ្វើឱ្យប្រាកដថា រក្សាការប្រើប្រាស់ DSL ពិតជាទាប។ គុណវិបត្តិតែមួយគត់ដែលវាហាក់ដូចជាថាវាមិនផ្តល់នូវវិធីងាយស្រួលក្នុងការតុបតែងវិធីសាស្រ្តដែលត្រូវបានប៉ះពាល់ដោយឧបករណ៍បំបែកសៀគ្វី។ Resilience4J
ហាក់ដូចជាជម្រើសដ៏ល្អមួយសម្រាប់ការងាររបស់សហគ្រាសជាមួយនឹងឧបករណ៍បំបែកសៀគ្វី។ វាផ្តល់នូវការសរសេរកម្មវិធីផ្អែកលើចំណារពន្យល់ ប្រើប្រាស់អត្ថប្រយោជន៍ទាំងអស់របស់ van AOP ហើយម៉ូឌុលរបស់វាត្រូវបានបំបែកចេញពីគ្នា។ នៅក្នុងវិធីមួយ វាក៏អាចប្រើជាយុទ្ធសាស្ត្រសម្រាប់ចំណុចសំខាន់ៗនៅក្នុងកម្មវិធីផងដែរ។ វាក៏អាចត្រូវបានប្រើជាក្របខ័ណ្ឌពេញលេញ ដើម្បីគ្របដណ្តប់ទិដ្ឋភាពជាច្រើននៃកម្មវិធីមួយ។
ដោយមិនគិតពីម៉ាកដែលយើងជ្រើសរើស គោលដៅគឺតែងតែមានកម្មវិធីធន់។ នៅក្នុងអត្ថបទនេះ ខ្ញុំបានបង្ហាញពីឧទាហរណ៍មួយចំនួននៃរបៀបដែលខ្ញុំផ្ទាល់បានជួបប្រទះការស៊ើបអង្កេតឧបករណ៍បំបែកសៀគ្វី និងការរកឃើញរបស់ខ្ញុំនៅលើកម្រិតខ្ពស់បំផុត។ នេះមានន័យថា អត្ថបទនេះពិតជាត្រូវបានសរសេរឡើងសម្រាប់អ្នកដែលចង់ដឹងថា តើឧបករណ៍បំប្លែងសៀគ្វីជាអ្វី និងអ្វីដែល Limiters អាចធ្វើបាន។
លទ្ធភាពគឺពិតជាគ្មានទីបញ្ចប់នៅពេលគិតអំពីការកែលម្អកម្មវិធីរបស់យើងជាមួយនឹងយន្តការធន់ទ្រាំដូចជា circuit breakers
។ គំរូនេះអនុញ្ញាតឱ្យធ្វើការកែតម្រូវកម្មវិធីមួយ ដើម្បីប្រើប្រាស់ធនធានដែលមានឱ្យកាន់តែប្រសើរឡើងដែលយើងមាន។ ភាគច្រើននៅក្នុងពពក វានៅតែមានសារៈសំខាន់ខ្លាំងណាស់ក្នុងការបង្កើនប្រសិទ្ធភាពការចំណាយរបស់យើង និងថាតើធនធានប៉ុន្មានដែលយើងត្រូវការដើម្បីបែងចែក។
ការកំណត់រចនាសម្ព័ន្ធ CircuitBreakers
មិនមែនជាកិច្ចការតូចតាចដូចដែលវាគឺសម្រាប់ Limiters នោះទេ ហើយយើងពិតជាត្រូវយល់អំពីលទ្ធភាពនៃការកំណត់រចនាសម្ព័ន្ធទាំងអស់ ដើម្បីឈានដល់កម្រិតល្អបំផុតនៃដំណើរការ និងភាពធន់។ នេះជាហេតុផលដែលខ្ញុំមិនចង់និយាយលម្អិតនៅក្នុងអត្ថបទណែនាំនេះអំពីឧបករណ៍បំបែកសៀគ្វី។
Circuit-breakers
អាចត្រូវបានអនុវត្តចំពោះប្រភេទផ្សេងៗគ្នាជាច្រើននៃកម្មវិធី។ ប្រភេទកម្មវិធីផ្ញើសារ និងស្ទ្រីមភាគច្រើននឹងត្រូវការវា។ សម្រាប់កម្មវិធីដែលគ្រប់គ្រងបរិមាណដ៏ធំនៃទិន្នន័យដែលត្រូវការក៏អាចប្រើបានខ្ពស់ផងដែរ យើងអាច និងគួរអនុវត្តទម្រង់មួយចំនួននៃសៀគ្វីបំបែក។ ហាងលក់រាយតាមអ៊ិនធរណេតធំ ៗ ត្រូវការគ្រប់គ្រងទិន្នន័យយ៉ាងច្រើនជារៀងរាល់ថ្ងៃ ហើយកាលពីមុន Hystrix
ត្រូវបានប្រើប្រាស់យ៉ាងទូលំទូលាយ។ បច្ចុប្បន្ននេះ យើងហាក់ដូចជាកំពុងធ្វើដំណើរក្នុងទិសដៅនៃ Resilience4J
ដែលគ្របដណ្តប់ច្រើនជាងនេះទៅទៀត។
ខ្ញុំសង្ឃឹមថាអ្នកចូលចិត្តអត្ថបទនេះដូចដែលខ្ញុំបានធ្វើវា! សូមទុកការពិនិត្យឡើងវិញ មតិយោបល់ ឬមតិកែលម្អណាមួយដែលអ្នកចង់ផ្តល់ឱ្យលើសង្គមណាមួយនៅក្នុងតំណភ្ជាប់ខាងក្រោម។ ខ្ញុំពិតជាមានអំណរគុណណាស់ប្រសិនបើអ្នកចង់ជួយខ្ញុំធ្វើឱ្យអត្ថបទនេះកាន់តែប្រសើរឡើង។ ខ្ញុំបានដាក់កូដប្រភពទាំងអស់នៃកម្មវិធីនេះនៅលើ GitHub ។ សូមអរគុណសម្រាប់ការអាន!