如何使用Polly库进行断路分析

109 阅读4分钟

在Polly库中,进行断路分析(Circuit Breaker)的目的是当系统的一部分出现故障时,快速失败(fail fast)以避免进一步对系统造成压力。断路器会在检测到一定数量的连续失败之后打开(进入断路状态),阻止对故障服务的进一步调用,并在一段时间后尝试恢复服务调用。

以下是使用Polly库进行断路分析的一个基本示例:

首先,确保已经安装了Polly库。你可以通过NuGet包管理器来安装它:

bash
Install-Package Polly

然后,你可以创建一个断路器策略并使用它:

csharp
using System;  
using Polly;  
using Polly.CircuitBreaker;  
  
public class CircuitBreakerExample  
{  
    private static readonly IAsyncPolicy<HttpResponseMessage> _circuitBreakerPolicy = Policy<HttpResponseMessage>  
        .Handle<Exception>() // 可以指定更具体的异常类型  
        .CircuitBreakerAsync(  
            handledEventsAllowedBeforeBreaking: 5, // 在断路器跳闸前允许通过的失败事件数  
            durationOfBreak: TimeSpan.FromSeconds(30), // 断路器跳闸后保持打开的时间  
            onBreak: (ex, breakTimeout) => // 当断路器跳闸时执行的回调  
            {  
                Console.WriteLine("The circuit is now open. Calls to the service will not be made for the next " +  
                                  breakTimeout.TotalSeconds + " seconds.");  
            },  
            onReset: () => // 当断路器从跳闸状态恢复时执行的回调  
            {  
                Console.WriteLine("The circuit is now closed. Calls to the service are allowed again.");  
            },  
            onHalfOpen: () => // 当断路器从关闭状态变为半开状态时执行的回调  
            {  
                Console.WriteLine("The circuit is now half-open. Allowing a single call to see if the service is available.");  
            }  
        );  
  
    public async Task CallServiceWithCircuitBreakerAsync()  
    {  
        // 假设HttpCallAsync是一个异步方法,它调用某个远程服务并返回HttpResponseMessage  
        // 在这里我们使用一个模拟的异步方法来代替  
        Func<Task<HttpResponseMessage>> httpCallAsync = async () =>  
        {  
            // 模拟服务调用可能会失败  
            if (DateTime.Now.Second % 2 == 0) // 例如,每两秒失败一次  
            {  
                throw new Exception("Service call failed!");  
            }  
  
            // 模拟成功的服务响应  
            return new HttpResponseMessage(System.Net.HttpStatusCode.OK);  
        };  
  
        // 使用断路器策略来调用服务  
        await _circuitBreakerPolicy.ExecuteAsync(httpCallAsync);  
    }  
  
    // 主方法或其他启动点  
    public static void Main()  
    {  
        var example = new CircuitBreakerExample();  
  
        // 假设我们多次调用服务  
        for (int i = 0; i < 10; i++)  
        {  
            example.CallServiceWithCircuitBreakerAsync().Wait();  
            // 等待一段时间以便观察断路器的行为  
            System.Threading.Thread.Sleep(1000);  
        }  
    }  
}

在上面的示例中,CircuitBreakerAsync方法配置了一个断路器策略。当连续发生5次失败时,断路器会跳闸,并在接下来的30秒内阻止对服务的调用。当断路器跳闸时,它会执行onBreak回调。当断路器从跳闸状态恢复时,它会执行onReset回调。在断路器从关闭状态变为半开状态时,它会执行onHalfOpen回调。

请注意,上面的示例使用了一个模拟的异步服务调用httpCallAsync。在实际应用中,你应该替换为调用远程服务的真实代码。

此外,由于ExecuteAsync是一个异步方法,你应该在异步上下文中调用它,例如在一个async方法中。在上面的Main方法中,我使用了.Wait()来等待异步操作完成,但这通常不是推荐的做法,因为它会阻塞调用线程。在真实的应用程序中,你应该使用asyncawait关键字来避免阻塞。

结果:

这个AggregateException异常是在你尝试等待一个或多个异步操作的结果时发生的,其中一个或多个操作抛出了异常。在你的例子中,CallServiceWithCircuitBreakerAsync 方法内部调用了一个模拟的服务调用 httpCallAsync,它有时会抛出一个 Service call failed! 的异常。由于这个异常没有被捕获或处理,它最终导致了 AggregateException

当你使用 .Wait() 来等待一个异步方法完成时,如果该异步方法抛出了异常,那么异常会被封装在 AggregateException 中。这是.NET Framework和.NET Core中处理异步异常的方式。

要正确处理这个异常,你应该在异步上下文中使用 await 关键字,而不是 .Wait()。同时,你可以使用 try-catch 语句来捕获并处理异常。下面是一个修改后的示例,展示了如何在异步方法中使用 awaittry-catch

csharp
using System;  
using System.Net.Http;  
using System.Threading.Tasks;  
using Polly;  
using Polly.CircuitBreaker;  
  
public class CircuitBreakerExample  
{  
    private static readonly IAsyncPolicy<HttpResponseMessage> _circuitBreakerPolicy = Policy<HttpResponseMessage>  
        .Handle<Exception>()  
        .CircuitBreakerAsync(  
            handledEventsAllowedBeforeBreaking: 5,  
            durationOfBreak: TimeSpan.FromSeconds(30),  
            onBreak: (ex, breakTimeout) =>  
            {  
                Console.WriteLine("The circuit is now open. Calls to the service will not be made for the next " +  
                                  breakTimeout.TotalSeconds + " seconds.");  
            },  
            onReset: () =>  
            {  
                Console.WriteLine("The circuit is now closed. Calls to the service are allowed again.");  
            },  
            onHalfOpen: () =>  
            {  
                Console.WriteLine("The circuit is now half-open. Allowing a single call to see if the service is available.");  
            }  
        );  
  
    public async Task CallServiceWithCircuitBreakerAsync()  
    {  
        // 假设HttpCallAsync是一个异步方法,它调用某个远程服务并返回HttpResponseMessage  
        Func<Task<HttpResponseMessage>> httpCallAsync = async () =>  
        {  
            // 模拟服务调用可能会失败  
            if (DateTime.Now.Second % 2 == 0) // 例如,每两秒失败一次  
            {  
                throw new Exception("Service call failed!");  
            }  
  
            // 模拟成功的服务响应  
            return new HttpResponseMessage(System.Net.HttpStatusCode.OK);  
        };  
  
        try  
        {  
            // 使用断路器策略来调用服务  
            await _circuitBreakerPolicy.ExecuteAsync(httpCallAsync);  
        }  
        catch (BrokenCircuitException ex)  
        {  
            // 处理断路器跳闸的异常  
            Console.WriteLine("Circuit is broken: " + ex.Message);  
        }  
        catch (Exception ex)  
        {  
            // 处理其他异常  
            Console.WriteLine("An error occurred: " + ex.Message);  
        }  
    }  
  
    public static async Task Main() // 注意这里使用了async Main  
    {  
        var example = new CircuitBreakerExample();  
  
        // 假设我们多次调用服务  
        for (int i = 0; i < 10; i++)  
        {  
            await example.CallServiceWithCircuitBreakerAsync();  
            // 等待一段时间以便观察断路器的行为  
            await Task.Delay(1000);  
        }  
    }  
}

请注意,我修改了 Main 方法以使用 async Task Main,这是C# 7.1及更高版本支持的特性,它允许你编写返回 TaskTask<T>Main 方法。如果你的环境不支持这一点,你可以将 Main 方法保持为同步的,并使用 .GetAwaiter().GetResult() 来等待异步操作,但这通常不是推荐的做法,因为它可能导致死锁和其他问题。