Spring Cloud
Josh Longのセッションをなぞってみた。
Config-Server
プロパティをアプリケーションの外に設定する。 gitリポジトリー(GitHub,Gitbucket...)にyml、もしくは propertiesファイルで設定できる。
git リポジトリーの作成
Josh のリポジトリーをcloneして使う。
$ git clone git@github.com:joshlong/bootiful-microservices-config.git Cloning into 'bootiful-microservices-config'... remote: Counting objects: 158, done. remote: Compressing objects: 100% (9/9), done.
config-server を作成
http://start.spring.io/で、Config-Serverにチェックして、Download。
DemoApplication.java
に@EnableConfigServer
を追加
@EnableConfigServer @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
application.properties
にconfig−serverがlistenするportと変数を定義するgitリポジトリを定義する
server.port=8888 spring.cloud.config.server.git.uri=${CONFIG_REPO:${HOME}/bootiful/bootiful-microservices-config}
起動してConfig-Serverはできあがり。
http://localhost:8888/reservation-service/masterにアクセスすると、gitに登録してある情報を参照できる。
http://localhost:8888/${file_name}/${branch_name} で他のファイルやブランチにアクセスできる。
$ ls
application-cloud.properties eureka-service.properties reservation-client.properties
application.properties hystrix-dashboard.properties reservation-service.properties
auth-service.properties reservation-client-github.properties
今回は8つのファイルを登録しているので、下記のURLでアクセスできる。 http://localhost:8888/application-cloud/master http://localhost:8888/eureka-service/master http://localhost:8888/reservation-client/master http://localhost:8888/application/master http://localhost:8888/hystrix-dashboard/master http://localhost:8888/reservation-service/master http://localhost:8888/auth-service/master http://localhost:8888/reservation-client-github/master
Config-Client
Reservation-Service
をConfig-Serverにする。
pom.xml
に依存関係を定義
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency>
MessageRestController
を作成する。
gitにコミットした値をすぐにInjectionして反映させるため、
@RefreshScope
アノテーションをつける。
@RefreshScope @RestController class MessageRestController { @Value("${message}") private String message; @RequestMapping("/message") String msg() { return this.message; } }
applicationが起動する前に設定を定義するため
bootstrap.properties
にconfig-server の場所と、アプリケーションの名前を定義する。
spring.cloud.config.uri=${CONFIG_SERVER:http://localhost:8888} spring.application.name=reservation-service
config-server (gitリポジトリー)には下記のように登録されているので、portは8000で起動し、 messageに"konnichiwa JSUG!!!"がインジェクションされる。
{ name: "/Users/setoguchi/bootiful/bootiful-microservices-config/reservation-service.properties", source: { server.port: "8000", spring.cloud.stream.bindings.input: "reservations", message: "konnichiwa JSUG!!!" } }
Reservation-service (config-client)を起動してアクセス。
reservation-service.properties
のmessageを変更
$ vi reservation-service.properties server.port=8000 message = Hello Config-Server! $ git commit -a -m update\ message [master dfe712c] update message 1 file changed, 1 insertion(+), 1 deletion(-)
config-serverにアクセスすると、変更が反映されているのがわかる。
Reservation-serviceにアクセスすると
変わっていない!?
アプリケーションに以下のコマンドで変更を知らせる必要がある。
$ curl -d {} http://localhost:8000/refresh ["message"]
無事変更が反映された。
変更を通知するアプリケーションがたくさんある場合、
/bus/refresh
とすればすべてに通知されるらしいけど、まだ試していない。
Eureka
サービス名でURLを検索できるようにするサービス。
eureka-server を作成
http://start.spring.io/で、Eureka-server,Config-Clientにチェックして、Download。 Config-ClientをチェックするのはEureka-Serverのportをconfig−server経由で設定するため。
bootstrap.properties
にconfig-serverの場所と、application名を定義
spring.cloud.config.uri=${CONFIG_SERVER:http://localhost:8888} spring.application.name=eureka-service
eureka-serverを起動して、http://localhost:8761/ にアクセスすると何も登録されていないのがわかる。
eureka-server に登録する。
pom.xml
に依存関係を定義する。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency>
DemoApplication.java
に @EnableDiscoveryClient
をつける
@EnableDiscoveryClient @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); }
これで起動する。起動後、30秒してから http://localhost:8761 にアクセスすると、
reservation-service
という名前で登録されているのがわかる。
reservation-clientの作成
reservation-service
を利用するreservation-client
を作成する。
pom.xml
に依存関係を追加。
feign
,zuul
,hystrix
はあとから使うので、追記しておく。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency>
DiscoveryClient
DemoApplication.java
にアノテーションと、reservation-service
にアクセスする処理を追加
@EnableDiscoveryClient @SpringBootApplication public class DemoApplication { @Bean CommandLineRunner dc(DiscoveryClient dc) { return args -> dc.getInstances("reservation-service") .forEach(si -> System.out.println(String.format("%s %s:%s", si.getServiceId(), si.getHost(), si.getPort()))); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
起動すると、DiscoveryClient
が取得したinstanceが表示される。
RestTemplate
@Bean CommandLineRunner rt(RestTemplate rt) { return args -> { ParameterizedTypeReference<List<Reservation>> ptr = new ParameterizedTypeReference<List<Reservation>>() {}; ResponseEntity<List<Reservation>> response = rt.exchange("http://reservation-service/reservations", HttpMethod.GET, null, ptr); response.getBody().forEach(System.out::println); }; }
http://localhost:8000/reservations にアクセスして取得できるjsonをRestTemplate
でreservation-service
という名前で解決して取得した値を表示する。
Feign
@EnableFeignClients
を付与する。
@FeignClient("reservation-service")
をつけることで、Reservation-client
が、reservation-service
の振りをする(feign)ことができるようになる。
/reservations にアクセスすると、reservatin-serviceの/reservations にdispach される。
@EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class DemoApplication { @Bean CommandLineRunner fc(ReservationClient rc) { return args -> rc.getReservations().forEach(System.out::println); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } } @FeignClient("reservation-service") interface ReservationClient { @RequestMapping(method = RequestMethod.GET, value="/reservations") Collection<Reservation> getReservations();
Hystrix
Circuit Breaker Pattern を実装している。 www.sawanoboly.net martinfowler.com
簡単に言うと、障害が発生したときにその障害を拡大させず、代替手段でサービスを提供し続けるようにする。
今回の例ではreservaation-serviceはeurekaで何台あってもロードバランスしてアクセスできるが、1台も動いていないときに例外*1が発生する。 そんなときに、代替メソッドを実行できるようになる。
DemoApplication.java
に @EnableCircuitBreaker
アノテーションをつける。
@EnableFeignClients @EnableCircuitBreaker @EnableDiscoveryClient @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
ReservationIntegration
を作成する。
@HystrixCommand(fallbackMethod = "reservationNamesFallback")
でメソッド実行失敗時の代替のメソッドを指定する。
fallbackMethod も もともとのメソッドもpublic
でないと正常に動かない。
@Component class ReservationIntegration { @Autowired ReservationClient rc; public Collection<String> reservationNamesFallback() { return Collections.emptyList(); } @HystrixCommand(fallbackMethod = "reservationNamesFallback") public Collection<String> reservationNames() { return rc.getReservations().stream() .map(Reservation::getReservationName) .collect(Collectors.toList()); } }
reservation-client を起動する。reservation-serviceが動いている時は正常に値を返却する。
reservation-serviceを停止してアクセスすると、
fallbackMethodの結果のemptyList()が表示される。
初回失敗時は少し時間がかかるが、2回目以降はすぐに結果が返却される。reservation-serviceの状態を記憶していて、すぐにfallbackMethodがよばれるからである。 (circuit が open 状態になる。)
reservation-serviceを再起動してしばらくすると、正常にもどる。 (circuit が close 状態にもどる。)
HystrixDashboard
http://start.spring.io/でhystrix-dashboard,eureka,config-clientにチェックをいれてダウンロード。
pom.xml
はこんな感じ。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId> </dependency>
DemoApplication.java
に@EnableHystrixDashboard
をつける。
@EnableHystrixDashboard @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
bootstrap.properties
を同じように作成。
spring.cloud.config.uri=${CONFIG_SERVER:http://localhost:8888} spring.application.name=hystrix-dashboard
Hystrix-dashboard アプリケーションを起動する。
http://localhost:9999/hystrix.stream にアクセスするとエンドレスで流れる。
http://localhost:9999/hystrix.stream をモニタリングできる。
連続してアクセスするとこんな感じ。
Zuul
バックエンドサービスをUIから呼び出す際のproxy。
reservation-client の application.yml
に設定
zuul: routes: reservation-service: /myservice/**
reservation-service
はeurekaに登録したapplication名で、/myservice/** というURL でアクセスしてきたリクエストをreservation-service
に転送する。
DemoApplication.java
に@EnableZuulProxy
アノテーションをつける。
@EnableFeignClients @EnableCircuitBreaker @EnableZuulProxy @EnableDiscoveryClient @SpringBootApplication public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
起動する。
2015-09-24 10:27:18.741 INFO 7345 --- [ main] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/myservice/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController] 2015-09-24 10:27:18.741 INFO 7345 --- [ main] o.s.c.n.zuul.web.ZuulHandlerMapping : Mapped URL path [/reservation-service/**] onto handler of type [class org.springframework.cloud.netflix.zuul.web.ZuulController]
/myservice/** をマッピングしたログが出力される。
http://localhost:9999/myservice/reservationsにアクセスするとreservation-service(バックエンド)のproxyとして機能していることがわかる。
*1:cannot connect to reservation-service