Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 25 — Streaming — Kinesis, Pub/Sub, Kafka

Đã đọc 0/14 mục · còn khoảng 12 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Ở bài trước, chúng ta đã tìm hiểu về Message Queue (SQS, Pub/Sub, Service Bus) — cách gửi một tin nhắn, để một consumer xử lý, rồi tin nhắn biến mất. Đó là mô hình rất tốt cho "làm một việc một lần". Nhưng thế giới thực tế của dữ liệu hiện đại lại đặt ra những câu hỏi mà queue không trả lời được: Làm sao để 5 hệ thống khác nhau cùng đọc một luồng sự kiện? Làm sao để tua lại (replay) toàn bộ đơn hàng của ngày hôm qua khi phát hiện bug? Làm sao để xử lý 500.000 sự kiện click mỗi giây từ một app thương mại điện tử vào giờ cao điểm mà không mất mát dữ liệu?

Đây chính là địa hạt của event streaming — luồng sự kiện. Nếu bạn từng nghe đến các cụm từ như "real-time analytics", "clickstream", "change data capture", "event-driven pipeline" thì streaming chính là xương sống đằng sau chúng.

Là một người làm DevOps hay kỹ sư hạ tầng, bạn sẽ liên tục gặp ba cái tên: Amazon Kinesis, Google Cloud Pub/Sub và Apache Kafka. Hiểu rõ bản chất của streaming — nó khác queue ở chỗ nào, khi nào nên dùng, cấu hình ra sao — sẽ giúp bạn thiết kế được những hệ thống dữ liệu vừa chịu tải cao, vừa không mất dữ liệu, vừa cho phép phân tích trong thời gian thực. Bài này sẽ đưa bạn đi từ khái niệm cốt lõi đến thực hành cụ thể.

Khái niệm cốt lõi

Stream là gì và khác Queue thế nào

Hãy tưởng tượng một cuốn sổ nhật ký (log) mà mọi sự kiện được ghi vào theo đúng thứ tự thời gian, không bao giờ bị xóa ngay lập tức. Đó chính là stream. Mỗi bản ghi (record) có một vị trí cố định gọi là offset (Kafka) hay sequence number (Kinesis). Consumer tự ghi nhớ mình đã đọc đến đâu, và có thể quay lại đọc từ đầu bất cứ lúc nào.

Sự khác biệt căn bản so với queue nằm ở đây:

Đặc điểmQueue (SQS)Stream (Kafka / Kinesis)
Replay (đọc lại)Không — tin nhắn xử lý xong là mấtCó — cấu hình được thời gian lưu (retention)
Nhiều consumer độc lậpKhó — một tin thường chỉ một consumer lấyDễ — nhiều nhóm consumer cùng đọc, mỗi nhóm giữ offset riêng
Thứ tựChỉ đảm bảo trong FIFO queueĐảm bảo trong từng partition/shard
Mô hình xóaConsumer xóa tin sau khi xử lýDữ liệu tự hết hạn theo retention, consumer không xóa
Thông lượng điển hìnhVừa phảiRất cao (hàng trăm nghìn – triệu bản ghi/giây)
Điểm mấu chốt cần khắc cốt ghi tâm: queue là "hàng đợi công việc", còn stream là "dòng chảy sự kiện có thể phát lại". Khi bạn cần một sự kiện được nhiều hệ thống tiêu thụ theo cách khác nhau (một để lưu kho, một để phân tích, một để cảnh báo gian lận), stream là lựa chọn đúng.

Partition / Shard — chìa khóa của khả năng mở rộng

Một stream không phải là một dòng duy nhất, mà được chia thành nhiều partition (Kafka) hoặc shard (Kinesis). Mỗi partition là một log độc lập, và đây chính là đơn vị song song hóa: càng nhiều partition, càng nhiều consumer có thể đọc song song, thông lượng càng cao.

Cách một bản ghi rơi vào partition nào phụ thuộc vào partition key (khóa phân vùng). Ví dụ, nếu bạn dùng user_id làm khóa, thì mọi sự kiện của cùng một người dùng luôn rơi vào cùng một partition — nhờ đó thứ tự sự kiện của người đó được đảm bảo. Đây là mẹo cực kỳ quan trọng: thứ tự chỉ được đảm bảo trong phạm vi một partition, không phải toàn stream.

Với Kinesis, mỗi shard có giới hạn cụ thể: ghi được 1 MB/giây hoặc 1.000 bản ghi/giây, đọc được 2 MB/giây. Muốn tăng thông lượng, bạn tăng số shard. Kafka thì linh hoạt hơn, số partition do bạn quyết định khi tạo topic.

Consumer Group và Offset

Trong Kafka, các consumer được tổ chức thành consumer group. Kafka tự động chia các partition cho các consumer trong cùng nhóm — mỗi partition chỉ được đúng một consumer trong nhóm đọc, giúp cân bằng tải. Nếu bạn có 6 partition và 3 consumer, mỗi consumer nhận 2 partition. Thêm consumer thứ 7 vào nhóm mà chỉ có 6 partition thì consumer đó sẽ... ngồi chơi.

Nhiều nhóm khác nhau đọc cùng một topic hoàn toàn độc lập với nhau. Nhóm "analytics" và nhóm "billing" cùng đọc topic orders nhưng giữ offset riêng, không ảnh hưởng lẫn nhau. Đây chính là sức mạnh "fan-out" mà queue không có.

Ba lựa chọn: Kinesis, Pub/Sub, Kafka

  • Amazon Kinesis Data Streams: dịch vụ managed của AWS, không cần quản lý server. Bạn trả tiền theo shard (chế độ Provisioned) hoặc theo lượng dữ liệu (chế độ On-Demand). Tích hợp sâu với Lambda, Firehose, S3. Phù hợp khi bạn đã ở trong hệ sinh thái AWS và muốn ít vận hành.
  • Google Cloud Pub/Sub: thú vị ở chỗ nó lai giữa queue và stream. Về bản chất nó là pub/sub messaging toàn cầu, tự động mở rộng, không cần khai báo partition. Kèm theo là Pub/Sub Lite rẻ hơn cho khối lượng lớn. Pub/Sub mạnh ở khả năng scale tự động và hỗ trợ replay tối đa 7 ngày (khi bật retention).
  • Apache Kafka: mã nguồn mở, "vua" của streaming, chuẩn công nghiệp. Linh hoạt nhất, hệ sinh thái phong phú (Kafka Connect, Kafka Streams, ksqlDB), nhưng tự vận hành rất nặng. May mắn là có các bản managed: Amazon MSK, Confluent Cloud, hay Aiven — giúp bạn dùng Kafka mà không phải tự lo Zookeeper/KRaft, broker, rebalancing.

Tình huống thực tế

Ví dụ 1 — Sàn thương mại điện tử Việt Nam xử lý clickstream ngày Sale 12.12

Một sàn TMĐT tại TP.HCM (giả định tên "ChợViệt") có khoảng 8 triệu người dùng. Vào ngày sale 12.12, lượng sự kiện click, xem sản phẩm, thêm giỏ hàng bùng nổ lên khoảng 350.000 sự kiện/giây vào lúc 12h trưa. Trước đây họ dùng một hàng đợi thông thường để đẩy dữ liệu vào hệ thống phân tích, nhưng queue bị nghẽn, dữ liệu trễ tới 40 phút, và đội marketing không thể theo dõi hiệu quả flash sale theo thời gian thực.

Đội kỹ thuật chuyển sang Amazon Kinesis Data Streams chế độ On-Demand. Họ tạo stream clickstream-events với partition key là session_id để giữ thứ tự sự kiện trong một phiên. Ba consumer độc lập được gắn vào: (1) một Lambda ghi vào S3 để lưu trữ lâu dài, (2) một job phân tích real-time đẩy vào dashboard doanh thu, (3) một hệ thống phát hiện bot/gian lận. Nhờ retention 24 giờ, khi hệ thống gian lận bị lỗi lúc 12h30, họ chỉ cần replay lại từ offset lúc 12h00 sau khi vá bug — không mất một sự kiện nào.

Bài học rút ra: Khi cùng một luồng dữ liệu cần phục vụ nhiều mục đích và bạn cần khả năng phát lại, stream vượt trội hơn queue. Chế độ On-Demand cứu bạn khỏi việc đoán trước số shard trong ngày cao điểm.

Ví dụ 2 — Ngân hàng số dùng Kafka cho Change Data Capture

Một ngân hàng số ở Đông Nam Á cần đồng bộ dữ liệu giao dịch từ core banking (PostgreSQL) sang nhiều hệ thống hạ nguồn: kho dữ liệu phân tích, hệ thống chống rửa tiền (AML), và ứng dụng thông báo cho khách hàng. Họ không thể để mỗi hệ thống tự query trực tiếp vào database core vì sẽ gây tải nặng và rủi ro.

Giải pháp là Apache Kafka (chạy trên Confluent Cloud) kết hợp Debezium để làm Change Data Capture — mỗi thay đổi dòng dữ liệu trong PostgreSQL biến thành một event trong topic db.transactions. Topic này có 12 partition, key là account_id. Ba consumer group đọc song song, mỗi hệ thống hạ nguồn xử lý theo tốc độ riêng. Khi hệ thống AML cần chạy lại phân tích cho toàn bộ 30 ngày (do quy định compliance), họ đơn giản đặt lại offset về đầu — Kafka retention của họ được cấu hình 30 ngày.

Bài học rút ra: Kafka làm "trục xương sống dữ liệu" (data backbone) cho phép tách rời (decouple) hệ thống nguồn khỏi các hệ thống tiêu thụ. Retention dài và replay là tính năng sống còn cho các ngành có yêu cầu compliance.

Ví dụ 3 — Startup IoT dùng Pub/Sub vì không muốn quản lý partition

Một startup nông nghiệp thông minh ở Đà Lạt lắp cảm biến độ ẩm, nhiệt độ trên các nhà kính. Lượng dữ liệu ban đầu chỉ vài nghìn message/phút nhưng tăng trưởng thất thường theo mùa vụ và số khách hàng mới. Đội ngũ chỉ có 3 kỹ sư, không ai muốn dành thời gian tính toán số shard hay vận hành cụm Kafka.

Họ chọn Google Cloud Pub/Sub vì nó tự động mở rộng — không cần khai báo partition, chỉ cần publish vào topic sensor-readings và subscribe. Khi vào vụ và lượng dữ liệu tăng gấp 20 lần, hệ thống tự co giãn mà không cần can thiệp. Họ dùng một subscription đẩy dữ liệu vào BigQuery để phân tích, một subscription khác cho cảnh báo tức thời khi nhiệt độ vượt ngưỡng.

Bài học rút ra: Khi đội nhỏ và tải khó dự đoán, một dịch vụ tự-scale như Pub/Sub giảm gánh nặng vận hành đáng kể, đổi lại bạn kiểm soát chi tiết ít hơn so với Kafka.

Hướng dẫn từng bước

Hãy cùng dựng một pipeline streaming cơ bản với Amazon Kinesis — mô hình dễ bắt đầu nhất cho người mới.

Bước 1 — Tạo stream. Trên AWS CLI:

aws kinesis create-stream \
  --stream-name orders-stream \
  --stream-mode-details StreamMode=ON_DEMAND
Chế độ On-Demand giúp bạn khỏi phải đoán số shard; AWS tự điều chỉnh theo tải.

Bước 2 — Xác định partition key. Đây là bước tư duy quan trọng nhất. Hãy chọn khóa sao cho: (a) các sự kiện cần giữ thứ tự chung nằm cùng partition, (b) dữ liệu phân bố đều tránh "hot partition". Với đơn hàng, order_id hoặc customer_id là lựa chọn hợp lý.

Bước 3 — Producer đẩy dữ liệu. Đoạn Python dùng boto3:

import boto3, json
client = boto3.client('kinesis')
client.put_record(
    StreamName='orders-stream',
    Data=json.dumps({'order_id': 'A123', 'amount': 250000}),
    PartitionKey='A123'
)

Bước 4 — Consumer đọc dữ liệu. Cách đơn giản nhất là gắn một Lambda trigger vào stream. AWS tự động gọi Lambda mỗi khi có bản ghi mới, tự quản lý checkpoint. Bạn chỉ cần viết logic xử lý. Với khối lượng lớn hơn, dùng thư viện KCL (Kinesis Client Library) để quản lý shard và checkpoint chuyên nghiệp hơn.

Bước 5 — Cấu hình retention. Mặc định Kinesis giữ 24 giờ, có thể tăng tới 365 ngày:

aws kinesis increase-stream-retention-period \
  --stream-name orders-stream \
  --retention-period-hours 168
Retention dài cho phép replay nhưng làm tăng chi phí — cân nhắc kỹ.

Bước 6 — Giám sát. Theo dõi metric IteratorAgeMilliseconds — nếu con số này tăng liên tục, nghĩa là consumer đang đọc chậm hơn tốc độ ghi (bị "tụt hậu"), bạn cần thêm shard hoặc tối ưu consumer.

Lỗi thường gặp & mẹo

Nhầm lẫn stream với queue. Đây là lỗi tư duy phổ biến nhất. Đừng dùng stream chỉ để làm hàng đợi công việc đơn giản — bạn sẽ trả tiền và độ phức tạp không cần thiết. Ngược lại, đừng ép queue làm việc của stream khi bạn cần replay và fan-out.

Hot partition / hot shard. Nếu partition key phân bố lệch (ví dụ 80% dữ liệu dùng cùng một key), một shard sẽ quá tải trong khi các shard khác nhàn rỗi. Mẹo: chọn key có độ đa dạng cao (high cardinality) và phân bố đều.

Quên rằng thứ tự chỉ đảm bảo trong một partition. Nhiều bạn ngạc nhiên khi thấy sự kiện "đến sai thứ tự" — thực ra vì chúng nằm ở các partition khác nhau. Nếu cần thứ tự tuyệt đối cho một thực thể, hãy dùng ID của thực thể đó làm partition key.

Consumer nhiều hơn partition thì thừa. Trong một consumer group Kafka, số consumer hoạt động không thể vượt số partition. Muốn scale consumer, phải tăng partition trước.

Không xử lý duplicate. Streaming thường đảm bảo at-least-once (ít nhất một lần), nghĩa là một bản ghi có thể được giao nhiều lần. Hãy thiết kế consumer idempotent (xử lý lại không gây sai lệch) — ví dụ dùng order_id để chống ghi trùng.

Bỏ qua giám sát độ trễ. IteratorAgeMilliseconds (Kinesis) hay consumer lag (Kafka) là chỉ số sống còn. Lag tăng dần là dấu hiệu sớm của sự cố. Hãy đặt cảnh báo (alert) ngay từ đầu.

Mẹo chọn công cụ nhanh: Đã ở AWS và muốn ít vận hành → Kinesis. Đội nhỏ, tải khó đoán, ở GCP → Pub/Sub. Cần linh hoạt tối đa, hệ sinh thái mạnh, chấp nhận vận hành (hoặc dùng managed như MSK/Confluent) → Kafka.

Bài tập thực hành

  • Vẽ sơ đồ so sánh: Lấy một tình huống thực (ví dụ: hệ thống ghi nhận lượt xem video của một app học trực tuyến). Vẽ hai phương án — một dùng queue, một dùng stream — và liệt kê ưu nhược điểm của mỗi phương án cho tình huống đó.
  • Thiết kế partition key: Cho luồng sự kiện "giao dịch thanh toán" của một ví điện tử. Đề xuất partition key phù hợp và giải thích: (a) vì sao nó giữ được thứ tự cần thiết, (b) làm sao tránh hot partition. Nếu 90% giao dịch đến từ 3 merchant lớn thì bạn xử lý thế nào?
  • Thực hành trên AWS (nếu có tài khoản): Tạo một Kinesis Data Stream On-Demand, viết một script Python đẩy 100 bản ghi giả lập, gắn một Lambda in ra nội dung mỗi bản ghi. Sau đó tăng retention lên 48 giờ và giải thích chi phí phát sinh.
  • Tình huống replay: Giả sử consumer "gửi email xác nhận" của bạn bị bug trong 2 giờ khiến 5.000 khách không nhận được email. Với stream, bạn sẽ replay như thế nào để gửi lại đúng 5.000 email đó mà không gửi trùng cho những người đã nhận? (Gợi ý: kết hợp offset và tính idempotent.)

Tóm tắt

Event streaming là mô hình xử lý dòng chảy sự kiện có thể phát lại, khác biệt căn bản với message queue ở khả năng replay, fan-out cho nhiều consumer độc lập, và thông lượng rất cao. Trái tim của streaming là partition/shard — đơn vị song song hóa quyết định thông lượng và phạm vi đảm bảo thứ tự, được điều khiển bởi partition key.

Ba lựa chọn chính: Kinesis (managed, gọn trong hệ sinh thái AWS), Pub/Sub (tự-scale, nhẹ vận hành, hợp đội nhỏ trên GCP), và Kafka (linh hoạt và mạnh nhất, dùng managed như MSK/Confluent để giảm gánh vận hành). Nguyên tắc vàng: dùng stream khi cần replay, cần nhiều consumer khác nhau, hoặc cần thông lượng cực cao; dùng queue cho các tác vụ "làm một lần" đơn giản.

Cuối cùng, hãy luôn nhớ ba điều khi vận hành: chọn partition key khôn ngoan để tránh hot partition, thiết kế consumer idempotent để sống chung với at-least-once, và giám sát consumer lag như một dấu hiệu sinh tồn của hệ thống. Nắm chắc những nguyên tắc này, bạn đã sẵn sàng xây dựng các pipeline dữ liệu thời gian thực vững chắc trên cloud.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi