1 of 71

本日の資料について

サンプルのソースコード

https://github.com/furyu-john/2018-jjug-spring

1

2 of 71

古いサービスでも�マイクロサービスがしたい

JJUG CCC 2018 SPRING 2018/05/26

#jjug_ccc #ccc_a6

フリュー株式会社

佐藤慧太 @SatohJohn

3 of 71

自己紹介

  • 佐藤慧太@SatohJohn
  • フリュー株式会社(京都)
  • サーバサイド開発と�フロントエンド開発やってます

3

4 of 71

本日の目的

  • 今回の発表が皆さんの業務のどこかに役立つ!
    • 特に古いフレームワークから脱却を考えている皆様へ
  • 傷を舐め合う。経験を分かち合う
  • フリューを知ってもらう

4

5 of 71

目次

  • 対象サービス(ピクトリンク)について
  • 古いサービスからのSpring Frameworkの�エコシステムの利用
    • Spring Cloud Config(Netflix Eureka)
    • Spring Cloud Stream(Apache Kafka)
  • まとめ

5

6 of 71

「古いサービス」とは

  • 古い技術、EOLなフレームワーク
    • 巨大なサービスで�ビジネスの観点から一気リプレースにさける時間はない
    • Java8まではアップデートしている

6

7 of 71

JJUG CCCとフリュー

7

8 of 71

ピクトリンク

  • 若年女性がターゲット
  • 2011年にSPサイトをリリース
  • Android、iOSアプリもリリース

8

9 of 71

ピクトリンク

  • 会員数:1300万人
  • 世間がイベントのときに�負荷が上がります

9

10 of 71

ピクトリンク

  • プリントシール機から送られてきた画像の閲覧
  • 画像を利用したSNS
  • 課金システム
  • コスメ商品のクチコミ
  • 体重管理機能
  • メールマガジンの配信
  • モデルの投稿した画像の閲覧

 などなどたくさんの機能がある

10

11 of 71

ピクトリンク

11

12 of 71

ピクトリンク

12

全部 Spring Framework

13 of 71

マイクロサービスを

Spring Framework�でしか利用できてない

13

14 of 71

古いフレームワークが

マイクロサービスの恩恵を�受けていない

14

15 of 71

解決したいこと

  • 古いサービスをマイクロサービスに入れたい
    • 運用負荷を下げる
    • サービスを分割してリプレースを進めやすくする

15

16 of 71

Spring Cloud Config�(Netflix Eureka)

16

17 of 71

Netflix Eureka

  • Service Discoveryと呼ばれ、内部DNSの役割をしている
  • Spring Boot プロジェクトとしてサーバに�デプロイして使える

17

18 of 71

Netflix Eureka

18

19 of 71

Netflix Eureka

19

20 of 71

Spring Cloud Config

  • アクセスするとGitHubや自前Repositoryから�設定を取得してJSONとして返却する
  • Spring Boot プロジェクトとしてサーバに�デプロイすることで使える

20

21 of 71

課題

  • Spring Bootアプリケーションから参照されていたが�古いサービスからも使えるようにしたい
    • 起動時にSpring Cloud Configを参照したい

21

22 of 71

構成

22

23 of 71

解決方法1

  • Ribbon Clientを利用する
    • Eurekaに登録されているサービスにアクセスできる
    • Eureka上のアプリケーション情報をキャッシュしているため�他のマイクロサービスを呼ぶ際にも利用できる
    • サービスへのアクセスをロードバランシングするので�連続で使うような場合も使える

23

24 of 71

解決方法1

  • Eureka Clientを利用する
    • Ribbon Clientの中に入っているClient
    • サービス名さえ与えれば、対象のサーバの場所を返してくれる

24

25 of 71

Eureka Clientを使う

25

ConfigurationManager.loadPropertiesFromResources(

"netflix.properties");

EurekaClient eurekaClient = new DiscoveryClient(

applicationInfoManager,

new DefaultEurekaClientConfig());

InstanceInfo nextServerInfo = eurekaClient.getNextServerFromEureka(

"config-test",

false);

String url = "http://" + nextServerInfo.getHostName() + ":" + nextServerInfo.getPort() + "/my-id/prod/master"

26 of 71

Eureka Clientを使う(netflix.properties)

26

eureka.region=default

eureka.hostname=config-access-test # Eurekaに登録される名前

eureka.registration.enabled=false # Eureka上に登録するか

eureka.serviceUrl.default=http://localhost:11801/eureka

27 of 71

社内ライブラリとして�提供してみた

27

28 of 71

失敗したところ

  • ライブラリを利用すると�既存フレームワークで利用していた�Ribbon Clientの中のEureka Clientの�アプリケーション情報が更新されなくなった
    • 参照先のサービスの場所を入れ替えたり�サービスのスケールインができなくなる

28

29 of 71

失敗したところ

  • Eureka Clientの使い方に問題があった
    • Eureka ClientではApplication Managerを�別スレッドで管理している
    • 別Eureka Clientインスタンスが作成されたときに�データの上書きができなくなっていた

29

30 of 71

じゃあ、もともと利用していた�Ribbon Clientを使おう

30

31 of 71

しかし、忘れていた事が�ありました

31

32 of 71

32

33 of 71

33

34 of 71

我々のアプリケーションは�すこし特殊でした

34

35 of 71

(^o^) < こまったぞ

35

36 of 71

解決方法2

  • Ribbon、Eureka Clientを利用せずに�Http Clientを利用してデータを取得する
    • REST APIが公開されている
    • Spring Cloud Starter Eureka Serverに対して/v2は不要

36

37 of 71

Http Clientを使う

37

HttpGet eurekaAccess = new HttpGet();

eurekaAccess.setURI(

URI.create("http://localhost:11801/eureka/apps/config-test"));

eurekaAccess.setHeader("Accept", "application/json");

CloseableHttpResponse response = HttpClientBuilder.create()

.setRetryHandler(new DefaultHttpRequestRetryHandler())

.build()

.execute(eurekaAccess)

Iterator<JsonNode> propertySources =

mapper.readTree(response.getEntity().getContent())

.findValue("instance").elements();

String content = propertySources.next().toString();

38 of 71

Demo

38

39 of 71

まとめ

  • Ribbon Client、Eureka Clientを使えば�古いサービスからもNetflix Eurekaを通して�Spring Cloud Configや他のサービスを使うことができる
    • ただし、複雑なアプリケーションでは�EurekaのREST APIを直に叩くのもあり

39

40 of 71

Spring Cloud Stream

(Apache Kafka)

40

41 of 71

背景

  • 古いサービスのある処理に後処理を�追加したくなった
    • プッシュ通知やメール送信機能
    • ほかサービスで利用している処理と同じもの

41

42 of 71

課題

  • 共通の処理をサービスごとに書きたくない
    • メンテナンスが面倒になる
    • モジュールにする場合、古いサービスで利用できる形に�合わせる必要が出てくる
  • 非同期的に実行されていてほしい

42

43 of 71

Spring Cloud Streamとは

  • 非同期的なメッセージングシステムを�Springで利用するためのエコシステム
    • Apache Kafka
    • RabbitMQ

43

44 of 71

Apache Kafkaとは

  • PubSub型の分散メッセージングシステム
  • メッセージの書き込みをキューにしている
    • メッセージを保存しているのは�Zookeeper�

44

45 of 71

Apache Kafkaとは

45

46 of 71

Apache Kafkaとは

46

47 of 71

通常のリクエストと違う点

  • 非同期的である
    • メッセージがどこで何に影響を与えているか�意識しなくて良い
    • 即効性はない

47

48 of 71

Spring Cloud Stream(build.gradle)

48

dependencies {

compile 'org.springframework.cloud:spring-cloud-starter-stream-kafka:2.0.0.RELEASE'

}

49 of 71

Spring Cloud Stream(Sourceの作成)

49

Properties properties = new Properties();

properties.put(

ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,

kafkaEndpoint);

properties.put(

ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,

LongSerializer.class);

properties.put(

ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,

JsonSerializer.class);

KafkaProducer<Long, Message> producer =

new KafkaProducer<>(properties);

50 of 71

Spring Cloud Stream(Sourceの作成)

50

String topic = "jjug-2018";

Message dto = new Message();

producer.send(new ProducerRecord<>(

topic, System.currentTimeMillis(), dto

), (recordMetadata, e) -> {

if (e != null) System.out.println(e.getMessage());

if (recordMetadata != null) System.out.println(recordMetadata);

});

producer.flush();

51 of 71

Spring Cloud Stream(Sink側 pom.xml)

51

<dependencies>

<dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-stream-binder-kafka</artifactId>

</dependency>

</dependencies>

<dependencyManagement>

<dependencies><dependency>

<groupId>org.springframework.cloud</groupId>

<artifactId>spring-cloud-dependencies</artifactId>

<version>Finchley.RC1</version>

<type>pom</type>

<scope>import</scope>

</dependency></dependencies>

</dependencyManagement>

52 of 71

Spring Cloud Stream(Sink側)

52

@SpringBootApplication

@EnableBinding(Sink.class)

public class KafkaDemoApplication {

@StreamListener(Sink.INPUT)

public void log(Message message) {

System.out.println("received " + message.message);

}

public static void main(String[] args) {

SpringApplication.run(KafkaDemoApplication.class, args);

}

}

53 of 71

Demo

53

54 of 71

構成

54

55 of 71

すごい便利

55

56 of 71

考えたところ

  • Topicの設計について
  • メッセージ内容の変更方法について

56

57 of 71

Topicの設計

  • 例:ある機能の後にPush通知をする
    • Push通知に必要な情報をメッセージとして送る?
      • 本文、タイトル、サムネイル…などなど
    • Topicは Push 的な名前になるのか?

57

58 of 71

Topicの設計

  • 「何をしたか」をTopicにするほうが良い
    • いいねした → Push通知
    • フォローした → Push通知
    • 投稿をした → Push通知

58

59 of 71

Topicの設計

  • 「何をしたか」をTopicにするほうが良い
    • いいねした、フォローしたなどのメッセージを�Push通知以外のサービスが受け取り�処理をすることができる

59

60 of 71

メッセージ内容の変更方法

  • あるTopicに対して送っていたメッセージを�修正したくなった
    • 内容が一部追加される場合
    • 内容が一部削除される場合
    • 型が変わった場合

60

61 of 71

メッセージ内容の変更方法

  • いっそTopicを新しく作成するほうが良い
    • Source側が新しいTopicにメッセージを送信し始めてから�Sink側は新しいTopicを参照する

61

62 of 71

あくまで個人の所感です

62

63 of 71

まとめ

  • Kafkaによるメッセージング処理により�サービスの肥大化をふせぐことで�古いサービスを置き換えやすくする

63

64 of 71

全体まとめ

64

65 of 71

まとめ

  • 古いサービスでもSpring Frameworkのエコシステムを利用してマイクロサービスができる
    • Netflix EurekaやApache Kafkaを利用する
    • 古いサービスをゆっくりでもいいからリプレース

65

66 of 71

でもやっぱり

66

67 of 71

Spring Bootの

マイクロサービスは楽

67

68 of 71

本日の目的

  • 今回の発表が皆さんの業務のどこかに役立つ!
    • 特に古いフレームワークから脱却を考えている皆様へ
  • 傷を舐め合う。経験を分かち合う
  • フリューを知ってもらう

68

69 of 71

69

70 of 71

70

71 of 71

Fin

71