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

Disyuntores: o cómo no terminar viviendo abajo de un puente

Disyuntores: o cómo no terminar viviendo abajo de un puente

En esta charla para ruby.conf.online repasamos rápidamente el patrón Circuit Breaker, sus usos, ventajas y desventajas, y cómo aprovecharlo utilizando la gema disyuntor.

Leandro López

December 04, 2015
Tweet

More Decks by Leandro López

Other Decks in Programming

Transcript

  1. o/

  2. @inkel • Web Developer • DevOps Wannabe • Citrusbyte •

    RubyConfAR • No sé diseñar slides • Fundamentalista de la barba • Lector
  3. • Más usuarios • Más consultas • Más lenta cada

    consulta • Mayor cantidad de recursos • Timeouts
  4. on get, "users" do users = Cache.fetch("user.all") do User.all end

    ! render("users/index", users: users) end
  5. on get, "users" do users = Cache.fetch("user.all") do User.all end

    ! render("users/index", users: users) end
  6. • Cache no anda • Sobrecarga base de datos •

    Saturación de red • Timeouts
  7. Disyuntor • Controla el consumo de energía eléctrica • Sobrepasado

    cierto nivel, abre el circuito • No se te prende fuego la casa
  8. Patrón Disyuntor • Controla el acceso y ejecución de un

    bloque de código • Sobrepasado el límite de errores consecutivos, abre el circuito • Todo intento a ejecutar el código falla con un error conocido • Pasado cierto timeout, intenta ejecutar el código • Si funciona correctamente, cierra el circuito • Si falla, comienza el ciclo nuevamente
  9. # A partir del décimo error consecutivo, abrir el circuito


    # y esperar 5 segundos para volver a intentarlo. disyuntor_cache = Disyuntor.new(threshold: 10, timeout: 5) ! # A partir del quinto error consecutivo, abrir el circuito
 # y esperar 1 minuto para volver a intentarlo. disyuntor_db = Disyuntor.new(threshold: 5, timeout: 60) ! # Ejecutar una acción personalizada cuando el circuito esté
 # abierto. disyuntor_db.on_circuit_open do fail DBNotFound end
  10. on get, "users" do begin users = get_users_from_cache render("users/index", users:

    users) rescue DBNotFound render("db_not_found") end end
  11. on get, "users" do begin users = get_users_from_cache render("users/index", users:

    users) rescue DBNotFound render("db_not_found") end end
  12. • Si uno de los servicios no funciona falla inmediatamente.

    • Menor consumo de recursos en los servidores, debido a no ejecutar código que sabemos va a fallar. • Menos intervención manual dado que el sistema se “arregla” automáticamente. • Disminución en el número de errores en cascada.
  13. –Martin Fowler “On their own, circuit breakers help reduce resources

    tied up in operations which are likely to fail. You avoid waiting on timeouts for the client, and a broken circuit avoids putting load on a struggling server.”
  14. inkel@miralejos ~/dev/disyuntor (master=) $ cloc lib/disyuntor.rb 1 text file. 1

    unique file. 0 files ignored. ! http://cloc.sourceforge.net v 1.64 T=0.01 s (67.1 files/s, 6243.7 lines/s) ------------------------------------------------------------------------------- Language files blank comment code ------------------------------------------------------------------------------- Ruby 1 19 0 74 -------------------------------------------------------------------------------
  15. require "micromachine" ! class Disyuntor CircuitOpenError = Class.new(RuntimeError) ! attr_reader

    :failures, :opened_at, :threshold, :timeout ! def initialize(threshold: 5, timeout: 10) @threshold = threshold @timeout = timeout ! on_circuit_open { fail CircuitOpenError } ! close! end ! def states @states ||= MicroMachine.new(:closed).tap do |fsm| fsm.when(:trip, :half_open => :open, :closed => :open) fsm.when(:reset, :half_open => :closed, :closed => :closed) fsm.when(:try, :open => :half_open) ! fsm.on(:open) do @opened_at = Time.now.to_i end ! fsm.on(:closed) do @opened_at = nil @failures = 0 end end end ! def close! () states.trigger!(:reset) end def open! () states.trigger!(:trip) end def half_open! () states.trigger!(:try) end ! def closed? () states.state == :closed end def open? () states.state == :open end def half_open? () states.state == :half_open end ! def timed_out? open? && Time.now.to_i > next_timeout_at end ! def next_timeout_at closed? ? nil : (@opened_at + @timeout) end ! def increment_failures! @failures += 1 end ! def try(&block) half_open! if timed_out? ! case when closed? then circuit_closed(&block) when half_open? then circuit_half_open(&block) when open? then circuit_open end end ! def circuit_closed(&block) ret = block.call rescue open! if increment_failures! > threshold raise else close! ret end ! def circuit_half_open(&block) ret = block.call rescue increment_failures! open! raise else close! ret end ! def on_circuit_open(&block) raise ArgumentError, "Must pass a block" unless block_given? @on_circuit_open = block end ! def circuit_open @on_circuit_open.call(self) end end
  16. end ! def increment_failures! @failures += 1 end ! def

    try(&block) half_open! if timed_out? ! case when closed? then circuit_closed(&block) when half_open? then circuit_half_open(&block) when open? then circuit_open end end ! def circuit_closed(&block) ret = block.call rescue open! if increment_failures! > threshold raise else
  17. #! /usr/bin/env ruby ! require "redis" require "benchmark/ips" require_relative "lib/disyuntor"

    ! disyuntor = Disyuntor.new(threshold: 1, timeout: 60) ! disyuntor.on_circuit_open { "" } ! Benchmark.ips do |r| r.report("sin") do begin Redis.current.ping rescue Redis::BaseError "" end end ! r.report("con") do begin disyuntor.try { Redis.current.ping } rescue Redis::BaseError "" end end ! r.compare! end
  18. Benchmark - Up Calculating ------------------------------------- sin 1.284k i/100ms con 1.192k

    i/100ms ------------------------------------------------- sin 13.690k (± 3.9%) i/s - 69.336k con 12.533k (± 2.3%) i/s - 63.176k ! Comparison: sin: 13689.9 i/s con: 12533.5 i/s - 1.09x slower
  19. Benchmark - Down Calculating ------------------------------------- sin 5.000 i/100ms con 22.659k

    i/100ms ------------------------------------------------- sin 3.277k (±38.6%) i/s - 380.000 in 5.743486s con 349.269k (± 3.6%) i/s - 1.767M ! Comparison: con: 349268.6 i/s sin: 3277.1 i/s - 106.58x slower
  20. –Martin Fowler “I talk here about remote calls, which are

    a common case for circuit breakers, but they can be used in any situation where you want to protect parts of a system from failures in other parts.”
  21. • Release It!: Design and Deploy Production-Ready Software
 http://goo.gl/zMb9Uj •

    Martin Fowler: Circuit Breaker
 http://goo.gl/7Nmy6A • Making the Netflix API More Resilient
 http://goo.gl/VLO9Sq • Gemas
 github.com/inkel/disyuntor
 github.com/wsargent/circuit_breaker • Esta presentación
 https://goo.gl/B2YbBA