Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Finagle for Beginners

Finagle for Beginners

Introduction to Finagle and how build a small distributed system.

Nepomuk Seiler

September 02, 2016
Tweet

Transcript

  1. What is finagle “Finagle is an extensible RPC system for

    the JVM, used to construct high-concurrency servers.” “Most of Finagle’s code is protocol agnostic, simplifying the implementation of new protocols.” https://twitter.github.io/finagle/
  2. The reasons for this talk • gutefrage.net has used finagle

    for more than 3 years => Not many devs from the early days are still here • Rebuilding knowledge • Finagle documentation is incomplete
  3. App object MyApp extends com.twitter.app.App { premain { Dtabs.init() }

    onExit { log.info("Shutting down") } def main() { // ... start server closeOnExit(server) Await.ready(server) } }
  4. Flags val port = flag[Int]("port", 8080, "port this server should

    use") val env = flag[Env]("env", Env.Local, "environment this server runs") import com.twitter.app.Flaggable implicit val flaggableEnv = new Flaggable[Env] { override def parse(env: String): Env = ??? }
  5. What is Thrift and why use it? • Cross-language services

    development framework* • Provides code-generation for various languages • Very good finagle integration ➟ Describe your API in a language agnostic way * https://thrift.apache.org/static/files/thrift-20070401.pdf
  6. Service definition namespace * net.gutefrage.temperature.thrift struct TemperatureDatum { 1: i32

    celsius, 2: i64 timestamp } service TemperatureService { void add(1: TemperatureDatum datum); double mean(); }
  7. Scrooge - Code Generation • Generates native Scala thrift codecs

    • Generates client/server adaptors • SBT Plugin // plugins.sbt resolvers += "twitter-repo" at "https://maven.twttr.com" addSbtPlugin("com.twitter" %% "scrooge-sbt-plugin" % "4.8.0")
  8. Services - Theory trait Service[Req, Rep] extends (Req => Future[Rep])

    “Systems boundaries are represented by asynchronous functions called services. They provide a symmetric and uniform API: the same abstraction represents both clients and servers.”
  9. Services - Server val service = new Service[http.Request, http.Response] {

    def apply(req: http.Request): Future[http.Response] = Future.value( http.Response(req.version, http.Status.Ok) ) } val server = Http.serve(":8080", service) Await.ready(server) import com.twitter.finagle.{Http, Service} import com.twitter.finagle.http import com.twitter.util.{Await, Future}
  10. Services - Client import com.twitter.finagle.Service import com.twitter.finagle.http val client: Service[http.Request,

    http.Response] = Http.newService("www.scala-lang.org:80") client(Request("/foo/bar")).onSuccess { response: http.Response => println("received response " + response.contentString) }
  11. import net.gutefrage.temperature.thrift._ import com.twitter.finagle._ val service = new TemperatureService.FutureIface {

    override def add(datum: TemperatureDatum): Future[Unit] = ??? override def mean(): Future[Double] = ??? } val finagledService = new TemperatureService.FinagledService( service, thrift.Protocols.binaryFactory() )
  12. // run and announce the service val server: ListeningServer =

    ThriftMux.server .withLabel("temperature-service") .serveAndAnnounce( // schema ! host ! path ! shardId name = "zk!127.0.0.1:2181!/service/temperature!0", addr = s":8080", service = finagledService )
  13. A service must have a name Finagle uses names to

    identify network locations. A name can be: case class Name.Bound(va: Var[Addr]) case class Name.Path(path: Path) Resolver.eval parses Strings into Names.
  14. Resolver There are two ways to resolve a service 1.

    schema ! arg (always resolves to Name.Bound) Uses registered resolvers for schema, e.g. - zk ! host ! path - inet ! gutefrage.net:80 2. /path/to/service (Names.Path ~> Names.Bound) Hierachic path resolved with Dtabs
  15. Creating a Thrift Client import net.gutefrage.temperature.thrift._ val client = ThriftMux.client.newIface[TemperatureService.FutureIface](

    dest = "zk2!127.0.0.1:2181!/service/temperature", label = "temperature-sensor" ) val datum = TemperatureDatum(24, now()) client.add(datum)
  16. Finch “Finch is a thin layer of purely functional basic

    blocks atop of Finagle for building composable HTTP APIs. Its mission is to provide developers simple and robust HTTP primitives being as close as possible to the bare metal Finagle API.” https://github.com/finagle/finch
  17. case class Mean(mean: Double) val mean: Endpoint[Mean] = get("weather" /

    "mean") { client.mean().map(mean => Ok(Mean(mean))) } val server = Http.server .withLabel("weather-api") .withResponseClassifier(HttpResponseClassifier.ServerErrorsAsFailures) .serveAndAnnounce( name = "zk!127.0.0.1:2181/service/weather!0", addr = s":8000", service = api.toService )
  18. #1 Recap • Three services with a few lines of

    code • Generic and sharable API definition with Thrift • Hardcoded service paths in Zookeeper => static request routing • No way of sending request metadata
  19. What are Dtabs Remember: Finagle uses names to identify network

    locations. case class Name.Path(path: Path) A logical path starting with / is interpreted with a Dtab. http://twitter.github.io/finagle/guide/Names.html
  20. Interpreting When a name has src as a prefix, the

    prefix is replaced with dest src => dest otherwise the rule does not apply. http://twitter.github.io/finagle/guide/Names.html
  21. Important properties 1. Resolving via Dtabs must always terminate No

    recursion! 2. Dtabs a applied bottom to top Append a new entry to override existing ones 3. Dtabs are applied per request Overrides are used for the entire request tree
  22. (1) /s/temperature /zk# => /$/com.twitter.serverset /zk => /zk# /s## =>

    /zk/local:2181 /s# => /s##/prod /s => /s# Resolving step by step (2) /s#/temperature (3) /s##/prod/temperature (4) /zk/local:2181/prod/temperature (5) /zk#/local:2181/prod/temperature (6) /$/com.twitter.serverset/local:2181/prod/temperature
  23. Changing the destination val client = ThriftMux.client.newIface[TemperatureService.FutureIface]( dest = "/s/temperature",

    label = "temperature-sensor" ) import com.twitter.finagle.Dtab Dtab.base = Dtab.read("""<delegation table here>""") http://twitter.github.io/finagle/docs/#com.twitter.finagle.Dtab$
  24. HTTP Dtab Header • Route request to local --header ‘dtab-local’

    = ‘/s# => /s##/local’ • The docs? Read HttpDtab.scala and HttpDtabTest.scala
  25. Contexts • Request-scoped state, such as request deadline or authentication

    information • Managed across Threads / Execution Contexts ( Futures, Future Pools, Timers ) and client/server boundaries • Not explicitly passed • Two types: Local and Broadcast
  26. Create your own broadcast Context 1. Define a data structure

    case class UserContext(userId: Long) 2. Define a key object UserContext extends Contexts.broadcast.Key[UserContext]("..") 3. Implement marshalling override def marshal(userContext: UserContext): Buf = ??? override def tryUnmarshal(body: Buf): Try[UserContext] = ???
  27. val userContext = UserContext(userId) Contexts.broadcast.let(UserContext, userContext) { client.mean().map(mean => Ok(Mean(mean)))

    } UserContext.current.foreach { userContext => appLog.info(s"Getting mean for user ${userContext.userId}") } Access the context in a service Providing the context for a request
  28. #2 Recap We learned how to • route each request

    with dtabs • separate request metadata from business logic • implement cross-cutting request functionality
  29. Next steps • https://github.com/gutefrage/the-finagle-docs ◦ Extend our toy project ◦

    Write a comprehensive documentation • Leverage more finagle features in our own codebase and remove custom code
  30. Resources • Linkerd - Dtabs Documentation • Finagle 101 by

    @vkostyukov • Finagle RPC Redux by @marius • Finagle Blog • Finagle at Soundcloud by @pcalcado • Soundcloud - Synchronous communication for microservices