はじめに
こんにちは。ECプラットフォーム部のAPI基盤ブロックに所属している籏野 @gold_kou と申します。普段は、GoでAPI GatewayやID基盤(認証マイクロサービス)のバックエンド開発をしています。
ZOZOでは、API Gatewayを内製しています。これまでも以下の記事を公開し、ご好評いただいております。ありがとうございます。
今回は、API Gatewayのスロットリング機能を開発しましたので、そこで得られた知見を共有いたします。ソースコードもたくさん掲載しております。マイクロサービスに興味ある方や、API Gatewayを内製する方の参考になれば幸いです。
また、本記事に掲載されているソースコードは分かりやすさを優先するため、実際とは異なる部分がありますので、あらかじめご了承ください。
スロットリングとは
スロットリングとは、何らかのリソースに対して使用量の上限を設定し、上限を超えるものについてはその使用を制限するような処理です。本記事ではリクエストに対するスロットリングを扱うため、設定した上限を超えたリクエストを制限する機能を指します。過剰な数のリクエストを制限することで、システムが停止してしまうことや一部のクライアントがリソースを占有してしまうことを防ぎます。制限方法はいくつかあります。例えば、HTTPステータスコード429を即時に返す方法です。
要件
今回開発するスロットリング機能の要件です。
クライアントタイプ毎に同時接続数で制限する
ZOZOTOWNのAPIでは外部サービスやクライアント端末のOSなどに応じて、クライアントをいくつかの分類に分けています。その分類をこの記事では「クライアントタイプ」と呼ぶこととします。このクライアントタイプ毎に「同時接続数」でリクエストを制限します。同時接続とは、API Gatewayがリクエストを転送処理している間の接続のことです。同時接続数はマイクロサービスへのリクエスト処理時に1つ増え、マイクロサービスからのレスポンス処理時に1つ減ります。
データストアを利用しない
スロットリング機能を開発しようとすると、閾値判定するための情報をサーバ間で共有するデータストアが欲しくなります。しかし、API Gatewayはデータベースなどの外部のデータストアサービスをこれまで必要としてきませんでした。そして、今後も出来る限り、使用したくありません。なぜならば、利用しない方が可用性の低下を抑えられるからです。データストアサービスを利用するとどうしても、全体の可用性は落ちてしまいます。そこで、各サーバで動作するAPI Gatewayがそれぞれのオンメモリの情報を使ってスロットリングする要件としました。この場合、サーバ間で情報を共有しないため、スロットリングが適用されるサーバとそうでないサーバが混在してしまいますが、今回は許容できる点でした。
仮に外部のデータストアを利用する設計では、Amazon ElastiCache for Redisの導入を検討していました。導入しなかったことにより、以下の問題を避けることができました。
- 可用性の低下
- インフラコストの増加
ノード数が3、キャッシュエンジンがRedis、インスタンスタイプがr6g.xlarge、料金モデルがOnDemandとします。その条件でAWS Pricing Calculatorを参考に計算すると、年間約12,000 USDの節約となります。
Canary Deploymentsに対応する
API GatewayはAmazon Elastic Kubernetes Service上に構築されており、複数のPodにデプロイされています。また、IstioのVirtualServiceにより、API GatewayをCanary Deploymentsしています。Canary Deploymentsが発生しても、変わらずにスロットリングできる必要があります。
なお、本記事ではCanary DeploymentsによりPrimaryとCanaryのそれぞれに加重がかかっている状態を「カナリアリリース状態」と呼ぶこととします。
機能の概要
スロットリング機能の処理内容は、大きく分けて、閾値の計算に必要な情報を取得する処理(図の「取得処理」)とAPIリクエスト毎に閾値判定する処理(図の「APIリクエスト処理」)に分かれます。
取得処理では、初回起動時のみ「ホスト名」と「最大の同時接続数」を取得します。また、定期的にKubernetesとIstioから「Primary/CanaryのPod数」と「Primary/Canaryへの加重比率」の情報を取得します。定期的に取得するのは、これらの値が変動するからです。
APIリクエスト処理では、リクエストを処理する際に、スロットリング対象のクライアントタイプに関して同時接続数の閾値判定をします。閾値を超していれば、転送処理をせずにクライアントへHTTPステータスコード429を即時に返します。閾値は、取得処理により取得した情報を使用して、リクエストの処理ごとに計算し直されます。計算し直すのは、閾値がインフラの状態により、変動するためです。
実装
各処理の実装とその説明です。
取得処理
以下は初回起動時のみ取得する情報です。
- 最大の同時接続数
- ホスト名Podのデプロイメント種別(Primary用あるいはCanary用)
以下は定期的に取得する情報です。
- Primary/CanaryのPod数
- Primary/Canaryへの加重比率
それぞれの取得方法について説明します。
続きはこちら