Slide 1

Slide 1 text

o/

Slide 2

Slide 2 text

Disyuntores O cómo no terminar viviendo abajo de un puente

Slide 3

Slide 3 text

@inkel • Web Developer • DevOps Wannabe • Citrusbyte • RubyConfAR • No sé diseñar slides • Fundamentalista de la barba • Lector

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

¿Dónde se usa? • Aplicaciones distribuidas • SOA (Service Oriented Architecture) • Microservicios

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

@adrianco ex-CTO de Netflix

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

on get, "users" do render("users/index", users: User.all) end

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

• Más usuarios • Más consultas • Más lenta cada consulta • Mayor cantidad de recursos • Timeouts

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Falla en un servicio Lo que creemos que sucede

Slide 21

Slide 21 text

Falla en un servicio Lo que realmente sucede

Slide 22

Slide 22 text

• Cache no anda • Sobrecarga base de datos • Saturación de red • Timeouts

Slide 23

Slide 23 text

No content

Slide 24

Slide 24 text

No content

Slide 25

Slide 25 text

Disyuntor • Controla el consumo de energía eléctrica • Sobrepasado cierto nivel, abre el circuito • No se te prende fuego la casa

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

Ejemplo

Slide 29

Slide 29 text

# 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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

def get_users_from_cache disyuntor_cache.try do Cache.fetch("user.all") do get_users end end rescue Disyuntor::CircuitOpenError get_users end

Slide 33

Slide 33 text

def get_users_from_cache disyuntor_cache.try do Cache.fetch("user.all") do get_users end end rescue Disyuntor::CircuitOpenError get_users end

Slide 34

Slide 34 text

def get_users_from_cache disyuntor_cache.try do Cache.fetch("user.all") do get_users end end rescue Disyuntor::CircuitOpenError get_users end

Slide 35

Slide 35 text

def get_users disyuntor_db.try do User.all end end

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

No content

Slide 38

Slide 38 text

• 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.

Slide 39

Slide 39 text

–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.”

Slide 40

Slide 40 text

github.com/inkel/ disyuntor Shame on me!

Slide 41

Slide 41 text

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 -------------------------------------------------------------------------------

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

• Simple • Configurable • Disyuntor::CircuitOpenError
 #on_circuit_open • Más código • Latencia

Slide 45

Slide 45 text

#! /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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

–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.”

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

¿Preguntas?

Slide 51

Slide 51 text

• 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