【问题标题】:How do I use a circuit breaker?如何使用断路器?
【发布时间】:2011-10-06 14:11:37
【问题描述】:

我正在寻找方法在连接成功之前远程调用不受我控制的服务。我也不想简单地设置一个计时器,每 n 秒/分钟执行一次操作,直到成功。经过大量研究,断路器模式似乎非常适合。

我找到了一个implementation,它使用了 Castle Windsor 拦截器,看起来很棒。唯一的问题是我不知道如何使用它。从我发现的有关该主题的几篇文章中,我能找到的唯一用法示例是简单地使用断路器来调用一个动作 once,这似乎不是很有用。由此看来,我似乎需要在while(true) 循环中使用断路器来简单地运行我的操作。

我如何使用 Windsor 拦截器来执行调用外部服务的操作,直到它成功而不会撞击我们的服务器?

有人可以补上缺失的部分吗?

这是我能想到的

while(true)
{
    try
    {
        service.Subscribe();
        break;
    }
    catch (Exception e)
    {
        Console.WriteLine("Gotcha!");

        Thread.Sleep(TimeSpan.FromSeconds(10));
    }
}

Console.WriteLine("Success!");

public interface IService
{
    void Subscribe();
}

public class Service : IService
{
    private readonly Random _random = new Random();

    public void Subscribe()
    {
        var a = _random.Next(0, 10) % 2421;
        if(_random.Next(0, 10) % 2 != 0)
            throw new AbandonedMutexException();
    }
}

基于此,我想我现在理解了这个概念以及如何应用它。

【问题讨论】:

  • 这个实现使用了一个计时器,其中每 n 秒/分钟执行一次操作,直到成功。
  • 这是一件非常复杂的事情,它并不适合 Stack Overflow 上的答案。但是,FWIW,我的书的第 9 章讨论了如何使用带有普通装饰器的断路器,以及 Castle Windsor,其他章节包含 Unity 和 Spring.NET 的相应实现。还有完整的代码可供下载。 affiliate.manning.com/idevaffiliate.php?id=1150_236
  • @MarkSeemann 要完全理解正在发生的事情以及如何正确实施,肯定需要相当多的辅助知识。

标签: c# design-patterns dependency-injection interceptor circuit-breaker


【解决方案1】:

如果您有很多线程访问同一个资源,这是一个有趣的想法。其工作方式是汇集来自所有线程的尝试计数。您无需担心编写循环尝试在实际失败之前访问数据库 5 次,而是让断路器跟踪所有访问资源的尝试。

在一个例子中,你说有 5 个线程运行这样一个循环(伪代码):

int errorCount = 0;
while(errorCount < 10) // 10 tries
{
    if(tryConnect() == false)
      errorCount++;
    else
      break;
}

假设你的错误处理是正确的,这个循环可以运行 5 次,并且 ping 资源总共 50 次。

断路器尝试减少它尝试访问资源的总次数。每个线程或请求尝试都会增加一个错误计数器。一旦达到错误限制,断路器将不会尝试连接到它的资源以在任何线程上进行更多调用,直到超时结束。在资源准备好之前轮询资源的效果仍然相同,但是您减少了总负载。

static volatile int errorCount = 0;

while(errorCount < 10)
{
   if(tryConnect() == false)
      errorCount++;
   else
       break;
}

使用这个拦截器实现,拦截器被注册为单例。因此,对于对任何方法的任何调用,资源类的所有实例都将首先通过断路器重定向代码。 interceptor is just a proxy 给你的班级。它基本上会覆盖您的方法并在调用您的方法之前先调用拦截器方法。

如果您没有任何电路理论知识,则打开/关闭位可能会令人困惑。 wiki:

如果电路没有完整的路径,它就是“开路” 电源正负极之间

理论上,该电路在连接断开时打开,在连接可用时关闭。您的示例的重要部分是:

public void Intercept(IInvocation invocation)
    {
        using (TimedLock.Lock(monitor))
        {
            state.ProtectedCodeIsAboutToBeCalled(); /* only throws an exception when state is Open, otherwise, it doesn't do anything. */
        }

        try
        {
            invocation.Proceed(); /* tells the interceptor to call the 'actual' method for the class that's being proxied.*/
        }
        catch (Exception e)
        {
            using (TimedLock.Lock(monitor))
            {
                failures++; /* increments the shared error count */
                state.ActUponException(e); /* only implemented in the ClosedState class, so it changes the state to Open if the error count is at it's threshold. */ 
            }
            throw;
        }

        using (TimedLock.Lock(monitor))
        {
            state.ProtectedCodeHasBeenCalled(); /* only implemented in HalfOpen, if it succeeds the "switch" is thrown in the closed position */
        }
    }

【讨论】:

  • 很棒的回复。我现在了解它的作用以及如何使用 Windsor 拦截器自动调用它。真正让我失望的是它如何抛出我可能收到的异常。这让我相信我仍然需要在 while 循环中设置一些代码 (try/catch)。成功调用操作后会发生什么?我有一种感觉,我只缺少一小部分来实现这一点。
  • 是的,您仍然需要确保您可以访问您的资源(无论是在 while 循环中还是其他)并处理 OpenCircuitException 而不是说 SqlException。
  • 查看编辑。那是我正在使用的测试代码,它似乎正在工作。仍然很奇怪,我需要照顾循环调用服务,直到它自己成功。感谢所有的帮助。非常感激。 :D
  • @gcso 您的示例仍然只是轮询服务;你不代表那里的断路器。
  • 你建议我如何调用可能失败的服务?
【解决方案2】:

我创建了一个名为CircuitBreaker.Net 的库,它封装了所有服务逻辑以安全地执行调用。它易于使用,示例如下:

// Initialize the circuit breaker
var circuitBreaker = new CircuitBreaker(
    TaskScheduler.Default,
    maxFailures: 3,
    invocationTimeout: TimeSpan.FromMilliseconds(100),
    circuitResetTimeout: TimeSpan.FromMilliseconds(10000));

try
{
    // perform a potentially fragile call through the circuit breaker
    circuitBreaker.Execute(externalService.Call);
    // or its async version
    // await circuitBreaker.ExecuteAsync(externalService.CallAsync);
}
catch (CircuitBreakerOpenException)
{
    // the service is unavailable, failover here
}
catch (CircuitBreakerTimeoutException)
{
    // handle timeouts
}
catch (Exception)
{
    // handle other unexpected exceptions
}

可通过a nuget package 获得。你可以找到the sources on github。

【讨论】:

    猜你喜欢
    • 2014-08-01
    • 2019-07-05
    • 2021-02-15
    • 1970-01-01
    • 2019-05-01
    • 2021-01-17
    • 1970-01-01
    • 2015-05-23
    • 1970-01-01
    相关资源
    最近更新 更多