ビズリーチの新サービスをscalaで作ってみた...

39
ビズリーチの新サービスを Scalaで作ってみた Naoki Takezoe BizReach, Inc

Upload: takezoe

Post on 15-Jul-2015

12.093 views

Category:

Engineering


0 download

TRANSCRIPT

Page 1: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

ビズリーチの新サービスを Scalaで作ってみた

Naoki Takezoe BizReach, Inc

Page 2: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

About BizReach

Page 3: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

今日は新しいサービスの 話をします

Page 4: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

求人検索エンジン スタンバイ

Available for PC Web, Mobile Web, iOS and Android

Page 5: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

今日話すこと• マイクロサービスアーキテクチャ

• Apache Spark

• Scalaで開発する上での課題

Page 6: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

マイクロサービス アーキテクチャ

Page 7: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

マイクロサービスとは?• サービスによるコンポーネント化:ライブラリではなく別プロセスで動作するサービスによってアプリケーションのコンポーネント化を実現している。

• ビジネスケイパビリティに基づく組織化:役割ごとにチームが構成されるのではなく、複数の役割が混在したチームがひとつのサービスを構築する。(コンウェイの法則!)

• プロジェクトではなくプロダクト:コンポーネントは期限のあるプロジェクトとして開発されるではなく、継続的なプロダクトとして提供される。

• スマートエンドポイント、ダムパイプ:サービス間のメッセージは、HTTP経由でAPI呼び出しされるか、RabbitMQやZeroMQといった軽量メッセージングシステムによる通信で交換される。

• 分散ガバナンス:サービスごとに言語やデータベースなどは統一されず、個別に適切なものが選択される。

• 分散データ管理:サービスごとにデータを持ち、統合されていない。 • インフラストラクチャ自動化:継続的デリバリが実現され、自動テスト、自動デプロイなどが採用されている。

• 障害設計:構成されるサービスの障害に耐性を持つように設計されている。 • 進化的設計:各サービスごとに変更が行なわれ、漸進的に設計がされる。

Page 8: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

疎結合な小さなサービスの 集合体としてシステムを構築する

マイクロサービス

マイクロサービス

マイクロサービス

HTTP

Messaging

HTTP

Page 9: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

• 独立して開発・メンテナンスが可能

• 耐障害性の高いシステムを構築可能

なにがうれしいのか?

Page 10: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

• 並列処理を記述するのが容易

• ノンブロッキングI/Oベースのミドルウェア、フレームワーク、ライブラリが豊富

• 巨大なシステムにおける静的型付けの安全性

なぜScalaなのか?

Page 11: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

アーキテクチャPC Web Mobile Web iOS App Android App

Front API

Backend APIs Backend APIs Backend APIs

Elasticsearch MySQL Memcached

JSON over HTTP

JSON over HTTP

elasticsearch4s Slick ???

Page 12: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

アーキテクチャPC Web Mobile Web iOS App Android App

Front API

Backend APIs Backend APIs Backend APIs

Elasticsearch MySQL Memcached

JSON over HTTP

JSON over HTTP

elasticsearch4s Slick ???

Webアプリもフロントエンドと サーバサイドでレイヤリング

Page 13: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Webアプリもレイヤリング

play2-stub play2-handlebars

フロントサーバ(Play2)

APIサーバ(Play2)

JSON over HTTP

開発時は固定の JSONを返す

JSONをテンプレートに 渡してHTMLをレンダリング

request response

Page 14: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

ねらい•サーバサイドとフロントエンドのライフサイクルは異なるので別々にデプロイできるようにしたい

•フロントエンドの修正はフロントエンジニアだけで完結したい

•サーバサイドはScalaで固く、フロントエンドはJavaScriptやテンプレートエンジンで柔らかく作ることで両者のメリットを活かせる

Page 15: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

play2-stub

•リクエストを受け取り、routingやstubbingを行うフロントコントローラ •フロントエンドとサーバサイドを分離して開発するためのもの •リクエストやレスポンスを加工するフィルタを挟んだり、複雑な処理は別のコントローラにデリゲートしたりすることもできる

play2stub {! routes: [! {! "GET /author/~authorName/books" {! template = "author-biology"! data = "authors/:authorName.json"! }! }! ]!}

https://github.com/bizreach/play2-stub

リモートAPIを呼び出す代わりに 静的なJSONでレンダリング

Page 16: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

play2-handlebars

• Play2でHandlebarsを使うためのプラグイン

•コンパイルなしでテンプレートを修正可能

•デザイナにも扱いやすいテンプレート記法

• Play2のTwirlテンプレートはコンパイルが遅いし記法も複雑で、カッチリ作るにはいいが、デザインを迅速に反映・修正するのには向いてない

https://github.com/bizreach/play2-stubobject Application extends Controller {!! def simple = Action {! Ok(HBS("simple", "who" -> "World"))! }!!}

Page 17: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

現実は厳しい•Handlebarsの自由度が低すぎるので大量にヘルパーを作らなくてはいけない(ヘルパーの作成がボトルネックになることも)

•結局画面の仕様に対応したAPIが必要になるケースが多く、再利用可能なAPIにならない

•性能面・工数面などの理由で直接DBに接続せざるを得ない、というような部分もあり、密結合になってしまっている

Page 18: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

複数のAPIの呼び出しWeb API

Web API

Web API

Web API

Controller

1つのコントローラから複数のWeb APIを呼び出す必要がある シリアルに呼び出していてはAPIが増えるほど遅くなってしまう

Page 19: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Futureimport scala.concurrent._!import scala.concurrent.ExecutionContext.Implicits.global!!val fileNames = Seq("1.jpg", "2.jpg", "3.jpg", …)!!fileNames.foreach { fileName =>! Future {! resizeAndStore(fileName)! }!}

•FutureはScalaにおける非同期処理の基本的なインターフェース

•Play標準のWS APIはFutureを返す

•Dispatchなど他の通信系ライブラリもFutureを返すものが多い

Page 20: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

PlayのWS API// 戻り値としてFutureを返す!val f: Future[WSResponse] = WS.url(requestUrl).get()!!// アクションの実行結果をFutureで返す!def wsAction = Action.async {! WS.url(requestUrl).get().map { res =>! Ok(res.body)! }!}

Page 21: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Futureによる並列処理

// 1つ目のAPIを呼び出す!val res1: WSResponse ! = Await.result(WS.url(url1).get(), Duration.Inf)!// レスポンスを取得!val body1: String = res1.body!!// 2つ目のAPIを呼び出す!val res2: WSResponse ! = Await.result(WS.url(url2).get(), Duration.Inf)!// レスポンスを取得!val body2: String = res2.body

複数のWeb APIをシリアルに呼び出す

Page 22: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Futureによる並列処理

// 1つ目のAPIを呼び出す!val res1: WSResponse ! = Await.result(WS.url(url1).get(), Duration.Inf)!// レスポンスを取得!val body1: String = res1.body!!// 2つ目のAPIを呼び出す!val res2: WSResponse ! = Await.result(WS.url(url2).get(), Duration.Inf)!// レスポンスを取得!val body2: String = res2.body

複数のWeb APIをシリアルに呼び出すAwait.resultを使うとFutureから値を 取り出すことができるがブロックしてしまう

Page 23: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Futureによる並列処理

val f1 = WS.url(url1).get()!val f2 = WS.url(url2).get()!!val f: Future[(String, String)] = for {! res1 <- f1! res2 <- f2!} yield {! (res1.body, res2.body)!}

複数のWeb APIをパラレルに呼び出す

Page 24: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

やってみて思ったこと• 開発時のオーバーヘッドやサービス毎の冗長化によるコスト増など、短期的にはデメリットの方が大きい

• デバッグが難しくなる、並列プログラミングのスキルが必要になる

• 最初はモノリシックに作り、大きくなってきたタイミングでマイクロサービスにするのがよい

• 大きくなるタイミングはサービスとしては攻め時なので判断としては難しい

Page 25: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Apache Spark

Page 26: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

クローリング・インデキシング

データを加工 Elasticsearchクロール JSONHTML

DispatchでHTMLを取得する アクターをスケジュール実行する ことでサーバのリソースを活用

HTMLを解析したり、別のデータと 結合して検索用のインデックスを 生成する処理をSparkで実行

Page 27: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

クローリング・インデキシング

データを加工 Elasticsearchクロール JSONHTML

DispatchでHTMLを取得する アクターをスケジュール実行する ことでサーバのリソースを活用

HTMLを解析したり、別のデータと 結合して検索用のインデックスを 生成する処理をSparkで実行

Page 28: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Apache Sparkとは?• Scala製の高速バッチ処理フレームワーク

• 簡単なプログラムで分散処理を記述できる

• 機械学習のMLlib、SQLインターフェースを提供するSpark-SQL、ストリーミング処理用のSpark-Streamingなど様々なサブプロジェクトが存在する

• elastichadoop-sparkというアダプタを使うことでElasticsearchへの入出力が可能

Page 29: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

並列分散処理を手軽に記述できるdef main(args: Array[String]) {! val sc = new SparkContext("local", "Log Query", ! System.getenv("SPARK_HOME"), ! SparkContext.jarOfClass(this.getClass)) // ログファイルをロード!! val dataSet = sc.textFile("hdfs://...")! // ERRORで始まるデータを抽出しキャッシュ! val cached = dataSet.filter(_ startsWith "ERROR").cache()!! val counts1 = cached! .flatMap(_ split " ") // スペースで分割しフラット化! .map(_ -> 1) // 文字列とカウントのタプルに変換! .reduceByKey(_ + _) // 集計!! // キャッシュしたデータを使って別の条件で集計する! val counts2 = cached.filter・・・!}

毎回ストレージにアクセスしないようキャッシュ

通常のScalaプログラムと 同じ感覚でコードを書ける

Page 30: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

複数のインデックスを結合val conf = new SparkConf().setAll(Seq(! ES_NODES -> "localhost",! ES_PORT -> “9200",! ES_RESOURCE -> "job",! ES_QUERY -> "?q=*:*"!))!!val spark = new SparkContext(conf)!!val rdd = spark.esRDD.leftOuterJoin(spark.esRDD(Map(! ES_RESOURCE -> "geo",! ES_QUERY -> "?q=*:*"!))).flatMap { case (_id, (_source, geo)) =>! geo.map { x =>! _source ++ Map("location" -> x(“location"))! }!}

jobインデックスに geoインデックスを外部結合

Page 31: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

複数のインデックスを更新// 加工したデータをキャッシュしておく!val rdd = spark.esRDD.map { x=>! …!}.cache!!// 1台目のElasticsearchにデータを登録!rdd.saveToEs(Map(! ES_NODES -> "localhost",! ES_PORT -> "9200"!))!!// 2台目のElasticsearchにデータを登録!rdd.saveToEs(Map(! ES_NODES -> "localhost",! ES_PORT -> "9201"!)) 加工したデータをキャッシュしておくことで複数の

インデックスを高速に更新することができる

Page 32: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

注意点• I/Oが多いとCPUを使い切れずSparkの良さが出せない

• 細かいジョブを大量に投げると失敗することがある

• 処理の並列度がElasticsearchのシャード数に依存する(elastichadoop-sparkの場合)

• 記述の仕方を少し変えただけで猛烈にパフォーマンスが変わるので使いこなすのが難しい

Page 33: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Scalaを使う上での 一番の課題

Page 34: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

一番の課題• Scalaプログラマの教育・採用

• JavaよりもLLからのほうが入りやすい様子

• 長期的にはScalaをもっと普及させる

• 短期的には社内で育成していくしかない

Page 35: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

長期的な取り組み

• 書籍・雑誌記事等の執筆

• イベントでの登壇・支援(スポンサー)

• 他社さんとの合同での勉強会

• OSS活動(足りない物を作る)

Page 36: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

短期的な取り組み

• Daily Scala(毎朝30分Scalaの勉強会)

• GitHubでのプルリクレビュー

• ハンズオンコンテンツの作成

Page 37: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

Play2ハンズオンhttps://github.com/bizreach/play2-hands-on

Page 38: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

まとめ• マイクロサービスは良いことばかりではない

• Future大事

• Apache Sparkは便利だけど使いこなすのが難しい

• Scalaプログラマの教育・採用が一番の課題です

Page 39: ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala

お知らせ• 弊社オフィスのイベントスペースを使用して渋谷java、Swiftもくもく会などいろんな勉強会をやってます

• 会場提供も可能です

お問い合わせはhttp://dcube.ioまたは@takezoenまで!