Advertisement

Async and Non-blocking IO w/ JRuby

JVM Languages Overlord at Heroku
Sep. 19, 2017
Advertisement

More Related Content

Slideshows for you(20)

Advertisement
Advertisement

Async and Non-blocking IO w/ JRuby

  1. ASYNCHRONOUS AND 
 NON-BLOCKING I/O WITH JRUBY Joe Kutner
  2. Joe Kutner @codefinger "Deploying with JRuby 9k" "The Healthy Programmer"
  3. SYNCHRONOUS WAIT (BLOCKING) CLIENT SERVER DATABASE BLOCKING
 WAIT
  4. BLOCKING IS BAD ▸ Each thread consumes resources ▸ Memory, Stack frames ▸ OS context switching ▸ GC has to walk the stack frames ▸ More $$$
  5. ASYCHRONOUS WAIT (NON-BLOCKING) CLIENT SERVER DATABASE ASYNC
 WAIT
  6. MULTIPLE CLIENTS CLIENT CLIENT SERVERCLIENT
  7. MULTIPLE BACKING SERVICES CLIENT SERVER DATABASE REDIS
  8. TEXT ASYNC IS GOOD ▸ Each request uses fewer resources ▸ Fewer threads ▸ Fewer servers ▸ Less $$$
  9. TEXT WHO'S DOING ASYNC ▸ Apple ▸ Google ▸ Twitter ▸ Facebook ▸ eBay
  10. NETTY @ APPLE https://speakerdeck.com/normanmaurer/connectivity
  11. Ratpack Netty Sinatra Rack ~=
  12. Synchronous and Blocking (Sinatra) REQUEST REQUEST REQUEST REQUEST
  13. REQUEST EVENT 
 LOOP REQUEST REQUEST REQUEST Asynchronous and Non-blocking (Ratpack)
  14. Events Event result Event
 Loop Event handler Event emitters
  15. PROMISES promise = Blocking.get do # execution segment end
 promise.then do |result| # execution segment end
  16. PROMISES promise = Blocking.get do # execution segment end promise.then do |result| # execution segment end EVENT
 LOOP
  17. EBAY SEARCH A SYNTHETIC DEMO
  18. EBAY Search term(s) CLIENT SERVER (RATPACK)
  19. SYNCHRONOUS EXAMPLE results = terms.map do |item| url = rest_url(item) response = Net ::HTTP.get(url) JSON.parse(response)["Item"] end.flatten render(results)
  20. SYNCHRONOUS WAIT (BLOCKING) CLIENT SERVER EBAY BLOCKING
 WAIT
  21. ASYNCHRONOUS EXAMPLE results = [] terms.each do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url).then do |response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end end Promise.value(results).then { |r| render(r) }
  22. ASYNCHRONOUS EXAMPLE results = [] terms.each do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url).then do |response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end end Promise.value(results).then { |r| render(r) } Promise
  23. ASYNCHRONOUS EXAMPLE results = [] terms.each do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url).then do |response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end end Promise.value(results).then { |r| render(r) }
  24. ASYNCHRONOUS EXAMPLE results = [] terms.each do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url).then do |response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end end Promise.value(results).then { |r| render(r) }
  25. SYNCHRONOUS WAIT (BLOCKING) CLIENT SERVER EBAY
  26. ASYNCHRONOUS WAIT (NON-BLOCKING) CLIENT SERVER EBAY
  27. ASYNC, SERIAL Events Event handler Event emitters EVENT 
 LOOP
  28. ASYNC, PARALLEL EVENT 
 LOOPEVENT 
 LOOP Events Event handler Event emitters EVENT 
 LOOPS
  29. promises = terms.map do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url) end batch = ParallelBatch.of(promises) results = Collections.synchronized_list([]) operation = batch.for_each do |i, response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end operation.then { render(results) }
  30. promises = terms.map do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url) end batch = ParallelBatch.of(promises) results = Collections.synchronized_list([]) operation = batch.for_each do |i, response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end operation.then { render(results) } Promise
  31. promises = terms.map do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url) end batch = ParallelBatch.of(promises) results = Collections.synchronized_list([]) operation = batch.for_each do |i, response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end operation.then { render(results) }
  32. promises = terms.map do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url) end batch = ParallelBatch.of(promises) results = Collections.synchronized_list([]) operation = batch.for_each do |i, response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end operation.then { render(results) }
  33. promises = terms.map do |item| url = rest_url(item) http_client = ctx.get(HttpClient.java_class) http_client.get(url) end batch = ParallelBatch.of(promises) results = Collections.synchronized_list([]) operation = batch.for_each do |i, response| body = response.get_body.get_text results << JSON.parse(body)["Item"] end operation.then { render(results) }
  34. ASYNCHRONOUS WAIT (NON-BLOCKING) CLIENT SERVER EBAY
  35. ASYNCHRONOUS WAIT (NON-BLOCKING) AND PARALLEL CLIENT SERVER EBAY
  36. HOW TO ASSEMBLE A JRUBY + RATPACK APP
  37. Gemfile source 'https: //rubygems.org' ruby '2.3.3', engine: 'jruby', engine_version: '9.1.12.0' gem 'jbundler'
  38. Jarfile jar 'io.ratpack:ratpack-core', '1.4.6' jar 'org.slf4j:slf4j-simple', '1.7.25'
  39. INSTALL DEPENDENCIES $ bundle install $ jbundle install
 ...
 
 jbundler runtime classpath:
 ------------------------
 .../ratpack-core-1.4.6.jar
 .../netty-common-4.1.6.Final.jar
 ...
 
 jbundle complete !
  40. lib/server.rb require 'java' require 'bundler/setup' Bundler.require java_import 'ratpack.server.RatpackServer' RatpackServer.start do |b| b.handlers do |chain| chain.get("async") do |ctx| # async request handling end chain.get("sync") do |ctx| # sync request handling end end end
  41. RUN THE APP $ bundle exec ruby lib/server.rb
 [main] INFO ratpack.server.RatpackServer - Starting server ...
 [main] INFO ratpack.server.RatpackServer - Building registry ...
 [main] INFO ratpack.server.RatpackServer - Ratpack started ...
  42. FIND IT ON GITHUB https://github.com/jkutner/jruby-ratpack-async-demo
  43. BOOKS EXAMPLE A REAL-WORLD DEMO
  44. BOOKSTORE OR LIBRARY MANAGEMENT APP
  45. DB HTTP Local Inventory isbndb.com BOOK Ruby object
  46. RATPACK INTEGRATES WITH JAVA LIBRARIES ▸ RxJava ▸ compose & transform asynchronous operations
 ▸ Hystrix ▸ fault tolerance ▸ caching ▸ de-duping ▸ batching
  47. Promise Observable~=
  48. MAP(F)
  49. TIMEOUT( )
  50. ZIP
  51. Jarfile jar 'io.ratpack:ratpack-core', '1.4.6' jar 'io.ratpack:ratpack-rx', '1.4.6' jar 'io.ratpack:ratpack-hystrix', '1.4.6' jar 'org.slf4j:slf4j-simple', '1.7.25'
  52. lib/server.rb RatpackServer.start do |b| book_service = BookService.new b.handlers do |chain| chain.get do |ctx| book_service.all(ctx).subscribe do |books| render_erb(ctx, "index.html.erb", binding) end end
  53. lib/server.rb RatpackServer.start do |b| book_service = BookService.new b.handlers do |chain| chain.get do |ctx| book_service.all(ctx).subscribe do |books| render_erb(ctx, "index.html.erb", binding) end end Observable Subscription
  54. lib/server.rb RatpackServer.start do |b| book_service = BookService.new b.handlers do |chain| chain.get do |ctx| book_service.all(ctx).subscribe do |books| render_erb(ctx, "index.html.erb", binding) end end
  55. lib/book_service.rb class BookService def initialize @db = BookDbCommands.new @isbn_db = IsbnDbCommands.new end def all(ctx) @db.all.flat_map do |row| @isbn_db.get_book(ctx, row[:isbn]).map do |json| row.merge(JSON.parse(json)) end end.to_list end
  56. lib/book_service.rb class BookService def initialize @db = BookDbCommands.new @isbn_db = IsbnDbCommands.new end def all(ctx) @db.all.flat_map do |row| @isbn_db.get_book(ctx, row[:isbn]).map do |json| row.merge(JSON.parse(json)) end end.to_list end Observable
  57. lib/book_service.rb class BookService def initialize @db = BookDbCommands.new @isbn_db = IsbnDbCommands.new end def all(ctx) @db.all.flat_map do |row| @isbn_db.get_book(ctx, row[:isbn]).map do |json| row.merge(JSON.parse(json)) end end.to_list end Observable
  58. lib/book_service.rb class BookService def initialize @db = BookDbCommands.new @isbn_db = IsbnDbCommands.new end def all(ctx) @db.all.flat_map do |row| @isbn_db.get_book(ctx, row[:isbn]).map do |json| row.merge(JSON.parse(json)) end end.to_list end Observable
  59. lib/book_service.rb class BookService def initialize @db = BookDbCommands.new @isbn_db = IsbnDbCommands.new end def all(ctx) @db.all.flat_map do |row| @isbn_db.get_book(ctx, row[:isbn]).map do |json| row.merge(JSON.parse(json)) end end.to_list end Observable
  60. lib/book_service.rb class BookService def initialize @db = BookDbCommands.new @isbn_db = IsbnDbCommands.new end def all(ctx) @db.all.flat_map do |row| @isbn_db.get_book(ctx, row[:isbn]).map do |json| row.merge(JSON.parse(json)) end end.to_list end Observable
  61. OBSERVABLES ▸ all: 
 Observable that will emit items ▸ flat_map: 
 Applies a function to each item, and returns an Observable that emits items ▸ to_list: 
 Returns an Observable that emits an Array of items ▸ subscribe: 
 Returns a Subscription to the Observable
  62. operation OBSERVABLE operation OBSERVABLE subsribe OBSERVABLE SUBSCRIPTION
  63. lib/book_db_commands.rb class BookDbCommands def all(ctx) s = HystrixObservableCommand ::Setter. with_group_key(GROUP_KEY). and_command_key(HystrixCommandKey ::Factory.as_key("getAll")) Class.new(HystrixObservableCommand) do def construct RxRatpack.observe_each(Blocking.get { DB["select isbn, quantity, price from books order by isbn"].all }) end def get_cache_key "db-bookdb-all" end end.new(s).to_observable end
  64. lib/book_db_commands.rb class BookDbCommands def all(ctx) s = HystrixObservableCommand ::Setter. with_group_key(GROUP_KEY). and_command_key(HystrixCommandKey ::Factory.as_key("getAll")) Class.new(HystrixObservableCommand) do def construct RxRatpack.observe_each(Blocking.get { DB["select isbn, quantity, price from books order by isbn"].all }) end def get_cache_key "db-bookdb-all" end end.new(s).to_observable end
  65. lib/book_db_commands.rb class BookDbCommands def all(ctx) s = HystrixObservableCommand ::Setter. with_group_key(GROUP_KEY). and_command_key(HystrixCommandKey ::Factory.as_key("getAll")) Class.new(HystrixObservableCommand) do def construct RxRatpack.observe_each(Blocking.get { DB["select isbn, quantity, price from books order by isbn"].all }) end def get_cache_key "db-bookdb-all" end end.new(s).to_observable end
  66. lib/book_db_commands.rb class BookDbCommands def all(ctx) s = HystrixObservableCommand ::Setter. with_group_key(GROUP_KEY). and_command_key(HystrixCommandKey ::Factory.as_key("getAll")) Class.new(HystrixObservableCommand) do def construct RxRatpack.observe_each(Blocking.get { DB["select isbn, quantity, price from books order by isbn"].all }) end def get_cache_key "db-bookdb-all" end end.new(s).to_observable end
  67. FIND IT ON GITHUB https://github.com/jkutner/jruby-ratpack-books-example
  68. IN RUBY… WAYS TO DO ASYNC ▸ Netty (via Ratpack) ▸ Servlet 3.1 Async (via Warbler) ▸ Vert.x ▸ EventMachine
  69. TEXT EVENTMACHINE ▸ Difficult to avoid callback hell ▸ Not integrated into your web framework
  70. TEXT WHY NOT ASYC? ▸ Bottleneck is often the DB ▸ Async is hard ▸ Non-deterministic ▸ Callback hell ▸ Error handling/propagation
  71. LINKS ▸ http://jruby.org ▸ https://ratpack.io ▸ https://netty.io ▸ http://reactivex.io ▸ https://github.com/jkutner/jruby-ratpack-async-demo ▸ https://github.com/jkutner/jruby-ratpack-books-example
  72. JOE KUTNER
 @codefinger THANKS!
Advertisement