Durable Functions 是 Azure Functions 的扩展,可用于使用普通代码构建无服务器编排。 有关 Durable Functions 的详细信息,请参阅 Durable Functions 概述。
本文提供了一个指南,用于排除 Durable Functions 应用中的常见场景故障。
备注
Microsoft支持工程师随时可以帮助诊断您的应用程序问题。 如果无法使用本指南诊断问题,可以通过访问 Azure 门户中函数应用页的“支持 + 故障排除”部分中的“新建支持请求”边栏选项卡来提交支持票证。
提示
调试和诊断问题时,建议首先确保应用使用的是最新的 Durable Functions 扩展版本。 大多数情况下,使用最新版本可缓解其他用户已报告的已知问题。 有关如何升级扩展版本的说明,请阅读 升级 Durable Functions 扩展版本 一文。
Azure 门户中的 “诊断和解决问题 ”选项卡是一种有用的资源,用于监视和诊断与应用程序相关的可能问题。 它还根据诊断提供问题的潜在解决方案。 有关更多详细信息,请参阅 Azure Function App 诊断 。
如果上述资源无法解决问题,以下部分提供有关特定应用程序症状的建议:
当您启动一个编排时,“开始”消息将写入由 Durable 扩展管理的内部队列,并且编排的状态被设置为“等待中”。 当编排消息被一个可用的应用实例选取并成功处理后,状态将转换为“正在运行”(或转变为其他非“挂起”状态)。
使用以下步骤对无限期停滞在“挂起”状态的业务流程实例进行故障排除。
检查 Durable Task Framework 跟踪中的警告或错误,以便查看受影响的编排实例 ID。 可以在 “跟踪错误/警告”部分找到示例查询。
检查分配给停滞业务流程协调程序的 Azure 存储控制队列,以查看其“启动消息”是否仍然存在。有关控制队列的详细信息,请参阅 Azure 存储提供程序控制队列文档。
将应用的 平台配置 版本更改为“64 位”。 有时编排不会启动,因为应用程序内存不足。 切换到 64 位进程允许应用分配更多总内存。 这仅适用于应用服务基本、标准、高级和弹性高级计划。 免费或消耗计划 不支持 64 位进程。
通常,编排任务在计划后几秒钟内开始。 但是,在某些情况下,编排流程可能会需要更长的时间才能启动。 使用以下步骤排查当编排需要超过几秒钟才能开始执行时的问题。
请参阅 有关 Azure 存储中延迟业务流程的文档 ,以检查延迟是否可能是由已知限制引起的。
检查 Durable Task Framework 跟踪,查找具有受影响编排实例 ID 的警告或错误。 可以在 “跟踪错误/警告”部分找到示例查询。
如果一个编排长时间处于“正在运行”状态,通常意味着它在等待一个计划完成的长时间运行的任务。 例如,它可能正在等待一个持久计时器任务、一个活动任务或一个外部事件任务的完成。 但是,如果观察到计划任务已成功完成,但业务流程仍未取得进展,则可能会有问题阻止业务流程继续执行下一个任务。 我们通常将此状态下的业务流程称为“停滞业务流程”。
使用以下步骤排查停滞的业务流程问题:
请尝试重启函数应用。 如果业务流程因应用或扩展代码中的暂时性 bug 或死锁而停滞,此步骤可能会有所帮助。
检查 Azure 存储帐户控制队列,查看是否有任何队列持续增长。 此 Azure 存储消息传送 KQL 查询 可帮助识别出队编排消息的问题。 如果问题仅影响单个控制队列,则可能表示仅在特定应用程序实例上存在的问题,这种情况下,缩放或缩小以移除不健康的 VM 实例可能会有所帮助。
在 Azure 存储消息部分 中使用 Application Insights 查询,以该队列名称作为分区 ID 进行筛选,并查找与该控制队列分区相关的任何问题。
查看 Durable Functions 最佳做法和诊断工具中的指南。 某些问题可能是由已知的 Durable Functions 反模式引起的。
查看 Durable Functions 版本控制文档。 某些问题可能是由于进行中的编排实例发生破坏性变更造成的。
大量数据处理、内部错误和计算资源不足可能会导致业务流程的执行速度低于正常。 使用以下步骤对执行时间超过预期时间的业务流程进行故障排除:
检查 Durable Task Framework 跟踪,以查找与受影响的编排实例 ID 相关的警告或错误。 可以在 “跟踪错误/警告”部分找到示例查询。
如果应用使用 .NET 进程内模型,请考虑启用 扩展会话。 扩展会话可以最大程度地减少历史记录负载,这可能会降低处理速度。
检查性能和可伸缩性瓶颈。 应用程序性能取决于许多因素。 例如,CPU 使用率较高或内存消耗大可能会导致延迟。 阅读 Durable Functions 中的性能和缩放,获取详细指南。
本部分介绍如何通过在为 Azure Functions 应用配置的 Azure Application Insights 实例中编写自定义 KQL 查询 来排查问题。
使用默认的 Azure 存储提供程序时,所有 Durable Functions 行为都由 Azure 存储队列消息驱动,与业务流程相关的所有状态都存储在表存储和 Blob 存储中。 启用 Durable Task Framework 跟踪后,所有 Azure 存储交互都会记录到 Application Insights,并且此数据对于调试执行和性能问题至关重要。
从 Durable Functions 扩展的 v2.3.0 开始,可以通过更新 host.json 文件中的日志记录配置,将这些 Durable Task Framework 日志发布到 Application Insights 实例。 有关如何执行此作的信息和说明,请参阅 Durable Task Framework 日志记录文章 。
以下查询用于检查特定业务流程实例的端到端 Azure 存储交互。 编辑 start
并 orchestrationInstanceID
按时间范围和实例 ID 进行筛选。
let start = datetime(XXXX-XX-XXTXX:XX:XX); // edit this
let orchestrationInstanceID = "XXXXXXX"; //edit this
traces
| where timestamp > start and timestamp < start + 1h
| where customDimensions.Category == "DurableTask.AzureStorage"
| extend taskName = customDimensions["EventName"]
| extend eventType = customDimensions["prop__EventType"]
| extend extendedSession = customDimensions["prop__IsExtendedSession"]
| extend account = customDimensions["prop__Account"]
| extend details = customDimensions["prop__Details"]
| extend instanceId = customDimensions["prop__InstanceId"]
| extend messageId = customDimensions["prop__MessageId"]
| extend executionId = customDimensions["prop__ExecutionId"]
| extend age = customDimensions["prop__Age"]
| extend latencyMs = customDimensions["prop__LatencyMs"]
| extend dequeueCount = customDimensions["prop__DequeueCount"]
| extend partitionId = customDimensions["prop__PartitionId"]
| extend eventCount = customDimensions["prop__TotalEventCount"]
| extend taskHub = customDimensions["prop__TaskHub"]
| extend pid = customDimensions["ProcessId"]
| extend appName = cloud_RoleName
| extend newEvents = customDimensions["prop__NewEvents"]
| where instanceId == orchestrationInstanceID
| sort by timestamp asc
| project timestamp, appName, severityLevel, pid, taskName, eventType, message, details, messageId, partitionId, instanceId, executionId, age, latencyMs, dequeueCount, eventCount, newEvents, taskHub, account, extendedSession, sdkVersion
以下查询搜索给定编排实例的错误和警告。 需要提供一个值 orchestrationInstanceID
。
let orchestrationInstanceID = "XXXXXX"; // edit this
let start = datetime(XXXX-XX-XXTXX:XX:XX);
traces
| where timestamp > start and timestamp < start + 1h
| extend instanceId = iif(isnull(customDimensions["prop__InstanceId"] ) , customDimensions["prop__instanceId"], customDimensions["prop__InstanceId"] )
| extend logLevel = customDimensions["LogLevel"]
| extend functionName = customDimensions["prop__functionName"]
| extend status = customDimensions["prop__status"]
| extend details = customDimensions["prop__Details"]
| extend reason = customDimensions["prop__reason"]
| where severityLevel >= 1 // to see all logs of severity level "Information" or greater.
| where instanceId == orchestrationInstanceID
| sort by timestamp asc
以下查询搜索与 instanceId 的控制队列关联的所有活动。 需要提供 instanceID 的值 orchestrationInstanceID
以及查询的开始时间 start
。
let orchestrationInstanceID = "XXXXXX"; // edit this
let start = datetime(XXXX-XX-XXTXX:XX:XX); // edit this
traces // determine control queue for this orchestrator
| where timestamp > start and timestamp < start + 1h
| extend instanceId = customDimensions["prop__TargetInstanceId"]
| extend partitionId = tostring(customDimensions["prop__PartitionId"])
| where partitionId contains "control"
| where instanceId == orchestrationInstanceID
| join kind = rightsemi(
traces
| where timestamp > start and timestamp < start + 1h
| where customDimensions.Category == "DurableTask.AzureStorage"
| extend taskName = customDimensions["EventName"]
| extend eventType = customDimensions["prop__EventType"]
| extend extendedSession = customDimensions["prop__IsExtendedSession"]
| extend account = customDimensions["prop__Account"]
| extend details = customDimensions["prop__Details"]
| extend instanceId = customDimensions["prop__InstanceId"]
| extend messageId = customDimensions["prop__MessageId"]
| extend executionId = customDimensions["prop__ExecutionId"]
| extend age = customDimensions["prop__Age"]
| extend latencyMs = customDimensions["prop__LatencyMs"]
| extend dequeueCount = customDimensions["prop__DequeueCount"]
| extend partitionId = tostring(customDimensions["prop__PartitionId"])
| extend eventCount = customDimensions["prop__TotalEventCount"]
| extend taskHub = customDimensions["prop__TaskHub"]
| extend pid = customDimensions["ProcessId"]
| extend appName = cloud_RoleName
| extend newEvents = customDimensions["prop__NewEvents"]
) on partitionId
| sort by timestamp asc
| project timestamp, appName, severityLevel, pid, taskName, eventType, message, details, messageId, partitionId, instanceId, executionId, age, latencyMs, dequeueCount, eventCount, newEvents, taskHub, account, extendedSession, sdkVersion
上述查询结果所涉及的列及其各自说明的列表如下。
列 | DESCRIPTION |
---|---|
pid | 函数应用实例的进程 ID。 在执行编排时,这对于确定进程是否被重启非常有用。 |
任务名称 | 要记录的事件的名称。 |
事件类型 | 这是一类消息,通常表示编排器完成的工作。 此处提供了其可能值及其说明的完整列表 |
扩展会话 | 指示是否启用 扩展会话的 布尔值。 |
帐户 | 应用使用的存储帐户。 |
详情 | 有关特定事件的其他信息(如果有)。 |
instanceId | 给定业务流程或实体实例的 ID。 |
消息ID | 给定队列消息的唯一 Azure 存储 ID。 此值最常出现在 ReceivedMessage、ProcessingMessage 和 DeletingMessage 跟踪事件中。 请注意,发送消息 后 ,它不存在于 SendMessage 事件中,因为消息 ID 由 Azure 存储生成。 |
执行ID | 协调器执行的 ID,每当 continue-as-new 被调用时都会更改为新的 ID。 |
年龄 | 消息排队以来的毫秒数。 大量数字通常表示性能问题。 一个例外是 TimerFired 消息类型,该类型的年龄值可能较大,这具体取决于计时器的持续时间。 |
延迟毫秒 | 某些存储操作所耗费的毫秒数。 |
dequeueCount | 取消排队消息的次数。 在正常情况下,此值始终为 1。 如果有多个,则可能存在问题。 |
分区标识ID | 与此日志关联的队列的名称。 |
总事件计数 | 当前操作中涉及的历史事件个数。 |
taskHub | 任务中心的名称。 |
新事件 | 正在写入存储中的历史表的以逗号分隔的历史事件列表。 |