Azure Functions

你的公司已经在使用 Microsoft 365 和 Azure 生态系统。团队提出要把一部分后台任务迁移到 Serverless,评估后决定使用 Azure Functions——毕竟和 Azure AD、Azure SQL、Azure Storage 的集成是原生级别的。

「选什么云,就用它的 Serverless。」 Azure Functions 的价值,不只是「能运行代码」,更在于和 Azure 生态的深度整合。

核心概念

Azure Functions 基于相同的 Serverless 原则,但有自己的术语和架构:

Azure Functions
├── Functions(函数):最小的执行单元
├── Triggers(触发器):启动函数的事件源
├── Bindings(绑定):连接数据的声明式方式
├── Durable Functions(持久函数):有状态的函数编排
└── Proxies(代理):路由和 API 管理

与 Lambda 的核心区别

维度AWS LambdaAzure Functions
触发器类型事件源集成内置触发器 + 绑定
状态管理外部服务Durable Functions
执行时间最长 15 分钟最长无限制(消耗计划)
冷启动100ms-2s100ms-10s
语言支持
K8s 支持EKS + FargateAzure Arc / KEDA

触发器详解

HTTP 触发器

HttpTriggerFunction.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Company.Function
{
    public class HttpTriggerFunction
    {
        private readonly ILogger<HttpTriggerFunction> _logger;

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

        [Function("HttpTriggerFunction")]
        public IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req,
            FunctionContext context)
        {
            _logger.LogInformation("C# HTTP trigger function processed a request.");

            string name = req.Query["name"];

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            name ??= data?.name;

            return name != null
                ? new OkObjectResult($"Hello, {name}")
                : new BadRequestObjectResult("Please pass a name on the query string or in the request body.");
        }
    }
}

Timer 触发器(定时任务)

TimerTriggerFunction.cs
using Microsoft.Azure.Functions.Worker;

namespace Company.Function
{
    public class TimerTriggerFunction
    {
        [Function("TimerTriggerFunction")]
        public void Run([TimerTrigger("0 */5 * * * *")] TimerInfo myTimer, FunctionContext context)
        {
            var logger = context.GetLogger("TimerTriggerFunction");
            logger.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
        }
    }
}

Blob 触发器

BlobTriggerFunction.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Company.Function
{
    public class BlobTriggerFunction
    {
        private readonly ILogger<BlobTriggerFunction> _logger;

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

        [Function("BlobTriggerFunction")]
        [BlobInput("samples-workitems/{name}", Connection = "AzureWebJobsStorage")]
        public void Run(
            [BlobTrigger("samples-workitems/{name}")] Stream myBlob,
            string name,
            FunctionContext context)
        {
            _logger.LogInformation($"C# Blob trigger function processed blob\n Name: {name} \n Size: {myBlob.Length} Bytes");
        }
    }
}

Queue 触发器

QueueTriggerFunction.cs
using Microsoft.Azure.Functions.Worker;
using Azure.Storage.Queues;

namespace Company.Function
{
    public class QueueTriggerFunction
    {
        [Function("QueueTriggerFunction")]
        public void Run(
            [QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string myQueueItem,
            FunctionContext context)
        {
            var logger = context.GetLogger<QueueTriggerFunction>();
            logger.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
        }
    }
}

绑定(Bindings)

绑定是 Azure Functions 独特的概念,它简化了输入输出的声明:

BindingsFunction.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
using Microsoft.Azure.Functions.Worker.Http;

namespace Company.Function
{
    public class BindingsFunction
    {
        private readonly ILogger<BindingsFunction> _logger;

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

        [Function("BindingsFunction")]
        public async Task<HttpResponseData> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
            [Table("MyTable", "MyPartition", Connection = "AzureWebJobsStorage")] TableClient tableClient,
            [Blob("samples-workitems/{Query.name}", FileAccess.Read, Connection = "AzureWebJobsStorage")] string blobContent,
            FunctionContext context)
        {
            _logger.LogInformation($"Table query result: {tableClient.ToString()}");
            _logger.LogInformation($"Blob content: {blobContent}");

            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Headers.Add("Content-Type", "text/plain; charset=utf-8");
            await response.WriteStringAsync("Bindings processed successfully!");

            return response;
        }
    }
}

输出绑定

OutputBindingsFunction.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;

namespace Company.Function
{
    public class OutputBindingsFunction
    {
        [Function("OutputBindingsFunction")]
        [QueueOutput("outqueue", Connection = "AzureWebJobsStorage")]
        public string[] Run(
            [QueueTrigger("inqueue", Connection = "AzureWebJobsStorage")] string message,
            FunctionContext context)
        {
            var logger = context.GetLogger<OutputBindingsFunction>();
            logger.LogInformation($"Input message: {message}");

            // 返回多个消息到输出队列
            return new[]
            {
                $"Processed: {message}",
                $"Acknowledged: {message}"
            };
        }
    }
}

Durable Functions(持久函数)

Durable Functions 是 Azure Functions 的有状态扩展,用于编排长时间运行的工作流。

编排器模式

OrchestratorFunction.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;
using Microsoft.Extensions.Logging;

namespace Company.Function
{
    public static class OrchestratorFunction
    {
        [Function(nameof(OrchestratorFunction))]
        public static async Task<List<string>> OrchestratorFunction(
            [OrchestrationTrigger] TaskOrchestrationContext context)
        {
            var outputs = new List<string>();

            _logger.LogInformation("Starting orchestration.");

            // 顺序执行活动
            outputs.Add(await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Tokyo"));
            outputs.Add(await context.CallActivityAsync<string>(nameof(SayHelloActivity), "Seattle"));
            outputs.Add(await context.CallActivityAsync<string>(nameof(SayHelloActivity), "London"));

            return outputs;
        }

        [Function(nameof(SayHelloActivity))]
        public static string SayHelloActivity([ActivityTrigger] string name, ILogger log)
        {
            log.LogInformation("Saying hello to {name}.", name);
            return $"Hello {name}!";
        }
    }
}

人类交互模式

ApprovalWorkflow.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;

namespace Company.Function
{
    public static class ApprovalWorkflow
    {
        [Function(nameof(ApprovalWorkflow))]
        public static async Task<bool> ApprovalWorkflow(
            [OrchestrationTrigger] TaskOrchestrationContext context)
        {
            var orderId = context.GetInput<string>();

            // 发送审批请求
            await context.CallActivityAsync(nameof(SendApprovalRequest), orderId);

            // 等待外部事件(超时 7 天)
            using var timeoutCts = new CancellationTokenSource(TimeSpan.FromDays(7));
            using var approvalCts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token);

            var eventTask = context.WaitForExternalEvent<bool>("Approval");
            var winner = await Task.WhenAny(eventTask, Task.Delay(Timeout.Infinite, approvalCts.Token));

            if (winner == eventTask && eventTask.Result)
            {
                await context.CallActivityAsync(nameof(ProcessOrder), orderId);
                return true;
            }
            else
            {
                await context.CallActivityAsync(nameof(CancelOrder), orderId);
                return false;
            }
        }
    }
}

Fan-Out Fan-In

FanOutFanIn.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.DurableTask;

namespace Company.Function
{
    public static class FanOutFanIn
    {
        [Function(nameof(FanOutFanInOrchestrator))]
        public static async Task<long> FanOutFanInOrchestrator(
            [OrchestrationTrigger] TaskOrchestrationContext context)
        {
            var inputs = context.GetInput<List<int>>(); // [1, 2, 3, 4, 5]

            // Fan-Out: 并行启动多个活动
            var tasks = inputs.Select(item =>
                context.CallActivityAsync<long>(nameof(ProcessItemActivity), item));

            // Fan-In: 等待所有活动完成
            var results = await Task.WhenAll(tasks);

            return results.Sum();
        }

        [Function(nameof(ProcessItemActivity))]
        public static long ProcessItemActivity([ActivityTrigger] int item, FunctionContext context)
        {
            var logger = context.GetLogger(nameof(ProcessItemActivity));
            logger.LogInformation($"Processing item {item}");

            // 模拟处理
            return item * item; // 返回平方
        }
    }
}

部署选项

消耗计划(Consumption)

按执行时间和内存计费,有冷启动问题。

Premium 计划

提供:

  • 预热实例(无冷启动)
  • VNet 集成
  • 无限执行时间
  • 更强大的实例

专用计划(App Service)

与 App Service 共享资源,适合有现有 App Service 计划的组织。

KEDA 集成

Azure Functions 可以通过 KEDA 在 Kubernetes 上运行:

keda-functions.yaml
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: azure-function-auth
spec:
  podIdentityProvider: azure
---
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: azure-functions-scaler
spec:
  scaleTargetRef:
    name: function-app
  triggers:
  - type: azure-queue
    metadata:
      queueName: "workitems"
      connectionFromEnv: "AzureWebJobsStorage"
    authenticationRef:
      name: azure-function-auth

监控与诊断

Application Insights 集成

host.json
{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "maxTelemetryItemsPerSecond": 20
      }
    }
  }
}

自定义遥测

custom-telemetry.cs
using Microsoft.ApplicationInsights;
using Microsoft.Azure.Functions.Worker;

public class TelemetryFunction
{
    private readonly TelemetryClient _telemetry;

    public TelemetryFunction(TelemetryClient telemetry)
    {
        _telemetry = telemetry;
    }

    [Function("TelemetryFunction")]
    public void Run([QueueTrigger("myqueue")] string message)
    {
        var customEvent = new EventTelemetry("ProcessStarted");
        customEvent.Properties["MessageLength"] = message.Length.ToString();
        customEvent.Context.Operation.Name = "ProcessQueueMessage";

        _telemetry.TrackEvent(customEvent);

        // 自定义指标
        _telemetry.GetMetric("ProcessingDuration").TrackValue(duration);
    }
}

最佳实践

1. 使用依赖注入

Startup.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.DependencyInjection;
using Company.Function.Services;

[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]

namespace MyNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsWorkerApplicationBuilder applicationBuilder)
        {
            // 注册服务
            applicationBuilder.Services.AddSingleton<IMyService, MyService>();
            applicationBuilder.Services.AddScoped<IRepository, Repository>();
        }
    }
}

2. 中间件

ExceptionMiddleware.cs
using Microsoft.Extensions.Logging;

public class ExceptionMiddleware
{
    private readonly ILogger<ExceptionMiddleware> _logger;
    private readonly RequestDelegate _next;

    public ExceptionMiddleware(ILogger<ExceptionMiddleware> logger, RequestDelegate next)
    {
        _logger = logger;
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unhandled exception");
            // 自定义错误处理
        }
    }
}

3. 安全配置

function-auth.cs
public class SecureFunction
{
    [Function("SecureFunction")]
    public IActionResult Run(
        [HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest req,
        FunctionContext context)
    {
        // 验证 ClaimsPrincipal
        var userId = context.Features.Get<JwtRouteData>()?.Values["userId"]?.ToString();

        if (string.IsNullOrEmpty(userId))
        {
            return new UnauthorizedResult();
        }

        return new OkObjectResult($"User {userId} authenticated successfully");
    }
}

与 AWS Lambda 对比

场景AWS LambdaAzure Functions
HTTP APIAPI GatewayHTTP 触发器(内置)
有状态编排Step FunctionsDurable Functions
定时任务EventBridgeTimer Trigger
VPC 访问VPC ConfigVNet 集成
K8s 运行EKS + FargateKEDA
IDE 支持VS Code / IDEAVisual Studio(最佳)
.NET 开发支持最佳体验(C# 首选)

延伸思考

Azure Functions 的最大优势是与 Microsoft 生态的深度整合。如果你的组织使用:

  • Microsoft 365:Azure AD 认证无缝集成
  • Azure SQL / Cosmos DB:原生绑定支持
  • Power Platform:Power Automate 集成
  • Logic Apps:可视化工作流编排

那么 Azure Functions 是自然的选择。

但如果你是 AWS 重度用户,或者追求多云灵活性,Lambda 可能更合适。选择 Serverless 平台时,云厂商锁定是一个需要权衡的因素。