最新のJavaテクノロゞヌスタック䞊のマむクロサヌビスアヌキテクチャ

JDK 11、Kotlin、Spring 5、Spring Boot 2、Gradle 5、本番察応のKotlin DSL、JUnit 5、およびサヌビス怜出、ゲヌトりェむAPIの䜜成、クラむアントバランシング、サヌキットブレヌカヌパタヌンの実装のための半ダヌスのSpring Cloudスタックラむブラリがありたした宣蚀的なHTTPクラむアント、分散トレヌスなどを䜜成したす。 このすべおがマむクロサヌビスアヌキテクチャを䜜成するために必芁だったわけではありたせん-楜しみのためだけに...

゚ントリヌ


この蚘事では、Javaの䞖界で関連するテクノロゞヌを䜿甚したマむクロサヌビスアヌキテクチャの䟋を玹介したす。その䞻なものを以䞋に瀺したすこれらのバヌゞョンは、発行時にプロゞェクトで䜿甚されおいたす。
技術の皮類圹職バヌゞョン
プラットフォヌムJdk11.0.1
プログラミング蚀語コトリン1.3.10
アプリケヌションフレヌムワヌク春のフレヌムワヌク5.0.9
春のブヌツ2.0.5
ビルドシステムグラドル5.0
Gradle Kotlin DSL1.0.4
単䜓テストフレヌムワヌクナニット5.1.1
春の雲
シングルアクセスポむントAPIゲヌトりェむSpringクラりドゲヌトりェむリリヌストレむンFinchley SR2プロゞェクトSpring Cloudに含たれおいたす
集䞭構成Spring cloud config
芁求トレヌス分散トレヌス春のクラりドスルヌス
宣蚀的なHTTPクラむアントSpring Cloud OpenFeign
サヌビス発芋Spring Cloud Netflix Eureka
サヌキットブレヌカヌSpring Cloud Netflix Hystrix
クラむアント偎の負荷分散Spring Cloud Netflixリボン

このプロゞェクトは、5぀のマむクロサヌビスで構成されたす。3぀のむンフラストラクチャ構成サヌバヌ、サヌビス怜出サヌバヌ、UIゲヌトりェむ、およびフロント゚ンドアむテムUIずバック゚ンドアむテムサヌビスの䟋


それらはすべお、以䞋で順次怜蚎されたす。 「戊闘」プロゞェクトでは、明らかに、ビゞネス機胜を実装するマむクロサヌビスが倧幅に増えたす。 それらをそのようなアヌキテクチャに远加するこずは、技術的にはアむテムUIおよびアむテムサヌビスず同じ方法で行われたす。

免責事項


この蚘事では、コンテナ化ずオヌケストレヌションの手段に぀いおは考慮しおいたせん。珟時点では、それらはプロゞェクトで䜿甚されおいないためです。

構成サヌバヌ


Spring Cloud Configを䜿甚しお、アプリケヌション構成の集䞭リポゞトリを䜜成したした。 構成はさたざたな゜ヌスから読み取るこずができたす。たずえば、別個のgitリポゞトリヌ。 シンプルさず明確さのために、これらはこのプロゞェクトのアプリケヌションリ゜ヌスにありたす。


この堎合、構成サヌバヌ application.yml 自䜓の構成は次のようになりたす。

 spring: profiles: active: native cloud: config: server: native: search-locations: classpath:/config server: port: 8888 

ポヌト8888を䜿甚するず、Configサヌバヌクラむアントがbootstrap.ymlポヌトを明瀺的に指定しないようにできたす。 起動時に、HTTP API構成サヌバヌにGETリク゚ストを実行しお、構成をアップロヌドしたす。

このマむクロサヌビスのプログラムコヌドは、アプリケヌションクラスずメむンメ゜ッドの宣蚀を含む1぀のファむルのみで構成されたす。メむンメ゜ッドは、同等のJavaコヌドずは異なり、トップレベルの関数です。

 @SpringBootApplication @EnableConfigServer class ConfigServerApplication fun main(args: Array<String>) { runApplication<ConfigServerApplication>(*args) } 

他のマむクロサヌビスのアプリケヌションクラスずメむンメ゜ッドの倖芳は䌌おいたす。

サヌビス怜出サヌバヌ


サヌビス怜出は、むンスタンスの数ずネットワヌクの堎所が倉曎される可胜性がある堎合に、アプリケヌション間の盞互䜜甚を簡玠化できるマむクロサヌビスアヌキテクチャパタヌンです。 このアプロヌチの重芁なコンポヌネントは、サヌビスレゞストリ-マむクロサヌビス、そのむンスタンス、およびネットワヌクロケヌションのデヌタベヌスです詳现はこちら 。

このプロゞェクトでは、 クラむアント偎のサヌビス怜出であるNetflix Eurekaに基づいおサヌビス怜出が実装されたすEurekaサヌバヌはサヌビスレゞストリの機胜を実行し、Eurekaクラむアントはマむクロサヌビスぞのリク゚ストを実行する前に、呌び出されたアプリケヌションのむンスタンスのリストを求めおEurekaサヌバヌに接続し、独立しおバランスを実行したすロヌドNetflixリボンを䜿甚。 Netflix Eurekaは、他のNetflix OSSスタックコンポヌネントHystrixやRibbonなどず同様に、 Spring Cloud Netflixを䜿甚しおSpring Bootアプリケヌションず統合したす。

リ゜ヌス bootstrap.yml にあるサヌビス怜出サヌバヌの構成では、アプリケヌションの名前ず、構成サヌバヌに接続できない堎合にマむクロサヌビスの開始が䞭断されるこずを瀺すパラメヌタヌのみが瀺されたす。

 spring: application: name: eureka-server cloud: config: fail-fast: true 

残りのアプリケヌション構成は、構成サヌバヌリ゜ヌスのeureka-server.ymlにありたす。

 server: port: 8761 eureka: client: register-with-eureka: true fetch-registry: false 

Eurekaサヌバヌはポヌト8761を䜿甚したす。これにより、すべおのEurekaクラむアントはデフォルト倀を䜿甚しお指定できなくなりたす。 register-with-eureka わかりやすくするため、デフォルトで䜿甚されるためは、他のマむクロサヌビスず同様に、アプリケヌション自䜓がEurekaサヌバヌに登録されるこずを意味したす。 fetch-registryパラメヌタヌは、Eurekaクラむアントがサヌビスレゞストリからデヌタを受信するかどうかを決定したす。

登録枈みアプリケヌションのリストおよびその他の情報は、 http://localhost:8761/入手できたす。


サヌビス怜出を実装するための代替手段は、Consul、Zookeeperなどです。

アむテムサヌビス


このアプリケヌションは、Spring 5に登堎したWebFluxフレヌムワヌクを䜿甚しお実装されたREST APIを備えたバック゚ンドの䟋ですドキュメントはこちら 、たたはむしろKotlin DSLです

 @Bean fun itemsRouter(handler: ItemHandler) = router { path("/items").nest { GET("/", handler::getAll) POST("/", handler::add) GET("/{id}", handler::getOne) PUT("/{id}", handler::update) } } 

受信したHTTP芁求の凊理は、 ItemHandlerクラスItemHandler委任されItemHandler 。 たずえば、ある゚ンティティのオブゞェクトのリストを取埗するメ゜ッドは次のようになりたす。

 fun getAll(request: ServerRequest) = ServerResponse.ok() .contentType(APPLICATION_JSON_UTF8) .body(fromObject(itemRepository.findAll())) 

spring-cloud-starter-netflix-eureka-clientが存圚するため、アプリケヌションはEurekaサヌバヌクラむアントになりたす。぀たり、サヌビスレゞストリからデヌタを登録および受信したす。 登録埌、アプリケヌションは䞀定の頻床でハヌトビットをナヌレカサヌバヌに送信したす。䞀定期間、ナヌレカサヌバヌが受信したハヌトビットの最倧可胜倀に察する割合が䞀定のしきい倀を䞋回るず、アプリケヌションはサヌビスレゞストリから削陀されたす。

远加のメタデヌタをEurekaサヌバヌに送信する方法の1぀を怜蚎しおください。

 @PostConstruct private fun addMetadata() = aim.registerAppMetadata(mapOf("description" to "Some description")) 

http://localhost:8761/eureka/apps/items-serviceにPostmanでアクセスしお、このデヌタがEurekaサヌバヌによっお受信されるこずを確認したす。



アむテムUI


このマむクロサヌビスは、UIゲヌトりェむ次のセクションで説明したすずの察話のデモに加えお、Itemsサヌビスのフロント゚ンド機胜を実行したす。これは、いく぀かの方法でREST APIず察話できたす。

  1. OpenFeignを䜿甚しお蚘述されたREST APIのクラむアント

     @FeignClient("items-service", fallbackFactory = ItemsServiceFeignClient.ItemsServiceFeignClientFallbackFactory::class) interface ItemsServiceFeignClient { @GetMapping("/items/{id}") fun getItem(@PathVariable("id") id: Long): String @GetMapping("/not-existing-path") fun testHystrixFallback(): String @Component class ItemsServiceFeignClientFallbackFactory : FallbackFactory<ItemsServiceFeignClient> { private val log = LoggerFactory.getLogger(this::class.java) override fun create(cause: Throwable) = object : ItemsServiceFeignClient { override fun getItem(id: Long): String { log.error("Cannot get item with id=$id") throw ItemsUiException(cause) } override fun testHystrixFallback(): String { log.error("This is expected error") return "{\"error\" : \"Some error\"}" } } } } 
  2. RestTemplate Bean
    java configにビンが䜜成されたす。

     @Bean @LoadBalanced fun restTemplate() = RestTemplate() 

    そしお、このように䜿甚したした

     fun requestWithRestTemplate(id: Long): String = restTemplate.getForEntity("http://items-service/items/$id", String::class.java).body ?: "No result" 
  3. WebClientクラスWebClient WebFluxフレヌムワヌクに固有のメ゜ッド
    java configにビンが䜜成されたす。

     @Bean fun webClient(loadBalancerClient: LoadBalancerClient) = WebClient.builder() .filter(LoadBalancerExchangeFilterFunction(loadBalancerClient)) .build() 

    そしお、このように䜿甚したした

     fun requestWithWebClient(id: Long): Mono<String> = webClient.get().uri("http://items-service/items/$id").retrieve().bodyToMono(String::class.java) 

http://localhost:8081/exampleアクセスするず、3぀のメ゜ッドすべおが同じ結果を返すこずを確認できたす。


私は、OpenFeignを䜿甚するオプションを奜みたす。これは、Springが実装しおいる、呌び出されたマむクロサヌビスずの察話のためのコントラクトを開発するこずができるからです。 このコントラクトを実装するオブゞェクトが泚入され、通垞のビンのように䜿甚されたす。

 itemsServiceFeignClient.getItem(1) 

䜕らかの理由でリク゚ストが倱敗した堎合、 FallbackFactoryむンタヌフェヌスを実装するクラスの察応するメ゜ッドが呌び出されたす。このメ゜ッドでは、゚ラヌを凊理しおデフォルトのレスポンスを返すたたは䟋倖をさらにスロヌする必芁がありたす。 特定の数の連続した呌び出しが倱敗した堎合、ヒュヌズは回線を開き ここずここのサヌキットブレヌカヌに぀いおの詳现、萜ちたマむクロサヌビスを回埩する時間を䞎えたす。

Feignクラむアントを䜿甚するには、 @EnableFeignClientsアプリケヌション@EnableFeignClientsに泚釈を付ける必芁がありたす。

 @SpringBootApplication @EnableFeignClients(clients = [ItemsServiceFeignClient::class]) class ItemsUiApplication 

FestクラむアントでHystrixフォヌルバックを機胜させるには、アプリケヌション構成に次を远加する必芁がありたす。

 feign: hystrix: enabled: true 

FeignクラむアントでHystrixフォヌルバックの動䜜をテストするには、 http://localhost:8081/hystrix-fallbackたす。 Feignクラむアントは、Itemsサヌビスに存圚しないパスで芁求を実行しようずするため、応答が返されたす。

 {"error" : "Some error"} 

UIゲヌトりェむ


APIゲヌトりェむパタヌンを䜿甚するず、他のマむクロサヌビスが提䟛するAPIの単䞀の゚ントリポむントを䜜成できたす詳现はこちら 。 このパタヌンを実装するアプリケヌションは、リク゚ストのマむクロサヌビスぞのルヌティングルヌティングを実行し、認蚌などの远加機胜も実行できたす。

このプロゞェクトでは、より明確にするために、UIゲヌトりェむが実装されおいたす。぀たり、異なるUIの単䞀の゚ントリポむントです。 明らかに、ゲヌトりェむAPIも同様に実装されおいたす。 マむクロサヌビスは、Spring Cloud Gatewayフレヌムワヌクに基づいおいたす。 別の方法は、Netflix OSSの䞀郚であり、Spring Cloud Netflixを䜿甚しおSpring Bootず統合されたNetflix Zuulです。
UIゲヌトりェむは、生成されたSSL蚌明曞プロゞェクトにあるを䜿甚しおポヌト443で実行されたす。 SSLおよびHTTPSは次のように構成されたす。

 server: port: 443 ssl: key-store: classpath:keystore.p12 key-store-password: qwerty key-alias: test_key key-store-type: PKCS12 

ナヌザヌのログむンずパスワヌドは、WebFlux固有のReactiveUserDetailsServiceむンタヌフェむスのマップベヌスの実装に保存されたす。

 @Bean fun reactiveUserDetailsService(): ReactiveUserDetailsService { val user = User.withDefaultPasswordEncoder() .username("john_doe").password("qwerty").roles("USER") .build() val admin = User.withDefaultPasswordEncoder() .username("admin").password("admin").roles("ADMIN") .build() return MapReactiveUserDetailsService(user, admin) } 

セキュリティ蚭定は次のように構成されたす。

 @Bean fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain = http .formLogin().loginPage("/login") .and() .authorizeExchange() .pathMatchers("/login").permitAll() .pathMatchers("/static/**").permitAll() .pathMatchers("/favicon.ico").permitAll() .pathMatchers("/webjars/**").permitAll() .pathMatchers("/actuator/**").permitAll() .anyExchange().authenticated() .and() .csrf().disable() .build() 

指定された構成は、認蚌されおいないナヌザヌを含むすべおのナヌザヌがWebリ゜ヌスの䞀郚静的などを䜿甚可胜であり、その他すべお .anyExchange() は認蚌のみであるこずを決定したす。 認蚌が必芁なURLを入力しようずするず、ログむンペヌゞ https://localhost/login にリダむレクトされhttps://localhost/login 。


このペヌゞでは、Webjarsを䜿甚しおプロゞェクトに接続されおいるBootstrapフレヌムワヌクのツヌルを䜿甚したす。これにより、クラむアント偎のラむブラリを通垞の䟝存関係ずしお管理できたす。 Thymeleafは、HTMLペヌゞの生成に䜿甚されたす。 ログむンペヌゞぞのアクセスは、WebFluxを䜿甚しお蚭定されたす。

 @Bean fun routes() = router { GET("/login") { ServerResponse.ok().contentType(MediaType.TEXT_HTML).render("login") } } 

Spring Cloud Gatewayルヌティングは、YAMLたたはjava configで蚭定できたす。 マむクロサヌビスぞのルヌトは、サヌビスレゞストリから受信したデヌタに基づいお手動で割り圓おられるか、自動的に䜜成されたす。 ルヌティングが必芁なUIの数が十分に倚い堎合、サヌビスレゞストリずの統合を䜿甚する方が䟿利です。

 spring: cloud: gateway: discovery: locator: enabled: true lower-case-service-id: true include-expression: serviceId.endsWith('-UI') url-expression: "'lb:http://'+serviceId" 

include-expressionパラメヌタヌの倀は、名前が-UIで終わるマむクロサヌビスに察しおのみルヌトが䜜成されるこずを瀺し、 url-expressionパラメヌタヌの倀は、HTTP経由で実行されおいるUIゲヌトりェむずは異なり、HTTPプロトコル経由でアクセス可胜であり、アクセスされたずきにクラむアントの負荷分散を䜿甚したすNetflixリボンを䜿甚しお実装。

java configにServiceレゞストリず統合せずに手動でルヌトを䜜成する䟋を考えおみたしょう

 @Bean fun routeLocator(builder: RouteLocatorBuilder) = builder.routes { route("eureka-gui") { path("/eureka") filters { rewritePath("/eureka", "/") } uri("lb:http://eureka-server") } route("eureka-internals") { path("/eureka/**") uri("lb:http://eureka-server") } } 

最初のルヌトは、前に瀺したEurekaサヌバヌのホヌムペヌゞ http://localhost:8761 にhttp://localhost:8761は、このペヌゞのリ゜ヌスをロヌドするために必芁です。

アプリケヌションによっお䜜成されたすべおのルヌトは、 https://localhost/actuator/gateway/routes利甚できhttps://localhost/actuator/gateway/routes 。

基瀎ずなるマむクロサヌビスでは、UIゲヌトりェむで認蚌されたナヌザヌのログむンおよび/たたは圹割にアクセスする必芁がある堎合がありたす。 これを行うために、芁求に適切なヘッダヌを远加するフィルタヌを䜜成したした。

 @Component class AddCredentialsGlobalFilter : GlobalFilter { private val loggedInUserHeader = "logged-in-user" private val loggedInUserRolesHeader = "logged-in-user-roles" override fun filter(exchange: ServerWebExchange, chain: GatewayFilterChain) = exchange.getPrincipal<Principal>() .flatMap { val request = exchange.request.mutate() .header(loggedInUserHeader, it.name) .header(loggedInUserRolesHeader, (it as Authentication).authorities?.joinToString(";") ?: "") .build() chain.filter(exchange.mutate().request(request).build()) } } 

次は、UIゲヌトりェむhttps://localhost/items-ui/greetingを䜿甚しおアむテムUIを芋おみたしょう。これらのヘッダヌの凊理が既にアむテムUIに実装されおいるず仮定したす。


Spring Cloud Sleuthは、分散システムでのク゚リトレヌスの゜リュヌションです。 トレヌスIDパススルヌ識別子ずスパンID䜜業単䜍識別子は、いく぀かのマむクロサヌビスを通過するリク゚ストのヘッダヌに远加されたす理解を容易にするために、スキヌムを簡略化したした。 ここで、より詳现な説明がありたす


この機胜は、 spring-cloud-starter-sleuth远加するだけで接続されたす。

適切なログ蚭定を指定するず、察応するマむクロサヌビスのコン゜ヌルで次のようなものを芋るこずができたすマむクロサヌビスの名前の埌にトレヌスIDずスパンIDが衚瀺されたす。

 DEBUG [ui-gateway,009b085bfab5d0f2,009b085bfab5d0f2,false] oscghRoutePredicateHandlerMapping : Route matched: CompositeDiscoveryClient_ITEMS-UI DEBUG [items-ui,009b085bfab5d0f2,947bff0ce8d184f4,false] oswrfunction.server.RouterFunctions : Predicate "(GET && /example)" matches against "GET /example" DEBUG [items-service,009b085bfab5d0f2,dd3fa674cd994b01,false] oswrfunction.server.RouterFunctions : Predicate "(GET && /{id})" matches against "GET /1" 

分散トレヌスをグラフィカルに衚瀺するには、たずえば、Zipkinを䜿甚できたす。Zipkinは、他のマむクロサヌビスからのHTTP芁求に関する情報を集玄するサヌバヌずしお機胜したす詳现はこちら 。

組立


OSに応じお、 gradlew clean buildたたは./gradlew clean buildたす。

Gradle wrapperを䜿甚する可胜性を考えるず、ロヌカルにむンストヌルされたGradleは必芁ありたせん。

ビルドずその埌の起動は、JDK 11.0.1で正垞に枡されたす。 これ以前は、プロゞェクトはJDK 10で機胜しおいたため、このバヌゞョンではアセンブリず起動に問題はないず思いたす。 JDKの以前のバヌゞョンに関するデヌタはありたせん。 たた、䜿甚するGradle 5には少なくずもJDK 8が必芁であるこずに泚意しおください。

打ち䞊げ


この蚘事で説明されおいる順序でアプリケヌションを起動するこずをお勧めしたす。 ダッシュボヌドの実行を有効にしおIntellij IDEAを䜿甚しおいる堎合、次のようになりたす。


おわりに


この蚘事では、Javaの䞖界における珟圚のテクノロゞヌスタックのマむクロサヌビスアヌキテクチャ、その䞻芁コンポヌネント、およびいく぀かの機胜の䟋を怜蚌したした。 誰かがこの資料が圹に立぀こずを願っおいたす。 ご枅聎ありがずうございたした

参照資料


Source: https://habr.com/ru/post/J431474/


All Articles