Hoy en día, es bastante común que nuestra aplicación dependa de otras, especialmente si estamos trabajando en un entorno de microservicios. Es bastante común que nuestra aplicación comience a informar errores y, al investigar, notamos que alguna API de un equipo asociado o proveedor no funciona.
Una buena práctica para aumentar la resiliencia de nuestra aplicación es cortar la comunicación con aquellas aplicaciones que se encuentran en estado obsoleto. Observando otras áreas, absorbimos el concepto de Disyuntor de la Ingeniería Eléctrica. En él se coloca un equipo, o disyuntor, que se apaga automáticamente si ocurre una falla. Esto es muy común en nuestros hogares, que cuentan con disyuntores que se apagan solos si la red eléctrica comienza a volverse inestable.
En informática, nuestro Disyuntor es un poco más complejo, ya que también definimos un estado intermedio. El siguiente dibujo explica mejor cómo funciona un disyuntor:
Finalmente, los estados son:
Muy bien, ¿verdad? Pero para ejemplificar mejor el concepto, ¿qué tal si lo hacemos en la práctica?
Primero construyamos nuestro servicio A. Este será el responsable de recibir las solicitudes, es decir, será el servicio del que depende nuestra aplicación, el servicio del proveedor, etc. Para hacerlo más fácil, expondremos dos puntos finales, un /success que siempre devolverá 200 y un /failure que siempre devolverá 500.
package main import ( "fmt" "log" "net/http" ) func main() { http.HandleFunc("/success", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) http.HandleFunc("/failure", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) }) fmt.Println("Server is running at http://localhost:8080") log.Fatal(http.ListenAndServe(":8080", nil)) }
El servicio B será responsable de llamar al servicio A. Construirá nuestro disyuntor. ¡Por suerte para nosotros, la comunidad Go ya tiene la biblioteca gobreaker que implementa el patrón! Primero, definimos las propiedades de nuestro interruptor:
var st gobreaker.Settings st.Name = "Circuit Breaker PoC" st.Timeout = time.Second * 5 st.MaxRequests = 2 st.ReadyToTrip = func(counts gobreaker.Counts) bool { return counts.ConsecutiveFailures >= 1 }
Aunque la biblioteca nos permite personalizar más cosas, nos centraremos en tres:
Luego podemos inicializar el disyuntor y realizar solicitudes:
cb := gobreaker.NewCircuitBreaker[int](st) url := "http://localhost:8080/success" cb.Execute(func() (int, error) { return Get(url) }) fmt.Println("Circuit Breaker state:", cb.State()) // closed! url = "http://localhost:8080/failure" cb.Execute(func() (int, error) { return Get(url) }) fmt.Println("Circuit Breaker state:", cb.State()) // open! time.Sleep(time.Second * 6) url = "http://localhost:8080/success" cb.Execute(func() (int, error) { return Get(url) }) fmt.Println("Circuit Breaker state:", cb.State()) // half-open! url = "http://localhost:8080/success" cb.Execute(func() (int, error) { return Get(url) }) fmt.Println("Circuit Breaker state:", cb.State()) // closed!
Podemos notar que gobreaker funciona como un contenedor alrededor de una función. Si la función devuelve un error, aumenta la cantidad de errores, si no, aumenta la cantidad de éxitos. Entonces definamos esta función:
func Get(url string) (int, error) { r, _ := http.Get(url) if r.StatusCode != http.StatusOK { return r.StatusCode, fmt.Errorf("failed to get %s", url) } return r.StatusCode, nil }
¡Y tenemos nuestro servicio Go usando un disyuntor! Al utilizar este patrón, puede aumentar la resiliencia y la tolerancia a fallos de sus servicios. Podemos notar que al usar la biblioteca, la complejidad se abstrajo por completo, lo que hace que el proceso de integración de esto en nuestra vida diaria sea muy simple. Si desea ver el código completo de prueba de concepto, simplemente vaya aquí.
Si tienes curiosidad por conocer otros patrones de resiliencia, ¡Elton Minetto publicó una excelente publicación sobre el tema!
Dime qué piensas de esta publicación en los comentarios y aquí tienes una pregunta: ¿alguna vez has usado disyuntores antes? ¡Oh, también puedes encontrarme en mi blog personal!
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3