Como tornar o sistema mais resiliente e tolerante a falhas?
resiliência é apenas uma palavra bonita para a capacidade do seu sistema de se recuperar de falhas e continuar operando. Quem popularizou esse padrão foi Miachel Nygard.
Por que desse nome Circuit Breaker ? 💡
Esse padrão funciona exatamente como um disjuntor desses que você tem na sua casa, quando ele recebe uma sobrecarga de energia ele cai(abre) e corta o fluxo de energia ai alguém precisa ir la e fechar o circuito para o fluxo voltar. No software ele funciona da mesma maneira, quando o sistema tem um fluxo muito alto de requisições e você precisa se comunicar com outra api e este recurso remoto esta dando timeout, geralmente 30 segundos para devolver a requisição, se isso acontecer você terá um encadeamento de erros, sobrecarga no servidor e isso pode provocar a queda do seu serviço.
Então o padrão Circuit Breaker(Disjuntor) ele ajuda a evitar falhas em cascata, introduzindo uma camada de proteção fornecendo um mecanismo para quando uma requisição remota falhar ou demorar muito para responder, o seu sistema identifique isso e devolve uma resposta para evitar que o sistema fique sobrecarregado.
Existem três estados em que um Circuit Breaker pode estar:
- Close: No estado fechado, o Circuit Breaker permite que as solicitações passem para o sistema subjacente. Esse é o estado normal de operação.
- Open: Quando o Circuit Breaker está no estado aberto, ele impede que todas as solicitações sejam enviadas para o sistema subjacente. Isso é feito para evitar novas falhas e para dar ao sistema a chance de se recuperar.
- HalfOpen: No estado semiaberto, o Circuit Breaker permite que um número limitado de solicitações passe para o sistema subjacente. Isso é feito para ver se o sistema pode lidar com solicitações novamente, sem sobrecarregá-lo. Se as solicitações forem bem-sucedidas, o Circuit Breaker fará a transição de volta para o estado fechado(normal). Se as solicitações falharem, o disjuntor retornará ao estado aberto.
Você deve estar pensando mas porque existe o estado HalfOpen(semi aberto), bom em um disjuntor ai da sua casa você precisa acionar ele manualmente depois que ele é aberto, mas no software não faz sentido essa ação de fechar o circuito ser manual, ao implementar o padrão você vai configurar para quando o circuito foi aberto(erro) quanto tempo vamos esperar para tentar se comunicar novamente com o recurso remoto, apos esse tempo o sistema envia uma requisição novamente para verificar se esta tudo certo, estando tudo ok o circuito é fechado novamente e tudo volta ao normal.
Então enquanto o circuito estiver close(fechado) esta tudo certo, ele envia e recebe o fluxo de requisições e comunicação com um sistema/api remota.
Se a api demorar X tempo, que será definido por você 200ms, 1s etc...
se a api remota demorar o tempo definido o sistema entende que deu timeout sem precisar esperar os 30 segundos, quando isso acontece o circuito é aberto e a nossa aplicação para de se comunicar com o recurso remoto e passa a responder sem encavalar requisições.
Segue uma implementação que eu fiz em node.js bem simples mas que da pra ver isso acontecer.
Com o express eu subi dois servidores http em portas diferentes 3000 e 3001, o 3001 ta fazendo o papel de um serviço externo, quando eu acesso localhost:3000/ ele chama o 3001 que dependendo de um numero randomico de 0 a 10 vai me retornar 200 ou 400(com delay de 150ms).
Eu usei uma lib conhecida chamada opossum e nela esta configurado timeout de 100ms, resetTimeout de 10 segundos e errorThresholdPercentage a lib vai acumulando as requisiçoes e quando chegar em uma porcentagem definida de requisições com erro ele abre o circuito.
Coloquei a chamada http pra porta 3001 dentro da função asyncFunctionThatCouldFail(função assincrona que pode falhar) e jogo essa função pra dentro do meu objeto breaker.fire ele vai executar a função e observar o tempo e as responses.
import express from "express";
import fetch from 'node-fetch';
import CircuitBreaker from 'opossum';
const app = express();
const app2 = express();
async function asyncFunctionThatCouldFail() {
return new Promise((resolve, reject) => {
fetch('http://localhost:3001/')
.then(res => res.json())
.then(json => {
resolve(json)
}).catch(err => reject(err))
});
}
const options = {
timeout: 100, // If our function takes longer than 3 seconds, trigger a failure
errorThresholdPercentage: 50, // When 50% of requests fail, trip the circuit
resetTimeout: 10000 // After 10 seconds, try again.
};
const breaker = new CircuitBreaker(asyncFunctionThatCouldFail, options);
app.get("/", async (req, response) => {
breaker.fire()
.then(res => response.json(res))
.catch(err => response.status(500).json(err));
});
app2.get("/", (req, res) => {
const randomNumber = Math.round(Math.random() * 10)
console.log(randomNumber)
if (randomNumber <= 5) {
res.status(200).json({msg:"Success!"});
} else {
setTimeout(()=>{
res.status(400).json({msg:"Failed!"});
},150)
}
});
app.listen(3000, () => console.log(`Listening at http://localhost:3000`));
app2.listen(3001, () => console.log(`Listening at http://localhost:3001`));
breaker.on('open',()=>{
console.log('circuit has opened')
})
breaker.on('halfOpen',()=>{
console.log('circuit has halfOpened')
})
breaker.on('close',()=>{
console.log('circuit has closed')
})
Código no GIT
https://github.com/h1bertobarbosa/circuit-breaker-pattern
Links pesquisados:
https://blog.appsignal.com/2020/07/22/nodejs-resiliency-concepts-the-circuit-breaker.html
https://learn.microsoft.com/en-us/azure/architecture/patterns/circuit-breaker
https://martinfowler.com/bliki/CircuitBreaker.html
https://medium.com/trainingcenter/design-pattern-para-microservices-circuit-breaker-f4a5b68f73d1
Top comments (0)