当前位置: 首页 > web >正文

[Polly智能维护网络] 弹性上下文 | `ResiliencePropertyKey<TValue>`

第7章:弹性上下文

在第6章中,我们学习了谓词构建器,这是一个强大的工具,用于定义哪些条件(如特定异常或结果值)应该触发弹性策略的响应。

现在我们已经知道如何精确地告诉Retry策略在网络错误时重试,而不是在无效输入时重试。

但想象一下:我们的应用程序处理许多请求,每个请求都有一个唯一的"追踪ID",帮助我们跟踪它在各个系统中的流转过程。当这个请求到达使用Polly弹性管道的代码部分时,我们可能希望策略知道这个"追踪ID"。例如,OnRetry回调可以记录:“正在为追踪ID: ABC-123重试”,这会让调试变得更容易。

或者,如果弹性策略本身想要记录一些可能对后续其他策略或管道执行的应用程序代码有用的信息呢?例如,Retry策略可能希望记录总尝试次数,而Circuit Breaker策略可以读取这个信息来决定是否应该更快地打开断路器。

这就是弹性上下文的用武之地!

什么是弹性上下文?

弹性上下文视为伴随操作通过弹性管道的"旅行剪贴板"或"记事本"。每次通过pipeline.ExecuteAsync(...)运行操作时,Polly会为该特定执行创建一个全新的、唯一的弹性上下文

这个"剪贴板"保存了关于当前操作的关键信息:

  • 唯一标识符(Id):Polly自动为每个上下文分配一个唯一的字符串ID。这对日志记录和跟踪非常有用。
  • CancellationToken:如果我们希望能够在操作中途停止(例如用户关闭窗口),这个令牌允许我们的代码和Polly策略响应取消请求。
  • 自定义属性(Properties):这是"记事本"功能大放异彩的地方!我们可以在这里存储任何自定义数据与当前操作关联。策略可以读取这些属性,也可以向其中写入新信息。

这是一个共享的工作区,允许弹性管道的不同部分(以及我们自己的代码)就正在进行的操作进行通信和共享数据。

为什么需要弹性上下文?

我们需要弹性上下文有几个关键原因:

  1. 共享信息:它提供了一种一致的方式来传递与当前执行相关的信息,贯穿弹性管道中的所有弹性策略对象。
  2. 向策略注入外部数据:我们可以从应用程序中注入数据(如"追踪ID"或"用户ID")到上下文中,策略可以读取这些数据来调整它们的行为。
  3. 策略间内部通信:策略可以记录它们自己的状态或发现(例如"这是第3次重试尝试",或"服务在这里太慢了")到上下文中,允许链中的后续其他策略或应用程序代码做出反应。
  4. 取消管理:它集中了整个操作的CancellationToken,允许管道的所有部分响应取消请求。

如何使用弹性上下文

通常我们不会直接创建ResilienceContext对象。Polly在我们调用ExecuteAsync时会创建它们。不过,我们可以提供一个初始ResilienceContext,其中预填充了属性,或者简单地访问Polly传递给回调的context参数。

让我们用"追踪ID"的例子来说明。我们将设置一个自定义的TraceId,并让Retry策略在上下文的属性中记录其当前的AttemptNumber

using Polly;
using Polly.Retry;// 1. 定义一个"键"来在上下文中存储我们的自定义数据。
// 这确保了类型安全并避免了魔法字符串。
public static class ContextKeys
{public static readonly ResiliencePropertyKey<string> TraceId =new("TraceId");public static readonly ResiliencePropertyKey<int> LastAttemptNumber =new("LastAttemptNumber");
}// 准备带有重试策略的弹性管道
ResiliencePipeline pipeline = new ResiliencePipelineBuilder().AddRetry(new RetryStrategyOptions{MaxRetryAttempts = 2,Delay = TimeSpan.FromSeconds(0.1),OnRetry = args =>{// 2. 从上下文中读取'TraceId'string? traceId = args.Context.Properties.GetValue(ContextKeys.TraceId, null);Console.WriteLine($"[策略] 正在为TraceId重试: {traceId ?? "N/A"}。尝试次数: {args.AttemptNumber}");// 3. 将'AttemptNumber'写入上下文供后续使用args.Context.Properties.Set(ContextKeys.LastAttemptNumber, args.AttemptNumber);return default; // 返回一个完成的ValueTask}}).Build();// --- 执行我们的操作 ---
string myTraceId = Guid.NewGuid().ToString().Substring(0, 8); // 生成唯一ID// 4. 创建一个新的ResilienceContext并注入我们的自定义TraceId
ResilienceContext context = ResilienceContextPool.Shared.Get().WithResultType<int>();
context.Properties.Set(ContextKeys.TraceId, myTraceId);int callCount = 0;
try
{await pipeline.ExecuteAsync(async currentContext =>{callCount++;// 5. 在操作中直接访问TraceIdConsole.WriteLine($"[操作] 正在为TraceId执行: {currentContext.Properties.GetValue(ContextKeys.TraceId, "N/A")}。调用次数: {callCount}");if (callCount < 3){throw new InvalidOperationException("模拟瞬时错误!");}return 123; // 模拟成功结果}, context); // 在这里传递我们的自定义上下文!
}
catch (Exception ex)
{Console.WriteLine($"[应用] 操作失败: {ex.Message}");
}
finally
{// 6. 执行后从上下文中检索'LastAttemptNumber'int lastAttempt = context.Properties.GetValue(ContextKeys.LastAttemptNumber, 0);Console.WriteLine($"[应用] 操作完成。应用观察到的总重试次数: {lastAttempt}");ResilienceContextPool.Shared.Return(context); // 重要:将上下文返回到池中!
}

解释:

  1. 我们定义ResiliencePropertyKey<T>对象(如ContextKeys.TraceId),以提供类型安全的方式从context.Properties存储和检索数据。这对避免错误至关重要。
  2. OnRetry内部,我们使用args.Context.Properties.GetValue读取我们设置的TraceId
  3. 然后使用args.Context.Properties.Setargs.AttemptNumber存储到上下文中。这些数据现在对其他策略或我们的应用程序可用。
  4. 在执行之前,我们从ResilienceContextPool.Shared获取一个ResilienceContext,并使用Set添加我们的myTraceIdWithResultType<int>()确保上下文准备好接收int结果。
  5. 当调用pipeline.ExecuteAsync时,我们传递准备好的context对象。在实际操作中(async currentContext => { ... } lambda),我们也接收这个currentContext,并可以读取其属性。
  6. 最后,在finally块中,管道完成工作(成功或异常)后,我们可以检索OnRetry回调存储的LastAttemptNumber

预期输出(简化):
在这里插入图片描述

这展示了弹性上下文如何作为共享状态机制,允许数据从应用程序流入管道,策略之间流动,甚至返回到应用程序。

理解ResiliencePropertyKey<TValue>

当我们需要向ResilienceContext.Properties添加自定义数据时,使用ResiliencePropertyKey<TValue>。这就像为数据创建一个唯一的、类型化的"标签"。

// 为字符串值定义一个键
public static readonly ResiliencePropertyKey<string> MyStringKey = new("MyCustomString");// 为布尔值定义一个键
public static readonly ResiliencePropertyKey<bool> IsUrgentRequest = new("IsUrgent");// 存储数据:
context.Properties.Set(MyStringKey, "一些重要数据");
context.Properties.Set(IsUrgentRequest, true);// 检索数据:
string? myString = context.Properties.GetValue(MyStringKey, "未找到时的默认值");
bool isUrgent = context.Properties.GetValue(IsUrgentRequest, false);

使用ResiliencePropertyKey<TValue>是推荐的方式,因为它:

  • 防止拼写错误:使用静态字段,而不是魔法字符串。
  • 确保类型安全:如果尝试用错误的类型检索数据,会得到编译时错误。
  • 提供默认值GetValue方法允许指定在未找到键时返回的内容。

使用CancellationToken

ResilienceContext中的CancellationToken由Polly自动管理,并提供给ExecuteAsync回调。如果管道中有Timeout策略,且操作超时,Polly会触发这个CancellationToken,让操作知道应该停止。

// 在管道执行中:
await pipeline.ExecuteAsync(async context =>
{// 使用上下文中的取消令牌Console.WriteLine("开始长时间操作...");await Task.Delay(TimeSpan.FromSeconds(5), context.CancellationToken);Console.WriteLine("长时间操作完成!");
}, CancellationToken.None); // 或者在这里传递一个外部的CancellationToken

如果Timeout策略激活且计时器用完,context.CancellationToken会被触发,导致Task.Delay抛出OperationCanceledException

弹性上下文的内部工作原理

当我们调用pipeline.ExecuteAsync(...)时,Polly执行以下步骤:

  1. 上下文创建:Polly使用我们提供的ResilienceContext(如果有的话),或者在内部创建一个新的。这个上下文就像一个全新的、空的"剪贴板"。
  2. 上下文传递:这个单一的ResilienceContext对象作为参数传递给管道中最外层弹性策略的ExecuteCore方法。
  3. 传播:每个策略依次将完全相同的ResilienceContext对象传递给链中的下一个策略,一直传递到实际的操作。
  4. 读/写:当操作执行且不同策略介入(例如重试、处理回退)时,它们可以从这个共享的ResilienceContext对象的Properties中读取或写入。
  5. 返回:一旦操作完成(成功或异常),ResilienceContext对象被返回,允许我们检查策略可能添加的任何数据。

以下是简化的序列图:

在这里插入图片描述

如你所见,弹性上下文对象保持不变,并伴随执行流程传递,作为动态的、共享的信息容器。

Polly内部的ResilienceContext类简单但强大。它保存了对所有策略和应用程序有用的核心属性:

// ResilienceContext的简化内部概念
public sealed class ResilienceContext // 这是一个密封类
{public string Id { get; } // 此执行的唯一IDpublic CancellationToken CancellationToken { get; } // 用于取消public ResilienceProperties Properties { get; } // 实际的自定义数据'剪贴板'// (为简洁省略内部构造函数和池管理)// 我们从池中获取此上下文或由Polly创建。
}// ResilienceProperties的简化内部概念(类似字典的部分)
public sealed class ResilienceProperties
{// 内部使用字典或类似结构// 存储与ResiliencePropertyKey关联的数据internal Dictionary<ResiliencePropertyKey, object?> _properties = new();public void Set<TValue>(ResiliencePropertyKey<TValue> key, TValue value) { /* ... */ }public TValue? GetValue<TValue>(ResiliencePropertyKey<TValue> key, TValue? defaultValue) { /* ... */ }public bool Contains<TValue>(ResiliencePropertyKey<TValue> key) { /* ... */ }
}

我们可以在许多示例文件中观察到ResilienceContext作为参数传递,例如samples/Chaos/ChaosManager.cssamples/DependencyInjection/Program.cssamples/Extensibility/Proactive/TimingResilienceStrategy.cs。注意context参数如何出现在自定义策略的ExecuteCore方法中,以及各种回调参数类型(如OnRetryArguments.Context)中。这种一致性是Polly确保ResilienceContext在策略可能需要它的任何地方都可用。

结论

弹性上下文是Polly必不可少的"旅行剪贴板",提供了一种统一的方式,在整个弹性管道中传递与特定操作相关的信息。

它允许我们注入自定义数据,促进不同弹性策略对象之间的通信,并集中取消管理,使我们的弹性逻辑更智能、更灵活。通过理解和利用弹性上下文,我们可以构建复杂的弹性策略,不仅响应结果,还响应操作本身的上下文。

END ★,°:.☆( ̄▽ ̄).°★

http://www.xdnf.cn/news/18405.html

相关文章:

  • WPF Alert弹框控件 - 完全使用指南
  • 2025年电赛A题省一方案
  • AR 虚实叠加技术在工业设备运维中的实现流程方案
  • 5G-A赋能AR眼镜:毫米级虚实融合的未来已来
  • 通过try-catch判断数据库唯一键字段是否重复
  • 网络流量分析——基础知识
  • MySQL 数据与表结构导出 Excel 技术文档
  • Ubuntu 主机名:精通配置与管理
  • Kafka-Eagle安装
  • SpringBoot + MyBatis-Plus 使用 listObjs 报 ClassCastException 的原因与解决办法
  • 自动驾驶汽车机器学习安全实用解决方案
  • Meta 再次重组人工智能部门
  • 自学嵌入式第二十三天:数据结构(3)-双链表
  • C语言基础:(二十)自定义类型:结构体
  • Linux 文本处理三剑客:awk、grep、sed 完全指南
  • 如何在 Ubuntu 24.04 配置 SFTP Server ?
  • AI 驱动三维逆向:点云降噪算法工具与机器学习建模能力的前沿应用
  • vue3源码reactivity响应式之数组代理的方法
  • MySQL/Kafka数据集成同步,增量同步及全量同步
  • 深入理解数据结构:从数组、链表到B树家族
  • 医疗AI与医院数据仓库的智能化升级:异构采集、精准评估与高效交互的融合方向(上)
  • 【工具使用-Docker容器】构建自己的镜像和容器
  • 栈上创建和堆上创建区别
  • 低开高走的典例:DeepSeek V3.1于8月19日晚更新:128K 上下文击败 Claude 4 Opus
  • 攻克PostgreSQL专家认证
  • RabbitMQ:消息转化器
  • Java EE ----- Spring Boot 日志
  • 第四章:大模型(LLM)】07.Prompt工程-(5)self-consistency prompt
  • 【自动化运维神器Ansible】Roles中Tags使用详解:提升自动化效率的利器
  • 氢元素:宇宙基石与未来能源之钥的多维探索