Your SlideShare is downloading. ×
ビズリーチの新サービスをScalaで作ってみた 〜マイクロサービスの裏側 #jissenscala
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×

Introducing the official SlideShare app

Stunning, full-screen experience for iPhone and Android

Text the download link to your phone

Standard text messaging rates apply

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

253
views

Published on

実戦での Scala 〜 6つの事例から知る Scala の勘所〜 https://jissenscala.doorkeeper.jp/events/19660 で発表したスライドです。

実戦での Scala 〜 6つの事例から知る Scala の勘所〜 https://jissenscala.doorkeeper.jp/events/19660 で発表したスライドです。

Published in: Engineering

0 Comments
4 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
253
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
3
Comments
0
Likes
4
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. ビズリーチの新サービスを Scalaで作ってみた Naoki Takezoe BizReach, Inc
  • 2. About BizReach
  • 3. 今日は新しいサービスの 話をします
  • 4. 求人検索エンジン スタンバイ Available for PC Web, Mobile Web, iOS and Android
  • 5. 今日話すこと • マイクロサービスアーキテクチャ • Apache Spark • Scalaで開発する上での課題
  • 6. マイクロサービス アーキテクチャ
  • 7. マイクロサービスとは? • サービスによるコンポーネント化:ライブラリではなく別プロセスで動作するサービスに よってアプリケーションのコンポーネント化を実現している。 • ビジネスケイパビリティに基づく組織化:役割ごとにチームが構成されるのではなく、複 数の役割が混在したチームがひとつのサービスを構築する。(コンウェイの法則!) • プロジェクトではなくプロダクト:コンポーネントは期限のあるプロジェクトとして開発 されるではなく、継続的なプロダクトとして提供される。 • スマートエンドポイント、ダムパイプ:サービス間のメッセージは、HTTP経由でAPI呼 び出しされるか、RabbitMQやZeroMQといった軽量メッセージングシステムによる通信 で交換される。 • 分散ガバナンス:サービスごとに言語やデータベースなどは統一されず、個別に適切なも のが選択される。 • 分散データ管理:サービスごとにデータを持ち、統合されていない。 • インフラストラクチャ自動化:継続的デリバリが実現され、自動テスト、自動デプロイな どが採用されている。 • 障害設計:構成されるサービスの障害に耐性を持つように設計されている。 • 進化的設計:各サービスごとに変更が行なわれ、漸進的に設計がされる。
  • 8. 疎結合な小さなサービスの 集合体としてシステムを構築する マイクロサービス マイクロサービス マイクロサービス HTTP Messaging HTTP
  • 9. • 独立して開発・メンテナンスが可能 • 耐障害性の高いシステムを構築可能 なにがうれしいのか?
  • 10. • 並列処理を記述するのが容易 • ノンブロッキングI/Oベースのミドルウェア、フ レームワーク、ライブラリが豊富 • 巨大なシステムにおける静的型付けの安全性 なぜScalaなのか?
  • 11. アーキテクチャ 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 ???
  • 12. アーキテクチャ 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アプリもフロントエンドと サーバサイドでレイヤリング
  • 13. Webアプリもレイヤリング play2-stub play2-handlebars フロントサーバ(Play2) APIサーバ(Play2) JSON over HTTP 開発時は固定の JSONを返す JSONをテンプレートに 渡してHTMLをレンダリング request response
  • 14. ねらい •サーバサイドとフロントエンドのライフサイクルは異な るので別々にデプロイできるようにしたい •フロントエンドの修正はフロントエンジニアだけで完結 したい •サーバサイドはScalaで固く、フロントエンドは JavaScriptやテンプレートエンジンで柔らかく作ること で両者のメリットを活かせる
  • 15. 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でレンダリング
  • 16. play2-handlebars • Play2でHandlebarsを使うためのプラグイン • コンパイルなしでテンプレートを修正可能 • デザイナにも扱いやすいテンプレート記法 • Play2のTwirlテンプレートはコンパイルが遅いし記法も複雑で、カッチリ作る にはいいが、デザインを迅速に反映・修正するのには向いてない https://github.com/bizreach/play2-stub object Application extends Controller {! ! def simple = Action {! Ok(HBS("simple", "who" -> "World"))! }! ! }
  • 17. 現実は厳しい •Handlebarsの自由度が低すぎるので大量にヘルパーを 作らなくてはいけない(ヘルパーの作成がボトルネック になることも) •結局画面の仕様に対応したAPIが必要になるケースが多 く、再利用可能なAPIにならない •性能面・工数面などの理由で直接DBに接続せざるを得 ない、というような部分もあり、密結合になってしまっ ている
  • 18. 複数のAPIの呼び出し Web API Web API Web API Web API Controller 1つのコントローラから複数のWeb APIを呼び出す必要がある シリアルに呼び出していてはAPIが増えるほど遅くなってしまう
  • 19. Future import 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を返すものが多い
  • 20. 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)! }! }
  • 21. 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をシリアルに呼び出す
  • 22. 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から値を 取り出すことができるがブロックしてしまう
  • 23. 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をパラレルに呼び出す
  • 24. やってみて思ったこと • 開発時のオーバーヘッドやサービス毎の冗長化によるコス ト増など、短期的にはデメリットの方が大きい • デバッグが難しくなる、並列プログラミングのスキルが必 要になる • 最初はモノリシックに作り、大きくなってきたタイミング でマイクロサービスにするのがよい • 大きくなるタイミングはサービスとしては攻め時なので判 断としては難しい
  • 25. Apache Spark
  • 26. クローリング・インデキシング データを加工 Elasticsearchクロール JSONHTML DispatchでHTMLを取得する アクターをスケジュール実行する ことでサーバのリソースを活用 HTMLを解析したり、別のデータと 結合して検索用のインデックスを 生成する処理をSparkで実行
  • 27. クローリング・インデキシング データを加工 Elasticsearchクロール JSONHTML DispatchでHTMLを取得する アクターをスケジュール実行する ことでサーバのリソースを活用 HTMLを解析したり、別のデータと 結合して検索用のインデックスを 生成する処理をSparkで実行
  • 28. Apache Sparkとは? • Scala製の高速バッチ処理フレームワーク • 簡単なプログラムで分散処理を記述できる • 機械学習のMLlib、SQLインターフェースを提供するSpark-SQL、 ストリーミング処理用のSpark-Streamingなど様々なサブプロ ジェクトが存在する • elastichadoop-sparkというアダプタを使うことでElasticsearch への入出力が可能
  • 29. 並列分散処理を手軽に記述できる 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プログラムと 同じ感覚でコードを書ける
  • 30. 複数のインデックスを結合 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インデックスを外部結合
  • 31. 複数のインデックスを更新 // 加工したデータをキャッシュしておく! 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"! )) 加工したデータをキャッシュしておくことで複数の インデックスを高速に更新することができる
  • 32. 注意点 • I/Oが多いとCPUを使い切れずSparkの良さが出せない • 細かいジョブを大量に投げると失敗することがある • 処理の並列度がElasticsearchのシャード数に依存する (elastichadoop-sparkの場合) • 記述の仕方を少し変えただけで猛烈にパフォーマンス が変わるので使いこなすのが難しい
  • 33. Scalaを使う上での 一番の課題
  • 34. 一番の課題 • Scalaプログラマの教育・採用 • JavaよりもLLからのほうが入りやすい様子 • 長期的にはScalaをもっと普及させる • 短期的には社内で育成していくしかない
  • 35. 長期的な取り組み • 書籍・雑誌記事等の執筆 • イベントでの登壇・支援(スポンサー) • 他社さんとの合同での勉強会 • OSS活動(足りない物を作る)
  • 36. 短期的な取り組み • Daily Scala(毎朝30分Scalaの勉強会) • GitHubでのプルリクレビュー • ハンズオンコンテンツの作成
  • 37. Play2ハンズオン https://github.com/bizreach/play2-hands-on
  • 38. まとめ • マイクロサービスは良いことばかりではない • Future大事 • Apache Sparkは便利だけど使いこなすのが難しい • Scalaプログラマの教育・採用が一番の課題です
  • 39. お知らせ • 弊社オフィスのイベントスペースを使用して渋谷java、Swift もくもく会などいろんな勉強会をやってます • 会場提供も可能です お問い合わせはhttp://dcube.ioまたは@takezoenまで!