Spring CloudのRibbonによるロードバランシング
Spring Cloudとは
マイクロサービスでよく見られる共通パターンを迅速に構築するためのツール群。
- Distributed/versioned configuration (Spring Cloud Config)
- Service registration and discovery (Eureka)
- Routing (Zuul)
- Service-to-service calls (Eureka)
- Load balancing (Ribbon)
- Circuit Breakers (Hystrix)
- Global locks (Spring Cloud Cluster?)
- Leadership election and cluster state (Spring Cloud Cluster?)
- Distributed messaging (Spring Cloud Bus?)
括弧内には書いたのは対応するであろうライブラリ。
Ribbonとは
クライアントサイドロードバランサー。その名の通り、呼び出し元で呼び出し先のサービスをどのようにロードバランシングするかを定義する。 ドキュメントとしては以下のあたりを参考に。
実装例(kotlin)
getting startedをほぼそのままの実装例。以下の2つのサービスが登場人物。
- say-hello-service
GET /
-> 「Hi!」と返すだけ。ヘルスチェックのためのエンドポイントっぽい。GET /greeting
-> 挨拶を返す(挨拶の種類はランダム)
- user-service
GET /hi?name=xxx
-> 受け取ったnameに対して挨拶を返すサービス。どんな挨拶をするかはsay-hello-serviceを呼び出して決める。
user-serviceがクライアントであり、Ribbonの実装はこちらに記述する。say-hello-serviceが複数立ち上がっている状態でuser-serviceを経由して呼び出し、ロードバランシングされることが確認できればOK。
コードは https://github.com/suzukimit/spring-cloud-demo にあります。
say-hello-serviceの実装
application.yml
spring: application: name: say-hello server: port: 8090
アプリ名に say-hello
を指定。
build.gradle
dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") }
とりあえずspring-boot-starter-webがあればOK。
Controller
@RestController class SayHelloController { companion object { private val log = LoggerFactory.getLogger(SayHelloController::class.java) } @Value("\${server.port}") lateinit var port: String @RequestMapping(value = "/greeting") fun greet(): String { log.info("Access /greeting") val greetings = listOf("Hi there", "Greetings", "Salutations") val rand = Random() val randomNum = rand.nextInt(greetings.size) return greetings[randomNum] + "(from $port)" } @RequestMapping(value = "/") fun home(): String { log.info("Access /") return "Hi!" } }
/greeting
を呼び出すことで適当な挨拶を返す。ルート( /
)はクライアントサイドからのpingに応答するヘルチェック用のもので、アクセスされた際にログに出力。
user-serviceの実装
application.yml
spring: application: name: user server: port: 8888 say-hello: ribbon: eureka: enabled: false listOfServers: localhost:8090,localhost:9092,localhost:9999 ServerListRefreshInterval: 15000
ロードバランシングの対象となるサービスのアプリ名(say-hello)に対してribbonの設定を記述する。
ここではeurekaを使用をOFFにしている。そのため listOfServers
で静的にリクエスト先を指定している(eurekaを使う場合は不要と思われる)。
build.gradle
dependencies { implementation("org.springframework.cloud:spring-cloud-starter-netflix-ribbon") implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") testImplementation("org.springframework.boot:spring-boot-starter-test") } dependencyManagement { imports { mavenBom("org.springframework.cloud:spring-cloud-dependencies:Finchley.SR2") } }
spring-boot-starter-webに加え、spring-cloud-starter-netflix-ribbonが必要。
Configuration
@Configuration class RibbonConfiguration { @Bean fun ribbonPing(): IPing { return PingUrl() } @Bean fun ribbonRule(): IRule { return AvailabilityFilteringRule() } }
ping(ヘルスチェック)のurlや、ロードバランシングルールの設定など。
Controller
@RestController @RibbonClient(name = "say-hello", configuration = [RibbonConfiguration::class]) class UserController { @LoadBalanced @Bean fun restTemplate() = RestTemplate() @Autowired lateinit var restTemplate: RestTemplate @RequestMapping("/hi") fun hi(@RequestParam(value = "name", defaultValue = "Artaban") name: String): String { val greeting = this.restTemplate.getForObject("http://say-hello/greeting", String::class.java) return String.format("%s, %s!", greeting, name) } }
RestTemplateに @LoadBalanced
が付与されており、これによって http://say-hello/greeting
のようにアプリケーション名でリクエストを送ることができる。
動作確認
say-hello-service は全部で三つ立ち上げる。ポートは何も指定しないと8090になるので、コマンドライン引数なり環境変数なりで9092, 9999ポートを指定して起動する。
あとは、curlでuser-serviceにリクエストを投げ、毎回ポート番号が異なることを確認できればOK。
$ curl http://localhost:8888/hi Salutations(from 9092), Artaban!