Шаблон автоматического выключенияCircuit Breaker pattern

Выполняйте обработку ошибок, которая может занять разное количество времени при подключении к удаленной службе или ресурсу.Handle faults that might take a variable amount of time to recover from, when connecting to a remote service or resource. Это может повысить стабильность и устойчивость приложения.This can improve the stability and resiliency of an application.

Контекст и проблемаContext and problem

В распределенной среде вызовы удаленных ресурсов и служб могут завершиться со сбоем из-за временных сбоев, таких как медленные сетевые подключения, длительное время ожидания, перегруженные или временно недоступные ресурсы.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. Эти ошибки обычно устраняются через короткий промежуток времени. Следует подготовить надежное облачное приложение для их обработки с использованием стратегии, такой как шаблон повторов.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.

Однако в некоторых ситуациях ошибки возникают из-за непредвиденных событий. На их устранение может потребоваться гораздо больше времени.However, there can also be situations where faults are due to unanticipated events, and that might take much longer to fix. Эти ошибки могут варьироваться по серьезности от частичной потери возможности подключения до полного отказа службы.These faults can range in severity from a partial loss of connectivity to the complete failure of a service. В таких ситуациях приложение, вместо постоянных попыток повторить операцию, которая, скорее всего, не будет успешно выполнена, должно быстро принять сбой операции и обработать его.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.

Кроме того, если служба занята, сбой в одной из частей системы может привести к лавинообразному накоплению сбоев.Additionally, if a service is very busy, failure in one part of the system might lead to cascading failures. Например, операция, вызывающая службу, может быть настроена для реализации времени ожидания и будет отвечать сообщением об ошибке, если служба не отвечает в течение этого периода.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. Однако эта стратегия может привести к тому, что многие параллельные запросы к одной операции будут заблокированы до истечения периода ожидания.However, this strategy could cause many concurrent requests to the same operation to be blocked until the timeout period expires. Эти заблокированные запросы могут содержать критические системные ресурсы, такие как память, потоки, подключения к базе данных и т. д.These blocked requests might hold critical system resources such as memory, threads, database connections, and so on. Следовательно, эти ресурсы могут закончиться, что приведет к сбою других, возможно, несвязанных частей системы, которым нужно использовать те же ресурсы.Consequently, these resources could become exhausted, causing failure of other possibly unrelated parts of the system that need to use the same resources. В этих ситуациях предпочтительно, чтобы операция немедленно завершалась с ошибкой и пыталась вызвать службу, только если такой вызов может быть успешно выполнен.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 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.

РешениеSolution

Шаблон автоматического выключения, популяризированный Майклом Найгардом (Michael Nygard) в книге Release It! Design and Deploy Production-Ready Software (Выпускаем в свет! Разработка и внедрение ПО, готового к выпуску), может помешать повторной попытке приложения выполнить операцию, которая, скорее всего, завершится со сбоем.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. Разрешите ему продолжить выполнение, не ожидая устранения ошибки или расхода ресурсов процессора на определение того, что предполагается долгий сбой.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. Шаблон автоматического выключения также позволяет приложению определять, была ли устранена неисправность.The Circuit Breaker pattern also enables an application to detect whether the fault has been resolved. Если проблема устранена, приложение может попытаться вызвать операцию.If the problem appears to have been fixed, the application can try to invoke the operation.

У шаблона автоматического выключения и шаблона повторов разные цели.The purpose of the Circuit Breaker pattern is different than the Retry pattern. Шаблон повторов позволяет приложению повторять операцию, ожидая, что она будет успешного выполнена.The Retry pattern enables an application to retry an operation in the expectation that it'll succeed. Шаблон автоматического выключения в приложении предотвращает выполнение операции, которая, вероятнее всего, завершится ошибкой.The Circuit Breaker pattern prevents an application from performing an operation that is likely to fail. Приложение может сочетать оба шаблона, используя шаблон повтора для вызова операции через автоматическое выключение.An application can combine these two patterns by using the Retry pattern to invoke an operation through a circuit breaker. Однако логика повторения должна быть чувствительной к любым исключениям, возвращаемым автоматическим выключением, и отказываться от повторных попыток, если автоматическое выключение указывает, что неисправность не является временной.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.

Автоматическое выключение действует в качестве прокси-сервера операций, которые могут завершиться со сбоем.A circuit breaker acts as a proxy for operations that might fail. Прокси-сервер должен отслеживать количество недавних сбоев и использовать эту информацию, чтобы решить, разрешить ли продолжение операции или же немедленно вернуть исключение.The proxy should monitor the number of recent failures that have occurred, and use this information to decide whether to allow the operation to proceed, or simply return an exception immediately.

Прокси-сервер может быть реализован как компьютер со следующими состояниями, имитирующими возможности электрического автоматического выключателя:The proxy can be implemented as a state machine with the following states that mimic the functionality of an electrical circuit breaker:

  • Закрытый. Запрос приложения перенаправляется на операцию.Closed: The request from the application is routed to the operation. Прокси-сервер ведет подсчет числа недавних сбоев, и если вызов операции не завершился успешно, прокси-сервер увеличивает это число.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. Если число недавних сбоев превышает заданный порог в течение заданного периода времени, прокси-сервер переводится в состояние Открытый.If the number of recent failures exceeds a specified threshold within a given time period, the proxy is placed into the Open state. На этом этапе прокси-сервер запускает таймер времени ожидания, и по истечении времени этого таймера прокси-сервер переводится в состояние Полуоткрытый.At this point the proxy starts a timeout timer, and when this timer expires the proxy is placed into the Half-Open state.

    Цель таймера времени ожидания — дать системе время на исправление ошибки, которая вызвала сбой, прежде чем разрешить приложению попытаться выполнить операцию еще раз.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.

  • Открыто. Запрос приложения немедленно завершается со сбоем, и исключение возвращается в приложение.Open: The request from the application fails immediately and an exception is returned to the application.

  • Полуоткрытый. Ограниченному числу запросов от приложения разрешено проходить через операцию и вызывать ее.Half-Open: A limited number of requests from the application are allowed to pass through and invoke the operation. Если эти запросы выполняются успешно, предполагается, что ошибка, которая ранее вызывала сбой, устранена, а автоматический выключатель переходит в состояние Закрытый (счетчик сбоев сбрасывается).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). Если какой-либо запрос завершается со сбоем, автоматическое выключение предполагает, что неисправность все еще присутствует, поэтому он возвращается в состояние Открытый и перезапускает таймер времени ожидания, чтобы дать системе дополнительное время на восстановление после сбоя.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.

    Состояние Полуоткрытый полезно для предотвращения внезапного переполнения службы восстановления запросами.The Half-Open state is useful to prevent a recovering service from suddenly being flooded with requests. По мере восстановления службы она может поддерживать ограниченный объем запросов до полного восстановления, но при восстановлении переполнение может привести к истечению времени ожидания службы или повторному сбою.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.

Состояния автоматического выключения

На рисунке показан счетчик сбоев на основе времени, используемый состоянием Закрытый.In the figure, the failure counter used by the Closed state is time based. Он сбрасывается через периодические интервалы.It's automatically reset at periodic intervals. Это позволяет предотвратить переход автоматического выключения в состояние Открытый при случайных ошибках.This helps to prevent the circuit breaker from entering the Open state if it experiences occasional failures. Порог сбоев, который переводит автоматическое выключение в состояние Открытый, достигается, только если указанное количество сбоев произошло в течение заданного интервала.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. Счетчик, используемый состоянием Полуоткрытый, записывает количество успешных попыток вызвать операцию.The counter used by the Half-Open state records the number of successful attempts to invoke the operation. Автоматическое выключение возвращается в состояние Закрытый после определенного числа последовательных успешных вызовов операций.The circuit breaker reverts to the Closed state after a specified number of consecutive operation invocations have been successful. Если вызов завершается со сбоем, автоматическое выключение немедленно переходит в состояние Открытый, а счетчик успешных выполнений будет сброшен до следующего раза, когда автоматический выключатель перейдет в состояние Полуоткрытый.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.

Восстановление системы обрабатывается извне, возможно, путем восстановления или перезапуска неисправного компонента либо исправления сетевого соединения.How the system recovers is handled externally, possibly by restoring or restarting a failed component or repairing a network connection.

Шаблон автоматического выключения обеспечивает стабильность, пока система восстанавливается после сбоя и снижает влияние на производительность.The Circuit Breaker pattern provides stability while the system recovers from a failure and minimizes the impact on performance. Благодаря этому можно поддерживать определенный показатель времени отклика системы, быстро отклоняя запрос на операцию, которая, скорее всего, завершится со сбоем, вместо того чтобы ждать, пока не истечет время ожидания операции, или ждать в течение неопределенного времени (так как операция никогда не возвратится).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. Если автоматическое выключение порождает событие каждый раз, когда оно меняет состояние, эта информация может использоваться для мониторинга работоспособности части системы, защищенной автоматическим выключением, или для оповещения администратора о переходе автоматического выключения в состояние Открытый.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.

Шаблон настраивается и может быть адаптирован в соответствии с типом возможного сбоя.The pattern is customizable and can be adapted according to the type of the possible failure. Например, вы можете применить таймер увеличения времени ожидания к автоматическому выключателю.For example, you can apply an increasing timeout timer to a circuit breaker. Вы можете перевести автоматическое выключение в состояние Открытый на несколько секунд, а затем, если сбой не был устранен, увеличить время ожидания до нескольких минут и так далее.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. В некоторых случаях вместо состояния Открытый, возвращающего сбой и вызывающего исключение, полезнее возвращать значение по умолчанию, которое было значимо для приложения.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.

Проблемы и рекомендацииIssues and considerations

При выборе схемы реализации этого шаблона следует учитывать следующие моменты.You should consider the following points when deciding how to implement this pattern:

Обработка исключений.Exception Handling. Приложение, вызывающее операцию через автоматический выключатель, должно быть подготовлено к обработке исключений, возникающих, если операция недоступна.An application invoking an operation through a circuit breaker must be prepared to handle the exceptions raised if the operation is unavailable. Способ обработки исключения будет зависеть от приложения.The way exceptions are handled will be application specific. Например, приложение может временно понизить функциональность, вызвать альтернативную операцию для выполнения той же задачи или получения тех же данных или сообщить об исключении пользователю и попросить его повторить попытку позже.For example, an application could temporarily degrade its functionality, invoke an alternative operation to try to perform the same task or obtain the same data, or report the exception to the user and ask them to try again later.

Типы исключений.Types of Exceptions. Запрос может завершиться со сбоем по многим причинам. Некоторые из них могут указывать на более серьезные сбои, чем другие.A request might fail for many reasons, some of which might indicate a more severe type of failure than others. Например, запрос может завершиться со сбоем, так как произошло аварийное завершение удаленной службы и требуется несколько минут для восстановления, или из-за истечения времени ожидания в связи с временной перегрузкой службы.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. Автоматическое выключение может исследовать типы возникающих исключений и корректировать свою стратегию в зависимости от характера этих исключений.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. Например, может потребоваться большее количество исключений времени ожидания для перевода автоматического выключения в состояние Открытый по сравнению с количеством ошибок из-за того, что служба полностью недоступна.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.

Ведение журналов.Logging. При автоматическом выключении должны регистрироваться все невыполненные запросы (и возможно, успешные запросы), чтобы позволить администратору выполнять мониторинг работоспособности операции.A circuit breaker should log all failed requests (and possibly successful requests) to enable an administrator to monitor the health of the operation.

Возможность восстановления.Recoverability. Автоматическое выключение нужно настроить в соответствии с вероятным шаблоном восстановления защищаемой операции.You should configure the circuit breaker to match the likely recovery pattern of the operation it's protecting. Например, если автоматическое выключение остается в состоянии Открытый в течение длительного периода времени, оно может создавать исключения, даже если сбой был устранен.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. Аналогично автоматическое выключение может меняться и уменьшать время отклика приложений, если оно переключится из состояния Открытый в состояние Полуоткрытый слишком быстро.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.

Тестирование неудачных операций.Testing Failed Operations. В состоянии Открытый вместо использования таймера для определения момента перехода в состояние Полуоткрытый автоматическое выключение может периодически проверять удаленную службу или ресурс, чтобы определить, когда они снова станут доступны.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. Проверка связи может принимать форму попытки вызвать операцию, которая ранее завершилась со сбоем, или она может использовать специальную операцию, предоставляемую удаленной службой специально для проверки работоспособности службы, как описано в шаблоне мониторинга конечной точки работоспособности.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.

Ручное переопределение.Manual Override. В системе, где время восстановления завершившейся сбоем операции чрезвычайно изменчиво, можно предоставить вариант ручного сброса, который позволяет администратору закрыть автоматическое выключение (и сбросить счетчик сбоев).In a system where the recovery time for a failing operation is extremely variable, it's beneficial to provide a manual reset option that enables an administrator to close a circuit breaker (and reset the failure counter). Аналогично администратор может принудительно отключить автоматическое выключение в состоянии Открытый (и перезапустить таймер времени ожидания), если операция, защищенная автоматическим выключением, временно недоступна.Similarly, an administrator could force a circuit breaker into the Open state (and restart the timeout timer) if the operation protected by the circuit breaker is temporarily unavailable.

Параллелизм.Concurrency. К одному автоматическому выключению может обращаться множество параллельных экземпляров приложения.The same circuit breaker could be accessed by a large number of concurrent instances of an application. Реализация не должна блокировать параллельные запросы или добавлять слишком большие нагрузки для каждого вызова операции.The implementation shouldn't block concurrent requests or add excessive overhead to each call to an operation.

Различия между ресурсами.Resource Differentiation. Будьте внимательны при использовании одного автоматического выключения для одного типа ресурсов, если может быть несколько базовых независимых поставщиков.Be careful when using a single circuit breaker for one type of resource if there might be multiple underlying independent providers. Например, в хранилище данных, которое содержит несколько сегментов, один сегмент может быть полностью доступен, в то время как другой может испытывать временные проблемы.For example, in a data store that contains multiple shards, one shard might be fully accessible while another is experiencing a temporary issue. Если сообщения об ошибках в этих сценариях объединены, приложение может попытаться получить доступ к некоторым сегментам даже при высокой вероятности сбоя, в то время как другие сегменты могут быть заблокированы, несмотря на то что они могут быть успешно выполнены.If the error responses in these scenarios are merged, an application might try to access some shards even when failure is highly likely, while access to other shards might be blocked even though it's likely to succeed.

Ускорение автоматического выключения.Accelerated Circuit Breaking. Иногда сообщение о сбое может содержать достаточно информации для активации и работы автоматического выключения в течение минимального количества времени.Sometimes a failure response can contain enough information for the circuit breaker to trip immediately and stay tripped for a minimum amount of time. Например, сообщение об ошибке перегруженного общего ресурса может указывать на то, что выполнение немедленной повторной попытки не рекомендуется и приложению следует повторить попытку через несколько минут.For example, the error response from a shared resource that's overloaded could indicate that an immediate retry isn't recommended and that the application should instead try again in a few minutes.

Примечание

Служба может возвращать код ошибки HTTP 429 (слишком много запросов), если регулируется количество запросов клиента, или ошибку HTTP 503 (служба недоступна), если служба недоступна в данный момент.A service can return HTTP 429 (Too Many Requests) if it is throttling the client, or HTTP 503 (Service Unavailable) if the service is not currently available. Сообщение может включать дополнительные сведения, например, предполагаемую длительность задержки.The response can include additional information, such as the anticipated duration of the delay.

Воспроизведение неудачных запросов.Replaying Failed Requests. В состоянии Открытый, вместо того, чтобы просто быстро выполнить завершение со сбоем, автоматическое выключение может также записывать сведения о каждом запросе к журналу и подготавливать эти запросы для воспроизведения, когда удаленный ресурс или служба станут доступны.In the Open state, rather than simply failing quickly, a circuit breaker could also record the details of each request to a journal and arrange for these requests to be replayed when the remote resource or service becomes available.

Неподходящее время ожидания внешних служб.Inappropriate Timeouts on External Services. Автоматическое выключение может оказаться не в состоянии полностью защитить приложения от операций, завершающихся со сбоем во внешних службах, настроенных с длительным периодом ожидания.A circuit breaker might not be able to fully protect applications from operations that fail in external services that are configured with a lengthy timeout period. Если время ожидания слишком велико, поток, выполняющий автоматическое выключение, может быть заблокирован в течение длительного периода времени, прежде чем автоматическое выключение покажет, что операция не выполнена.If the timeout is too long, a thread running a circuit breaker might be blocked for an extended period before the circuit breaker indicates that the operation has failed. В это время множество других экземпляров приложений могут также попытаться вызвать службу через автоматическое выключение и связать значительное количество потоков, прежде чем все они завершатся со сбоем.In this time, many other application instances might also try to invoke the service through the circuit breaker and tie up a significant number of threads before they all fail.

Когда следует использовать этот шаблонWhen to use this pattern

Используйте этот шаблон в следующих случаях:Use this pattern:

  • Чтобы предотвратить попытки вызова приложением удаленной службы или получения доступа к общему ресурсу, если эта операция, скорее всего, завершится со сбоем.To prevent an application from trying to invoke a remote service or access a shared resource if this operation is highly likely to fail.

Этот шаблон не рекомендуется использовать в следующих случаях:This pattern isn't recommended:

  • Для обработки доступа к локальным закрытым ресурсам в приложении, например, в структуре данных в памяти.For handling access to local private resources in an application, such as in-memory data structure. В этой среде при использовании автоматического выключения нагрузка в системе возрастет.In this environment, using a circuit breaker would add overhead to your system.
  • В качестве замены для обработки исключений в бизнес-логике приложений.As a substitute for handling exceptions in the business logic of your applications.

ПримерExample

В веб-приложении несколько страниц заполняются данными, полученными из внешней службы.In a web application, several of the pages are populated with data retrieved from an external service. Если система реализует минимальное кэширование, большинство обращений к этим страницам вызовет переход в службу.If the system implements minimal caching, most hits to these pages will cause a round trip to the service. Для подключений из веб-приложения в службу может быть настроено время ожидания (обычно 60 секунд). Если служба не отвечает за это время, логика на каждой веб-странице предполагает, что служба недоступна, и вызовет исключение.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.

Однако, если служба завершается со сбоем и система очень занята, пользователи могут быть вынуждены ждать в течение минуты до возникновения исключения.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. В конечном итоге ресурсы, такие как память, подключения и потоки, могут быть исчерпаны, из-за чего другие пользователи не смогут подключаться к системе, даже если они не обращаются к страницам, которые извлекают данные из службы.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.

Масштабирование системы путем добавления дополнительных веб-серверов и реализации балансировки нагрузки может вызвать задержку при исчерпании ресурсов. Однако проблема не будет решена, так как на запросы пользователей по-прежнему нет ответов, а все веб-серверы все еще могут в конечном итоге исчерпать ресурсы.Scaling the system by adding further web servers and implementing load balancing might delay when resources become exhausted, but it won't resolve the issue because user requests will still be unresponsive and all web servers could still eventually run out of resources.

Поместив логику, которая подключается к службе и извлекает данные, в автоматическое выключение можно решить эту проблему и более эффективно обработать отказ службы.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. Запросы пользователей будут по-прежнему завершаться со сбоем, но это будет происходить быстрее, а ресурсы не будут блокироваться.User requests will still fail, but they'll fail more quickly and the resources won't be blocked.

Класс CircuitBreaker сохраняет информацию о состоянии автоматического выключения в объекте, который реализует интерфейс ICircuitBreakerStateStore, как показано в следующем коде.The CircuitBreaker class maintains state information about a circuit breaker in an object that implements the ICircuitBreakerStateStore interface shown in the following code.

interface ICircuitBreakerStateStore
{
  CircuitBreakerStateEnum State { get; }

  Exception LastException { get; }

  DateTime LastStateChangedDateUtc { get; }

  void Trip(Exception ex);

  void Reset();

  void HalfOpen();

  bool IsClosed { get; }
}

Свойство State указывает на текущее состояние автоматического выключения (Открытый, Полуоткрытый и Закрытый, как определено в соответствии с перечислением CircuitBreakerStateEnum).The State property indicates the current state of the circuit breaker, and will be either Open, HalfOpen, or Closed as defined by the CircuitBreakerStateEnum enumeration. Свойство IsClosed должно иметь значение true, если автоматическое выключение закрыто, и false, если оно открыто или полуоткрыто.The IsClosed property should be true if the circuit breaker is closed, but false if it's open or half open. Метод Trip переводит автоматическое выключение в состояние "Открытый" и записывает исключение, которое вызывает изменения в состоянии, а также время и дату создания исключения.The Trip method switches the state of the circuit breaker to the open state and records the exception that caused the change in state, together with the date and time that the exception occurred. Свойства LastException и LastStateChangedDateUtc возвращают эти сведения.The LastException and the LastStateChangedDateUtc properties return this information. Метод Reset закрывает автоматическое выключение, а HalfOpen — переводит его в состояние "Полуоткрытый".The Reset method closes the circuit breaker, and the HalfOpen method sets the circuit breaker to half open.

Класс InMemoryCircuitBreakerStateStore в примере содержит реализацию интерфейса ICircuitBreakerStateStore.The InMemoryCircuitBreakerStateStore class in the example contains an implementation of the ICircuitBreakerStateStore interface. Класс CircuitBreaker создает экземпляр этого класса для хранения состояния автоматического выключения.The CircuitBreaker class creates an instance of this class to hold the state of the circuit breaker.

Метод ExecuteAction в классе CircuitBreaker помещает операцию, указанную как делегат Action, в оболочку.The ExecuteAction method in the CircuitBreaker class wraps an operation, specified as an Action delegate. Если автоматическое выключение закрыто, ExecuteAction вызывает делегат Action.If the circuit breaker is closed, ExecuteAction invokes the Action delegate. Если операция завершается со сбоем, обработчик исключений вызывает TrackException, который устанавливает автоматическое выключение в состояние "Открытый".If the operation fails, an exception handler calls TrackException, which sets the circuit breaker state to open. Эта процедура представлена в следующем примере кода.The following code example highlights this flow.

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);
  }
}

В следующем примере показан код (отсутствующий в предыдущем примере), который выполняется, если автоматическое выключение не закрыто.The following example shows the code (omitted from the previous example) that is executed if the circuit breaker isn't closed. Сначала в нем проверяется, было ли открыто автоматическое выключение в течение периода, большего, чем задано в локальном поле OpenToHalfOpenWaitTime в классе 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. Если это так, метод ExecuteAction задает автоматическому выключению состояние "Полуоткрытый", а затем пытается выполнить операцию, заданную делегатом 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.

Если операция выполнена успешно, автоматическое выключение сбрасывается до закрытого состояния.If the operation is successful, the circuit breaker is reset to the closed state. Если операция завершается со сбоем, автоматическое выключение возвращается в открытое состояние, а время, когда произошло исключение, обновляется, чтобы автоматическое выключение ожидало еще некоторое время, прежде чем пытаться выполнить операцию еще раз.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.

Если автоматическое выключение находилось в открытом состоянии в течение короткого времени (меньшем, чем значение OpenToHalfOpenWaitTime), метод ExecuteAction просто вызывает исключение CircuitBreakerOpenException и возвращает сообщение об ошибке, которая вызвала переход автоматического выключения в состояние "Открытый".If the circuit breaker has only been open for a short time, less than the OpenToHalfOpenWaitTime value, the ExecuteAction method simply throws a CircuitBreakerOpenException exception and returns the error that caused the circuit breaker to transition to the open state.

Кроме того, метод использует блокировку для предотвращения попыток выполнения параллельных вызовов автоматического выключения, пока оно находится в состоянии "Полуоткрытый".Additionally, it uses a lock to prevent the circuit breaker from trying to perform concurrent calls to the operation while it's half open. Одновременная попытка вызвать операцию будет обрабатываться так, как если бы автоматическое выключение было открыто, и она завершится с исключением, как описано ниже.A concurrent attempt to invoke the operation will be handled as if the circuit breaker was open, and it'll fail with an exception as described later.

    ...
    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);
    }
    ...

Чтобы использовать объект CircuitBreaker для защиты операции приложение создает экземпляр класса CircuitBreaker и вызывает метод ExecuteAction, указывая операцию, выполняемую в качестве параметра.To use a CircuitBreaker object to protect an operation, an application creates an instance of the CircuitBreaker class and invokes the ExecuteAction method, specifying the operation to be performed as the parameter. Приложение следует подготовить для перехвата исключения CircuitBreakerOpenException, если операция завершится со сбоем из-за того, что автоматическое выключение открыто.The application should be prepared to catch the CircuitBreakerOpenException exception if the operation fails because the circuit breaker is open. Пример кода приведен ниже.The following code shows an example:

var breaker = new CircuitBreaker();

try
{
  breaker.ExecuteAction(() =>
  {
    // Operation protected by the circuit breaker.
    ...
  });
}
catch (CircuitBreakerOpenException ex)
{
  // Perform some different action when the breaker is open.
  // Last exception details are in the inner exception.
  ...
}
catch (Exception ex)
{
  ...
}

Следующие шаблоны также могут быть полезными при реализации этого шаблона:The following patterns might also be useful when implementing this pattern:

  • Шаблон повторов.Retry pattern. Описывает механизм обработки ожидаемых временных сбоев, при котором приложение может повторно подключаться к службе или сетевому ресурсу, обращение к которым завершилось сбоем, не прерывая потока событий.Describes how an application can handle anticipated temporary failures when it tries to connect to a service or network resource by transparently retrying an operation that has previously failed.

  • Шаблон мониторинга конечных точек работоспособности.Health Endpoint Monitoring pattern. Автоматическое выключение может проверить работоспособность службы, отправив запрос в конечную точку, открытую службой.A circuit breaker might be able to test the health of a service by sending a request to an endpoint exposed by the service. Служба должна вернуть информацию о своем состоянии.The service should return information indicating its status.