IBM TechXchange Japan WebSphere User Group (日本WebSphereユーザーグループ)

 View Only

マイクロサービス・アプリケーションのパフォーマンス向上に関するベストプラクティス

By KAORI ASADA posted Thu August 18, 2022 03:25 AM

  
(このBlogはBest Practices for Improving Performance Throughput in Microservices Applicationsの翻訳記事です)

著者:Joe McClur

このブログでは、マイクロサービスを使用するアプリケーションのスループットを最適化したベストプラクティスをご紹介します。特に、サービス間通信と JSON 処理のに焦点を当てています。


サービス間の通信


マイクロサービスでは、サービス間で RESTful  APIを実行することが多いですが、これがパフォーマンスのボトルネックになる可能性があります。 呼び出しを行うクライアントが正しく構成されていることを確認する必要があります。

例 1: MicroProfile Rest クライアント

MicroProfile Rest クライアントにおけるベスト・プラクティスは単に @ApplicationScopedを作成することです。 デフォルトでは、 MicroProfileの Rest クライアントのスコープは@Dependentです。これを例えばJakarta RESTfulエンドポイントに注入すると、 Jakarta RESTful クラスのスコープ(デフォルトは @RequestScoped)を継承します。

このようにデフォルトのスコープを使うと、毎回新しい MicroProfile Rest クライアントが作成されてしまって、 その都度CPU コストがかかり、クラスの読み込みのオーバーヘッドが大きくなって処理速度が低下します。MicroProfile Rest クライアントを@ApplicationScopedで作成すれば、クライアントは一度だけ作成され、コストを節約することができます。
 

* : Jakarta RESTful クラスの実際のデフォルト・スコープは、少々分かりづらいのですが、 @RequestScoped と同等のものと考えられるでしょう。読者のみなさんのマイクロサービス・アプリケーションがステートレスであることを考えると、Jakarta RESTfulクラスを @ApplicationScopedで作ることもパフォーマンス向上の効果があると思います。

上記の説明では、分かりづらいかもしれないので、もう少しはっきりした具体例を下記にご紹介します。

この例では、 Apache JMeterから 、server1上のアプリケーション ( 下記のクライアントのコードがあるアプリケーション ) にリクエストを送って、負荷をかけます。 このアプリケーションは、 server2でホストされている別のマイクロサービスを呼び出します。

 

ケース 1 - デフォルトのスコープを使う場合

ここでは、 MicroProfile Rest クライアントを注入し、 server1 から server2への呼び出しを行う REST エンドポイントのコードを示します。

@Path("/mp-restclient-test1")
public class MicroProfileRestClientTest1 {

  @Inject @RestClient
  private DefaultRestClient defaultClient;

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String ping() throws Exception {
    String returnString = defaultClient.ping();
    defaultClient.close();
    return returnString;
  }
}

以下に、 MicroProfile Rest クライアント・インターフェースを示します。

@Path("/")
@RegisterRestClient(configKey="defaultRestClient")
public interface DefaultRestClient extends AutoCloseable {

  @GET
  @Path("/endpoint")
  @Produces(MediaType.TEXT_PLAIN)
  public String ping();
}

ケース 2 – ApplicationScopedを使う場合

この場合、 REST エンドポイントは類似していますが、すべての呼び出し後にクライアントをクローズする必要がありません、というのもスコープの範囲外になることがないからです。

@Path("/mp-restclient-test2")
public class MicroProfileRestClientTest2 {

  @Inject @RestClient
  private AppScopedRestClient appScopedClient;
 
  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String ping() {
    return appScopedClient.ping();
  }
}

以下に、 MicroProfile Rest クライアントを示します。@ApplicationScoped アノテーションが付いているのにご注目ください。

@ApplicationScoped
@Path("/")
@RegisterRestClient(configKey="appScopedRestClient")
public interface AppScopedRestClient {

  @GET
  @Path("/endpoint")
  @Produces(MediaType.TEXT_PLAIN)
  public String ping();
}

パフォーマンスの違いを以下に示します。 これは単純/最良のケース・シナリオですが、ApplicationScoped は デフォルトのスコープに比べて373% も高速です。



2: Jakarta RESTfulクライアント

通常の Jakarta RESTful クライアントを使用している場合は、WebTarget を作成してキャッシュに入れ、すべての呼び出しで WebTarget を再作成しないようにします。上記の例と同様に、余分な CPU コストとクラス読み込みを回避することにより、パフォーマンスを向上できます。 以下に、その違いを示す例を示します。

ケース 1 - デフォルト

@Path("/client-test1")
public class ClientTestDefault {

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String ping() {

    Client client = ClientBuilder.newBuilder().build();
    WebTarget webTarget = client
      .target("http://localhost:9081/endpoint");
    String output = webTarget.request().get(String.class);
    client.close();
    return output;
  }
}

 

ケース 2 - キャッシュされた Web ターゲット

上の例と似ていますが、WebTarget をstaticで作ることにより、再利用でき、クライアントを閉じる必要がありません。Inovation.Builderをキャッシュすることも出来ますが、私が実験したところでは、WebTargetを使うのが一番パフォーマンス向上の効果がありました。

@Path("/client-test2")
public class ClientTestCached {

  private static WebTarget cachedWebTarget =
    ClientBuilder.newBuilder().build()
    .target("http://localhost:9081/endpoint");

  @GET
  @Produces(MediaType.TEXT_PLAIN)
  public String ping() {
    return cachedWebTarget.request().get(String.class);
  }
}

この場合も、パフォーマンスが大きく差が 210% 向上します。

 

JSON

 

マイクロサービスアプリケーションでボトルネックが発生しているもう一つの場所は、JSON処理です。特にJSONReader、JSONWriter、およびJSONObjectBuilderオブジェクトの作成の仕方がパフォーマンスに影響します。ベストプラクティスは、最初にファクトリを作成してキャッシュし、次にそのファクトリからリーダー、ライター、またはオブジェクトビルダーを作成することです。

下記は、JSONObjectBuilderを使用した例です。

Json.createメソッド(ケース1)を使用すると、毎回新しいファクトリが検索および作成されます。キャッシュされたファクトリを使用することで(ケース2)、JSONファクトリ実装の検索をスキップし、ファクトリを1回だけ作成して割り当てるため、かなりの時間を節約できます。

ケース1-デフォルト

@Path("/json-test1")
public class JsonTest1 {

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public JsonObject ping() {
    JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder();
    return jsonObjectBuilder.add("example", "example").build();
  }
}

ケース2キャッシュされたファクトリ
 
同様ですが、ここでは静的ファクトリを作成し、そのファクトリからObjectBuilderを作成します。
@Path("/json-test2")
public class JsonTest2 {

  private static final JsonBuilderFactory jsonBuilderFactory = Json.createBuilderFactory(null);

  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public JsonObject ping() {
    JsonObjectBuilder jsonObjectBuilder = jsonBuilderFactory.createObjectBuilder();
    return jsonObjectBuilder.add("example", "example").build();
  }
}

パフォーマンスの節約は、上記のクライアントの場合ほど劇的ではありませんが、21%はかなりのパフォーマンス向上です。


上記からわかるように、アプリケーションにいくつかの簡単な変更を加えると、スループットが大幅に向上します。

サービス間呼び出しとJSON処理に焦点を当てたこのブログ投稿がお役に立てば幸いです。

#WebSphere-performance

#Liberty

#OpenLiberty

#MicroProfile

#microservices​​


0 comments
19 views

Permalink