本文介绍了使用 Polly 库的 .NET 应用程序中的弹性和混沌工程的概念,重点介绍了支持混沌工程的新功能。它提供了在 HTTP 客户端中集成混沌策略的实用指南,并展示了如何配置弹性管道以提高容错能力。
长话短说
从版本 8.3.0 开始,Polly 库现在支持混沌工程。此更新允许您使用以下混乱策略:
- 故障:将故障(异常)引入您的系统。
- 结果:在您的系统中注入虚假结果(结果或异常)。
- 延迟:在执行调用之前增加执行延迟。
- 行为:在调用之前启用任何附加行为的注入。
.NET 的混沌工程最初是在Simmy 库中引入的。在 Polly 的第 8 版中,我们与 Simmy 的创建者合作,将 Simmy 库直接集成到 Polly 中。
要在 HTTP 客户端中使用新的混沌策略,请将以下包添加到您的 C# 项目中:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Resilience" />
<!-- This is required until Microsoft.Extensions.Http.Resilience updates the version of Polly it depends on. -->
<PackageReference Include="Polly.Extensions" />
</ItemGroup>
现在,您可以在设置弹性管道时使用新的混沌策略:
services
.AddHttpClient("my-client")
.AddResilienceHandler("my-pipeline", (ResiliencePipelineBuilder<HttpResponseMessage> builder) =>
{
// Start with configuring standard resilience strategies
builder
.AddConcurrencyLimiter(10, 100)
.AddRetry(new RetryStrategyOptions<HttpResponseMessage> { /* configuration options */ })
.AddCircuitBreaker(new CircuitBreakerStrategyOptions<HttpResponseMessage> { /* configuration options */ })
.AddTimeout(TimeSpan.FromSeconds(5));
// Next, configure chaos strategies to introduce controlled disruptions.
// Place these after the standard resilience strategies.
// Inject chaos into 2% of invocations
const double InjectionRate = 0.02;
builder
.AddChaosLatency(InjectionRate, TimeSpan.FromMinutes(1)) // Introduce a delay as chaos latency
.AddChaosFault(InjectionRate, () => new InvalidOperationException("Chaos strategy injection!")) // Introduce a fault as chaos
.AddChaosOutcome(InjectionRate, () => new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError)) // Simulate an outcome as chaos
.AddChaosBehavior(0.001, cancellationToken => RestartRedisAsync(cancellationToken)); // Introduce a specific behavior as chaos
});
在上面的例子中:
- 该IHttpClientFactory模式用于注册
my-client
HTTP 客户端。 - 扩展
AddResilienceHandler
方法用于设置 Polly 的弹性管道。有关 Polly 库弹性的更多信息,请参阅使用 .NET 8 构建弹性云服务。 - 弹性和混沌策略都是通过调用实例之上的扩展方法来配置的
builder
。
关于混沌工程
混沌工程是一种通过引入干扰或意外条件来测试系统的实践。目标是让人们对系统在实际生产环境中充满挑战的情况下保持稳定和可靠的能力充满信心。
对于那些有兴趣进一步探索混沌工程的人来说,有大量资源可供使用:
- 维基百科上的混沌工程:此链接提供了混沌工程的概述,包括其基本概念、历史和相关工具。
- 混沌工程,历史、原理和实践: Gremlin (一个专门研究混沌工程的平台)发表的一篇深入文章,涵盖了该主题的历史、关键原理和实际应用。
- 了解混沌工程和弹性: Azure Chaos Studio框架内的混沌工程初学者指南,该服务旨在通过混沌工程评估和增强云应用程序和服务的弹性。
然而,这篇博文不会深入探讨混沌工程的复杂性。相反,它重点介绍了如何使用 Polly 库实际向我们的系统注入混乱。我们将重点关注进程内混沌注入,这意味着我们将混沌直接引入到您的进程中。在本文中,我们不会介绍其他外部方法,例如重新启动虚拟机、模拟高 CPU 使用率或创建低内存条件。
设想
我们正在构建一个简单的 Web 服务,提供 TODO 列表。https://jsonplaceholder.typicode.com/todos
该服务通过使用 HTTP 客户端与端点通信来获取 TODO 。为了测试我们的服务处理问题的能力,我们将在 HTTP 通信中引入混乱。然后,我们将使用弹性策略来缓解这些问题,确保服务对用户仍然可靠。
您将学习如何使用 Polly API 来控制注入的混沌量。这种技术允许我们有选择地应用混沌,例如仅在某些环境(如开发或生产)或特定用户中。这样,我们可以确保稳定性,同时仍然测试我们服务的稳健性。
概念
Polly 库的混沌策略有几个共同的关键属性:
财产 | 默认值 | 描述 |
---|---|---|
InjectionRate | 0.001 | 这是一个介于 0 和 1 之间的十进制值。它代表引入混乱的机会。例如,比率为 0.2 意味着每次调用有 20% 的机会出现混乱;0.01 表示 1% 的机会;1表示每次调用都会发生混乱。 |
InjectionRateGenerator | null | 该函数决定每个特定实例的混乱率,值范围从 0 到 1。 |
Enabled | true | 这表明混沌注入当前是否处于活动状态。 |
EnabledGenerator | null | 该函数确定是否应针对每个特定实例激活混沌策略。 |
通过调整InjectionRate
,您可以控制注入系统的混沌量。它EnabledGenerator
允许您动态启用或禁用混沌注入。这意味着您可以在特定条件下打开或关闭混乱,从而提供测试和弹性规划的灵活性。
实际例子
在接下来的部分中,我们将逐步介绍一个简单 Web 服务的开发。此过程包括将混乱引入系统,然后探索如何动态管理引入的混乱程度。最后,我们将演示如何使用弹性策略来缓解混乱。
创建项目
首先,我们将使用控制台创建一个新项目。按着这些次序:
创建一个新项目:打开一个新的控制台窗口并运行以下命令来创建一个名为Chaos的新 Web 项目:
dotnet new web -o Chaos
此命令创建一个名为Chaos
“基本 Web 项目设置”的新目录。
修改Program.cs
文件:接下来,Program.cs
在新创建的项目中打开该文件,并将其内容替换为以下代码:
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var httpClientBuilder = builder.Services.AddHttpClient<TodosClient>(client => client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"));
var app = builder.Build();
app.MapGet("/", (TodosClient client, CancellationToken cancellationToken) => client.GetTodosAsync(cancellationToken));
app.Run();
和TodosClient
定义Todo
为:
public class TodosClient(HttpClient client)
{
public async Task<IEnumerable<TodoModel>> GetTodosAsync(CancellationToken cancellationToken)
{
return await client.GetFromJsonAsync<IEnumerable<TodoModel>>("/todos", cancellationToken) ?? [];
}
}
public record TodoModel(
[property: JsonPropertyName("id")] int Id,
[property: JsonPropertyName("title")] string Title);
上面的代码执行以下操作:
- 使用 来配置针对指定端点的
IHttpClientFactory
类型。TodosClient
- 将 注入
TodosClient
到请求处理程序中以从远程端点获取待办事项列表。
运行应用程序:设置项目和 后TodosClient
,运行应用程序。您应该能够通过访问根端点来检索和显示待办事项列表。
注入混乱
在本节中,我们将向 HTTP 客户端引入混乱,以观察其对 Web 服务的影响。
添加弹性库:首先,更新项目文件以包含弹性和混沌处理所需的依赖项:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="8.0.0" />
<PackageReference Include="Polly.Core" Version="8.3.0" />
</ItemGroup>
注意:我们
Polly.Core
直接包含,尽管Microsoft.Extensions.Http.Resilience
已经引用了它。这确保我们使用包含混沌策略的最新版本的 Polly。一旦Microsoft.Extensions.Http.Resilience
更新以纳入最新版本Polly.Core
,就不再需要这种直接引用。
将混沌注入 HTTP 客户端:接下来,增强代码中的 HTTP 客户端设置,以使用AddResilienceHandler
集成混沌策略:
var httpClientBuilder = builder.Services.AddHttpClient<TodosClient>(client => client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"));
// New code below
httpClientBuilder.AddResilienceHandler("chaos", (ResiliencePipelineBuilder<HttpResponseMessage> builder) =>
{
// Set the chaos injection rate to 5%
const double InjectionRate = 0.05;
builder
.AddChaosLatency(InjectionRate, TimeSpan.FromSeconds(5)) // Add latency to simulate network delays
.AddChaosFault(InjectionRate, () => new InvalidOperationException("Chaos strategy injection!")) // Inject faults to simulate system errors
.AddChaosOutcome(InjectionRate, () => new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError)); // Simulate server errors
});
此更改实现了以下目的:
- 应用扩展方法向我们的 HTTP 客户端
AddResilienceHandler
引入弹性管道。chaos
- 在回调中利用各种 Polly 扩展方法将不同类型的混乱(延迟、故障和错误结果)集成到我们的 HTTP 调用中。
运行应用程序:实施混乱策略后,运行应用程序并尝试检索 TODO 现在将导致随机问题。这些问题虽然以 5% 的比例人为诱发,但模拟了现实世界中依赖关系可能不稳定的场景,从而导致偶尔出现中断。
动态注入混沌
在上一节中,我们向 HTTP 客户端引入了混沌,但对混沌注入的时间和强度的控制有限。然而,Polly 库提供了强大的 API,可以精确控制何时以及如何引入混乱。这些功能支持多种场景:
- 特定于环境的混乱:仅在某些环境(例如测试或生产)中注入混乱,以评估弹性而不影响所有用户。
- 用户或租户混乱:专门为某些用户或租户引入混乱,这对于测试多租户应用程序的弹性很有用。
- 动态混沌强度:根据环境或特定用户调整注入的混沌量,以便进行更精细的测试。
- 选择性请求混沌:选择特定请求或 API 进行混沌注入,从而能够对特定系统部分进行集中测试。
为了实现这些场景,我们可以使用所有 Polly 混沌策略中可用的EnabledGenerator
和属性。InjectionRateGenerator
让我们探讨一下如何将这些应用到实践中。
创建IChaosManager
抽象:首先,我们将创建一个IChaosManager
接口来封装混沌注入逻辑。该界面可能如下所示:
public interface IChaosManager
{
ValueTask<bool> IsChaosEnabledAsync(ResilienceContext context);
ValueTask<double> GetInjectionRateAsync(ResilienceContext context);
}
该接口允许我们动态确定是否应启用混沌以及以何种速率启用,并且可以灵活地异步做出这些决策,例如通过从远程源获取配置设置。
合并IChaosManager
界面:我们将利用IChaosManager
. 这将使我们能够就何时注入混乱做出动态决策。
// Updated code below
httpClientBuilder.AddResilienceHandler("chaos", (builder, context) =>
{
// Get IChaosManager from dependency injection
var chaosManager = context.ServiceProvider.GetRequiredService<IChaosManager>();
builder
.AddChaosLatency(new ChaosLatencyStrategyOptions
{
EnabledGenerator = args => chaosManager.IsChaosEnabledAsync(args.Context),
InjectionRateGenerator = args => chaosManager.GetInjectionRateAsync(args.Context),
Latency = TimeSpan.FromSeconds(5)
})
.AddChaosFault(new ChaosFaultStrategyOptions
{
EnabledGenerator = args => chaosManager.IsChaosEnabledAsync(args.Context),
InjectionRateGenerator = args => chaosManager.GetInjectionRateAsync(args.Context),
FaultGenerator = new FaultGenerator().AddException(() => new InvalidOperationException("Chaos strategy injection!"))
})
.AddChaosOutcome(new ChaosOutcomeStrategyOptions<HttpResponseMessage>
{
EnabledGenerator = args => chaosManager.IsChaosEnabledAsync(args.Context),
InjectionRateGenerator = args => chaosManager.GetInjectionRateAsync(args.Context),
OutcomeGenerator = new OutcomeGenerator<HttpResponseMessage>().AddResult(() => new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError))
})
});
让我们来分解一下这些变化:
- 我们正在使用另一种
AddResilienceHandler
方法,该方法采用context
. 这context
使我们能够访问IServiceProvider
,从而使我们能够检索混沌设置所需的附加服务。 IChaosManager
获得并用于建立混沌策略。- 我们不使用简单的混沌方法,而是选择基于选项的扩展。这允许完全控制混沌功能,包括指定何时启用混沌的条件以及启用频率的能力。这包括对
EnabledGenerator
和InjectionRateGenerator
属性的访问。 - 的引入
FaultGenerator
使OutcomeGenerator<HttpResponseMessage>
我们能够定义混沌策略将注入的特定故障(错误)和结果。这些 API 还允许创建各种故障并为每个故障分配不同的概率,从而影响每个故障发生的可能性。有关更多详细信息,请参阅PollyDocs:生成结果和PollyDocs:生成故障。
实施IChaosManager
在我们深入讨论混沌管理器的实现之前,我们先概述一下它在混沌注入期间的行为方式:
- 在测试环境中,混沌始终活跃,注入率为5%。
- 在生产环境中,混沌仅针对测试用户启用,注入率为3%。
注意:对于此示例,我们将简化用户识别。我们将根据
user=<user-name>
查询字符串的存在来识别用户,而无需深入研究用户身份的复杂性。
ChaosManager
实现:为了满足指定的要求,我们如何实现ChaosManager
:
internal class ChaosManager(IWebHostEnvironment environment, IHttpContextAccessor contextAccessor) : IChaosManager
{
private const string UserQueryParam = "user";
private const string TestUser = "test";
public ValueTask<bool> IsChaosEnabledAsync(ResilienceContext context)
{
if (environment.IsDevelopment())
{
return ValueTask.FromResult(true);
}
// This condition is demonstrative and not recommended to use in real apps.
if (environment.IsProduction() &&
contextAccessor.HttpContext is {} httpContext &&
httpContext.Request.Query.TryGetValue(UserQueryParam, out var values) &&
values == TestUser)
{
// Enable chaos for 'test' user even in production
return ValueTask.FromResult(true);
}
return ValueTask.FromResult(false);
}
public ValueTask<double> GetInjectionRateAsync(ResilienceContext context)
{
if (environment.IsDevelopment())
{
return ValueTask.FromResult(0.05);
}
if (environment.IsProduction())
{
return ValueTask.FromResult(0.03);
}
return ValueTask.FromResult(0.0);
}
}
集成IChaosManager
:IServiceCollection
更新Program.cs以包含IChaosManager
在依赖注入 (DI) 容器中。
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
services.TryAddSingleton<IChaosManager, ChaosManager>(); // <-- Add this line
services.AddHttpContextAccessor(); // <-- Add this line
运行应用程序:启动应用程序以查看其行为方式。通过更改环境设置进行实验,观察应用程序对开发环境和生产环境中的混乱做出响应的差异。
使用弹性策略来解决混乱
在本节中,我们将使用弹性策略来缓解混乱。首先,让我们回顾一下注入的混沌类型:
- 延迟 5 秒。
- 异常注入
InvalidOperationException
。 - 注射
HttpResponseMessage
用HttpStatusCode.InternalServerError
.
我们可以使用以下弹性策略来缓解混乱。
有关 Polly 库提供的各种弹性策略的更多信息,请访问PollyDocs:弹性策略。
要增加 HTTP 客户端的弹性,您有两种选择:
就我们的目的而言,可以使用标准弹性处理程序来有效管理由前面提到的混乱策略引起的混乱。一般来说,建议使用标准处理程序,除非您发现它不能满足您的特定需求。以下是如何配置具有标准弹性的 HTTP 客户端:
var httpClientBuilder = builder.Services.AddHttpClient<TodosClient>(client => client.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"));
// Add and configure the standard resilience above the chaos handler
httpClientBuilder
.AddStandardResilienceHandler()
.Configure(options =>
{
// Update attempt timeout to 1 second
options.AttemptTimeout.Timeout = TimeSpan.FromSeconds(1);
// Update circuit breaker to handle transient errors and InvalidOperationException
options.CircuitBreaker.ShouldHandle = args => args.Outcome switch
{
{} outcome when HttpClientResiliencePredicates.IsTransient(outcome) => PredicateResult.True(),
{ Exception: InvalidOperationException } => PredicateResult.True(),
_ => PredicateResult.False()
};
// Update retry strategy to handle transient errors and InvalidOperationException
options.Retry.ShouldHandle = args => args.Outcome switch
{
{} outcome when HttpClientResiliencePredicates.IsTransient(outcome) => PredicateResult.True(),
{ Exception: InvalidOperationException } => PredicateResult.True(),
_ => PredicateResult.False()
};
});
httpClientBuilder.AddResilienceHandler("chaos", (builder, context) => { /* Chaos configuration omitted for clarity */ });
前面的例子:
- 使用添加标准弹性处理程序
AddStandardResilienceHandler
。将标准处理程序放置在混乱处理程序之前,以有效处理任何引入的混乱至关重要。 - 使用
Configure
扩展方法修改弹性策略配置。 - 将尝试超时设置为 1 秒。每个依赖项的超时持续时间可能不同。在我们的例子中,端点调用很快。如果超过 1 秒,则表明存在问题,建议取消并重试。
- 更新
ShouldHandle
重试和断路器策略的谓词。在这里,我们使用 switch 表达式来进行错误处理。该HttpClientResiliencePredicates.IsTransient
函数用于重试典型的瞬时错误,例如 500 及以上的 HTTP 状态代码或HttpRequestException
. 我们还需要处理InvalidOperationException
,因为该函数未涵盖它HttpClientResiliencePredicates.IsTransient
。
运行应用程序:将弹性集成到 HTTP 管道中,尝试运行应用程序并观察其行为。由于标准弹性处理程序有效地消除了混乱,错误不应再出现。
遥测
Polly默认提供全面的遥测,提供强大的工具来跟踪弹性策略的使用情况。此遥测包括日志和指标。
日志
启动应用程序并注意 Polly 生成的日志事件。查找“混乱事件”,例如Chaos.OnLatency
或Chaos.OnOutcome
标有Information
严重级别:
info: Polly[0]
Resilience event occurred. EventName: 'Chaos.OnOutcome', Source: 'TodosClient-chaos//Chaos.Outcome', Operation Key: '', Result: ''
info: Polly[0]
Resilience event occurred. EventName: 'Chaos.OnLatency', Source: 'TodosClient-chaos//Chaos.Latency', Operation Key: '', Result: ''
诸如弹性策略之类的弹性事件OnRetry
或OnTimeout
由弹性策略触发的弹性事件被分类为具有更高的严重性级别,例如Warning
或Error
。这些表明系统中存在异常活动:
fail: Polly[0]
Resilience event occurred. EventName: 'OnTimeout', Source: 'TodosClient-standard//Standard-AttemptTimeout', Operation Key: '', Result: ''
warn: Polly[0]
Resilience event occurred. EventName: 'OnRetry', Source: 'TodosClient-standard//Standard-Retry', Operation Key: '', Result: '500'
指标
Polly 提供了以下以Polly指标名称发出的工具。这些可以帮助您监控应用程序的运行状况:
resilience.polly.strategy.events
:发生弹性事件时触发。resilience.polly.strategy.attempt.duration
Retry
:衡量执行尝试花费的时间,与策略相关Hedging
。resilience.polly.pipeline.duration
:跟踪弹性管道所花费的总时间。
有关 Polly 指标的更多信息,请访问Polly 文档:指标。
要查看这些指标的实际效果,让我们使用该dotnet counters工具:
- 使用命令启动应用程序
dotnet run
。 - 打开新的终端窗口并执行以下命令以开始跟踪
Polly
我们Chaos
应用程序的指标:
dotnet counters monitor -n Chaos Polly
最后,多次访问应用程序根 API,直到弹性事件开始出现在控制台中。在处于活动状态的终端中,dotnet counters
您应该看到以下输出:
[Polly]
resilience.polly.pipeline.duration (ms)
error.type=200,event.name=PipelineExecuted,event.severit 130.25
error.type=200,event.name=PipelineExecuted,event.severit 130.25
error.type=200,event.name=PipelineExecuted,event.severit 130.25
error.type=200,event.name=PipelineExecuted,event.severit 133.75
error.type=200,event.name=PipelineExecuted,event.severit 133.75
error.type=200,event.name=PipelineExecuted,event.severit 133.75
error.type=500,event.name=PipelineExecuted,event.severit 2.363
error.type=500,event.name=PipelineExecuted,event.severit 2.363
error.type=500,event.name=PipelineExecuted,event.severit 2.363
event.name=PipelineExecuted,event.severity=Information,e 752
event.name=PipelineExecuted,event.severity=Information,e 752
event.name=PipelineExecuted,event.severity=Information,e 752
resilience.polly.strategy.attempt.duration (ms)
attempt.handled=False,attempt.number=0,error.type=200,ev 130.5
attempt.handled=False,attempt.number=0,error.type=200,ev 130.5
attempt.handled=False,attempt.number=0,error.type=200,ev 130.5
attempt.handled=False,attempt.number=1,error.type=200,ev 98.125
attempt.handled=False,attempt.number=1,error.type=200,ev 98.125
attempt.handled=False,attempt.number=1,error.type=200,ev 98.125
attempt.handled=True,attempt.number=0,error.type=500,eve 2.422
attempt.handled=True,attempt.number=0,error.type=500,eve 2.422
attempt.handled=True,attempt.number=0,error.type=500,eve 2.422
resilience.polly.strategy.events (Count / 1 sec)
error.type=500,event.name=OnRetry,event.severity=Warning 0
event.name=Chaos.OnOutcome,event.severity=Information,pi 0
event.name=dummy,event.severity=Error,pipeline.instance= 0
event.name=PipelineExecuting,event.severity=Debug,pipeli 0
event.name=PipelineExecuting,event.severity=Debug,pipeli 0
在实际应用中,使用 Polly 指标创建仪表板和监控应用程序弹性的监控工具是有益的。关于使用 OpenTelemetry 实现 .NET 可观测性的文章为此过程提供了坚实的起点。
概括
本文探讨了 Polly 版本8.3.0
及更高版本中提供的混沌工程功能。它引导您开发与远程依赖项进行通信的 Web 应用程序。通过利用 Polly 的混沌工程能力,我们可以将受控的混沌引入 HTTP 客户端通信,然后实施弹性策略来抵消混沌。将此方法应用于生产应用程序使您能够主动解决影响应用程序弹性的问题。不要等待不可预见的问题出现,而是使用混沌工程来模拟并为它们做好准备。
您可以在 GitHub 上的文件夹中查看完整示例Polly/Samples/Chaos。
文章作者 | Martin Tomka(马丁·托姆卡)