The Play Framework at LinkedIn

156,944 views

Published on

Video of the presentation: http://www.youtube.com/watch?v=8z3h4Uv9YbE

At LinkedIn, we have started to use the Play Framework to build front-end and back-end services at massive scale. Play does things a little differently: it's a Java and Scala web framework, but it doesn't follow the servlet spec; it's fairly new, but it runs on top of robust technologies like Akka and Netty; it uses a thread pool, but it's built for non-blocking I/O and reactive programming; most importantly, it's high performance, but also high productivity. We've found that the Play Framework is one of the few frameworks that is able to maintain the delicate balance of performance, reliability, and developer productivity. In the Java and Scala world, nothing even comes close. In this talk, I'll share what we've learned so far, including details of rapid iteration with Java and Scala, the story behind async I/O on the JVM, support for real time web apps (comet, WebSockets), and integrating Play into a large existing codebase.

Published in: Technology, Education
9 Comments
299 Likes
Statistics
Notes
No Downloads
Views
Total views
156,944
On SlideShare
0
From Embeds
0
Number of Embeds
74,861
Actions
Shares
0
Downloads
715
Comments
9
Likes
299
Embeds 0
No embeds

No notes for slide

The Play Framework at LinkedIn

  1. atProductivity and Performance at Scale
  2. You are a developer using Java tobuild web services.
  3. This is your life:
  4. Waiting. [1]
  5. tail -f logs/catalina.out
  6. XML soup
  7. "Convenient proxy factory bean superclass for proxyfactory beans that create only singletons." [2]
  8. Thread pool usageLatencyThread pool hell
  9. There is a better way.
  10. Ruby on Rails!
  11. Nah, just kidding. We have real work to do.
  12. A modern web framework for Java and Scala
  13. The world’s largest professional network
  14. atWeve been using Play in production formore than 6 months [3]
  15. A few apps built on Play
  16. Channels (frontend)
  17. Premium Subscriptions (frontend)
  18. Polls (frontend + backend)
  19. REST search (internal tool)
  20. About meLeading the Play project as part of LinkedIns Service Infrastructure Team.Also: hackdays, engineering blog, incubator, open source.
  21. This talk is the story of building web servicesat massive scale with Java and Scala...
  22. ... while maintaining performance,reliability, and developerproductivity.
  23. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  24. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  25. Download and install Play fromhttp://www.playframework.com
  26. > play new my-app
  27. > play idea> play eclipse
  28. > play run
  29. http://localhost:9000
  30. Application layoutapp → Application sources└ assets → Compiled asset sources└ controllers → Application controllers└ models → Application business layer└ views → Templatesconf → Configurations files└ application.conf → Main configuration file└ routes → Routes definitionpublic → Public assets└ stylesheets → CSS files└ javascripts → Javascript files└ images → Image filesproject → sbt configuration files└ Build.scala → Application build script└ plugins.sbt → sbt pluginslib → Unmanaged librariesdependencieslogs → Standard logs foldertarget → Generated stufftest → Unit or functional tests
  31. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  32. public class HelloWorld extends Controller {public static Result index() {return ok("Hello World");}}Create a new controller and actionapp/controllers/HelloWorld.java
  33. Dont worry about the use of static. Yes,Play supports IOC. Using static (and othershortcuts) lets me keep the examples simple.
  34. GET /hello controllers.HelloWorld.index()Expose the controller/action at a URLconf/routes
  35. Now, restart the server.
  36. Nah, just kidding. Refresh the page.
  37. Woohoo, hot reload!
  38. http://localhost:9000/hello
  39. public class HelloWorld extends Controller {public static Result index(String name) {return ok("Hello " + name);}}Add a parameterapp/controllers/HelloWorld.java
  40. GET /hello controllers.HelloWorld.index( name)Read the parameter from the query stringconf/routes
  41. http://localhost:9000/hello?name=Jim
  42. GET /hello/:name controllers.HelloWorld.index(name)Read the parameter from the URL insteadconf/routes
  43. http://localhost:9000/hello/Jim
  44. public class HelloWorld extends Controller {public static Result index(String name, int age) {return ok("Hello " + name + " you are " + age +" years old");}}Add another parameter, this time an intapp/controllers/HelloWorld.java
  45. GET /hello/:name/ :age controllers.HelloWorld.index(name: String, age: Int)Add the parameter. Note the type checking!conf/routes
  46. http://localhost:9000/hello/Jim/28
  47. @(name: String, age: Int)<html><head></head><body><img src="/assets/images/play-logo.png"/><p>Hello <b>@name</b>, you are <b>@age</b> years old</p></body></html>Add a viewapp/views/hello.scala.html
  48. public class HelloWorld extends Controller {public static Result index(String name, int age) {return ok(views.html.hello.render(name, age));}}Render the view from the controllerapp/controllers/HelloWorld.java
  49. http://localhost:9000/hello/Jim/28
  50. location = "JaxConf"How about some config?app/conf/application.conf
  51. public class HelloWorld extends Controller {public static Result index(String name, int age) {String location = getConfig().getString("location");return ok(views.html.hello.render(name, age,location));}private static Configuration getConfig() {return Play.application().configuration();}}Read the config and pass it to the viewapp/controllers/HelloWorld.java
  52. @(name: String, age: Int, location: String)<html><head></head><body><img src="/assets/images/play-logo.png"/><p>Hello <b>@name</b>, you are <b>@age</b> years oldand you are at <b>@location</b></p></body></html>Update the viewapp/views/hello.scala.html
  53. Refresh the page. Config hot reloads too!
  54. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  55. In dev mode, Play shows error messagesright in the browser.
  56. Parameter type checking
  57. A helpful 404 page
  58. Compile errors show problematic source codein the browser
  59. Views are compiled as well
  60. The routes file too
  61. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  62. Most people are used to threaded servers
  63. Threaded servers assign one thread perrequest and use blocking I/Ovoid doGet(HttpServletRequest req, HttpServletResponse res) {// Apache HttpClientHttpClient client = new HttpClient();GetMethod method = new GetMethod("www.example.com/");// executeMethod is a blocking, synchronous callint statusCode = client.executeMethod(method);System.out.println("Response " + statusCode);}MyServlet.java
  64. Evented servers are gaining popularity
  65. Evented servers have one thread/process perCPU core and use non-blocking I/OMyNodeApp.jsvar callback = function(data) {console.log("Response: " + data);};var options = {hostname: www.google.com,path: /upload};// Non-blocking HTTP callhttp.request(options, callback);console.log(This line may execute before the callback!);
  66. Why threaded vs. evented matters forLinkedIn
  67. LinkedIn uses a Service Oriented ArchitectureInternet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStore
  68. void doGet(HttpServletRequest req, HttpServletResponse res) {// Call a number of backend services to get dataProfile profile = profileSvc.getProfile();Company company = companySvc.getCompany();Skills skills = skillsSvc.getSkills();}MyServlet.javaOur services spend most of their time waitingfor data from other services and data stores
  69. I/O is very expensive [4]
  70. In a threaded server, threads spend most ofthe time idle, waiting on I/O
  71. Threading dilemma1. Creating new threads on the fly is expensive:a. Use a thread pool2. Too many threads in the thread pool:a. Memory overheadb. Context switching overhead3. Too few threads in the thread pool:a. Run out of threads, latency goes upb. Sensitive to downstream latency!
  72. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreLets say latency goesup a little here
  73. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreCauses threads to getbacked up here
  74. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreLatency goes up
  75. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreNow threads getbacked up here
  76. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreAnd here
  77. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreHere too
  78. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreAnd there
  79. Internet LoadBalancerFrontendServerFrontendServerFrontendServerBackendServerBackendServerBackendServerBackendServerBackendServerDataStoreDataStoreDataStoreDataStoreAnd... the site is down.
  80. This is thread pool hell [5]
  81. Play is built on top of Netty, so it supports non-blocking I/O [6]
  82. NIO benefits1. No sensitivity to downstream slowness2. Easy to parallelize I/O3. Supports many concurrent and long-runningconnections, enabling:a. WebSocketsb. Cometc. Server-Sent Events
  83. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  84. public class Proxy extends Controller {public static Result index(String url) {// Non blocking HTTP callPromise<Response> responsePromise = WS.url(url).get();// How do we turn a Promise into a Play Result?}}app/controllers/Proxy.javaUse Plays built in WS library to make a non-blocking HTTP call
  85. A Promise<T> will eventually contain thevalue T (or an error)
  86. (Play Framework source code)Play has a built-in subclass of Result calledAsyncResult that takes a Promise<Result>public static class AsyncResult implements Result {private final Promise<Result> promise;public AsyncResult(Promise<Result> promise) {this.promise = promise;}}
  87. public class Proxy extends Controller {public static Result index(String url) {// Non blocking HTTP callPromise<Response> response = WS.url(url).get();// Transform asynchronously into a Play ResultPromise<Result> result = response.map(toResult);return async(result);}// A function that can transform a Response into a Resultprivate static Function<Response, Result> toResult =new Function<Response, Result>() {public Result apply(Response response) {return ok(response.getBody()).as("text/html");}};}app/controllers/Proxy.javaWe can use the map method to turn aPromise<Response> into a Promise<Result>
  88. GET /proxy controllers.Proxy.index(url)Lets add this endpoint to the routes fileconf/routes
  89. http://localhost:9000/proxy?url=http://example.com
  90. We just built a completelynon-blocking proxy!
  91. public class Proxy extends Controller {public static Result index(String url) {Logger.info("Before the HTTP call");Promise<Response> response = WS.url(url).get();Promise<Result> result = response.map(toResult);Logger.info("After the HTTP call");return async(result);}private static Function<Response, Result> toResult =new Function<Response, Result>() {public Result apply(Response response) {Logger.info("Inside the toResult function");return ok(response.getBody()).as("text/html");}};}app/controllers/Proxy.javaTo see that its non-blocking, lets add logging
  92. Refresh the page and the logs show theHTTP call really is non-blocking
  93. NIO makes parallel requests easy.Lets try it.
  94. class Timing {public String url;public long latency;public Timing(String url, long latency) {this.url = url;this.latency = latency;}// Fire an HTTP request and record how long it tookpublic static Promise<Timing> timedRequest(final String url) {final long start = System.currentTimeMillis();Promise<Response> res = WS.url(url).get();return res.map(new Function<Response, Timing>() {public Timing apply(Response response) {long latency = System.currentTimeMillis() - start;return new Timing(url, latency);}});}}app/models/Timing.javaFirst, define a class that times an HTTP request
  95. public class Parallel extends Controller {public static Result index() {// A Promise that will redeem when the 3 parallel// HTTP requests are donePromise<List<Timing>> all = Promise.waitAll(Timing.timedRequest("http://www.yahoo.com"),Timing.timedRequest("http://www.google.com"),Timing.timedRequest("http://www.bing.com"));// Asynchronously transform timings into a JSON responsereturn async(all.map(new Function<List<Timing>, Result>() {public Result apply(List<Timing> timings) {return ok(Json.toJson(timings));}}));}}app/controllers/Parallel.javaNext, add a controller that fetches 3 URLs inparallel and returns the timings as JSON
  96. GET /parallel controllers.Parallel.index()Add it to the routes fileconf/routes
  97. http://localhost:9000/parallel
  98. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  99. Play has native support for Scala
  100. app/controllers/HelloWorldScala.scalaJust add a .scala file under /app andPlay will compile itobject HelloWorldScala extends Controller {def index = Action {Ok("Hello World Scala")}}
  101. GET /scala controllers.HelloWorldScala.index()Add it to the routes file as usualconf/routes
  102. http://localhost:9000/scala
  103. Play/Scala: the good parts
  104. Sequential async calls// 2 sequential async callsfor {foo <- WS.url(url1).get()bar <- WS.url(buildUrl(foo)).get()} yield {Ok(...)}API is more concise, expressive, &composable, especially for async code.Sequential async calls// 2 parallel async callsval fooFuture = WS.url(url1).get()val barFuture = WS.url(url2).get()for {foo <- fooFuturebar <- barFuture} yield {Ok(...)}
  105. Interactive console with full classpath of your app
  106. Play is Scala at its core: main libraries,routes, templates, SBT, etc [7]
  107. Play/Scala: the less good parts
  108. Simple Build Tool: Plays build system. Verypowerful, but very steep learning curve.
  109. project/Build.scalaval settings = Seq(bootPath <<= target( _ / "boot" ),buildDir <<= baseDirectory(_ / ".." / "build"),srcDir <<= baseDirectory(_/ ".." / "src"),enableConfigCompile := true,fabrics <++= Seq("EI", "EI2"))Abandon all hope, ye who enter herewithout knowing Scala.
  110. project/Build.scalafabrics <++= Seq("EI", "EI2")WTF is <++=? Some kind of fish bones?How do I google that?
  111. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  112. Is Play web scale? [8]
  113. Scalability can be measured alongmany dimensions
  114. RawhorsepowerConcurrenthorsepowerSingledeveloperMultipledevelopers
  115. Raw horsepower: theoretical maximumperformance for the server in idealconditions. A measure of language andframework overhead.
  116. Concurrent horsepower: performance withmany users, I/O, and more real-worldscenarios. A measure of the frameworksapproach to concurrency.
  117. Single developer: how easy it is to getstarted, how quickly a single developer canbuild things. A measure of the frameworksraw productivity and tooling.
  118. Multiple developers: how the frameworktolerates many developers workingconcurrently over many years. A measure ofcode rot and maintainability.
  119. Here is how Id rate some of theframeworks Ive used. YMMV.
  120. ● Pros○ Good raw throughput (qps, latency)○ Type safety reduces code rot● Cons○ Not developer friendly. Getting things done takesforever.○ Threaded, synchronous approach difficult to scalefor lots of I/O in a SOA environment.MVC
  121. RawhorsepowerConcurrenthorsepowerSingledeveloperMultipledevelopersSpringMVC
  122. ● Pros○ Set the bar for developer productivity; all otherframeworks are still trying to catch up.● Cons○ Ruby is slow○ Ruby doesnt have real multithreading nor a greatevented framework○ Dynamic language makes it tougher to maintain alarge codebase
  123. RawhorsepowerConcurrenthorsepowerSingledeveloperMultipledevelopersRails
  124. ● Pros○ v8 engine is pretty fast for raw throughput○ Non-blocking I/O at the core makes concurrencyeasy○ Strong open source community and lightning faststartup time makes it easy to get things done quickly● Cons○ Dynamic language makes it tougher to maintain alarge codebase○ Lots of immature libraries that constantly makebackwards incompatible changes
  125. RawhorsepowerConcurrenthorsepowerSingledeveloperMultipledevelopersNode.js
  126. ● Pros○ Fast for raw throughput○ Non-blocking I/O at the core makes concurrencyeasy○ Hot reload makes it possible to get things donequickly○ Strong type safety throughout reduces code rot● Cons○ Even with hot reload, a compiled statically typedlanguage isnt quite as fast as an interpreteddynamically typed language
  127. RawhorsepowerConcurrenthorsepowerSingledeveloperMultipledevelopersPlay
  128. Outline1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  129. Play is open source [7]
  130. LinkedIn is contributing
  131. Very active google group (mailing list) [9]
  132. StackOverflow tag [10]
  133. Open source modules and plugins [11]
  134. Weve open sourced a few of our plugins,with many more on the way [12]
  135. Commercial support from Typesafe [13]
  136. Recap1. Getting started with Play2. Make a change and reload3. Error handling4. Threaded vs. evented5. Non-blocking I/O6. Scala7. Performance8. Community
  137. Thats the story of Play at LinkedIn so far...
  138. But were just getting started.
  139. LinkedIn Engineering Blog [14]
  140. @LinkedInEng on twitter [15]
  141. Thank you!
  142. References1. http://zeroturnaround.com/java-ee-productivity-report-2011/2. http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/...3. http://engineering.linkedin.com/play/play-framework-linkedin4. http://www.eecs.berkeley.edu/~rcs/research/interactive_latency.html5. http://engineering.linkedin.com/play/play-framework-async-io-without...6. http://netty.io/7. https://github.com/playframework/Play208. http://mongodb-is-web-scale.com/9. https://groups.google.com/forum/?fromgroups#!forum/play-framework10. http://stackoverflow.com/tags/playframework11. http://www.playframework.com/documentation/2.1.1/Modules12. https://github.com/linkedin13. http://typesafe.com/platform/runtime/playframework14. http://engineering.linkedin.com/15. https://twitter.com/LinkedInEng

×