コードジガー

色々と書くと思います

Finagleハッカソンへ行ってきたっ! #finagle_hack

Scalaを始めて数ヶ月、より深くScalaを学ぶためにTwitter製RPCフレームワークであるFinagleで遊び倒そうの会へ参加してきました。
まったくのFinagle初心者の私でしたが、水島さん(@kmizu)のわかりやすい資料や中村さん(@gakuzzzz)の解説のお陰でFinagleは触って遊ぶことができました!
ありがとうございました(´∀`)
また、会場を提供してくださったTwitter Japanの山本裕介さん(@yusukey)にも良くしていただいて、至れり尽くせりでした!
f:id:takaya1219:20120618004842j:plain
沢山のノベルティグッズも頂きました!

以下、ハッカソンを通じて学んだことのメモみたいなものφ(`д´)メモメモ...

Finagle(ふぃねーぐる)とは、Twitter製の非同期RPCフレームワークで、RPCのシステムを作るためのフレームワーク

Apache Thriftのように、それ自体がRPCを提供するわけではない

  • 様々なプロトコルのServerとClientを簡単に作れる。
  • JSONのメッセージングとか簡単に作れちゃう。
  • FinagleはだいたいScalaで書かれている。
  • JVM上で動作する言語からも利用できるが基本的にScalaがオススメ


Twitter社が出しているEffective Scalaでは以下のように書かれている

Twitterにおいて、最も重要な標準ライブラリはUtilとFinagleだ。Utilは、ScalaJavaの標準ライブラリの拡張という位置付けで、それらに欠けている機能やより適切な実装を提供する。Finagleは、TwitterのRPCシステムで、分散システムの構成要素の中核だ。


FinagleはTwitter外でも使われている

TumblrPHPからScala(Finagle)に移行
  • FinagleがあるからScalaにした
  • Node.jsも検討したが、大規模で運用できるか不安があった
  • FinagleはTwitterでの実績がある
  • JVMだから安心

High Scalability - High Scalability - Tumblr Architecture - 15 Billion Page Views a Month and Harder to Scale than Twitter

Heroku:サンプルがもろにFinagle!!

Heroku | Scala on Heroku

Finagleのアーキテクチャ

f:id:takaya1219:20120616053230p:plain

  • 中ではNettyが動いている
    • NettyはJava NIOをラップしている
    • ノンブロッキングなI/O操作ができる
  • FinagleはJBoss Nettyをラップして使いやすくしたもの
  • Filter, Codec, Serviceという概念がある

Twitter Finagle server as an alternate HTTP ser... - Scala Resources - Quora

Finagleの要素

重要な要素はFuture, Service, Filter, Codecの4つ!

Futureクラス
class Future[+A]{
    def apply():A
}
  • 非同期計算の結果を表すクラス
  • 計算中、失敗、完了の3状態をとる
  • JavaのFutureとの違いとして写像(map)や合成(flatMap)がある
Service

Finagleにおけるロジックを実装する部分

class Service[-Req,+Rep] extends (Req) => Future[Rep]{
  def apply(request:Req):Future[Rep]
}
  • Req型を受け取ってFuture[Rep]型を非同期に返す関数
  • Server
    • Serverは任意のプロトコルに任意のServiceを結びつける
//EchoServerの例
val server:Server = ServerBuilder()
  .codec(Http())
  .bindTo(new InetSocketAddress(8000))
  .name("ServerName")
  .build(service)
  • Client
    • Clientは任意のプロトコルのServiceそのもの
//EchoClientの例
val client: Service[HttpRequest, HttpResponse] = ClientBuilder()
  .codec(Http())
  .hosts(new InetSocketAddress("localhost",8000))
  .build()
Filter

FilterはServiceに対して何か変換を行ったりする

class Filter[-ReqI, +RepO, +ReqO, -RepI] extends(ReqI, Service[ReqO,  RepI])=>Future[RepO] {
  def apply (request: ReqI, service: Service[ReqO, RepI]): Future[RepO]
}
Codec
  • Codecは、NettyにPipelineと密接な関係がある
  • Codecは、各プロトコルのencodeとdecodeを司るNettyとのつなぎ役
  • Finagleに標準でサポートされているCodec
    • HTTP
    • Stream(Http chunked transfer encoding)
    • Memcached
    • protocol buffer
    • Redis
    • Thrift
    • Kestrel
trait Codec[Req,Rep]

//Httpの例
val client: Service[HttpRequest,HttpResponse] = ClientBuilder()
  .codec(Http())
  .hosts(address)
  .build()

//Memcachedの例
val client: Service[Command, Response] = ClientBuilder()
  .codec(Memcached())
  .hosts(address)
  .build()

その他の機能

  • Server Support
    • Backpressure
    • Service Registration
    • Native OpenSSL bindings
  • Client Support
    • Connection Pooling
    • Load Balancing
    • Failure Detection
    • Failover/Retry
    • Distributed Tracing(a la Dapper)
    • Service Discovery(e.g., via Zookeeper)
    • Rich Statistics
    • Native OpenSSL bindings
    • Sharding

Hello World

  • Echoサーバ/クライアントでHello Worldを出力

Server側:HelloServer.scala

import com.twitter.util.Future
import com.twitter.finagle.Service
import com.twitter.finagle.builder.Server
import com.twitter.finagle.builder.ServerBuilder
import java.net.InetSocketAddress

object HelloServer {
  def main(atgs:Array[String]){
    val service = new Service[String,String]{
      def apply(request: String) = Future.value(request)
    }
    val server:Server = ServerBuilder()
            .codec(StringCodec)
            .bindTo(new InetSocketAddress(8080))
            .name("TestServer")
            .build(service)
  }
}


Client側:HelloClient.scala

import com.twitter.finagle.Service
import com.twitter.finagle.builder.ClientBuilder
import java.net.InetSocketAddress

object HelloClient {
  def main(args:Array[String]){
    val client:Service[String,String] = ClientBuilder()
        .codec(StringCodec)
        .hosts(new InetSocketAddress("localhost",8080))
        .hostConnectionLimit(1)
        .build()
            
    client("Hello, Finagle!!\n") onSuccess{ result =>
      println(result)
    }onFailure { error => 
      error.printStackTrace()
    }ensure{
      println("end")
      client.release()
    }
  }
}

実行結果

Hello, Finagle!!