我该如何使用断路器?

15

我正在寻找一种方法,在连接成功之前,以一种无需设置定时器的方式对我无法控制的服务进行远程调用。通过大量研究后,电路断路器模式似乎是一个很好的选择。

我发现一个使用Castle Windsor拦截器的实现,看起来很棒。唯一的问题是我不知道如何使用它。关于这个话题,我找到的文章很少,唯一找到的使用示例仅限于使用电路断路器调用一个操作仅一次,这似乎并不是很有用。因此,似乎我需要在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();
    }
}

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


2
此实现使用定时器,在每n秒/分钟执行一个操作,直到成功为止。 - cadrell0
4
这是一个非常复杂的事情,在Stack Overflow上无法通过简短的回答来解决。但是,我的书第9章介绍了如何使用断路器(Circuit Breaker)和普通装饰器(Decorators),同时还提供了Castle Windsor、Unity和Spring.NET相应的实现代码。你可以通过下载获取完整的代码。http://affiliate.manning.com/idevaffiliate.php?id=1150_236 - Mark Seemann
2
@MarkSeemann 要完全理解正在发生的事情并正确实现它,肯定需要相当多的附加知识。 - scottm
2个回答

10
如果有很多线程同时访问同一资源,这是一个有趣的想法。它的工作原理是从所有线程汇总尝试次数的计数器。不必担心编写循环来尝试在实际失败之前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;
}

使用此拦截器实现,拦截器被注册为单例。因此,您的所有资源类实例在调用任何方法时都将首先通过断路器重定向代码。拦截器只是您的类的代理。它基本上覆盖了您的方法并在调用您的方法之前先调用拦截器方法。
如果您没有电路理论知识,则可能会对开/闭位产生困惑。维基百科:

如果电源的正极和负极之间缺少完整路径,则电路是“开路”的

理论上,当连接断开时,该电路是开路的,当连接可用时,该电路是闭合的。您示例的重要部分是这个:
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 */
        }
    }

非常棒的回复。我现在明白它是做什么的,以及如何使用温莎拦截器自动调用它。真正困扰我的是它如何抛出我可能会收到的异常。这让我相信我仍然需要一些设置代码(try/catch)在一个 while 循环中。当操作成功调用后会发生什么?我有一种感觉,我只是缺少实现这个的一个小细节。 - gcso
是的,您仍需要确保能够访问资源(无论是在while循环中还是其他任何地方),并处理OpenCircuitException而不是SqlException - scottm
这是我正在使用的测试代码,看起来它正在工作。但仍然觉得奇怪,我需要自己循环调用服务直到成功。感谢所有的帮助,非常感激。 :D - gcso
@gcso,你的例子仍然只是轮询服务;你没有在那里代表断路器。 - scottm
你建议我如何调用可能失败的服务? - gcso
@gcso 你提供的示例非常好。在你的代码中,你不会将所有线程的尝试汇总到一个地方进行轮询。你仍然会像我第一个示例中那样进行轮询。 - scottm

5

我创建了一个名为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
}

这个工具可以通过NuGet包获取。你可以在Github上找到源代码


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接