Rails
RabbitMQ
WebSockets
EventMachine
o wyprawie królika przez maszynkę do
gniazdka, czyli Event Machine w praktyce
grzegorz
bryliński
Ja
• opowiem wam o pewnym projekcie
• pisałem w PHP i już nie chcę (-:
• teraz piszę w Ruby
• i bardzo się staram, bo nie wiem kto siedzi
obok, a jak mówi chińskie przysłowie:
Zawsze pisz kod tak, jakby osoba która będzie
pracowała nad nim po tobie była psychopatycznym
mordercą, który wie gdzie mieszkasz.
Był sobie projekt
• Panel administracyjny z podstawową
funkcjonalnością
• RESTful API
• Serwer WebSocket
• Aplikacja mobilna dla iOS-a
Klient chciałby wiele…
…oczekiwania ↦ ∞
A może sobie pozwolić…
…stosownie do budżetu ↦ 0
Damy radę, ale…
• Niska ilość potrzebnych mendejsów, a więc…
• szybki development, a więc…
• prosta architektura, a więc…
• jednorodne środowisko, a więc…
Ein Reich Ruby dla wszystkich…
• Mniej języków, mniej problemów
• Panel administracyjny - RoR
• Serwer WS - Ruby
• Interfejs MQ - Ruby
Ruby on Rails
http://rubyonrails.org/
public function indexAction()
{
return $this->render('default/index.html.twig');
}
def index
end
czy…
!ruby albo zdrowie, wybór należy do ciebie…
RabbitMQ
• Message Broker
• po polsku: Mesedż Broker 😜
• po prostu działa ™
• Erlang, no trudno
• https://www.rabbitmq.com/
EventMachine
• implementuje wzorzec reaktora
• jest lekka i szybka
• cięższe zadania -> Deferrable
• https://github.com/eventmachine/
eventmachine
Bunny
• tunel do RabbitMQ
• prosty (bardzo)
• to już duży, dojrzały królik
• działa z… EM
• https://github.com/ruby-amqp/bunny
EM-WebSockets
• Serwer WebSocket w Ruby
• prosty (bardzo)
• działa z… EM
• deja vu, deja vu
• https://github.com/igrigorik/em-websocket
Nasz projekt
koćmy zatem…
Pętla główna
require "event_machine"
EventMachine.run do
definition_of_some_useful_handlers
end
Disclaimer:
prezentowany kod to złożona forma cyfrowego odwzorowania ludzkiej
myśli. Jest obszerny i skomplikowany, może też być niezrozumiały.
żart, przecież to Ruby
+= WebSockets
require "event_machine"
require "em-websocket"
server_config_hash = {host: "0", port: 8080}
EventMachine.run do
EM::WebSocket.run(server_config_hash) do |ws|
ws.onopen { |handshake| do_something_on_open(ws, handshake) }
ws.onmessage { |msg| do_something_else_on_message(ws, msg) }
ws.onclose { do_nothing_on_close(ws) }
ws.onerror { |error| definitely_do_nothing_on_error(error) }
end
end
Identyfikacja klienta?
def on_open(ws, handshake)
if user = User.for_token( handshake.query["token"] )
connection_pool.update(ws, user.id)
else
log "Open without a valid user. Foch..."
ws.close
end
rescue => exception
log "OPEN exception: #{exception}"
end
+= Bunny
require "event_machine"
require "bunny"
connection = Bunny.new
channel = connection.create_channel
queue = channel.queue("a_test_queue", durable: false)
EventMachine.run do
queue.subscribe(manual_ack: true, block: false) do
|delivery_info, properties, body|
message = JSON.parse(body) # it's smart and convenient to use JSON
do_magic_on(message) && channel.ack(delivery_info.delivery_tag)
end
end
+= Bunny
queue.subscribe(manual_ack: true, block: false) do …
o tym warto pamiętać
+= Timers
require "event_machine"
EventMachine.run do
EventMachine::PeriodicTimer.new(1) do
send_server_stats_to_manager_email
end
end
Razem prezentuje się…
require "event_machine"
require "bunny"
require "em-websocket"
counter = 0
server_config_hash = {host: "0", port: 8080}
connection = Bunny.new
channel = connection.create_channel
queue = channel.queue("a_test_queue", durable: false)
EventMachine.run do
EM::WebSocket.run(server_config_hash) do |ws|
ws.onopen { |handshake| do_something_on_open(ws, handshake) }
ws.onmessage { |msg| do_something_else_on_message(ws, msg) }
ws.onclose { do_nothing_on_close(ws) }
ws.onerror { |error| definitely_do_nothing_on_error(error) }
end
queue.subscribe(manual_ack: true, block: false) do |delivery_info, properties, body|
message = JSON.parse(body) # it's smart and convenient to use JSON
do_magic_on(message)
channel.ack(delivery_info.delivery_tag)
end
EventMachine::PeriodicTimer.new(1) do
send_server_stats_to_manager_email
end
end
Niewiele kodu,
który sporo robi
bo Facebook
sam się nie uzupełni
Tymczasem w aplikacji…
class Message
def push_to_bunny
$rabbit_session.publish_message(self.id)
end
end
Tyle wystarczy, żeby wysłać
naszą wiadomość
do kolejki. Ruby rulez (-:
Tadam!
• Działa
• Jest prosto
• Było szybko
• Będzie stabilnie
Obserwacje
• wyjątki dobrze jest obsługiwać w miejscu,
w którym się ich spodziewamy. EM
domyślnie je przechwytuje i tyle.
• nie należy serializować obiektów i
wrzucać ich do kolejek. Ale przecież nikt
tego nie robi. Robi?
Obserwacje…
• Bundler.require jest pożyteczny
• Obsługę poszczególnych sub-pętli dobrze
wrzucić do dedykowanych klas. Kod staje
się podatny na testowanie.
Dziękuję za uwagę
GitHub: http://goo.gl/jEopOq
email: grzegorz@tsh.io
Pytania?

O wyprawie królika przez maszynkę do gniazdka, czyli EventMachine w praktyce

  • 1.
    Rails RabbitMQ WebSockets EventMachine o wyprawie królikaprzez maszynkę do gniazdka, czyli Event Machine w praktyce grzegorz bryliński
  • 2.
    Ja • opowiem wamo pewnym projekcie • pisałem w PHP i już nie chcę (-: • teraz piszę w Ruby • i bardzo się staram, bo nie wiem kto siedzi obok, a jak mówi chińskie przysłowie: Zawsze pisz kod tak, jakby osoba która będzie pracowała nad nim po tobie była psychopatycznym mordercą, który wie gdzie mieszkasz.
  • 3.
    Był sobie projekt •Panel administracyjny z podstawową funkcjonalnością • RESTful API • Serwer WebSocket • Aplikacja mobilna dla iOS-a
  • 4.
  • 5.
    A może sobiepozwolić… …stosownie do budżetu ↦ 0
  • 6.
    Damy radę, ale… •Niska ilość potrzebnych mendejsów, a więc… • szybki development, a więc… • prosta architektura, a więc… • jednorodne środowisko, a więc…
  • 7.
    Ein Reich Rubydla wszystkich… • Mniej języków, mniej problemów • Panel administracyjny - RoR • Serwer WS - Ruby • Interfejs MQ - Ruby
  • 8.
    Ruby on Rails http://rubyonrails.org/ publicfunction indexAction() { return $this->render('default/index.html.twig'); } def index end czy… !ruby albo zdrowie, wybór należy do ciebie…
  • 9.
    RabbitMQ • Message Broker •po polsku: Mesedż Broker 😜 • po prostu działa ™ • Erlang, no trudno • https://www.rabbitmq.com/
  • 10.
    EventMachine • implementuje wzorzecreaktora • jest lekka i szybka • cięższe zadania -> Deferrable • https://github.com/eventmachine/ eventmachine
  • 11.
    Bunny • tunel doRabbitMQ • prosty (bardzo) • to już duży, dojrzały królik • działa z… EM • https://github.com/ruby-amqp/bunny
  • 12.
    EM-WebSockets • Serwer WebSocketw Ruby • prosty (bardzo) • działa z… EM • deja vu, deja vu • https://github.com/igrigorik/em-websocket
  • 13.
  • 14.
    Pętla główna require "event_machine" EventMachine.rundo definition_of_some_useful_handlers end Disclaimer: prezentowany kod to złożona forma cyfrowego odwzorowania ludzkiej myśli. Jest obszerny i skomplikowany, może też być niezrozumiały. żart, przecież to Ruby
  • 15.
    += WebSockets require "event_machine" require"em-websocket" server_config_hash = {host: "0", port: 8080} EventMachine.run do EM::WebSocket.run(server_config_hash) do |ws| ws.onopen { |handshake| do_something_on_open(ws, handshake) } ws.onmessage { |msg| do_something_else_on_message(ws, msg) } ws.onclose { do_nothing_on_close(ws) } ws.onerror { |error| definitely_do_nothing_on_error(error) } end end
  • 16.
    Identyfikacja klienta? def on_open(ws,handshake) if user = User.for_token( handshake.query["token"] ) connection_pool.update(ws, user.id) else log "Open without a valid user. Foch..." ws.close end rescue => exception log "OPEN exception: #{exception}" end
  • 17.
    += Bunny require "event_machine" require"bunny" connection = Bunny.new channel = connection.create_channel queue = channel.queue("a_test_queue", durable: false) EventMachine.run do queue.subscribe(manual_ack: true, block: false) do |delivery_info, properties, body| message = JSON.parse(body) # it's smart and convenient to use JSON do_magic_on(message) && channel.ack(delivery_info.delivery_tag) end end
  • 18.
    += Bunny queue.subscribe(manual_ack: true,block: false) do … o tym warto pamiętać
  • 19.
    += Timers require "event_machine" EventMachine.rundo EventMachine::PeriodicTimer.new(1) do send_server_stats_to_manager_email end end
  • 20.
    Razem prezentuje się… require"event_machine" require "bunny" require "em-websocket" counter = 0 server_config_hash = {host: "0", port: 8080} connection = Bunny.new channel = connection.create_channel queue = channel.queue("a_test_queue", durable: false) EventMachine.run do EM::WebSocket.run(server_config_hash) do |ws| ws.onopen { |handshake| do_something_on_open(ws, handshake) } ws.onmessage { |msg| do_something_else_on_message(ws, msg) } ws.onclose { do_nothing_on_close(ws) } ws.onerror { |error| definitely_do_nothing_on_error(error) } end queue.subscribe(manual_ack: true, block: false) do |delivery_info, properties, body| message = JSON.parse(body) # it's smart and convenient to use JSON do_magic_on(message) channel.ack(delivery_info.delivery_tag) end EventMachine::PeriodicTimer.new(1) do send_server_stats_to_manager_email end end Niewiele kodu, który sporo robi bo Facebook sam się nie uzupełni
  • 21.
    Tymczasem w aplikacji… classMessage def push_to_bunny $rabbit_session.publish_message(self.id) end end Tyle wystarczy, żeby wysłać naszą wiadomość do kolejki. Ruby rulez (-:
  • 22.
    Tadam! • Działa • Jestprosto • Było szybko • Będzie stabilnie
  • 23.
    Obserwacje • wyjątki dobrzejest obsługiwać w miejscu, w którym się ich spodziewamy. EM domyślnie je przechwytuje i tyle. • nie należy serializować obiektów i wrzucać ich do kolejek. Ale przecież nikt tego nie robi. Robi?
  • 24.
    Obserwacje… • Bundler.require jestpożyteczny • Obsługę poszczególnych sub-pętli dobrze wrzucić do dedykowanych klas. Kod staje się podatny na testowanie.
  • 25.