Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
ビズリーチの新サービスを
Scalaで作ってみた
Naoki Takezoe
BizReach, Inc
About BizReach
今日は新しいサービスの
話をします
求人検索エンジン
スタンバイ
Available for PC Web, Mobile Web, iOS and Android
今日話すこと
• マイクロサービスアーキテクチャ
• Apache Spark
• Scalaで開発する上での課題
マイクロサービス
アーキテクチャ
マイクロサービスとは?
• サービスによるコンポーネント化:ライブラリではなく別プロセスで動作するサービスに
よってアプリケーションのコンポーネント化を実現している。
• ビジネスケイパビリティに基づく組織化:役割ごとにチームが構成されるのでは...
疎結合な小さなサービスの
集合体としてシステムを構築する
マイクロサービス
マイクロサービス
マイクロサービス
HTTP
Messaging
HTTP
• 独立して開発・メンテナンスが可能
• 耐障害性の高いシステムを構築可能
なにがうれしいのか?
• 並列処理を記述するのが容易
• ノンブロッキングI/Oベースのミドルウェア、フ
レームワーク、ライブラリが豊富
• 巨大なシステムにおける静的型付けの安全性
なぜScalaなのか?
アーキテクチャ
PC Web Mobile Web iOS App Android App
Front API
Backend APIs Backend APIs Backend APIs
Elasticsearch MySQL Memcach...
アーキテクチャ
PC Web Mobile Web iOS App Android App
Front API
Backend APIs Backend APIs Backend APIs
Elasticsearch MySQL Memcach...
Webアプリもレイヤリング
play2-stub play2-handlebars
フロントサーバ(Play2)
APIサーバ(Play2)
JSON over HTTP
開発時は固定の
JSONを返す
JSONをテンプレートに
渡してHTML...
ねらい
•サーバサイドとフロントエンドのライフサイクルは異な
るので別々にデプロイできるようにしたい
•フロントエンドの修正はフロントエンジニアだけで完結
したい
•サーバサイドはScalaで固く、フロントエンドは
JavaScriptやテンプ...
play2-stub
• リクエストを受け取り、routingやstubbingを行うフロントコントローラ
• フロントエンドとサーバサイドを分離して開発するためのもの
• リクエストやレスポンスを加工するフィルタを挟んだり、複雑な処理は別のコ...
play2-handlebars
• Play2でHandlebarsを使うためのプラグイン
• コンパイルなしでテンプレートを修正可能
• デザイナにも扱いやすいテンプレート記法
• Play2のTwirlテンプレートはコンパイルが遅いし記法...
現実は厳しい
•Handlebarsの自由度が低すぎるので大量にヘルパーを
作らなくてはいけない(ヘルパーの作成がボトルネック
になることも)
•結局画面の仕様に対応したAPIが必要になるケースが多
く、再利用可能なAPIにならない
•性能面・...
複数のAPIの呼び出し
Web API
Web API
Web API
Web API
Controller
1つのコントローラから複数のWeb APIを呼び出す必要がある
シリアルに呼び出していてはAPIが増えるほど遅くなってしまう
Future
import scala.concurrent._!
import scala.concurrent.ExecutionContext.Implicits.global!
!
val fileNames = Seq("1.jpg"...
PlayのWS API
// 戻り値としてFutureを返す!
val f: Future[WSResponse] = WS.url(requestUrl).get()!
!
// アクションの実行結果をFutureで返す!
def wsAct...
Futureによる並列処理
// 1つ目のAPIを呼び出す!
val res1: WSResponse !
= Await.result(WS.url(url1).get(), Duration.Inf)!
// レスポンスを取得!
val b...
Futureによる並列処理
// 1つ目のAPIを呼び出す!
val res1: WSResponse !
= Await.result(WS.url(url1).get(), Duration.Inf)!
// レスポンスを取得!
val b...
Futureによる並列処理
val f1 = WS.url(url1).get()!
val f2 = WS.url(url2).get()!
!
val f: Future[(String, String)] = for {!
res1 <-...
やってみて思ったこと
• 開発時のオーバーヘッドやサービス毎の冗長化によるコス
ト増など、短期的にはデメリットの方が大きい
• デバッグが難しくなる、並列プログラミングのスキルが必
要になる
• 最初はモノリシックに作り、大きくなってきたタイミ...
Apache Spark
クローリング・インデキシング
データを加工 Elasticsearchクロール JSONHTML
DispatchでHTMLを取得する
アクターをスケジュール実行する
ことでサーバのリソースを活用
HTMLを解析したり、別のデータと
結合して検...
クローリング・インデキシング
データを加工 Elasticsearchクロール JSONHTML
DispatchでHTMLを取得する
アクターをスケジュール実行する
ことでサーバのリソースを活用
HTMLを解析したり、別のデータと
結合して検...
Apache Sparkとは?
• Scala製の高速バッチ処理フレームワーク
• 簡単なプログラムで分散処理を記述できる
• 機械学習のMLlib、SQLインターフェースを提供するSpark-SQL、
ストリーミング処理用のSpark-Str...
並列分散処理を手軽に記述できる
def main(args: Array[String]) {!
val sc = new SparkContext("local", "Log Query", !
System.getenv("SPARK_HO...
複数のインデックスを結合
val conf = new SparkConf().setAll(Seq(!
ES_NODES -> "localhost",!
ES_PORT -> “9200",!
ES_RESOURCE -> "job",!
...
複数のインデックスを更新
// 加工したデータをキャッシュしておく!
val rdd = spark.esRDD.map { x=>!
…!
}.cache!
!
// 1台目のElasticsearchにデータを登録!
rdd.saveToE...
注意点
• I/Oが多いとCPUを使い切れずSparkの良さが出せない
• 細かいジョブを大量に投げると失敗することがある
• 処理の並列度がElasticsearchのシャード数に依存する
(elastichadoop-sparkの場合)
•...
Scalaを使う上での
一番の課題
一番の課題
• Scalaプログラマの教育・採用
• JavaよりもLLからのほうが入りやすい様子
• 長期的にはScalaをもっと普及させる
• 短期的には社内で育成していくしかない
長期的な取り組み
• 書籍・雑誌記事等の執筆
• イベントでの登壇・支援(スポンサー)
• 他社さんとの合同での勉強会
• OSS活動(足りない物を作る)
短期的な取り組み
• Daily Scala(毎朝30分Scalaの勉強会)
• GitHubでのプルリクレビュー
• ハンズオンコンテンツの作成
Play2ハンズオン
https://github.com/bizreach/play2-hands-on
まとめ
• マイクロサービスは良いことばかりではない
• Future大事
• Apache Sparkは便利だけど使いこなすのが難しい
• Scalaプログラマの教育・採用が一番の課題です
お知らせ
• 弊社オフィスのイベントスペースを使用して渋谷java、Swift
もくもく会などいろんな勉強会をやってます
• 会場提供も可能です
お問い合わせはhttp://dcube.ioまたは@takezoenまで!
Upcoming SlideShare
Loading in …5
×

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

23,422 views

Published on

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

Published in: Software
  • Be the first to comment

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

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

×