Think Twice

Memorandum

Spring Cloud

Josh Longのセッションをなぞってみた。

www.youtube.com

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に登録してある情報を参照できる。 f:id:mix-juice001:20150923151438p:plain

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)を起動してアクセス。

f:id:mix-juice001:20150923153234p:plain

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にアクセスすると、変更が反映されているのがわかる。 f:id:mix-juice001:20150923153902p:plain

Reservation-serviceにアクセスすると

f:id:mix-juice001:20150923154027p:plain

変わっていない!?

アプリケーションに以下のコマンドで変更を知らせる必要がある。

$ curl -d {} http://localhost:8000/refresh
["message"]

f:id:mix-juice001:20150923154321p:plain

無事変更が反映された。

変更を通知するアプリケーションがたくさんある場合、 /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/ にアクセスすると何も登録されていないのがわかる。 f:id:mix-juice001:20150923155819p:plain

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 にアクセスすると、 f:id:mix-juice001:20150923161037p:plain

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が表示される。 f:id:mix-juice001:20150923162213p:plain

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 にアクセスして取得できるjsonRestTemplatereservation-serviceという名前で解決して取得した値を表示する。

f:id:mix-juice001:20150923162509p:plain f:id:mix-juice001:20150923162257p:plain

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が動いている時は正常に値を返却する。 f:id:mix-juice001:20150923205856p:plain

reservation-serviceを停止してアクセスすると、

f:id:mix-juice001:20150923205419p:plain

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 アプリケーションを起動する。 f:id:mix-juice001:20150923211310p:plain

http://localhost:9999/hystrix.stream にアクセスするとエンドレスで流れる。 f:id:mix-juice001:20150923211519p:plain

http://localhost:9999/hystrix.streamモニタリングできる。 f:id:mix-juice001:20150923211718p:plain

連続してアクセスするとこんな感じ。 f:id:mix-juice001:20150923212007p:plain

Zuul

バックエンドサービスをUIから呼び出す際のproxy。 reservation-client の application.yml に設定

zuul:
  routes:
    reservation-service: /myservice/**

reservation-serviceeurekaに登録した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として機能していることがわかる。

f:id:mix-juice001:20150924103934p:plain

*1:cannot connect to reservation-service