LabHub

博客

[AWS] Kinesis 实战架构:Kafka 对比与流式处理模式

한국어English日本語中文

1. Kinesis vs Apache Kafka 详细对比

1.1 架构差异

Apache Kafka 与 AWS Kinesis 都是面向实时数据流的平台, 但两者根本的架构理念并不相同。

Apache Kafka 架构
==================

+----------+     +------------------------------------------+
| Producer | --> | Kafka Cluster                             |
+----------+     |                                          |
                 |  Broker 1    Broker 2    Broker 3        |
                 |  +-------+  +-------+  +-------+        |
                 |  |Topic A|  |Topic A|  |Topic A|        |
                 |  |Part 0 |  |Part 1 |  |Part 2 |        |
                 |  |       |  |       |  |       |        |
                 |  |Topic B|  |Topic B|  |Topic B|        |
                 |  |Part 0 |  |Part 1 |  |Part 2 |        |
                 |  +-------+  +-------+  +-------+        |
                 |                                          |
                 |  KRaft (Kafka 4.0+, 移除 ZooKeeper)      |
                 +------------------------------------------+
                         |
                         v
                 +----------+
                 | Consumer |
                 | Group    |
                 +----------+

AWS Kinesis Data Streams 架构
==============================

+----------+     +------------------------------------------+
| Producer | --> | Kinesis Stream (完全托管型)                |
+----------+     |                                          |
                 |  Shard 1     Shard 2     Shard 3         |
                 |  +-------+  +-------+  +-------+        |
                 |  |Records|  |Records|  |Records|        |
                 |  |1MB/s W|  |1MB/s W|  |1MB/s W|        |
                 |  |2MB/s R|  |2MB/s R|  |2MB/s R|        |
                 |  +-------+  +-------+  +-------+        |
                 |                                          |
                 |  AWS 完全托管 (无需管理基础设施)          |
                 +------------------------------------------+
                         |
                         v
                 +----------+
                 | Consumer |
                 | (KCL)    |
                 +----------+

1.2 综合对比表

对比项目AWS KinesisApache Kafka (自建运维)Amazon MSK
管理方式完全托管型自建运维托管 Kafka
扩展单位分片分区 + BrokerBroker
每个分片/分区的写入1 MB/s无限制 (取决于磁盘 I/O)依赖磁盘 I/O
每个分片/分区的读取2 MB/s (共享)无限制依赖磁盘 I/O
最长保留期365 天无限制无限制
顺序保证分片内分区内分区内
记录最大尺寸1 MB默认 1 MB (可调整配置)默认 1 MB
消费者模型KCL、增强型扇出消费者组消费者组
协议HTTPS/HTTP2自有 TCP 协议自有 TCP
生态系统AWS 服务集成Kafka Connect、Schema Registry 等Kafka 生态系统
运维复杂度
初始搭建数分钟数小时 ~ 数天数十分钟

1.3 吞吐量与延迟

吞吐量对比
===========

Kinesis (预置模式):
  写入: 1 MB/s x 分片数
  读取: 2 MB/s x 分片数 (共享)
        2 MB/s x 分片数 x 消费者数 (增强型扇出)

  示例: 100 个分片
  写入: 100 MB/s
  读取: 200 MB/s (共享)200 MB/s x N (增强型扇出)

Kafka (自建运维):
  取决于 Broker 性能
  单个 Broker: 可达数百 MB/s
  集群: 可处理数 GB/s

  示例: 6Broker 的集群
  写入: 600+ MB/s
  读取:GB/s

延迟对比
=========
Kinesis:
  - PutRecord: 数十 ms
  - GetRecords (共享扇出): ~200 ms
  - Enhanced Fan-Out: ~70 ms

Kafka:
  - 生产者 -> 消费者: ~2-10 ms (依赖网络)
  - 端到端: ~10-50 ms

1.4 成本对比

月度成本估算 (以美国东部为准)
================================

场景: 持续 10 MB/s 数据摄取,3 个消费者

Kinesis (预置模式):
  - 需要 10 个分片 (10 MB/s / 1 MB/s per shard)
  - 分片成本: 10 x 0.015 x 720 小时 = ~108 USD
  - PUT 单元: ~360 USD (25.9B 单元/)
  - 增强型扇出 (3 个消费者): ~324 USD
  合计: ~792 USD/
Kinesis (按需 Advantage):
  - 数据写入: ~25.9 TB x 0.032 = ~829 USD
  - 数据读取: ~25.9 TB x 3 x 0.016 = ~1,243 USD
  合计: ~2,072 USD/
Kafka (EC2 自建运维):
  - 3Broker (m5.xlarge): 3 x 140 = ~420 USD
  - EBS 存储 (1TB x 3): ~300 USD
  - 运维人力成本: 另计
  合计: ~720 USD/+ 运维成本

Amazon MSK:
  - 3Broker (kafka.m5.large): ~456 USD
  - 存储: ~300 USD
  合计: ~756 USD/

1.5 何时该选择什么

选择 Kinesis 的场景:

选择 Kafka 的场景:


2. Kinesis vs SQS:何时使用哪一个

对比项目Kinesis Data StreamsAmazon SQS
数据处理模型流式处理 (连续处理)消息队列 (逐条处理)
消费者数量多消费者同时处理基本上为单消费者
顺序保证分片内保证仅 FIFO 队列保证
数据保留24 小时 ~ 365 天最长 14 天
数据重放可以 (基于序列号)不可以 (处理后即删除)
吞吐量每个分片 1 MB/s 写入几乎无限制
消息尺寸最大 1 MB最大 256 KB
延迟毫秒级毫秒级
计费模型分片小时数 + 数据传输量基于请求数
主要用途实时分析、日志采集微服务解耦
使用场景决策流程图
================================

数据处理需求分析
         |
    +----+----+
    |         |
同一份数据需要   消息只需要被
多个消费者读取   处理一次吗?
吗?                 |
    |              SQS
    |
是否必须保证
实时顺序?
    |
    +----+----+
    |         |
   YES        NO
    |         |
 Kinesis    SQS FIFO
 Data Streams    Kinesis

3.1 概述

Amazon Managed Service for Apache Flink (原 Kinesis Data Analytics) 是可以在完全托管的 基础设施上运行 Apache Flink 的服务。

注意:原有的 Kinesis Data Analytics for SQL 自 2025 年 10 月起已停止新建, 官方推荐迁移到 Amazon Managed Service for Apache Flink。

3.2 主要功能

Managed Flink 架构
====================

+-----------+     +----------------------------+     +-----------+
||     | Managed Flink              |     ||
|           | --> |                            | --> |           |
| - Kinesis |     | +------------------------+ |     | - Kinesis |
| - MSK     |     | | Flink Application      | |     | - S3      |
| - S3      |     | |                        | |     | - DynamoDB|
|           |     | | - SQL 查询             | |     | - Open-   |
|           |     | | - Java/Scala 应用      | |     |   Search  |
|           |     | | - Python (PyFlink)     | |     | - Redshift|
|           |     | |                        | |     |           |
|           |     | | 窗口聚合               | |     |           |
|           |     | | 模式检测               | |     |           |
|           |     | | CEP (复合事件处理)     | |     |           |
|           |     | +------------------------+ |     |           |
+-----------+     +----------------------------+     +-----------+

3.3 窗口处理类型

这是把流数据按时间分组后进行分析的核心功能。

窗口类型
=========

1) 滚动窗口 (Tumbling Window)
   - 固定大小,互不重叠
   |-------|-------|-------|-------|
   0       5       10      15      20 ()

2) 滑动窗口 (Sliding/Hopping Window)
   - 固定大小,按固定间隔滑动
   |-----------|
       |-----------|
           |-----------|
   0   2   4   6   8   10 ()
   大小: 6, 滑动: 2
3) 会话窗口 (Session Window)
   - 基于活动,以静默间隔划分
   |---event-event---| gap |--event-event-event--| gap |
   <-- Session 1 -->       <------ Session 2 ---->

4) 全局窗口 (Global Window)
   - 对整条流使用单一窗口
-- 定义 Kinesis 源表
CREATE TABLE clickstream (
    user_id VARCHAR,
    page VARCHAR,
    action VARCHAR,
    event_time TIMESTAMP(3),
    WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
) WITH (
    'connector' = 'kinesis',
    'stream' = 'clickstream-data',
    'aws.region' = 'ap-northeast-2',
    'scan.stream.initpos' = 'LATEST',
    'format' = 'json'
);

-- 用 1 分钟滚动窗口按页面聚合浏览量
SELECT
    page,
    COUNT(*) AS view_count,
    COUNT(DISTINCT user_id) AS unique_users,
    TUMBLE_START(event_time, INTERVAL '1' MINUTE) AS window_start,
    TUMBLE_END(event_time, INTERVAL '1' MINUTE) AS window_end
FROM clickstream
WHERE action = 'view'
GROUP BY
    page,
    TUMBLE(event_time, INTERVAL '1' MINUTE);

-- 异常行为检测: 1 分钟内同一用户点击 10 次以上
SELECT
    user_id,
    COUNT(*) AS click_count,
    TUMBLE_START(event_time, INTERVAL '1' MINUTE) AS window_start
FROM clickstream
WHERE action = 'click'
GROUP BY
    user_id,
    TUMBLE(event_time, INTERVAL '1' MINUTE)
HAVING COUNT(*) >= 10;

4. 实战流式架构模式

4.1 日志聚合流水线

日志聚合架构
==============

+----------+     +----------+     +---------+     +----------+
| App      |     | Kinesis  |     | Firehose|     | S3       |
| Server 1 | --> | Agent    | --> |         | --> | (Raw     |
+----------+     +----------+     |         |     |  Logs)   |
                                  |         |     +----------+
+----------+     +----------+     |         |         |
| App      |     | Kinesis  |     |         |         v
| Server 2 | --> | Agent    | --> |         |     +----------+
+----------+     +----------+     |         |     | Athena   |
                                  |         |     | (Query)  |
+----------+     +----------+     |         |     +----------+
| App      |     | Kinesis  |     |         |
| Server N | --> | Agent    | --> |         |
+----------+     +----------+     +---------+
                      |
                      v
                 +----------+     +----------+
                 | Lambda   | --> | Open-    |
                 | (实时    |     | Search   |
                 |  告警)   |     | (搜索/   |
                 +----------+     |  仪表板) |
                                  +----------+

4.2 实时分析仪表板

实时仪表板架构
================

+----------+     +----------+     +-----------+     +----------+
| 网页/移动|     | API      |     | Kinesis   |     | Managed  |
| 客户端    | --> | Gateway  | --> | Data      | --> | Flink    |
+----------+     +----------+     | Streams   |     | (聚合/   |
                                  +-----------+     |  分析)   |
                                                    +----------+
                                                         |
                                          +---------+----+----+---------+
                                          |         |         |         |
                                          v         v         v         v
                                     +--------+ +--------+ +--------+ +--------+
                                     |DynamoDB| |Timestream| | S3    | |CloudWatch|
                                     |(实时   | |(时序    | |(长期  | |(指标    |
                                     | 数据)  | | 数据)   | | 存储) | | 告警)   |
                                     +--------+ +--------+ +--------+ +--------+
                                          |         |
                                          v         v
                                     +--------------------+
                                     | 仪表板应用         |
                                     | (React/Vue +       |
                                     |  WebSocket)        |
                                     +--------------------+

4.3 IoT 数据采集

# IoT 设备模拟器
import boto3
import json
import time
import random
from datetime import datetime

kinesis = boto3.client('kinesis', region_name='ap-northeast-2')
STREAM_NAME = 'iot-sensor-data'

def simulate_sensor(device_id):
    """IoT 传感器数据模拟"""
    return {
        'device_id': device_id,
        'temperature': round(random.uniform(15.0, 45.0), 2),
        'humidity': round(random.uniform(20.0, 90.0), 2),
        'pressure': round(random.uniform(990.0, 1030.0), 2),
        'battery_level': round(random.uniform(0.0, 100.0), 1),
        'location': {
            'lat': round(random.uniform(33.0, 38.0), 6),
            'lon': round(random.uniform(126.0, 130.0), 6)
        },
        'timestamp': datetime.utcnow().isoformat() + 'Z'
    }

def ingest_iot_data(num_devices=100, interval=1.0):
    """把 IoT 数据采集到 Kinesis"""
    device_ids = [f'sensor-{i:04d}' for i in range(num_devices)]

    while True:
        records = []
        for device_id in device_ids:
            sensor_data = simulate_sensor(device_id)
            records.append({
                'Data': json.dumps(sensor_data).encode('utf-8'),
                'PartitionKey': device_id
            })

        # PutRecords 最多 500 条
        for batch_start in range(0, len(records), 500):
            batch = records[batch_start:batch_start + 500]
            response = kinesis.put_records(
                StreamName=STREAM_NAME,
                Records=batch
            )

            failed = response['FailedRecordCount']
            if failed > 0:
                print(f"Batch failed: {failed} records")
                # 指数退避重试逻辑
                retry_records = []
                for i, result in enumerate(response['Records']):
                    if 'ErrorCode' in result:
                        retry_records.append(batch[i])
                if retry_records:
                    time.sleep(0.5)
                    kinesis.put_records(
                        StreamName=STREAM_NAME,
                        Records=retry_records
                    )

        print(f"Ingested {len(records)} sensor readings")
        time.sleep(interval)

if __name__ == '__main__':
    ingest_iot_data()

4.4 事件溯源模式

事件溯源架构
==============

+----------+     +----------+     +-----------+
| Command  |     | Kinesis  |     | Event     |
| Handler  | --> | Data     | --> | Processor |
|          |     | Streams  |     | (Lambda/  |
| - 订单   |     | (事件    |     |  ECS)     |
| - 支付   |     |  存储)   |     +-----------+
| - 配送   |     +----------+          |
+----------+          |          +-----+-----+
                      |          |           |
                      v          v           v
                 +----------+ +--------+ +--------+
                 | S3       | |DynamoDB| |SNS     |
                 | (事件    | |(读取   | |(通知)  |
                 |  归档)   | | 模型)  | |        |
                 +----------+ +--------+ +--------+

事件流示例:
1. OrderCreated -> 订单创建事件
2. PaymentProcessed -> 支付处理事件
3. InventoryReserved -> 库存预留事件
4. ShipmentCreated -> 配送创建事件

4.5 ML 特征流水线

ML 特征流水线
===============

+----------+     +----------+     +-----------+     +----------+
| 事件     |     | Kinesis  |     | Managed   |     | Feature  |
|| --> | Data     | --> | Flink     | --> | Store    |
|          |     | Streams  |     | (特征     |     | (Sage-   |
| - 点击   |     |          |     |  计算)    |     |  Maker)  |
| - 购买   |     |          |     |           |     +----------+
| - 搜索   |     |          |     | 实时:     |         |
+----------+     +----------+     | - 会话数  |         v
                                  | - 近期    |     +----------+
                                  |   购买数   |     | ML 模型  |
                                  | - 平均    |     | 推理     |
                                  |   停留时长 |     +----------+
                                  +-----------+

5. 性能优化

5.1 分区键设计

分区键设计是决定 Kinesis 性能最重要的因素。

好的分区键需要满足:

好的分区键示例
================

1) 用户 ID (高基数)
   user-001 -> Shard 1
   user-002 -> Shard 3
   user-003 -> Shard 2
   ...
   分布均匀

2) UUID (分散度最佳)
   随机 UUID -> 完美分散
   缺点: 无法保证同一实体的顺序

3) 复合键
   "region-userType-userId"
   可以做精细的分散控制

不好的分区键示例
================

1) 日期 ("2026-03-20")
   所有记录都落到同一个分片 -> 热点分片

2) 国家代码 ("KR", "US", "JP")
   基数太低
   流量会倾斜到特定国家

3) 固定值 ("default")
   所有负载集中在单个分片

5.2 利用 KPL 进行聚合

KPL 聚合优化
=============

不使用聚合:
Record 1 (100B) -> PutRecord -> 1API 调用
Record 2 (200B) -> PutRecord -> 1API 调用
Record 3 (150B) -> PutRecord -> 1API 调用
合计: 3API 调用, 传输 450B

使用 KPL 聚合:
Record 1 (100B) --+
Record 2 (200B) --+--> 聚合记录 (450B) -> 1API 调用
Record 3 (150B) --+
合计: 1API 调用, 传输 450B

效果:
- API 调用次数大幅减少
- 降低 PUT 单元费用
- 吞吐量大幅提升

5.3 增强型扇出策略

# 注册增强型扇出消费者
import boto3

kinesis = boto3.client('kinesis', region_name='ap-northeast-2')

# 注册消费者
response = kinesis.register_stream_consumer(
    StreamARN='arn:aws:kinesis:ap-northeast-2:123456789012:stream/my-stream',
    ConsumerName='analytics-consumer'
)
consumer_arn = response['Consumer']['ConsumerARN']
print(f"Consumer ARN: {consumer_arn}")

# 确认消费者状态
response = kinesis.describe_stream_consumer(
    StreamARN='arn:aws:kinesis:ap-northeast-2:123456789012:stream/my-stream',
    ConsumerName='analytics-consumer'
)
print(f"Status: {response['ConsumerDescription']['ConsumerStatus']}")

5.4 错误处理与重试策略

import time
import random

def put_records_with_retry(kinesis_client, stream_name, records, max_retries=3):
    """使用指数退避的 PutRecords 重试"""

    for attempt in range(max_retries):
        response = kinesis_client.put_records(
            StreamName=stream_name,
            Records=records
        )

        failed_count = response['FailedRecordCount']

        if failed_count == 0:
            return response

        # 只提取失败的记录
        retry_records = []
        for i, result in enumerate(response['Records']):
            if 'ErrorCode' in result:
                error_code = result['ErrorCode']
                if error_code == 'ProvisionedThroughputExceededException':
                    retry_records.append(records[i])
                else:
                    print(f"Non-retryable error: {error_code}")

        if not retry_records:
            return response

        records = retry_records

        # 指数退避 + 抖动
        backoff = min(2 ** attempt * 0.1, 5.0)
        jitter = random.uniform(0, backoff * 0.5)
        wait_time = backoff + jitter
        print(f"Retry {attempt + 1}: {len(retry_records)} records, waiting {wait_time:.2f}s")
        time.sleep(wait_time)

    print(f"Failed after {max_retries} retries: {len(records)} records")
    return None

6. 监控:CloudWatch 指标

6.1 核心监控指标

指标说明告警阈值
IncomingBytes进入流的字节数分片容量的 80%
IncomingRecords进入流的记录数每个分片 800 rec/s
GetRecords.IteratorAgeMilliseconds消费者落后了多少60,000 ms (1 分钟)
WriteProvisionedThroughputExceeded写入超限次数超过 0 即告警
ReadProvisionedThroughputExceeded读取超限次数超过 0 即告警
GetRecords.LatencyGetRecords 调用延迟1,000 ms
PutRecord.LatencyPutRecord 调用延迟1,000 ms
GetRecords.SuccessGetRecords 成功率低于 99% 即告警

6.2 增强型监控

启用增强型监控后新增的指标
============================

分片级别指标:
- IncomingBytes (按分片)
- IncomingRecords (按分片)
- IteratorAgeMilliseconds (按分片)
- OutgoingBytes (按分片)
- OutgoingRecords (按分片)
- ReadProvisionedThroughputExceeded (按分片)
- WriteProvisionedThroughputExceeded (按分片)

热点分片探测:
  Shard 1: IncomingBytes = 200 KB/s  [正常]
  Shard 2: IncomingBytes = 950 KB/s  [警告! 已接近上限]
  Shard 3: IncomingBytes = 300 KB/s  [正常]
  -> 建议拆分 Shard 2

7. 最佳实践与反模式

7.1 最佳实践

1) 分区键设计

2) 生产者优化

3) 消费者优化

4) 容量管理

5) 成本优化

7.2 反模式

1) 使用单一分区键

2) 分片数量过多

3) 不使用检查点

4) 缺少错误处理

5) 过度调用 GetRecords


8. 综合对比汇总表

项目Kinesis Data StreamsKinesis FirehoseKafkaSQSManaged Flink
类型数据流式处理数据投递数据流式处理消息队列流式处理
管理完全托管型完全托管型自建/MSK完全托管型完全托管型
延迟ms60s+msmsms
顺序保证分片内分区内仅 FIFO取决于输入
重放可以不可以可以不可以取决于输入
扩缩容增加分片自动分区/Broker自动增加 KPU
转换无 (由消费者负责)LambdaKafka StreamsFlink 应用
计费模型分片+数据数据量实例请求数KPU 小时
AWS 集成非常高低/中
最佳用途实时采集自动投递大批量流式处理任务队列实时分析

9. 总结

服务选型指南

设计实时流式架构时,选择契合需求的服务至关重要。

理解每个服务的强项,并且经常把多个服务组合起来使用,才是实战中的最优模式。 例如,用 Kinesis Data Streams 采集、用 Managed Flink 做实时分析、用 Data Firehose 长期存储到 S3,这样的组合是非常常见的架构。

评论

还没有评论。

登录后即可发表评论