Durable Functions 故障排除指南

Durable Functions 是 Azure Functions 的扩展,支持使用普通代码生成无服务器业务流程。 有关 Durable Functions 的详细信息,请参阅 Durable Functions 概述

本文提供了有关 Durable Functions 应用中常见方案故障排除的指南。

注意

Microsoft 支持工程师可帮助诊断应用程序的问题。 如果无法使用本指南诊断问题,可以通过访问 Azure 门户中函数应用页“支持 + 故障排除”部分中的“新建支持请求”边栏选项卡来提交支持票证。

Screenshot of support request page in Azure Portal.

提示

在调试和诊断问题时,建议首先确保应用使用的是最新的 Durable Functions 扩展版本。 大多数情况下,使用最新版本可以缓解其他用户已报告的已知问题。 有关如何升级扩展版本的说明,请阅读升级 Durable Functions 扩展版本一文。

Azure 门户中的“诊断和解决问题”选项卡是监视和诊断与应用程序相关的可能问题的有用资源。 它还会根据诊断为问题提供潜在的解决方案。 有关更多详细信息,请参阅 Azure 函数应用诊断

如果上述资源无法解决问题,以下部分提供了有关特定应用程序症状的建议:

业务流程卡滞在 Pending 状态

在启动业务流程时,会将“启动”消息写入由持久扩展管理的内部队列,并且业务流程的状态会设置为“挂起”。 在可用应用实例选取业务流程消息并成功处理后,状态将转换为“正在运行”(或其他一些非“挂起”状态)。

使用以下步骤对无限期停滞在“挂起”状态的业务流程实例进行故障排除。

  • 检查持久任务框架跟踪中是否存在有关受影响业务流程实例 ID 的警告或错误。 有关示例查询,可在跟踪错误/警告部分中查找。

  • 检查分配给停滞业务流程协调程序的 Azure 存储控制队列,以确定其“启动消息”是否仍然存在。有关控制队列的详细信息,请参阅 Azure 存储提供程序控制队列文档

  • 将应用的平台配置版本更改为“64 位”。 有时业务流程会因为应用内存不足而无法启动。 切换到 64 位进程会允许应用分配更多总内存。 这仅适用于应用服务基本、标准、高级和弹性高级计划。 免费或消耗计划支持 64 位进程。

业务流程在长时间延迟后启动

通常,业务流程会在计划后的几秒钟内启动。 但在某些情况下,业务流程可能需要更长时间才能启动。 当业务流程开始执行需要的时间超过几秒钟时,请使用以下步骤进行故障排除。

业务流程未完成/停滞在 Running 状态

如果业务流程长时间保持“正在运行”状态,则通常意味着它正在等待计划完成的长期运行任务。 例如,它可能正在等待持久计时器任务、活动任务或外部事件任务完成。 但如果观察到计划任务已成功完成,但业务流程仍未取得进展,则可能存在某个问题导致业务流程无法继续执行其下一个任务。 我们通常将处于此状态的业务流程称为“停滞业务流程”。

使用以下步骤对停滞业务流程进行故障排除:

  • 尝试重启函数应用。 如果业务流程因应用或扩展代码中的暂时性 bug 或死锁而停滞,则此步骤将有所帮助。

  • 检查 Azure 存储帐户控制队列,以查看是否有任何队列在持续增长。 此 Azure 存储消息 KQL 查询可帮助识别取消业务流程消息排队的问题。 如果问题仅影响单个控制队列,则可能表示问题仅存在于特定应用实例上,在这种情况下,纵向扩展或缩减以移出运行不正常的 VM 实例可能会有所帮助。

  • 使用 Azure 存储消息部分中的 Application Insights 查询来筛选作为分区 ID 的队列名称,并查找与该控制队列分区相关的任何问题。

  • 查看 Durable Functions 最佳做法和诊断工具中的指导。 一些问题可能是由已知的 Durable Functions 反模式引起的。

  • 查看 Durable Functions 版本控制文档。 一些问题可能是外部测试版业务流程实例的中断性变更造成的。

业务流程运行缓慢

大量数据处理、内部错误和计算资源不足可能会导致业务流程的执行速度慢于平时。 使用以下步骤对执行时间超过预期的业务流程进行故障排除:

  • 检查持久任务框架跟踪中是否存在有关受影响业务流程实例 ID 的警告或错误。 有关示例查询,可在跟踪错误/警告部分中查找。

  • 如果应用利用 .NET 进程内模型,请考虑启用扩展会话。 扩展会话可以最大程度地减少历史记录负载,这可能会放慢处理速度。

  • 检查性能和可伸缩性瓶颈。 应用程序性能取决于多种因素。 例如,CPU 使用率过高或内存消耗量过大可能会导致延迟。 有关详细指导,请阅读 Durable Functions 的性能和缩放

示例查询

本部分介绍如何通过在为 Azure Functions 应用配置的 Azure Application Insights 实例中编写自定义 KQL 查询来排查问题。

Azure 存储消息

在使用默认 Azure 存储提供程序时,所有 Durable Functions 行为将由 Azure 存储队列消息驱动,并且与业务流程相关的所有状态将存储在表存储和 Blob 存储中。 启用持久任务框架跟踪后,所有 Azure 存储交互将记录到 Application Insights,而且此数据对于调试执行和性能问题至关重要。

从 Durable Functions 扩展的 v2.3.0 开始,可以通过更新 host.json 文件中的日志记录配置,将这些持久任务框架日志发布到 Application Insights 实例。 有关如何执行此操作的信息和说明,请参阅持久任务框架日志记录一文

以下查询可用于检查特定业务流程实例的端到端 Azure 存储交互。 编辑 startorchestrationInstanceID 以按时间范围和实例 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 

控制队列/分区 ID 日志

以下查询可搜索与 instanceId 的控制队列关联的所有活动。 需要为 orchestrationInstanceID 中的 instanceID 提供值,并在 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

Application Insights 列引用

下面是上述查询投影的列的列表及其各自的说明。

说明
pid 函数应用实例的进程 ID。 这对于确定在执行业务流程时是否回收了进程会非常有用。
taskName 正在记录的事件的名称。
eventType 消息的类型,通常表示业务流程协调程序所完成的工作。 此处提供了其可能值的完整列表及其说明
extendedSession 指示是否启用了扩展会话的布尔值。
account 应用使用的存储帐户。
详细信息 有关特定事件的其他信息(如果有)。
instanceId 给定业务流程或实体实例的 ID。
messageId 给定队列消息的唯一 Azure 存储 ID。 此值最常出现在 ReceivedMessage、ProcessingMessage 和 DeletingMessage 跟踪事件中。 请注意,它不存在于 SendMessage 事件中,因为消息 ID 是在我们发送消息之后由 Azure 存储生成的。
executionId 业务流程协调程序执行的 ID,每当调用 continue-as-new 时,该 ID 都会更改。
age 自消息排队以来的毫秒数。 较大的数字通常指示存在性能问题。 一个例外是 TimerFired 消息类型,它可能具有较大的 Age 值,具体取决于计时器的持续时间。
latencyMs 一些存储操作花费的毫秒数。
dequeueCount 消息已取消排队的次数。 在正常情况下,此值始终为 1。 如果超过一,则可能存在问题。
partitionId 与此日志关联的队列的名称。
totalEventCount 当前操作中涉及的历史记录事件数。
taskHub 任务中心的名称。
newEvents 正在写入存储中“历史记录”表的历史记录事件列表(以逗号分隔)。