最新のJavaテクノロジースタック上のマイクロサービスアーキテクチャ

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



エントリー



この記事では、Javaの世界で関連するテクノロジーを使用したマイクロサービスアーキテクチャの例を紹介します。その主なものを以下に示します(これらのバージョンは、発行時にプロジェクトで使用されています)。

技術の種類 役職 バージョン
プラットフォーム Jdk 11.0.1
プログラミング言語 コトリン 1.3.10
アプリケーションフレームワーク 春のフレームワーク 5.0.9
春のブーツ 2.0.5
ビルドシステム グラドル 5.0
Gradle Kotlin DSL 1.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の世界における現在のテクノロジースタックのマイクロサービスアーキテクチャ、その主要コンポーネント、およびいくつかの機能の例を検証しました。 誰かがこの資料が役に立つことを願っています。 ご清聴ありがとうございました!



参照資料






All Articles