Azure Functions 如何处理耗时约45分钟的 D365 大数据导出?
建议保留 Timer trigger 作为调度入口,把导出、转换和发送拆成可恢复的小任务。需要统一跟踪进度和协调多个步骤时,可以使用 Durable Functions。大文件存入 Blob Storage,编排过程和 Service Bus 尽量只传递文件引用。
需要区分 45 分钟是整个导出流程的耗时,还是一次无法拆分的 API 调用耗时。Durable Functions 可以协调长时间运行的流程,但单个 Activity 仍受所在 Azure Functions 托管计划和执行配置的限制。
先确认失败发生在哪里
目前缺少异常日志、托管计划和 D365 API 类型的信息,无法直接认定失败是由超时引起的。建议先检查 Application Insights 或函数执行日志,区分以下情况:
- 函数达到
functionTimeout,或宿主进程被重启。 - 下载、解析 XML 或生成 JSON 时内存不足。
- D365 API 返回限流、超时或其他错误。
- Service Bus 消息过大,或消息发送失败。
如果使用传统 Consumption plan,函数默认执行超时为 5 分钟,最大可配置为 10 分钟,无法通过设置 functionTimeout 支持一次 45 分钟的执行。其他托管计划允许更长的执行时间,但仍需考虑平台更新、实例回收和故障恢复。具体限制见 Azure Functions 执行超时说明。
更换触发器本身通常不能解决问题。应先减少单次执行的工作量,再根据剩余的执行时长和内存需求选择托管计划。
按 D365 API 的能力安排导出
API 支持分页或分区读取
可以将任务拆成“读取一页或一个分区 → 保存 XML → 转换 JSON → 发送消息”,让每一步都能在执行限制内完成,同时保存分页游标或分区进度。
如果各分区可以独立读取,可以采用有限并发;如果下一页依赖上一页返回的游标,就顺序获取页面。增加并发不一定更快,实际并发度需要结合 D365 的限流响应和处理端资源调整。Durable Functions 的每实例并发配置也不等同于整个应用的全局请求限速,参见并发控制说明。
API 支持异步导出作业
可以先提交导出请求并保存作业 ID,再通过短时状态查询等待完成。查询之间使用 Durable timer,避免在一个 Activity 中通过 Thread.Sleep 或长时间等待来维持整个 45 分钟的过程。
这种方式的前提是实际使用的 D365 API 提供异步作业和状态查询能力;现有信息不足以确定具体接口和参数。
API 只能通过一次长连接完成导出
如果一次请求确实需要持续 45 分钟,且无法分页或异步化,拆分后续转换步骤无法缩短该请求。此时需要评估支持该执行时长的托管计划,核对 HTTP 客户端、服务端和中间网络组件的超时设置,并设计失败后的重新执行方式。
Durable Functions 中只保存进度和文件引用
一个适合分页导出的流程是:
Timer trigger
→ 启动 Durable orchestration
→ ExportPage:读取一页,保存 XML 到 Blob Storage
→ TransformPage:读取 XML,生成 JSON 并保存到 Blob Storage
→ PublishReference:向 Service Bus 发送 JSON 文件引用
→ 保存下一页游标,继续处理
Orchestrator 负责调度,D365 请求、Blob 读写和 Service Bus 发送放在 Activity 中。Orchestrator 会重放,代码必须保持确定性,不能直接在其中执行普通网络或文件 I/O。具体约束见 Orchestrator 代码限制。
下面是采用 .NET isolated worker 模型的编排骨架,前提是 D365 API 支持分页。它需要 Durable Functions 扩展,还需要自行实现三个 Activity,并不是完整的 D365 接入项目。
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
public record ExportInput(string JobId, string? Cursor);
public record ExportPageResult(
string BatchId,
string XmlBlobName,
string? NextCursor);
public record PublishInput(
string JobId,
string BatchId,
string JsonBlobName);
public static class ExportWorkflow
{
[Function(nameof(ExportOrchestrator))]
public static async Task ExportOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var input = context.GetInput<ExportInput>()
?? throw new InvalidOperationException("Missing export input.");
var page = await context.CallActivityAsync<ExportPageResult>(
"ExportPage", input);
var jsonBlobName = await context.CallActivityAsync<string>(
"TransformPage", page.XmlBlobName);
await context.CallActivityAsync(
"PublishReference",
new PublishInput(input.JobId, page.BatchId, jsonBlobName));
if (page.NextCursor is not null)
{
context.ContinueAsNew(input with { Cursor = page.NextCursor });
}
}
}
这个示例每处理完一页,就通过 ContinueAsNew 携带下一页游标开始新一轮,避免编排历史随页数不断增长。BatchId 应由业务逻辑为同一批数据生成稳定标识。
Activity 的返回值应保持精简,不要把整份 XML、JSON 或全部记录返回给 Orchestrator。大对象会进入持久化历史,增加存储开销和重放时的内存开销。微软建议将大数据保存在外部存储,只在 Activity 中按需读取,参见数据持久化与序列化说明。
转换和消息发送也要控制数据量
XML 转 JSON 时,尽量按记录或批次读取、映射和写出,避免同时保留完整 XML 字符串、XML DOM 和完整 JSON 字符串。可以使用 XmlReader 逐条读取,但具体映射规则需要根据 XML 结构确定,例如属性、命名空间和重复节点如何表示。不能假设存在适用于所有数据的通用映射。
如果输出 JSON 较大,可以使用 Claim Check pattern:确认 JSON 已成功写入 Blob Storage 后,再向 Service Bus 发送文件引用,由消费者读取实际内容。例如:
{
"jobId": "export-001",
"batchId": "batch-001",
"container": "exports",
"blobName": "export-001/batch-001.json"
}
这样能减轻消息大小限制和大消息传输带来的压力。消费者需要具备读取 Blob 的权限,文件保留时间也应覆盖消息等待及重试周期。模式说明见 Claim Check pattern。如果每条 JSON 本身足够小,也可以直接发送,但应核对实际 Service Bus 层级和配置对应的消息限制。
补上重试、幂等和重复调度处理
拆分任务后,还需要处理执行中断带来的重复操作:
- 对可恢复错误设置有上限的重试和退避,按接口要求处理限流响应。
- 使用稳定的
JobId + BatchId标识输出,避免重试时重复生成同一批文件。 - 发送消息时设置稳定的
MessageId,消费者仍应实现幂等处理。 - 文件写入和消息发送分属不同服务,要考虑“消息已发送,但 Activity 尚未记录成功”时被再次执行的情况。
- Timer trigger 启动任务后,应通过作业状态或互斥机制决定下一次定时触发是跳过、排队还是启动新作业,避免无意重叠。
这些措施需要在业务代码中落实,不能仅依赖编排状态持久化来保证外部操作只发生一次。
可参考的 GitHub 项目
Azure-Samples/durable-functions-quickstart-dotnet-azd 提供 C# isolated worker 的 Durable Functions fan-out/fan-in 示例,可以用来学习编排、Activity 和部署方式。它不是 D365 导出项目,需要替换为实际的数据读取、XML 转换和 Service Bus 发送逻辑。
对于当前场景,可以先确认 D365 是否支持分页或异步导出,再实现一个批次的完整处理与重试,最后根据日志和接口限制调整批量大小及并发度。
备注:内容仅供参考。
