Azure 函数

azure-functions
分类通用
作者Agentic Awesome Skills 社区
许可MIT
评分4.70/5
使用9.5K

Azure Functions

Azure Functions 开发专家模式,包括隔离工作进程模型、Durable Functions 编排、冷启动优化及生产环境模式。涵盖 .NET、Python 和 Node.js 编程模型。

模式 (Patterns)

隔离工作进程模型 (.NET)

具有进程隔离功能的现代 .NET 执行模型

适用场景:构建新的 .NET Azure Functions 应用

模板

// Program.cs - 隔离工作进程模型
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
// 添加 Application Insights
services.AddApplicationInsightsTelemetryWorkerService();
services.ConfigureFunctionsApplicationInsights();

// 添加 HttpClientFactory (防止套接字耗尽)
services.AddHttpClient();

// 添加自定义服务
services.AddSingleton<IMyService, MyService>();
})
.Build();

host.Run();

// HttpTriggerFunction.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;

public class HttpTriggerFunction
{
private readonly ILogger<HttpTriggerFunction> _logger;
private readonly IMyService _service;

public HttpTriggerFunction(
ILogger<HttpTriggerFunction> logger,
IMyService service)
{
_logger = logger;
_service = service;
}

[Function("HttpTrigger")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
{
_logger.LogInformation("Processing request");

try
{
var result = await _service.ProcessAsync(req);

var response = req.CreateResponse(HttpStatusCode.OK);
await response.WriteAsJsonAsync(result);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing request");
var response = req.CreateResponse(HttpStatusCode.InternalServerError);
await response.WriteAsJsonAsync(new { error = "Internal server error" });
return response;
}
}
}

注意事项

  • 进程内 (In-process) 模型将于 2026 年 11 月弃用
  • 隔离工作进程支持 .NET 8, 9, 10 以及 .NET Framework
  • 全面支持依赖注入 (DI)
  • 支持自定义中间件

Node.js v4 编程模型

适用于 TypeScript/JavaScript 的现代以代码为中心的开发方式

适用场景:构建 Node.js Azure Functions

模板

// src/functions/httpTrigger.ts
import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";

export async function httpTrigger(
request: HttpRequest,
context: InvocationContext
): Promise<HttpResponseInit> {
context.log(Http function processed request for url "${request.url}");

try {
const name = request.query.get("name") || (await request.text()) || "world";

return {
status: 200,
jsonBody: { message: Hello, ${name}! }
};
} catch (error) {
context
.error("Error processing request:", error);
return {
status: 500,
jsonBody: { error: "Internal server error" }
};
}
}

// 使用 app 对象注册函数
app.http("httpTrigger", {
methods: ["GET", "POST"],
authLevel: "function",
handler: httpTrigger
});

// 定时触发器示例
app.timer("timerTrigger", {
schedule: "0 */5 * * * *", // 每 5 分钟执行一次
handler: async (myTimer, context) => {
context.log("Timer function executed at:", new Date().toISOString());
}
});

// Blob 触发器示例
app.storageBlob("blobTrigger", {
path: "samples-workitems/{name}",
connection: "AzureWebJobsStorage",
handler: async (blob, context) => {
context.log(Blob trigger processing: ${context.triggerMetadata.name});
context.log(Blob size: ${blob.length} bytes);
}
});

注意事项

  • v4 模型以代码为中心,无需 function.json 文件
  • 使用类似于 Express.js 的 app 对象
  • 原生支持 TypeScript
  • 所有触发器均在代码中注册

Python v2 编程模型

针对 Python 函数的基于装饰器(Decorator)的方法

适用场景:构建 Python Azure Functions

模板

function_app.py

import azure.functions as func import logging import json

app = func.FunctionApp(http_auth_level=func.AuthLevel.FUNCTION)

@app.route(route="hello", methods=["GET", "POST"])
async def http_trigger(req: func.HttpRequest) -> func.HttpResponse:
logging.info("Python HTTP trigger function processed a request.")

try:
name = req.params.get("name")
if not name:
try:
req_body = req.get_json()
name = req_body.get("name")
except ValueError:
pass

if name:
return func.HttpResponse(
json.dumps({"message": f"Hello, {name}!"}),
mimetype="application/json"
)
else:
return func.HttpResponse(
json.dumps({"message": "Hello, World!"}),
mimetype="application/json"
)
except Exception as e:
logging.error(f"Error processing request: {str(e)}")
return func.HttpResponse(
json.dumps({"error": "Internal server error"}),
status_code=500,
mimetype="application/json"
)

@app.timer_trigger(schedule="0 */5 * * * *", arg_name="myTimer")
def timer_trigger(myTimer: func.TimerRequest) -> None:
logging.info("Timer trigger executed")

@app.blob_trigger(arg_name="myblob", path="samples-workitems/{name}",
connection="AzureWebJobsStorage")
def blob_trigger(myblob: func.InputStream):
logging.info(f"Blob trigger: {myblob.name}, Size: {myblob.length} bytes")

@app.queue_trigger(arg_name="msg", queue_name="myqueue",
connection="AzureWebJobsStorage")
def queue_trigger(msg: func.QueueMessage) -> None:
logging.info(f"Queue message: {msg.get_body().decode('utf-8')}")

注意事项

  • v2 模型使用装饰器,无需 function.json 文件
  • Python 采用进程外运行(始终隔离)
  • Python 需要基于 Linux 的托管环境
  • 支持异步函数

Durable Functions - 函数链 (Function Chaining)

具有状态持久化的顺序执行

适用场景:需要具有自动重试机制的顺序工作流

模板

// C# 隔离工作进程 - 函数链
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;

public class OrderWorkflow
{
[Function("OrderOrchestrator")]
public

csharp
static async Task<OrderResult> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var order = context.GetInput<Order>();

// 函数顺序执行,状态在每次执行之间持久化
var validated = await context.CallActivityAsync<ValidatedOrder>(
"ValidateOrder", order);

var payment = await context.CallActivityAsync<PaymentResult>(
"ProcessPayment", validated);

var shipped = await context.CallActivityAsync<ShippingResult>(
"ShipOrder", new ShipRequest { Order = validated, Payment = payment });

var notification = await context.CallActivityAsync<bool>(
"SendNotification", shipped);

return new OrderResult
{
OrderId = order.Id,
Status = "Completed",
TrackingNumber = shipped.TrackingNumber
};
}

[Function("ValidateOrder")]
public static async Task<ValidatedOrder> ValidateOrder(
[ActivityTrigger] Order order, FunctionContext context)
{
var logger = context.GetLogger<OrderWorkflow>();
logger.LogInformation("Validating order {OrderId}", order.Id);

// 验证逻辑...
return new ValidatedOrder { /* ... */ };
}

[Function("ProcessPayment")]
public static async Task<PaymentResult> ProcessPayment(
[ActivityTrigger] ValidatedOrder order, FunctionContext context)
{
// 带有内置重试机制的支付处理...
return new PaymentResult { /* ... */ };
}

[Function("OrderWorkflow_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext context)
{
var order = await req.ReadFromJsonAsync<Order>();
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
"OrderOrchestrator", order);

return client.CreateCheckStatusResponse(req, instanceId);
}
}

笔记

  • 活动(Activity)之间的状态自动持久化
  • 针对瞬时故障自动重试
  • 可在进程重启后恢复
  • 内置用于监控的状态端点

Durable Functions - 扇出/扇入 (Fan-Out/Fan-In)

并行执行并聚合结果

适用场景:并行处理多个项目

模板

// C# 隔离工作进程 - 扇出/扇入
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;

public class ParallelProcessing
{
[Function("ProcessImagesOrchestrator")]
public static async Task<ProcessingResult> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
var images = context.GetInput<List<string>>();

// 扇出:并行启动所有任务
var tasks = images.Select(image =>
context.CallActivityAsync<ImageResult>("ProcessImage", image));

// 扇入:等待所有任务完成
var results = await Task.WhenAll(tasks);

// 聚合结果
var successful = results.Count(r => r.Success);
var failed = results.Count(r => !r.Success);

return new ProcessingResult
{
TotalProcessed = results.Length,
Successful = successful,
Failed = failed,
Results = results.ToList()
};
}

[Function("ProcessImage")]
public static async Task<ImageResult> ProcessImage(


[ActivityTrigger] string imageUrl, FunctionContext context)
{
var logger = context.GetLogger<ParallelProcessing>();
logger.LogInformation("Processing image: {Url}", imageUrl);

try
{
// 图像处理逻辑...
await Task.Delay(1000); // 模拟工作

return new ImageResult
{
Url = imageUrl,
Success = true,
ProcessedUrl = $"processed-{imageUrl}"
};
}
catch (Exception ex)
{
logger.LogError(ex, "Failed to process {Url}", imageUrl);
return new ImageResult { Url = imageUrl, Success = false };
}
}

// Python 等效代码
// @app.orchestration_trigger(context_name="context")
// def process_images_orchestrator(context: df.DurableOrchestrationContext):
// images = context.get_input()
//
// # 扇出 (Fan-out):创建并行任务
// tasks = [context.call_activity("ProcessImage", img) for img in images]
//
// # 扇入 (Fan-in):等待所有任务完成
// results = yield context.task_all(tasks)
//
// return {"processed": len(results), "results": results}
}

注意事项

  • 独立任务采用并行执行
  • 所有任务完成后汇总结果
  • 内存高效 —— 仅存储任务 ID
  • 支持多达数千个并行活动

冷启动优化

降低生产环境中的冷启动延迟

适用场景:生产环境需要快速响应时间

模板

// 1. 使用带有预热实例的 Premium 计划
// host.json
{
"version": "2.0",
"extensions": {
"durableTask": {
"hubName": "MyTaskHub"
}
},
"functionTimeout": "00:30:00"
}

// 2. 添加预热触发器 (Premium 计划)
[Function("Warmup")]
public static void Warmup(
[WarmupTrigger] object warmupContext,
FunctionContext context)
{
var logger = context.GetLogger("Warmup");
logger.LogInformation("Warmup trigger executed - initializing dependencies");

// 预初始化昂贵的资源
// 例如:数据库连接、HttpClients 等
}

// 3. 通过 DI 使用静态/单例客户端
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// HttpClientFactory 防止套接字耗尽
services.AddHttpClient<IMyApiClient, MyApiClient>(client =>
{
client.BaseAddress = new Uri("https://api.example.com");
client.Timeout = TimeSpan.FromSeconds(30);
});

// 为昂贵的初始化过程使用单例
services.AddSingleton<IExpensiveService>(sp =>
{
// 初始化一次,在多次调用中复用
return new ExpensiveService();
});
}
}

// 4. 减小包体积
// .csproj - 排除不必要的依赖
<PropertyGroup>
<PublishTrimmed>true</PublishTrimmed>
<TrimMode>partial</TrimMode>
</PropertyGroup>

// 5. 使用包部署运行
// Azure CLI
// az functionapp deployment source config-zip \
// --resource-group myResourceGroup \
// --name myFunctionApp \
// --src myapp.zip \
// --build-remote true

注意事项

  • 所有区域/语言的冷启动情况改善了约 53%
  • Premium 计划提供预热实例
  • 预热触发器在流量进入前进行初始化
  • 包部署可减少冷启动时间

带有错误处理的队列触发器

通过死信队列 (Poison Queue) 实现可靠的消息处理

适用场景:处理来自 Azure Storage Queue 的消息

模板

// C# 独立工作进程 (Isolated Worker) - 队列触发器
using Microsoft.Azure.Functions.Worker;

public class QueueProcessor
{
private readonly ILogger<QueueProcessor> _logger;
private readonly IMyService _service;

public QueueProcessor(ILogger<QueueProcessor> logger, IMyService service)
{
_logger = logger;
_service = service;
}

[Function("ProcessQueueMessage")]
public async Task Run(
[QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]
QueueMessage message)
{
_logger.LogInformation("正在处理消息: {Id}", message.MessageId);

try
{
var payload = JsonSerializer.Deserialize<MyPayload>(message.Body);
await _service.ProcessAsync(payload);

_logger.LogInformation("消息处理成功: {Id}", message.MessageId);
}
catch (Exception ex)
{
_logger.LogError(ex, "处理消息时出错: {Id}", message.MessageId);

// 消息将重试最多 maxDequeueCount 次(默认 5 次)
// 然后被移至毒队列 (poison queue): myqueue-items-poison
throw;
}
}

// 可选:监控毒队列
[Function("ProcessPoisonQueue")]
public async Task ProcessPoison(
[QueueTrigger("myqueue-items-poison", Connection = "AzureWebJobsStorage")]
QueueMessage message)
{
_logger.LogWarning("正在处理毒队列消息: {Id}", message.MessageId);

// 记录到监控系统、发送警报或存储以供手动审查
await _service.HandlePoisonMessageAsync(message);
}
}

// host.json - 队列配置
// {
// "version": "2.0",
// "extensions": {
// "queues": {
// "maxPollingInterval": "00:00:02",
// "visibilityTimeout": "00:00:30",
// "batchSize": 16,
// "maxDequeueCount": 5,
// "newBatchThreshold": 8
// }
// }
// }

注意事项

  • 消息最多重试 maxDequeueCount
  • 失败的消息将被移至毒队列 (poison queue)
  • 通过 visibilityTimeout 配置处理时间
  • batchSize 控制并行处理数量

带有长运行模式的 HTTP 触发器

处理超过 230 秒 HTTP 限制的任务

适用场景:HTTP 请求触发耗时较长的任务

模板

// 异步 HTTP 模式 - 立即返回,随后轮询状态
[Function("StartLongRunning")]
public static async Task<HttpResponseData> StartLongRunning(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext context)
{
var input = await req.ReadFromJsonAsync<WorkRequest>();

// 启动编排 (立即返回)
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
"LongRunningOrchestrator", input);

// 返回用于轮询状态的 URL
return client.CreateCheckStatusResponse(req, instanceId);
}

// 响应内容包含:
// {
// "id": "abc123",
// "statusQueryGetUri": "https://.../instances/abc123",
// "sendEventPostUri": "https://.../instances/abc123/raiseEvent/{eventName}",
// "terminatePostUri": "https://.../instances/abc123/terminate"
// }

// 替代方案:不使用 Durable Functions 的基于队列的模式
[Function("StartWork")]
[QueueOutput("work-queue")]
public static async Task<WorkItem> StartWork(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
FunctionContext context)
{
var input = await req.ReadFromJsonAsync<WorkRequest>();

csharp
var workId = Guid.NewGuid().ToString();

// 将工作入队,立即返回
var workItem = new WorkItem
{
Id = workId,
Request = input
};

// 返回 work ID 以便检查状态
var response = req.CreateResponse(HttpStatusCode.Accepted);
await response.WriteAsJsonAsync(new
{
workId = workId,
statusUrl = $"/api/status/{workId}"
});

return workItem;
}

[Function("ProcessWork")]
public static async Task ProcessWork(
[QueueTrigger("work-queue")] WorkItem work,
FunctionContext context)
{
// 此处执行耗时处理
// 在存储中更新状态以供轮询
}

注意事项

  • 无论使用哪种方案,HTTP 超时时间均为 230 秒
  • 异步模式建议使用 Durable Functions
  • 立即返回并提供状态端点
  • 客户端通过轮询确认完成情况

潜在陷阱 (Sharp Edges)

无论方案如何,HTTP 超时均为 230 秒

严重程度:高

场景:处理时间较长的 HTTP 触发函数

症状:

  • 约 4 分钟后出现 504 Gateway Timeout。

  • 请求在函数完成前终止。

  • 即使函数在后台继续运行,客户端仍收到超时响应。

  • host.json 中的超时设置对 HTTP 触发器无效。

原因:
Azure 负载均衡器对 HTTP 请求有硬编码的 230 秒空闲超时限制。无论你的函数应用超时设置如何,此限制均适用。

即使你在 host.json 中将 functionTimeout 设置为 30 分钟,从客户端的角度来看,HTTP 触发器在 230 秒后仍会超时。

函数在超时后可能会继续运行,但客户端无法收到响应。

推荐修复方案:

使用 Durable Functions 的异步模式

csharp
[Function("StartLongProcess")]
public static async Task<HttpResponseData> Start(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
    [DurableClient] DurableTaskClient client)
{
    var input = await req.ReadFromJsonAsync<WorkRequest>();

// 启动编排,立即返回
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
"LongRunningOrchestrator", input);

// 返回用于轮询的状态 URL
return client.CreateCheckStatusResponse(req, instanceId);
}

// 客户端轮询 statusQueryGetUri 直至完成

使用基于队列的异步模式

csharp
[Function("StartWork")]
public static async Task<HttpResponseData> StartWork(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
    [QueueOutput("work-queue")] out WorkItem workItem)
{
    var workId = Guid.NewGuid().ToString();

workItem = new WorkItem { Id = workId, /* ... */ };

var response = req.CreateResponse(HttpStatusCode.Accepted);
await response.WriteAsJsonAsync(new {
id = workId,
statusUrl = $"/api/status/{workId}"
});
return response;
}

使用 Webhook 回调模式

csharp
// 客户端提供回调 URL
// 函数将工作入队,返回 202 Accepted
// 完成后,将结果 POST 到回调 URL

HttpClient 实例化导致的套接字耗尽 (Socket Exhaustion)

严重程度:高

场景:在函数代码内部创建 HttpClient 实例

症状:

  • SocketException: "Unable to connect to remote server"

  • "An attempt was made to access a socket in a way forbidden"

  • 高负载下出现随机连接失败。

  • 本地运行正常,但在生产环境中失败。

原因:
为每个请求创建新的 HttpClient 会创建新的套接字连接。套接字在关闭后会处于 TIME_WAIT 状态 240 秒。

在 Serverless 环境中,随着...
在高吞吐量场景下,你会迅速耗尽可用套接字(sockets)。这会影响所有网络客户端,而不仅仅是 HttpClient

Azure Functions 在多个客户之间共享网络资源,这使得该问题更加关键。

建议修复方案:

使用 IHttpClientFactory(推荐)

csharp
// Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        services.AddHttpClient<IMyApiClient, MyApiClient>(client =>
        {
            client.BaseAddress = new Uri("https://api.example.com");
            client.Timeout = TimeSpan.FromSeconds(30);
        });
    })
    .Build();

// MyApiClient.cs
public class MyApiClient : IMyApiClient
{
private readonly HttpClient _client;

public MyApiClient(HttpClient client)
{
_client = client; // 通过注入,由工厂管理
}

public async Task<string> GetDataAsync()
{
return await _client.GetStringAsync("/data");
}
}

使用静态客户端(替代方案)

csharp
public static class MyFunction
{
    // 静态 HttpClient,在多次调用之间复用
    private static readonly HttpClient _httpClient = new HttpClient
    {
        Timeout = TimeSpan.FromSeconds(30)
    };

[Function("MyFunction")]
public static async Task Run(...)
{
var result = await _httpClient.GetAsync("...");
}
}

Azure SDK 客户端同样适用此模式

csharp
// 同样适用于:
// - BlobServiceClient
// - CosmosClient
// - ServiceBusClient
// 请使用 DI 或静态实例

阻塞异步调用导致线程饥饿

严重程度:高

场景:在异步代码中使用 .Result.Wait()Thread.Sleep

症状:

  • 高负载下出现死锁。

  • 请求无限期挂起。

  • 出现 "A task was canceled" 异常。

  • 低并发时正常,高并发时失败。

原因:
Azure Functions 的线程池有限。阻塞调用(.Result.Wait())在等待时会占用线程,导致其他工作无法执行。

Thread.Sleep 会阻塞本可以处理其他请求的线程。

在多个并发执行的情况下,线程会迅速耗尽,从而导致死锁和超时。

建议修复方案:

始终使用 async/await

csharp
// 错误 - 阻塞线程
var result = httpClient.GetAsync(url).Result;
someTask.Wait();
Thread.Sleep(5000);

// 正确 - 释放线程
var result = await httpClient.GetAsync(url);
await someTask;
await Task.Delay(5000);

修复同步方法调用

csharp
// 错误 - 在异步之上使用同步
public void ProcessData()
{
    var data = GetDataAsync().Result;  // 阻塞!
}

// 正确 - 全链路异步
public async Task ProcessDataAsync()
{
var data = await GetDataAsync();
}

在控制台/启动项中配置异步

csharp
// 如果必须在同步上下文中调用异步方法
public static void Main(string[] args)
{
    // 仅在入口点使用 GetAwaiter().GetResult()
    MainAsync(args).GetAwaiter().GetResult();
}

private static async Task MainAsync(string[] args)
{
// 此处编写异步代码
}

消费计划(Consumption Plan)10 分钟超时限制

严重程度:中

场景:在消费计划上运行长时间进程

症状:

  • 函数在 10 分钟后终止。

  • 日志中显示 "Function timed out"。

  • 处理未完成且未捕获到错误。

  • 开发环境(超时时间较长)正常,但生产环境失败。

原因:
消费计划的执行时间硬限制为 10 分钟。如果未配置,默认值为 5 分钟。

在消费计划中,该限制无法增加到 10 分钟以上。长时间运行的...
该工作需要 Premium 计划或不同的架构。

建议修复方案:

配置最大超时时间 (Consumption 计划)

json
// host.json
{
  "version": "2.0",
  "functionTimeout": "00:10:00"  // Consumption 计划的最大值
}

升级到 Premium 计划以获得更长的超时时间

json
// Premium 计划 - 默认 30 分钟,可设置为无限制
{
  "version": "2.0",
  "functionTimeout": "00:30:00"  // 或删除此项以实现无限制
}

对长工作流使用 Durable Functions

csharp
[Function("LongWorkflowOrchestrator")]
public static async Task<string> RunOrchestrator(
    [OrchestrationTrigger] TaskOrchestrationContext context)
{
    // 每个活动函数都有自己的超时时间
    // 工作流可以运行数日
    await context.CallActivityAsync("Step1", input);
    await context.CallActivityAsync("Step2", input);
    await context.CallActivityAsync("Step3", input);
    return "Complete";
}

将工作分解为更小的块

csharp
// 基于队列的分块处理
[Function("ProcessChunk")]
[QueueOutput("work-queue")]
public static IEnumerable<WorkChunk> ProcessChunk(
    [QueueTrigger("work-queue")] WorkChunk chunk)
{
    var results = Process(chunk);

// 如果还有工作,则将下一个块放入队列
if (chunk.HasMore)
{
yield return chunk.Next();
}
}

.NET In-Process 模型将于 2026 年 11 月弃用

严重程度:高

场景:创建新的 .NET 函数或维护现有函数

症状:

  • 在新项目中使用 In-process 模型。

  • 与宿主运行时产生依赖冲突。

  • 无法使用最新的 .NET 版本。

  • 未来的迁移负担。

失效原因:
In-process 模型将代码运行在与 Azure Functions 宿主相同的进程中。这会导致:

  • 程序集版本冲突

  • 仅限于 LTS .NET 版本

  • 无法使用最新的 .NET 特性

  • 与宿主运行时耦合过紧

支持将于 2026 年 11 月 10 日结束。在此日期之后,In-process 应用可能会停止工作或无法获得安全更新。

建议修复方案:

为新项目使用 Isolated Worker (隔离工作进程)

bash
# 创建新的隔离工作进程项目
func init MyFunctionApp --worker-runtime dotnet-isolated

或使用 .NET 8

dotnet new func --name MyFunctionApp --framework net8.0

将现有的 In-process 迁移至 Isolated

csharp
// 旧版 - In-process (使用 FunctionName 特性)
public class InProcessFunction
{
    [FunctionName("MyFunction")]
    public async Task<IActionResult> Run(
        [HttpTrigger] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("Processing");
        return new OkResult();
    }
}

// 新版 - Isolated worker (使用 Function 特性)
public class IsolatedFunction
{
private readonly ILogger<IsolatedFunction> _logger;

public IsolatedFunction(ILogger<IsolatedFunction> logger)
{
_logger = logger;
}

[Function("MyFunction")]
public async Task<HttpResponseData> Run(
[HttpTrigger(AuthorizationLevel.Function, "get")]
HttpRequestData req)
{
_logger.LogInformation("Processing");
return req.CreateResponse(HttpStatusCode.OK);
}
}

关键迁移变更

  • FunctionName $\rightarrow$ Function 特性
  • HttpRequest $\rightarrow$ HttpRequestData
  • IActionResult $\rightarrow$ HttpResponseData
  • ILogger 注入 $\rightarrow$ 构造函数注入
  • 添加包含 HostBuilderProgram.cs

ILogger 未输出到控制台或 AppInsights

严重程度:中

场景:在 Isolated Worker 中使用依赖注入的 ILogger

症状:

  • 日志未在本地控制台显示。

  • 日志未在 Application Insights 中显示。

  • 使用 context.GetLogger() 时日志正常工作,但

未注入 ILogger。
必须在所有方法调用中传递 logger。

失效原因:
在隔离工作进程(isolated worker)模型中,通过依赖注入的 ILogger 可能无法正确连接到 Azure Functions 的日志管道。

本地开发受影响尤为严重——日志可能无法输出。
Application Insights 需要显式配置。

来自 FunctionContext 的 ILogger 与注入的 ILogger<T> 工作机制不同。

推荐修复方案:

正确配置 Application Insights

csharp
// Program.cs
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults()
    .ConfigureServices(services =>
    {
        // 添加 App Insights 遥测
        services.AddApplicationInsightsTelemetryWorkerService();
        services.ConfigureFunctionsApplicationInsights();
    })
    .Build();

配置日志级别

json
// host.json
{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    },
    "logLevel": {
      "default": "Information",
      "Host.Results": "Error",
      "Function": "Information",
      "Host.Aggregator": "Trace"
    }
  }
}

使用 context.GetLogger 以确保可靠性

csharp
[Function("MyFunction")]
public async Task Run(
    [HttpTrigger] HttpRequestData req,
    FunctionContext context)
{
    // 此 logger 始终有效
    var logger = context.GetLogger<MyFunction>();
    logger.LogInformation("Processing request");
}

本地开发 - 检查 local.settings.json

json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=..."
  }
}

缺少扩展包导致静默失败

严重程度:中

场景: 在未安装扩展的情况下使用触发器/绑定。

症状:

  • 函数在事件触发时没有响应。

  • 出现 "No job functions found" 警告。

  • 尽管配置正确,但绑定失效。

  • 添加扩展包后恢复正常。

失效原因:
Azure Functions v2+ 使用扩展包(extension bundles)来处理触发器和绑定。
如果扩展配置不正确或未安装相关包,函数宿主将无法识别绑定。

在隔离工作进程中,需要显式的 NuGet 包。
在进程内(in-process)模型中,需要 Microsoft.Azure.WebJobs.Extensions.*

推荐修复方案:

检查扩展包(最常见情况)

json
// host.json - 扩展包可处理大多数情况
{
  "version": "2.0",
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[4.*, 5.0.0)"
  }
}

为隔离工作进程安装显式包

xml
<!-- .csproj - 隔离工作进程包 -->
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.20.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="1.16.0" />

<!-- 存储触发器/绑定 -->
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Storage" Version="6.2.0" />

<!-- Service Bus -->
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.14.0" />

<!-- Cosmos DB -->
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.CosmosDB" Version="4.6.0" />

<!-- Durable Functions -->
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.1.0" />

验证函数注册

bash
# 检查 re
已注册函数 func host start --verbose

查找:

"Found the following functions:"

如果为空,请检查扩展和属性

code
### Premium 计划在新实例上仍存在冷启动

严重程度:中 (MEDIUM)

场景:使用 Premium 计划并期望零冷启动

症状:
尽管使用了 Premium 计划,但仍经历冷启动。
对新实例的首次请求响应缓慢。
在扩容事件期间出现延迟峰值。
预热实例未被使用。

原因分析:
Premium 计划提供预热实例,但:

  • 默认仅提供一个预热实例

  • 快速扩容仍会创建冷实例

  • 预热实例仍需运行您的代码初始化

  • 预热触发器虽运行,但您的代码可能依然缓慢

“预热”意味着运行时(Runtime)已就绪,而非您的应用程序已就绪。

建议修复方案:

添加预热触发器以初始化代码

csharp [Function("Warmup")] public void Warmup( [WarmupTrigger] object warmupContext, FunctionContext context) { var logger = context.GetLogger("Warmup"); logger.LogInformation("Warmup trigger fired");

// 初始化昂贵的资源
_cosmosClient.GetContainer("db", "container");
_httpClient.GetAsync("https://api.example.com/health").Wait();
}

code
## 配置预热实例数量
bash

增加预热实例数量(成本更高)


az functionapp config set \
--name <app-name> \
--resource-group <rg> \
--prewarmed-instance-count 3
code
## 优化应用程序初始化
csharp
// 对重资源进行延迟初始化
private static readonly Lazy<ExpensiveClient> _client =
new Lazy<ExpensiveClient>(() => new ExpensiveClient());

// 连接池配置
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(connectionString, sql =>
sql.MinPoolSize(5)));

code
## 使用始终就绪实例(成本最高)
bash

实例始终运行,无冷启动


az functionapp config set \
--name <app-name> \
--resource-group <rg> \
--minimum-elastic-instance-count 2
```

验证检查项

硬编码连接字符串

严重程度:错误 (ERROR)

连接字符串绝不能硬编码

消息:硬编码连接字符串。请使用 Key Vault 或 App Settings。

代码中硬编码 API 密钥

严重程度:错误 (ERROR)

API 密钥应使用 Key Vault 或 App Settings

消息:硬编码 API 密钥。请使用 Key Vault 或环境变量。

生产环境中使用匿名授权级别

严重程度:警告 (WARNING)

匿名端点应通过其他手段进行保护

消息:匿名授权。请确保由 API Management 或其他认证方式保护。

阻塞的 .Result 调用

严重程度:错误 (ERROR)

使用 .Result 会阻塞线程并导致死锁

消息:阻塞的 .Result 调用。请改用 await。

阻塞的 .Wait() 调用

严重程度:错误 (ERROR)

使用 .Wait() 会阻塞线程

消息:阻塞的 .Wait() 调用。请改用 await。

使用 Thread.Sleep

严重程度:错误 (ERROR)

Thread.Sleep 会阻塞线程

消息:Thread.Sleep 阻塞线程。请改用 await Task.Delay()。

新建 HttpClient 实例

严重程度:警告 (WARNING)

每个请求创建 HttpClient 会导致套接字耗尽

消息:每个请求新建 HttpClient。请使用 IHttpClientFactory 或静态客户端。

HttpClient 位于 using 语句中

严重程度:警告 (WARNING)

释放 HttpClient 会导致套接字耗尽

消息:HttpClient 位于 using 语句中。请使用 IHttpClientFactory 以管理正确的生命周期。

进程内 (In-Process) FunctionName 属性

严重程度:信息 (INFO)

进程内模型将于 2026 年 11 月弃用

消息:进程内 FunctionName 属性。继续...
sider 正在迁移至隔离工作进程 (isolated worker)。

缺失 Function 特性

严重程度:警告 (WARNING)

隔离工作进程需要 [Function] 特性

消息:HttpTrigger 缺少 [Function] 特性(隔离工作进程必须包含此特性)。

协作

触发委派

  • 用户需要 AWS serverless $\rightarrow$ aws-serverless (Lambda, API Gateway, SAM)
  • 用户需要 GCP serverless $\rightarrow$ gcp-cloud-run (Cloud Run, Cloud Functions)
  • 用户需要基于容器的部署 $\rightarrow$ gcp-cloud-run (Azure Container Apps 或 Cloud Run)
  • 用户需要数据库设计 $\rightarrow$ postgres-wizard (Azure SQL, Cosmos DB 数据建模)
  • 用户需要身份验证 $\rightarrow$ auth-specialist (Azure AD, Easy Auth, 托管身份)
  • 用户需要复杂编排 $\rightarrow$ workflow-automation (Logic Apps, Power Automate)

使用场景

  • 用户提及或暗示:azure function
  • 用户提及或暗示:azure functions
  • 用户提及或暗示:durable functions
  • 用户提及或暗示:azure serverless
  • 用户提及或暗示:function app

局限性

  • 仅在任务明确符合上述范围时使用此技能。
  • 不要将输出视为特定环境验证、测试或专家评审的替代方案。
  • 如果缺少必要的输入、权限、安全边界或验收标准,请停止并请求澄清。