paint-brush
ពីប៉ារីសទៅប៊ែរឡាំង: របៀបបង្កើតសៀគ្វីបំបែកនៅ Kotlinដោយ@jesperancinha
420 ការអាន
420 ការអាន

ពីប៉ារីសទៅប៊ែរឡាំង: របៀបបង្កើតសៀគ្វីបំបែកនៅ Kotlin

ដោយ João Esperancinha26m2025/01/23
Read on Terminal Reader

យូរ​ពេក; អាន

ឧបករណ៍បំប្លែងសៀគ្វីត្រូវបានប្រើប្រាស់នាពេលបច្ចុប្បន្ននេះ ដើម្បីជៀសវាងការស្នើសុំច្រើនពេកដែលត្រូវបានធ្វើឡើងចំពោះសេវាកម្មមួយ ឬផ្សេងទៀតដែលមិនឆ្លើយតប។ នៅពេលដែលឧទាហរណ៍សេវាមួយបិទ ទោះជាមូលហេតុអ្វីក៏ដោយ ឧបករណ៍បំបែកសៀគ្វីគួរតែដូចឈ្មោះរបស់វា បំបែកសៀគ្វី។ នៅក្នុងអត្ថបទនេះ យើងនឹងពិនិត្យមើលវិធីបីយ៉ាងនៃការអនុវត្តកម្មវិធីបំបែកសៀគ្វីដោយប្រើការអនុវត្តដែលមានមូលដ្ឋានលើ AOP ។
featured image - ពីប៉ារីសទៅប៊ែរឡាំង: របៀបបង្កើតសៀគ្វីបំបែកនៅ Kotlin
João Esperancinha HackerNoon profile picture
0-item

1. សេចក្តីផ្តើម

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

ដ្យាក្រាម

2. ការត្រួតពិនិត្យរថយន្ត

នៅក្នុងចំណុចចុងក្រោយនៃអត្ថបទនេះ យើងនឹងពិនិត្យមើលហ្គេមប្រណាំងឡាន។ ទោះយ៉ាងណាក៏ដោយ មុននឹងយើងទៅដល់ទីនោះ ខ្ញុំចង់ណែនាំអ្នកអំពីទិដ្ឋភាពមួយចំនួននៃការបង្កើតកម្មវិធីដែលដំណើរការជាមួយ circuit breaker

២.១. Kystrix (ពី-ប៉ារីស-ទៅ-ប៊ែរលីន-kystrix-runnable-app)

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 ហាក់ដូចជាដំណើរការលើមូលដ្ឋានអតិថិជន។ នេះមានន័យថា យើងត្រូវប្រកាសកូដរបស់យើង មុនពេលធ្វើការស្នើសុំទៅកាន់សេវាកម្មដែលនៅពីក្រោយសេវាកម្មចម្បងរបស់យើង។

២.២. ភាពធន់ 4J

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 និង No Spring framework (ពី-paris-to-berlin-resilience4j-runnable-app)

នៅក្នុងម៉ូឌុលនេះ ខ្ញុំបានសម្រេចចិត្តប្រើបណ្ណាល័យ 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 ទៅកាន់ចំណុចបញ្ចប់ដែលមិនបរាជ័យ។ នេះ​ជា​មូលហេតុ​ដែល​រដ្ឋ​ត្រូវ​បាន​គេ​និយាយ​ថា​បើក​ចំហ​ពាក់កណ្តាល។

២.២.២. Resilience4J ជាមួយ Spring Boot និង No AOP (ពី-paris-to-berlin-resilience4j-spring-app)

នៅក្នុងផ្នែកមុន យើងបានឃើញពីរបៀបអនុវត្តវាតាមវិធីដែលមានលក្ខណៈជាកម្មវិធី ដោយមិនប្រើបច្ចេកវិទ្យា 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 យ៉ាងងាយស្រួល។

3. ករណី

រហូតមកដល់ពេលនេះ យើងបានឃើញវិធីសំខាន់ៗចំនួនបីក្នុងការអនុវត្ត CircuitBreakers និងឧបករណ៍កំណត់ដែលពាក់ព័ន្ធ។ លើសពីនេះ យើងនឹងពិនិត្យមើលវិធីដែលខ្ញុំចូលចិត្តបំផុតក្នុងការអនុវត្តឧបករណ៍បំបែកសៀគ្វីដោយឆ្លងកាត់កម្មវិធីដែលខ្ញុំបានបង្កើត ដែលជាហ្គេមសាមញ្ញបំផុតដែលយើងគ្រាន់តែចុចលើការ៉េដើម្បីចេញពីទីក្រុងប៉ារីសទៅកាន់ទីក្រុងប៊ែរឡាំង។ ហ្គេមនេះត្រូវបានបង្កើតឡើងដើម្បីយល់ពីរបៀបអនុវត្ត។ វាមិននិយាយច្រើនអំពីកន្លែងដែលត្រូវអនុវត្តនេះទេ។ វាគ្រាន់តែជាករណីដែលខ្ញុំបានបង្កើតឡើងដើម្បីចែករំលែកជាមួយអ្នកនូវចំណេះដឹង។


ចំណេះដឹងពេលខ្ញុំទុកអោយអ្នកសម្រេចចិត្តពេលក្រោយ។ សំខាន់យើងចង់បង្កើតរថយន្តមួយចំនួន និងបង្កើតផ្លូវទៅកាន់ទីក្រុងប៊ែរឡាំង។ នៅក្នុងទីតាំងផ្សេងៗគ្នានៅក្នុងផ្លូវនេះ យើងនឹងទៅដល់ទីក្រុងនានាដែលចៃដន្យ យើងនឹងបង្កើតបញ្ហា។ circuit breakers របស់យើងនឹងសម្រេចថាតើយើងត្រូវរង់ចាំរយៈពេលប៉ុន្មាន មុនពេលដែលយើងត្រូវបានអនុញ្ញាតឱ្យបន្តដំណើរទៅមុខទៀត។ ចំណែក​រថយន្ត​ផ្សេង​ទៀត​មិន​មាន​បញ្ហា​ទេ ហើយ​យើង​គ្រាន់​តែ​ជ្រើសរើស​ផ្លូវ​ត្រូវ​ប៉ុណ្ណោះ។


យើង​ត្រូវ​បាន​អនុញ្ញាត​ឱ្យ​ពិនិត្យ​មើល​តារាង​ពេលវេលា​ដែល​វា​ត្រូវ​បាន​ចុះ​ឈ្មោះ​នៅ​ពេល​ដែល​បញ្ហា​ជាក់លាក់​មួយ​នឹង​កើត​ឡើង​ក្នុង​ទីក្រុង​នៅ​លើ​សញ្ញា​នាទី​ជាក់លាក់។ សញ្ញានាទីមានសុពលភាពក្នុង 0 ទីតាំងដែលបានធ្វើលិបិក្រមរបស់វា។ នេះមានន័យថា 2 មានន័យថា every 2, 12, 22, 32, 42, 52 នាទីនៅលើនាឡិកានឹងមានសុពលភាពដើម្បីបង្កើតបញ្ហានេះ។ បញ្ហានឹងមានពីរប្រភេទ៖ ERROR និង TIMEOUT ។ ការបរាជ័យកំហុសនឹងផ្តល់ឱ្យអ្នកនូវការពន្យារពេល 20 វិនាទី។


ការអស់ពេលនឹងផ្តល់ឱ្យអ្នកនូវការពន្យារពេល 50 វិនាទី។ សម្រាប់ការផ្លាស់ប្តូរទីក្រុង អ្នកគ្រប់គ្នាត្រូវរង់ចាំ 10 វិនាទី។ ទោះយ៉ាងណាក៏ដោយ មុននឹងរង់ចាំ រថយន្តគឺរួចហើយនៅច្រកចូលទីក្រុងខាងក្រោម នៅពេលដែលវាត្រូវបានធ្វើនៅក្នុងវិធីសាស្រ្តធ្លាក់ចុះ។ ក្នុងករណីនេះទីក្រុងបន្ទាប់ត្រូវបានជ្រើសរើសដោយចៃដន្យ។

ទំព័រហ្គេម

4. ការអនុវត្ត

យើងបានឃើញពីមុនមក របៀបកំណត់រចនាសម្ព័ន្ធបញ្ជីឈ្មោះ 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>

AOPAspect 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

ហើយ​ចុច​លើ​ការ៉េ​ក្រហម​ឱ្យ​ច្បាស់​ដើម្បី​ធ្វើ​ឱ្យ​បរាជ័យ។

5. តើ Kystrix និង Resilience4J ខុសគ្នាយ៉ាងណា

Kystrix គឺល្អក្នុងករណីដែលកម្មវិធីរបស់អ្នកតូច ហើយអ្នកចង់ធ្វើឱ្យប្រាកដថា រក្សាការប្រើប្រាស់ DSL ពិតជាទាប។ គុណវិបត្តិតែមួយគត់ដែលវាហាក់ដូចជាថាវាមិនផ្តល់នូវវិធីងាយស្រួលក្នុងការតុបតែងវិធីសាស្រ្តដែលត្រូវបានប៉ះពាល់ដោយឧបករណ៍បំបែកសៀគ្វី។ Resilience4J ហាក់ដូចជាជម្រើសដ៏ល្អមួយសម្រាប់ការងាររបស់សហគ្រាសជាមួយនឹងឧបករណ៍បំបែកសៀគ្វី។ វាផ្តល់នូវការសរសេរកម្មវិធីផ្អែកលើចំណារពន្យល់ ប្រើប្រាស់អត្ថប្រយោជន៍ទាំងអស់របស់ van AOP ហើយម៉ូឌុលរបស់វាត្រូវបានបំបែកចេញពីគ្នា។ នៅក្នុងវិធីមួយ វាក៏អាចប្រើជាយុទ្ធសាស្ត្រសម្រាប់ចំណុចសំខាន់ៗនៅក្នុងកម្មវិធីផងដែរ។ វាក៏អាចត្រូវបានប្រើជាក្របខ័ណ្ឌពេញលេញ ដើម្បីគ្របដណ្តប់ទិដ្ឋភាពជាច្រើននៃកម្មវិធីមួយ។

6. សេចក្តីសន្និដ្ឋាន

ដោយមិនគិតពីម៉ាកដែលយើងជ្រើសរើស គោលដៅគឺតែងតែមានកម្មវិធីធន់។ នៅក្នុងអត្ថបទនេះ ខ្ញុំបានបង្ហាញពីឧទាហរណ៍មួយចំនួននៃរបៀបដែលខ្ញុំផ្ទាល់បានជួបប្រទះការស៊ើបអង្កេតឧបករណ៍បំបែកសៀគ្វី និងការរកឃើញរបស់ខ្ញុំនៅលើកម្រិតខ្ពស់បំផុត។ នេះមានន័យថា អត្ថបទនេះពិតជាត្រូវបានសរសេរឡើងសម្រាប់អ្នកដែលចង់ដឹងថា តើឧបករណ៍បំប្លែងសៀគ្វីជាអ្វី និងអ្វីដែល Limiters អាចធ្វើបាន។


លទ្ធភាពគឺពិតជាគ្មានទីបញ្ចប់នៅពេលគិតអំពីការកែលម្អកម្មវិធីរបស់យើងជាមួយនឹងយន្តការធន់ទ្រាំដូចជា circuit breakers ។ គំរូនេះអនុញ្ញាតឱ្យធ្វើការកែតម្រូវកម្មវិធីមួយ ដើម្បីប្រើប្រាស់ធនធានដែលមានឱ្យកាន់តែប្រសើរឡើងដែលយើងមាន។ ភាគច្រើននៅក្នុងពពក វានៅតែមានសារៈសំខាន់ខ្លាំងណាស់ក្នុងការបង្កើនប្រសិទ្ធភាពការចំណាយរបស់យើង និងថាតើធនធានប៉ុន្មានដែលយើងត្រូវការដើម្បីបែងចែក។


ការកំណត់រចនាសម្ព័ន្ធ CircuitBreakers មិនមែនជាកិច្ចការតូចតាចដូចដែលវាគឺសម្រាប់ Limiters នោះទេ ហើយយើងពិតជាត្រូវយល់អំពីលទ្ធភាពនៃការកំណត់រចនាសម្ព័ន្ធទាំងអស់ ដើម្បីឈានដល់កម្រិតល្អបំផុតនៃដំណើរការ និងភាពធន់។ នេះ​ជា​ហេតុផល​ដែល​ខ្ញុំ​មិន​ចង់​និយាយ​លម្អិត​នៅ​ក្នុង​អត្ថបទ​ណែនាំ​នេះ​អំពី​ឧបករណ៍​បំបែក​សៀគ្វី។


Circuit-breakers អាចត្រូវបានអនុវត្តចំពោះប្រភេទផ្សេងៗគ្នាជាច្រើននៃកម្មវិធី។ ប្រភេទកម្មវិធីផ្ញើសារ និងស្ទ្រីមភាគច្រើននឹងត្រូវការវា។ សម្រាប់កម្មវិធីដែលគ្រប់គ្រងបរិមាណដ៏ធំនៃទិន្នន័យដែលត្រូវការក៏អាចប្រើបានខ្ពស់ផងដែរ យើងអាច និងគួរអនុវត្តទម្រង់មួយចំនួននៃសៀគ្វីបំបែក។ ហាងលក់រាយតាមអ៊ិនធរណេតធំ ៗ ត្រូវការគ្រប់គ្រងទិន្នន័យយ៉ាងច្រើនជារៀងរាល់ថ្ងៃ ហើយកាលពីមុន Hystrix ត្រូវបានប្រើប្រាស់យ៉ាងទូលំទូលាយ។ បច្ចុប្បន្ននេះ យើងហាក់ដូចជាកំពុងធ្វើដំណើរក្នុងទិសដៅនៃ Resilience4J ដែលគ្របដណ្តប់ច្រើនជាងនេះទៅទៀត។

6. ឯកសារយោង

សូមអរគុណ!

ខ្ញុំសង្ឃឹមថាអ្នកចូលចិត្តអត្ថបទនេះដូចដែលខ្ញុំបានធ្វើវា! សូមទុកការពិនិត្យឡើងវិញ មតិយោបល់ ឬមតិកែលម្អណាមួយដែលអ្នកចង់ផ្តល់ឱ្យលើសង្គមណាមួយនៅក្នុងតំណភ្ជាប់ខាងក្រោម។ ខ្ញុំពិតជាមានអំណរគុណណាស់ប្រសិនបើអ្នកចង់ជួយខ្ញុំធ្វើឱ្យអត្ថបទនេះកាន់តែប្រសើរឡើង។ ខ្ញុំបានដាក់កូដប្រភពទាំងអស់នៃកម្មវិធីនេះនៅលើ GitHub ។ សូមអរគុណសម្រាប់ការអាន!

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

ព្យួរស្លាក

អត្ថបទនេះត្រូវបានបង្ហាញនៅក្នុង...