Serverless 架構

是什麼?

Serverless 是一種雲端執行模型,開發者只寫函式(Function),雲端平台自動管理伺服器的配置、擴縮、維護。按實際執行次數和時間計費,閒置時不花錢。

ℹ️FaaS vs BaaS

Serverless 包含兩種形式:**FaaS(Function as a Service)**如 Azure Functions、AWS Lambda — 你寫函式,雲端跑函式。**BaaS(Backend as a Service)**如 Firebase、Supabase — 雲端提供現成的後端功能(Auth、Database、Storage)。

核心觀念

常見誤區

⚠️常見誤區

  • Serverless 適合所有工作:長時間運行、需要穩定低延遲、高持續流量的工作不適合 Serverless。它最適合事件驅動、間歇性的工作
  • 不考慮 Cold Start 影響:面向使用者的 API 如果有 2 秒的 Cold Start,使用者體驗很差。用 Provisioned Concurrency 或改用 Container
  • 忽略執行時間上限:影片轉碼、大量資料處理等長時間任務會在 Timeout 時被強制中斷。拆分成小任務或用 Durable Functions

流程/步驟

1

辨識適合的工作

事件驅動、間歇性、短執行時間的任務

2

選擇觸發器

HTTP、Queue、Timer、Blob、Event Grid 等

3

撰寫函式

專注業務邏輯,輸入輸出由 Binding 處理

4

設定 Binding

Input/Output Binding 自動連接外部服務

5

測試和部署

本機測試後部署到雲端,CI/CD 自動化

6

監控和調優

追蹤執行時間、Cold Start、錯誤率

流程解讀:先確認工作適合 Serverless(事件驅動 + 短執行時間),選擇適當的觸發器。函式只寫業務邏輯,Binding 自動處理和外部服務的連接。部署後持續監控效能指標。

程式碼範例

C# 版本

csharp
// Azure Functions — 多種觸發器範例
public class Functions
{
    private readonly IOrderService _orderService;
 
    public Functions(IOrderService orderService)
    {
        _orderService = orderService; // 支援 DI
    }
 
    // HTTP 觸發 + Queue Output Binding
    [Function("CreateOrder")]
    public async Task<HttpResponseData> CreateOrder(
        [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
        [QueueOutput("order-notifications")] OutputBinding<string> queueOutput)
    {
        var order = await req.ReadFromJsonAsync<CreateOrderDto>();
        var result = await _orderService.CreateAsync(order!);
 
        // Output Binding 自動將訊息送到 Queue
        queueOutput.SetValue(JsonSerializer.Serialize(new { result.Id, result.Email }));
 
        var response = req.CreateResponse(HttpStatusCode.Created);
        await response.WriteAsJsonAsync(result);
        return response;
    }
 
    // Queue 觸發 — 自動處理 Queue 中的訊息
    [Function("SendNotification")]
    public async Task SendNotification(
        [QueueTrigger("order-notifications")] string message)
    {
        var data = JsonSerializer.Deserialize<NotificationMessage>(message);
        await _emailService.SendAsync(data!.Email, $"Order {data.Id} confirmed!");
    }
 
    // Timer 觸發 — 排程任務
    [Function("CleanupExpiredTokens")]
    public async Task Cleanup(
        [TimerTrigger("0 0 */6 * * *")] TimerInfo timer) // 每 6 小時
    {
        var count = await _tokenService.CleanupExpiredAsync();
        _logger.LogInformation("Cleaned up {Count} expired tokens", count);
    }
}

TypeScript 版本

typescript
// AWS Lambda — 多種觸發器
import { APIGatewayProxyHandler, S3Handler, ScheduledHandler } from "aws-lambda";
 
// HTTP 觸發(API Gateway)
export const createOrder: APIGatewayProxyHandler = async (event) => {
  const body = JSON.parse(event.body || "{}");
  const order = await orderService.create(body);
 
  // 送訊息到 SQS
  await sqs.send(new SendMessageCommand({
    QueueUrl: process.env.NOTIFICATION_QUEUE_URL,
    MessageBody: JSON.stringify({ orderId: order.id, email: order.email }),
  }));
 
  return { statusCode: 201, body: JSON.stringify(order) };
};
 
// S3 觸發 — 有檔案上傳就處理
export const processImage: S3Handler = async (event) => {
  for (const record of event.Records) {
    const bucket = record.s3.bucket.name;
    const key = record.s3.object.key;
    await imageService.generateThumbnail(bucket, key);
  }
};
 
// EventBridge 觸發 — 排程
export const dailyReport: ScheduledHandler = async () => {
  await reportService.generateDaily();
};

Python 版本

python
# Azure Functions (Python v2 model)
import azure.functions as func
 
app = func.FunctionApp()
 
# HTTP 觸發
@app.function_name("CreateOrder")
@app.route(route="orders", methods=["POST"])
@app.queue_output(arg_name="msg", queue_name="notifications", connection="StorageConn")
async def create_order(req: func.HttpRequest, msg: func.Out[str]) -> func.HttpResponse:
    order = req.get_json()
    result = await order_service.create(order)
 
    # Output Binding — 自動送到 Queue
    msg.set(json.dumps({"order_id": result["id"], "email": result["email"]}))
 
    return func.HttpResponse(json.dumps(result), status_code=201)
 
# Blob 觸發 — 有圖片上傳就產生縮圖
@app.function_name("GenerateThumbnail")
@app.blob_trigger(arg_name="blob", path="uploads/{name}", connection="StorageConn")
async def generate_thumbnail(blob: func.InputStream):
    image_data = blob.read()
    thumbnail = await image_service.resize(image_data, width=200)
    await storage_service.upload(f"thumbnails/{blob.name}", thumbnail)

架構圖/概念圖

Event Sources
HTTP Request
triggers
Queue Message
triggers
Timer / Cron
triggers
Serverless Function
writes to
Output (DB / Queue / Email)

多種事件來源觸發 Serverless Function。函式處理業務邏輯後,透過 Output Binding 將結果寫入資料庫、Queue 或其他服務。整個流程是事件驅動的,沒有事件就不執行。

實戰補充

Q: Cold Start 怎麼優化?

A: (1) 減少依賴套件大小。(2) 用 Provisioned Concurrency 預熱(增加成本但消除冷啟動)。(3) 選擇輕量 Runtime(Node.js、Python 冷啟動比 .NET、Java 快)。(4) 對延遲敏感的 API 不要用 Serverless。

Q: Serverless 怎麼處理長時間任務?

A: 用 Durable Functions(Azure)或 Step Functions(AWS)。把長任務拆成多個短步驟,每個步驟是一個函式,框架負責協調執行順序和狀態保存。

Q: Serverless 的測試策略?

A: (1) 業務邏輯和觸發器解耦 — 核心邏輯寫成純函式,可以用 Unit Test 覆蓋。(2) 用 Azure Functions Core Tools 或 SAM CLI 在本機模擬觸發器。(3) Integration Test 部署到測試環境驗證。

理解測驗

🤔 以下哪種場景最不適合 Serverless?

🤔 Serverless 函式的 Stateless 特性意味著什麼?

🤔 Azure Functions 的 Output Binding 有什麼好處?

重點整理

💡一句話記住

Serverless = 只寫邏輯、不管伺服器、用多少付多少。 口訣:「事件觸發、自動擴縮、閒置免費」

| 概念 | 說明 | |------|------| | FaaS | 寫函式、雲端跑函式 | | Event-Driven | 由事件觸發(HTTP、Queue、Timer) | | Cold Start | 閒置後首次觸發的額外延遲 | | Auto-Scaling | 自動擴縮,從 0 到數千實例 | | Binding | 宣告式連接外部服務 |

你可能也想看

身份與存取管理雲端成本優化

按 ← → 鍵切換課程