Serverless Là Gì và Khi Nào Nên Dùng
This is the first part
Chạy một service nhỏ trên server truyền thống nghĩa là bạn nuôi một cái máy luôn bật: trả tiền cho nó 24/7 kể cả lúc không ai gọi, vá hệ điều hành, theo dõi ổ đĩa đầy, và tự lo chuyện co giãn khi lượng truy cập tăng đột biến. Phần lớn công sức đó không liên quan gì tới logic nghiệp vụ bạn muốn viết. Serverless là cách đẩy toàn bộ phần "nuôi máy" đó sang cho nhà cung cấp, để bạn chỉ còn code và sự kiện kích hoạt nó.
Series này dựng một sản phẩm serverless thật từ đầu tới cuối trên AWS, rồi vận hành nó tới mức production. Bài đầu không tạo tài nguyên nào. Ta cần thống nhất serverless thật sự là gì, nó đánh đổi cái gì, khi nào nên tránh, và phác ra sản phẩm cùng kiến trúc để các bài sau gõ lệnh mà biết mình đang ghép cái gì vào đâu.
Serverless không có nghĩa là "không có server"
Vẫn có server chạy code của bạn. Khác biệt nằm ở chỗ bạn không thấy chúng, không cấp phát chúng, và không trả tiền khi chúng rảnh. Tài liệu AWS định nghĩa Lambda gọn một câu: "AWS Lambda is a compute service that runs code without the need to manage servers. Your code runs, scaling up and down automatically, with pay-per-use pricing." Ba ý trong câu đó là toàn bộ tinh thần serverless.
Thứ nhất, không quản lý server. AWS lo phần hạ tầng: "Lambda runs your code on a high-availability compute infrastructure and manages all the computing resources, including server and operating system maintenance, capacity provisioning, automatic scaling, and logging." Bạn giao một hàm, AWS lo chỗ chạy nó.
Thứ hai, co giãn tự động. Một request thì một bản sao hàm chạy. Một nghìn request đến cùng lúc thì Lambda dựng nhiều bản sao chạy song song, rồi thu lại khi hết việc. Bạn không cấu hình Auto Scaling Group, không đặt ngưỡng CPU.
Thứ ba, trả tiền theo lần dùng. Không có lần gọi nào thì hóa đơn phần compute bằng không. Đây là điểm khiến serverless hợp với những workload tải thất thường: lúc cao điểm nó tự nở ra, lúc vắng nó không tốn gì.
Đổi lại sự tiện đó là vài ràng buộc. Mỗi lần chạy hàm Lambda giới hạn 15 phút (tài liệu mô tả "Lambda functions run for up to 15 minutes"; gần đây có thêm durable functions chạy tới một năm cho workflow nhiều bước, nhưng đó là một mô hình riêng). Hàm không giữ trạng thái giữa các lần gọi, nên mọi thứ cần nhớ phải đẩy ra ngoài, vào database hay storage. Và vì code chạy trong môi trường do AWS dựng theo sự kiện, có lúc một request phải chờ AWS khởi tạo môi trường mới trước khi code chạy. Độ trễ chờ đó gọi là cold start, và ta sẽ mổ kỹ nó ở một bài riêng.
Event-driven: cách nghĩ đi kèm
Serverless trên AWS gắn liền với kiến trúc event-driven, hướng sự kiện. Một hàm không tự chạy; nó chạy khi có thứ gì đó kích hoạt: một HTTP request tới API Gateway, một file mới trong S3, một bản ghi đổi trong DynamoDB, một sự kiện đẩy vào EventBridge. Tài liệu liệt kê đúng kiểu việc Lambda sinh ra để làm: xử lý file khi upload, phản ứng với thay đổi database, chạy tác vụ theo lịch, xử lý luồng dữ liệu realtime, làm backend cho web và mobile.
Cách nghĩ này khác với ứng dụng web truyền thống, nơi một tiến trình chạy liên tục và tự quyết làm gì tiếp theo. Ở đây, các thành phần nhỏ ngồi yên cho tới khi một sự kiện gọi đến, làm đúng một việc, rồi tắt. Ghép nhiều mảnh như vậy lại bằng sự kiện, ta có một hệ thống không có chỗ nào chạy 24/7 nhưng vẫn phản ứng tức thì khi cần.
Cách truyền thống Cách event-driven (serverless)
┌────────────────────┐ sự kiện ──▶ ┌─────────┐
│ tiến trình chạy │ HTTP ──▶ │ hàm A │──▶ (xong, tắt)
│ liên tục 24/7 │ file ──▶ ┌─────────┐
│ tự vòng lặp xử lý │ DB thay ──▶ │ hàm B │──▶ (xong, tắt)
│ (trả tiền cả lúc │ đổi ┌─────────┐
│ rảnh) │ timer ──▶ │ hàm C │──▶ (xong, tắt)
└────────────────────┘ └─────────┘
không sự kiện = không chạy = không tốn
Khi nào KHÔNG nên dùng serverless
Serverless không phải lựa chọn mặc định cho mọi thứ. Có những workload mà nó đắt hơn hoặc vướng hơn so với một container chạy thường trực.
Tải cao và đều suốt ngày là trường hợp đầu tiên. Nếu hệ thống của bạn nhận lượng request lớn và ổn định 24/7, mô hình trả-theo-lần-gọi cộng dồn lại có thể đắt hơn một cái máy thuê theo giờ chạy hết công suất. Serverless thắng khi tải thất thường; tải phẳng và cao thì lợi thế đó mất.
Yêu cầu độ trễ cực thấp và ổn định là trường hợp thứ hai. Cold start thêm vài chục tới vài trăm mili-giây vào những request rơi vào lúc phải khởi tạo môi trường mới. Với phần lớn API điều đó chấp nhận được, nhưng một hệ thống cần độ trễ dưới mili-giây ổn định ở mọi request thì khó sống chung với đặc tính này (ta có cách giảm cold start, nhưng giảm không phải là xóa).
Tác vụ chạy rất lâu hoặc cần giữ trạng thái nặng trong bộ nhớ giữa các bước cũng không hợp khuôn 15 phút và mô hình không trạng thái của một lần gọi. Cuối cùng, ràng buộc về vendor lock-in: kiến trúc serverless bám khá chặt vào dịch vụ của một nhà cung cấp, nên dời sang chỗ khác tốn công hơn so với một ứng dụng đóng gói trong container chuẩn.
Sản phẩm trong series, một dịch vụ rút gọn URL, lại rơi đúng vào vùng serverless tỏa sáng: tải thất thường (lúc một link viral thì tăng vọt, lúc thường thì im ắng), mỗi request ngắn, và phần lớn thời gian hệ thống không làm gì.
Sản phẩm ta sẽ dựng
Một dịch vụ rút gọn URL nghe đơn giản, nhưng làm cho ra hồn thì chạm gần hết các mảnh quan trọng của serverless. Đây là những gì nó sẽ làm:
- Người dùng đăng nhập, gửi một URL dài, nhận về một mã ngắn (ví dụ
https://sho.rt/aK9x). - Ai đó mở link ngắn, hệ thống chuyển hướng họ tới URL gốc, đồng thời ghi nhận một lượt click.
- Mỗi lượt click bắn ra một sự kiện; một thành phần khác gom các sự kiện đó lại thành số liệu (tổng click, click theo thời gian).
- Chủ link mở một dashboard và thấy số click nhảy realtime mỗi khi có người bấm vào, không cần tải lại trang.
- Mỗi người chỉ thấy và quản lý link của mình (multi-tenant).
Trong cái khung đó, ta sẽ chạm: lưu trữ với một thiết kế bảng DynamoDB gọn, xác thực người dùng, ghi nhận sự kiện bất đồng bộ sao cho không đếm trùng và không mất, đẩy dữ liệu realtime xuống trình duyệt, điều phối một quy trình nhiều bước, rồi quan sát và tối ưu toàn bộ khi có tải thật.
Kiến trúc tổng thể
Đây là bức tranh ta nhắm tới ở cuối series. Đừng cố hiểu hết ngay; mỗi bài sẽ dựng và mổ một mảnh, và ta quay lại sơ đồ này nhiều lần.
┌──────────── Cognito (đăng nhập, JWT) ───────────┐
│ │ xác thực
Trình duyệt ──HTTPS──▶ API Gateway (HTTP API) │
│ │ │ │
│ POST /links ──────┼──▶ Lambda create ──▶ DynamoDB (single-table)
│ GET /{code} ──────┼──▶ Lambda resolve ─┬─▶ 301 chuyển hướng
│ │ │ └─▶ EventBridge (sự kiện click)
│ │ │ │
│ │ │ ┌──── rule ──────┤
│ │ │ ▼ ▼
│ │ │ Lambda aggregator Step Functions
│ │ │ (idempotent + DLQ) (kiểm duyệt link)
│ │ │ │
│ │ │ ▼
│ │ │ DynamoDB (bộ đếm)
│ │ │ │
└───WebSocket────────┴────────┴──▶ đẩy số click realtime về dashboard
Quan sát: Lambda Powertools (log/trace/metric) + X-Ray + CloudWatch
Vận hành: SAM (hạ tầng) · CI/CD canary + rollback · IAM least-privilege · WAF
Mỗi hộp là một dịch vụ AWS, mỗi mũi tên là một sự kiện hoặc một lời gọi. Không có hộp nào là server bạn phải nuôi. Lúc không ai dùng, gần như mọi thứ về trạng thái nghỉ và hóa đơn compute về không.
Công cụ và quy ước
Toàn bộ hạ tầng trong series dựng bằng AWS SAM (Serverless Application Model), một cách khai báo tài nguyên serverless gọn hơn CloudFormation thuần. Code hàm viết bằng Node.js + TypeScript. Mọi lệnh chạy thật trên một tài khoản AWS ở region ap-southeast-1 (Singapore), và mọi output trong bài là output thật. Account ID, tên tài nguyên cá nhân được che bằng giá trị giả (ví dụ 111122223333). Code hands-on nằm ở github.com/nghiadaulau/serverless-url-shortener-aws.
Một lưu ý về chi phí trước khi bắt đầu: vì sản phẩm hoàn toàn serverless, phần lớn thời gian nó không phát sinh tiền. Các bài có tạo tài nguyên sẽ ghi rõ chi phí ước tính, và bài cuối dọn sạch mọi thứ bằng một lệnh. Ước tính cho cả series nằm trong khoảng vài đô, phần lớn rơi vào bài load test.
Lộ trình
Series đi theo sáu phần. Phần đầu dựng nền: môi trường SAM, và hiểu Lambda chạy ra sao bên trong. Phần hai dựng lõi sản phẩm: API, và một thiết kế DynamoDB single-table cho URL shortener. Phần ba thêm đăng nhập và tách dữ liệu theo người dùng. Phần bốn là phần event-driven: phát và tiêu thụ sự kiện click, đẩy realtime, điều phối quy trình. Phần năm biến nó thành production: quan sát bằng X-Ray, đo và cắt cold start, siết bảo mật. Phần sáu là vận hành: CI/CD, bóc hóa đơn, load test, rồi dọn dẹp.
Bài sau dựng môi trường làm việc: cài SAM CLI, tạo khung project, deploy một hàm Lambda đầu tiên ra AWS rồi gọi nó, và quan trọng không kém, xóa sạch nó bằng một lệnh.
This is the first part