Neste artigo

Padrão de Disjuntor AutomáticoCircuit Breaker pattern

06/23/2017

31 minutos para ler

Contribuidores

Neste artigo

Processe falhas que possam demorar um período de tempo de recuperação variável ao ligar a um serviço ou recurso remoto.Handle faults that might take a variable amount of time to recover from, when connecting to a remote service or resource.Isto pode melhorar a estabilidade e resiliência de uma aplicação.This can improve the stability and resiliency of an application.

Contexto e problemaContext and problem

Num ambiente distribuído, as chamadas para serviços e recursos remotos podem falhar devido a falhas transitórias, como ligações de rede lentas, limites de tempo ou recursos sobrecarregados ou temporariamente disponíveis.In a distributed environment, calls to remote resources and services can fail due to transient faults, such as slow network connections, timeouts, or the resources being overcommitted or temporarily unavailable.Estas falhas normalmente corrigidos automaticamente após um curto período de tempo e uma aplicação na cloud robusta deve ser preparada para processá-las através de uma estratégia como o [padrão de repetição] [-padrão de repetição].These faults typically correct themselves after a short period of time, and a robust cloud application should be prepared to handle them by using a strategy such as the [Retry pattern][retry-pattern].

No entanto, pode também haver situações em que as falhas se devem a eventos imprevistos e podem demorar muito mais tempo a corrigir.However, there can also be situations where faults are due to unanticipated events, and that might take much longer to fix.Estas falhas podem variar em termos de gravidade, de uma perda parcial de conectividade à falha total de um serviço.These faults can range in severity from a partial loss of connectivity to the complete failure of a service.Nestas situações, pode ser inútil para uma aplicação repetir continuamente uma operação com baixas probabilidades de ser bem-sucedida. Em vez disso, a aplicação deverá aceitar rapidamente que a operação falhou e processar esta falha adequadamente.In these situations it might be pointless for an application to continually retry an operation that is unlikely to succeed, and instead the application should quickly accept that the operation has failed and handle this failure accordingly.

Adicionalmente, se um serviço estiver muito ocupado, a falha numa parte de um sistema poderá gerar falhas em cascata.Additionally, if a service is very busy, failure in one part of the system might lead to cascading failures.Por exemplo, uma operação que invoque um serviço poderá ser configurada para implementar um limite de tempo e responder com uma mensagem de falha se o serviço não responder dentro deste período.For example, an operation that invokes a service could be configured to implement a timeout, and reply with a failure message if the service fails to respond within this period.No entanto, esta estratégia poderá fazer com que muitos pedidos em simultâneo para a mesma operação sejam bloqueados até que o limite de tempo expire.However, this strategy could cause many concurrent requests to the same operation to be blocked until the timeout period expires.Estes pedidos bloqueados poderão conter recursos de sistema cruciais, como memória, threads, ligações de base de dados, etc.These blocked requests might hold critical system resources such as memory, threads, database connections, and so on.Consequentemente, estes recursos podem ficar esgotados, originando a falha de outras partes (possivelmente separadas) do sistema que precisam de utilizar os mesmos recursos.Consequently, these resources could become exhausted, causing failure of other possibly unrelated parts of the system that need to use the same resources.Nestas situações, é preferível que a operação falhe imediatamente e tente apenas invocar o serviço se tiver boas probabilidades de sucesso.In these situations, it would be preferable for the operation to fail immediately, and only attempt to invoke the service if it's likely to succeed.Note que a definir um limite de tempo mais curto poderá ajudar a resolver o problema, mas o limite de tempo não deve ser tão curto que faça com que a operação falhe na maioria das vezes, mesmo que o pedido ao serviço acabe por ser bem-sucedido.Note that setting a shorter timeout might help to resolve this problem, but the timeout shouldn't be so short that the operation fails most of the time, even if the request to the service would eventually succeed.

SoluçãoSolution

O padrão do Disjuntor Automático, popularizado por Michael Nygard, no seu livro Release It!, pode impedir uma aplicação de tentar repetidamente executar uma operação com boas probabilidades de falhar.The Circuit Breaker pattern, popularized by Michael Nygard in his book, Release It!, can prevent an application from repeatedly trying to execute an operation that's likely to fail.Permitir que esta continue sem esperar que a falha seja corrigida ou desperdiçar ciclos de CPU enquanto determina que a falha é duradoura.Allowing it to continue without waiting for the fault to be fixed or wasting CPU cycles while it determines that the fault is long lasting.O padrão do Disjuntor Automático também permite que uma aplicação detete se a falha foi resolvida.The Circuit Breaker pattern also enables an application to detect whether the fault has been resolved.Se o problema aparentar estar resolvido, a aplicação pode tentar invocar a operação.If the problem appears to have been fixed, the application can try to invoke the operation.

O objetivo do padrão do Disjuntor Automático é diferente do Padrão de repetição.The purpose of the Circuit Breaker pattern is different than the Retry pattern.O Padrão de repetição permite a uma aplicação repetir uma operação com a expectativa de que esta seja bem-sucedida.The Retry pattern enables an application to retry an operation in the expectation that it'll succeed.O padrão do Disjuntor Automático impede que uma aplicação efetue uma operação que é provável que falhe.The Circuit Breaker pattern prevents an application from performing an operation that is likely to fail.Uma aplicação pode combinar estes dois padrões ao utilizar o padrão de Repetição para invocar uma operação através de um disjuntor automático.An application can combine these two patterns by using the Retry pattern to invoke an operation through a circuit breaker.No entanto, a lógica de repetição deve ser sensível a eventuais exceções devolvidas pelo disjuntor automático e abandonar tentativas de repetição se o disjuntor automático indicar que uma falha não é transitória.However, the retry logic should be sensitive to any exceptions returned by the circuit breaker and abandon retry attempts if the circuit breaker indicates that a fault is not transient.

O proxy pode ser implementado como computador com estado, tendo os seguintes estados que emulam a funcionalidade de um disjuntor automático elétrico:The proxy can be implemented as a state machine with the following states that mimic the functionality of an electrical circuit breaker:

Fechado: O pedido da aplicação é encaminhado para a operação.Closed: The request from the application is routed to the operation.O proxy mantém uma contagem do número de falhas recentes e, se a chamada para a operação não for bem-sucedida, o proxy aumenta esta contagem.The proxy maintains a count of the number of recent failures, and if the call to the operation is unsuccessful the proxy increments this count.Se o número de falhas recentes exceder um limiar especificado num determinado período de tempo, o proxy é colocado em estado Aberto.If the number of recent failures exceeds a specified threshold within a given time period, the proxy is placed into the Open state.Nesta altura, o proxy inicia um temporizador de limite de tempo e, quando o mesmo terminar, o proxy é colocado em estado Meio-aberto.At this point the proxy starts a timeout timer, and when this timer expires the proxy is placed into the Half-Open state.

O objetivo do temporizador de limite de tempo é dar ao sistema algum tempo para corrigir o problema na origem da falha antes de permitir à aplicação que tente a operação novamente.The purpose of the timeout timer is to give the system time to fix the problem that caused the failure before allowing the application to try to perform the operation again.

Abra: O pedido da aplicação falha imediatamente e uma exceção é devolvida à aplicação.Open: The request from the application fails immediately and an exception is returned to the application.

Meio-aberto: Um número limitado de pedidos da aplicação está autorizado a passar e invocar a operação.Half-Open: A limited number of requests from the application are allowed to pass through and invoke the operation.Se estes pedidos forem bem-sucedidos, assume-se que a falha que estava a causar o problema foi corrigida e o disjuntor automático muda para o estado Fechado (a contagem de falhas é reposta).If these requests are successful, it's assumed that the fault that was previously causing the failure has been fixed and the circuit breaker switches to the Closed state (the failure counter is reset).Se algum pedido falhar, o disjuntor automático assume que a falha ainda está presente, pelo que reverte novamente para o estado Aberto e reinicia o temporizador de tempo limite, dando ao sistema mais tempo para recuperar da falha.If any request fails, the circuit breaker assumes that the fault is still present so it reverts back to the Open state and restarts the timeout timer to give the system a further period of time to recover from the failure.

O estado Meio-Aberto é útil para impedir um serviço de recuperação de ficar repentinamente sobrecarregado com pedidos.The Half-Open state is useful to prevent a recovering service from suddenly being flooded with requests.À medida que o serviço recupera, este poderá conseguir suportar um volume limitado de pedidos até a recuperação ser concluída, mas enquanto a recuperação se encontra em curso, uma sobrecarga de trabalho pode fazer com que o serviço exceda o tempo limite ou falhe novamente.As a service recovers, it might be able to support a limited volume of requests until the recovery is complete, but while recovery is in progress a flood of work can cause the service to time out or fail again.

Na figura, o contador de falhas utilizado pelo estado Fechado é baseado em tempo.In the figure, the failure counter used by the Closed state is time based.É automaticamente reiniciado em intervalos periódicos.It's automatically reset at periodic intervals.Isto ajuda a impedir o disjuntor automático de entrar no estado Aberto se experienciar falhas ocasionais.This helps to prevent the circuit breaker from entering the Open state if it experiences occasional failures.O limiar de falha que coloca o disjuntor automático em estado Aberto só é alcançado após um número especificado de falhas ter ocorrido durante um intervalo especificado.The failure threshold that trips the circuit breaker into the Open state is only reached when a specified number of failures have occurred during a specified interval.O contador utilizado pelo estado Meio-Aberto regista o número de tentativas bem-sucedidas de invocar a operação.The counter used by the Half-Open state records the number of successful attempts to invoke the operation.O disjuntor automático regressa ao estado Fechado após um número especificado de invocações de operação consecutivas ter sito efetuado com êxito.The circuit breaker reverts to the Closed state after a specified number of consecutive operation invocations have been successful.Se alguma invocação falhar, o disjuntor automático entra imediatamente no estado Aberto e a contagem de tentativas bem-sucedidas é reposta da próxima vez que entrar no estado Meio-Aberto.If any invocation fails, the circuit breaker enters the Open state immediately and the success counter will be reset the next time it enters the Half-Open state.

O padrão do Disjuntor Automático confere estabilidade enquanto o sistema recupera de uma falha e minimiza o impacto no desempenho.The Circuit Breaker pattern provides stability while the system recovers from a failure and minimizes the impact on performance.Pode ajudar a manter o tempo de resposta do sistema ao rejeitar rapidamente um pedido de uma operação com boas probabilidades de falhar, em vez de esperar que o limite de tempo de uma operação seja ultrapassado ou que esta nunca seja devolvida.It can help to maintain the response time of the system by quickly rejecting a request for an operation that's likely to fail, rather than waiting for the operation to time out, or never return.Se o disjuntor automático gerar um evento sempre que muda de estado, esta informação pode ser utilizada para monitorizar o estado de funcionamento da parte do sistema protegida pelo disjuntor automático ou para alertar um administrador quando um disjuntor automático passar para o estado Aberto.If the circuit breaker raises an event each time it changes state, this information can be used to monitor the health of the part of the system protected by the circuit breaker, or to alert an administrator when a circuit breaker trips to the Open state.

O padrão é personalizável e pode ser adaptado segundo o tipo da possível falha.The pattern is customizable and can be adapted according to the type of the possible failure.Por exemplo, pode aplicar um temporizador de tempo limite aumentado a um disjuntor automático.For example, you can apply an increasing timeout timer to a circuit breaker.Pode colocar o disjuntor automático em estado Aberto durante alguns segundos numa fase inicial e, se a falha não tiver sido resolvida, aumentar o tempo limite para uns minutos, e assim por diante.You could place the circuit breaker in the Open state for a few seconds initially, and then if the failure hasn't been resolved increase the timeout to a few minutes, and so on.Em alguns casos, em vez de o estado Aberto devolver uma falha e gerar uma exceção, poderá ser útil regressar a um valor predefinido que seja significativo para a aplicação.In some cases, rather than the Open state returning failure and raising an exception, it could be useful to return a default value that is meaningful to the application.

Problemas e consideraçõesIssues and considerations

Deve considerar os seguintes pontos ao decidir como implementar este padrão:You should consider the following points when deciding how to implement this pattern:

Tipos de Exceções.Types of Exceptions.Um pedido pode falhar por diversos motivos, alguns dos quais poderão indicar um tipo de falha mais grave do que os outros.A request might fail for many reasons, some of which might indicate a more severe type of failure than others.Por exemplo, um pedido pode falhar porque um serviço remoto falhou e serão necessários vários minutos para recuperar, ou devido a um limite de tempo devido ao facto de o serviço estar temporariamente sobrecarregado.For example, a request might fail because a remote service has crashed and will take several minutes to recover, or because of a timeout due to the service being temporarily overloaded.Um disjuntor automático poderá conseguir examinar os tipos de exceções que ocorrem e ajustar a respetiva estratégia consoante a natureza dessas exceções.A circuit breaker might be able to examine the types of exceptions that occur and adjust its strategy depending on the nature of these exceptions.Por exemplo, poderá precisar de um número maior de exceções ao limite de tempo para colocar o disjuntor automático no estado Aberto, relativamente ao número de falhas devido ao sistema se encontrar totalmente indisponível.For example, it might require a larger number of timeout exceptions to trip the circuit breaker to the Open state compared to the number of failures due to the service being completely unavailable.

Recuperabilidade.Recoverability.Deverá configurar o disjuntor automático de forma a corresponder ao padrão de recuperação provável da operação que está a proteger.You should configure the circuit breaker to match the likely recovery pattern of the operation it's protecting.Por exemplo, se o disjuntor automático permanecer muito tempo no estado Aberto, poderá colocar exceções, mesmo que o motivo da falha tenha sido resolvido.For example, if the circuit breaker remains in the Open state for a long period, it could raise exceptions even if the reason for the failure has been resolved.De forma semelhante, um disjuntor automático pode aumentar e reduzir os tempos de respostas de aplicações se mudar demasiado rapidamente do estado Aberto para o estado Meio-Aberto.Similarly, a circuit breaker could fluctuate and reduce the response times of applications if it switches from the Open state to the Half-Open state too quickly.

Testar Operações com Falhas.Testing Failed Operations.No estado Aberto, em vez de utilizar um temporizador para determinar quando mudar para o estado Meio-Aberto, um disjuntor automático pode enviar periodicamente um ping ao serviço ou recurso remoto para determinar se voltou a ficar disponível.In the Open state, rather than using a timer to determine when to switch to the Half-Open state, a circuit breaker can instead periodically ping the remote service or resource to determine whether it's become available again.Este ping pode assumir a forma de uma tentativa de invocar uma operação que tinha anteriormente falhado, ou poderá utilizar uma operação especial fornecida pelo serviço remoto especificamente para testar o estado de funcionamento do serviço, conforme descrito pelo Padrão de Monitorização do Estado de Pontos Finais.This ping could take the form of an attempt to invoke an operation that had previously failed, or it could use a special operation provided by the remote service specifically for testing the health of the service, as described by the Health Endpoint Monitoring pattern.

ExemploExample

Numa aplicação Web, várias páginas são preenchidas com dados obtidos de um serviço externo.In a web application, several of the pages are populated with data retrieved from an external service.Se o sistema implementar a cache mínima, a maioria dos resultados para estas páginas causará uma ida e volta ao serviço.If the system implements minimal caching, most hits to these pages will cause a round trip to the service.As ligações da aplicação Web ao serviço poderão ser configuradas com um período de tempo limite (geralmente de 60 segundos) e, se o serviço não responder neste tempo, a lógica em cada página Web irá assumir que o serviço está indisponível e lançar uma exceção.Connections from the web application to the service could be configured with a timeout period (typically 60 seconds), and if the service doesn't respond in this time the logic in each web page will assume that the service is unavailable and throw an exception.

No entanto, se o serviço falhar e o sistema estiver muito ocupado, os utilizadores poderão ser forçados a aguardar até 60 segundos antes de uma exceção ocorrer.However, if the service fails and the system is very busy, users could be forced to wait for up to 60 seconds before an exception occurs.Eventualmente, recursos como memória, ligações e threads poderão ficar esgotados, impedindo outros utilizadores de se ligar ao sistema, mesmo que não estejam a aceder a páginas que obtenham dados do serviço.Eventually resources such as memory, connections, and threads could be exhausted, preventing other users from connecting to the system, even if they aren't accessing pages that retrieve data from the service.

Encapsular a lógica que liga ao serviço e obtém os dados num disjuntor automático pode ajudar a resolver o problema e processar a falha de serviço de forma mais elegante.Wrapping the logic that connects to the service and retrieves the data in a circuit breaker could help to solve this problem and handle the service failure more elegantly.Os pedidos de utilizadores continuarão a falhar, mas irão falhar mais rapidamente e os recursos não serão bloqueados.User requests will still fail, but they'll fail more quickly and the resources won't be blocked.

A classe CircuitBreaker mantém as informações de estado relativas a um disjuntor automático num objeto que implementa a interface ICircuitBreakerStateStore mostrada no seguinte código.The CircuitBreaker class maintains state information about a circuit breaker in an object that implements the ICircuitBreakerStateStore interface shown in the following code.

public class CircuitBreaker
{
private readonly ICircuitBreakerStateStore stateStore =
CircuitBreakerStateStoreFactory.GetCircuitBreakerStateStore();
private readonly object halfOpenSyncObject = new object ();
...
public bool IsClosed { get { return stateStore.IsClosed; } }
public bool IsOpen { get { return !IsClosed; } }
public void ExecuteAction(Action action)
{
...
if (IsOpen)
{
// The circuit breaker is Open.
... (see code sample below for details)
}
// The circuit breaker is Closed, execute the action.
try
{
action();
}
catch (Exception ex)
{
// If an exception still occurs here, simply
// retrip the breaker immediately.
this.TrackException(ex);
// Throw the exception so that the caller can tell
// the type of exception that was thrown.
throw;
}
}
private void TrackException(Exception ex)
{
// For simplicity in this example, open the circuit breaker on the first exception.
// In reality this would be more complex. A certain type of exception, such as one
// that indicates a service is offline, might trip the circuit breaker immediately.
// Alternatively it might count exceptions locally or across multiple instances and
// use this value over time, or the exception/success ratio based on the exception
// types, to open the circuit breaker.
this.stateStore.Trip(ex);
}
}

O seguinte exemplo mostra o código (omitido do exemplo anterior) que é executado se o disjuntor automático não estiver fechado.The following example shows the code (omitted from the previous example) that is executed if the circuit breaker isn't closed.Começa por verificar se o disjuntor automático esteve aberto durante mais tempo do que o especificado pelo campo local OpenToHalfOpenWaitTime na classe CircuitBreaker.It first checks if the circuit breaker has been open for a period longer than the time specified by the local OpenToHalfOpenWaitTime field in the CircuitBreaker class.Se for este o caso, o método ExecuteAction define o disjuntor automático como meio aberto e, em seguida, tenta efetuar a operação especificada pelo delegado Action.If this is the case, the ExecuteAction method sets the circuit breaker to half open, then tries to perform the operation specified by the Action delegate.

Se a operação for bem-sucedida, o disjuntor automático volta ao estado fechado.If the operation is successful, the circuit breaker is reset to the closed state.Se a operação falhar, volta ao estado aberto e a hora da exceção ocorrida é atualizada, para que o disjuntor automático aguarde mais tempo antes de tentar voltar a efetuar a operação.If the operation fails, it is tripped back to the open state and the time the exception occurred is updated so that the circuit breaker will wait for a further period before trying to perform the operation again.

...
if (IsOpen)
{
// The circuit breaker is Open. Check if the Open timeout has expired.
// If it has, set the state to HalfOpen. Another approach might be to
// check for the HalfOpen state that had be set by some other operation.
if (stateStore.LastStateChangedDateUtc + OpenToHalfOpenWaitTime < DateTime.UtcNow)
{
// The Open timeout has expired. Allow one operation to execute. Note that, in
// this example, the circuit breaker is set to HalfOpen after being
// in the Open state for some period of time. An alternative would be to set
// this using some other approach such as a timer, test method, manually, and
// so on, and check the state here to determine how to handle execution
// of the action.
// Limit the number of threads to be executed when the breaker is HalfOpen.
// An alternative would be to use a more complex approach to determine which
// threads or how many are allowed to execute, or to execute a simple test
// method instead.
bool lockTaken = false;
try
{
Monitor.TryEnter(halfOpenSyncObject, ref lockTaken);
if (lockTaken)
{
// Set the circuit breaker state to HalfOpen.
stateStore.HalfOpen();
// Attempt the operation.
action();
// If this action succeeds, reset the state and allow other operations.
// In reality, instead of immediately returning to the Closed state, a counter
// here would record the number of successful operations and return the
// circuit breaker to the Closed state only after a specified number succeed.
this.stateStore.Reset();
return;
}
}
catch (Exception ex)
{
// If there's still an exception, trip the breaker again immediately.
this.stateStore.Trip(ex);
// Throw the exception so that the caller knows which exception occurred.
throw;
}
finally
{
if (lockTaken)
{
Monitor.Exit(halfOpenSyncObject);
}
}
}
// The Open timeout hasn't yet expired. Throw a CircuitBreakerOpen exception to
// inform the caller that the call was not actually attempted,
// and return the most recent exception received.
throw new CircuitBreakerOpenException(stateStore.LastException);
}
...