Azure Functions:编排器本地业务逻辑是否需移至活动函数?
Azure Durable Functions:编排器内数据处理的最佳实践
不需要把所有业务逻辑都迁移到活动函数,核心是根据你的数据处理开销和重放影响来做权衡:
核心原理
Durable Orchestrator的重放机制会跳过已经完成的CallActivityAsync调用(这些操作的结果已被框架持久化),但编排器内的本地计算逻辑会在每次重放时重新执行。如果你的处理逻辑是轻量的(比如小规模数据的过滤、排序),重复执行的资源开销可以忽略,完全可以留在编排器中。
什么时候需要把逻辑移到活动函数
如果出现以下情况,建议将数据处理逻辑封装到活动函数:
- 处理逻辑耗时较长:比如针对上万条以上数据的复杂过滤、聚合、排序,重复执行会浪费计算资源、延长整体执行时间
- 处理逻辑依赖外部可变状态:比如读取数据库、调用第三方服务(后续扩展有这类依赖时,必须移到活动函数,避免重放时的不一致)
- 需要持久化中间计算结果:活动函数的输出会被Durable Framework自动持久化,重放时直接复用结果,无需重新计算
优化方案示例
如果你的数据处理开销较大,可以将中间处理逻辑封装为活动函数,比如新增一个ProcessApiObjectsAsync活动函数:
[FunctionName("ProcessApiObjectsAsync")] public IEnumerable<ApiObject> ProcessApiObjects([ActivityTrigger] IEnumerable<ApiObject> someObjects) { var objectsStartWithA = someObjects .Where(x => !string.IsNullOrEmpty(x.Name) && x.Name.StartsWith("a", StringComparison.OrdinalIgnoreCase)) .OrderBy(x => x.Name) .ToList(); var objectsStartWithB = someObjects .Where(x => !string.IsNullOrEmpty(x.Name) && x.Name.StartsWith("b", StringComparison.OrdinalIgnoreCase)) .OrderBy(x => x.Name) .ToList(); return objectsStartWithA.Union(objectsStartWithB); }
然后在编排器中调用这个活动函数,替代本地计算:
[FunctionName(FunctionName)] public async Task ExecuteAsync([OrchestrationTrigger] IDurableOrchestrationContext context) { var logger = context.CreateReplaySafeLogger(_logger); logger.LogInformation("Process started"); var someObjects = (await context.CallActivityAsync<IEnumerable<ApiObject>>("GetApiObjectsAsync", null)).ToList(); // 调用活动函数处理数据,重放时直接复用结果 var objectsToCompare = await context.CallActivityAsync<IEnumerable<ApiObject>>("ProcessApiObjectsAsync", someObjects); var someOtherObjects = (await context.CallActivityAsync<IEnumerable<ApiObject>>("GetOtherApiObjectsAsync", null)).ToList(); // 如果filteredObjects的处理开销大,同样可以封装为活动函数 var filteredObjects = someOtherObjects .Where(x => objectsToCompare.All(z => z.Id != x.Id)) // 修正原代码笔误:z.Id != z.Id 改为 z.Id != x.Id .OrderBy(x => x.Id) .ToList(); await context.CallActivityAsync("SaveObjects", filteredObjects); logger.LogInformation("Process finished"); }
额外注意点
- 原代码中有一处笔误:
Where(x => objectsToCompare.All(z => z.Id != z.Id))逻辑完全错误,应改为z.Id != x.Id才能实现预期的过滤效果 - 编排器内的本地计算必须是确定性的(基于固定输入总能得到相同输出),否则重放会导致状态不一致,你的场景满足这个要求,所以无需担心功能问题
内容的提问来源于stack exchange,提问作者Prog
相关产品推荐
相关产品推荐

