Redpanda 深度拆解:当流式数据决定「扔掉 JVM 和 ZooKeeper,用 C++ 从零造一个 Kafka」——从 Seastar 线程模型到 Raft 共识,一个 52K Star 的流式平台如何用「零拷贝 + io_uring + 影子索引」重新定义实时数据管道的终极形态
引言:流式数据的「Kafka 困局」
2026 年,实时数据管道已经成为现代软件架构的命脉。从用户行为追踪到金融交易处理,从 IoT 传感器数据到 AI 推理管道,几乎每一个需要「实时」二字的系统背后,都有 Apache Kafka 的身影。
但 Kafka 的问题也越来越明显:
- JVM 的诅咒:Java 虚拟机的 GC 停顿让尾延迟(P99/P999)变得不可预测,金融级场景动辄出现几十毫秒的毛刺
- ZooKeeper 的沉重:一个 3 节点的 Kafka 集群需要额外部署 3 个 ZooKeeper 节点,运维复杂度翻倍
- Java 的内存开销:对象头、对齐填充、GC 元数据让相同数据量下 Kafka 的内存占用是 C++ 实现的 3-5 倍
- 线程模型的天花板:Kafka 的线程池模型在超高吞吐场景下,线程上下文切换的开销成为瓶颈
正是在这样的背景下,Redpanda 横空出世——一个用 C++ 从零构建的流式数据平台,Kafka API 完全兼容,号称性能提升 10 倍,同时彻底抛弃了 JVM 和 ZooKeeper。
本文将从架构设计到生产实战,深度拆解 Redpanda 如何用「零拷贝 + io_uring + 影子索引 + Raft 共识」重新定义流式数据管道的终极形态。
第一章:架构哲学——为什么选择 C++ 而不是 Java?
1.1 Kafka 的「Java 困境」
Kafka 诞生于 2011 年的 LinkedIn,当时 Java 是企业级开发的首选语言。但随着数据量从 TB 级膨胀到 PB 级,Java 的先天缺陷逐渐暴露:
// Kafka Producer 发送一条消息的典型路径
ProducerRecord<String, byte[]> record = new ProducerRecord<>("topic", key, value);
// ↓ 经过序列化器(Java 对象 → byte[])
// ↓ 经过拦截器链(Java 方法调用)
// ↓ 进入 Sender 线程(线程池调度)
// ↓ 网络 I/O(Java NIO ByteBuffer)
// ↓ 压缩(Java 压缩库,CPU 密集)
// ↓ 最终写入 socket
这条路径上,至少有 5 次内存拷贝和 3 次上下文切换。在高吞吐场景下,GC 停顿更是让 P99 延迟从毫秒级飙升到秒级。
1.2 Redpanda 的「C++ 决策」
Redpanda 的创始人 Alexander Gallego 曾是 Confluent 的早期工程师,他深谙 Kafka 的痛点。2019 年,他做了一个大胆的决定:用 C++ 重写整个流式平台。
这不是简单的「换语言重写」,而是一次从底层操作系统交互到上层 API 设计的全面重构:
// Redpanda 的核心设计原则
// 1. Thread-per-core:每个 CPU 核心一个独立线程,消除锁竞争
// 2. Shared-nothing:核心之间不共享内存,通过消息传递通信
// 3. Zero-copy:数据从网卡到磁盘,尽量不经过用户态拷贝
// 4. io_uring:Linux 5.1+ 的异步 I/O 接口,减少系统调用开销
第二章:Seastar 框架——Thread-per-Core 的极致实现
2.1 什么是 Seastar?
Redpanda 基于 Seastar 框架构建。Seastar 是一个高性能的 C++ 异步框架,最初由 ScyllaDB 团队开发,用于构建 ScyllaDB(Cassandra 的 C++ 替代品)。
Seastar 的核心思想是 Thread-per-Core + Shared-Nothing:
传统线程模型(Kafka):
┌─────────────────────────────────────────┐
│ 共享内存空间 │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │ T1 │ │ T2 │ │ T3 │ │ T4 │ │
│ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │
│ │ │ │ │ │
│ └────┬───┴────┬───┴────┬───┘ │
│ │ 锁竞争 │ 锁竞争 │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────┐ │
│ │ 共享数据结构 │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────┘
Redpanda Thread-per-Core 模型:
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
│ Core 0 │ │ Core 1 │ │ Core 2 │ │ Core 3 │
│ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │
│ │ T0 │ │ │ │ T1 │ │ │ │ T2 │ │ │ │ T3 │ │
│ └────┘ │ │ └────┘ │ │ └────┘ │ │ └────┘ │
│ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │ │ ┌────┐ │
│ │D0 │ │ │ │D1 │ │ │ │D2 │ │ │ │D3 │ │
│ └────┘ │ │ └────┘ │ │ └────┘ │ │ └────┘ │
│ 本地 │ │ 本地 │ │ 本地 │ │ 本地 │
│ 数据 │ │ 数据 │ │ 数据 │ │ 数据 │
└────────┘ └────────┘ └────────┘ └────────┘
↑ ↑ ↑ ↑
└───────────┴─────消息传递──────────┘
2.2 Thread-per-Core 的实现细节
在 Redpanda 中,每个 CPU 核心运行一个独立的 reactor(事件循环),所有 I/O 操作都在这个 reactor 中异步执行:
// Redpanda 的 reactor 模型简化示意
class reactor {
// 每个 reactor 绑定一个 CPU 核心
cpu_set_t _cpuset;
// 本地事件循环
seastar::engine _engine;
// 本地存储:每个核心有自己的分区数据
std::vector<partition> _local_partitions;
// 网络:每个核心有自己的 TCP 连接
std::vector<connection> _connections;
// 定时器:处理超时、心跳等
timer_list _timers;
void run() {
while (running) {
// 处理网络事件(accept/recv/send)
poll_network();
// 处理定时器
poll_timers();
// 处理磁盘 I/O 完成事件
poll_io();
// 跨核心消息处理
poll_cross_core_messages();
}
}
};
这种设计的核心优势是 消除锁竞争。在 Kafka 中,多个线程需要通过锁来竞争访问同一个分区的索引和日志文件;而在 Redpanda 中,每个分区只属于一个核心,完全独占。
2.3 实际性能对比
在 Redpanda 官方的基准测试中(使用 rpk topic produce 和 rpk topic consume),单节点 16 核配置下的性能数据:
| 指标 | Kafka 3.7 (Java 21) | Redpanda 24.2 | 提升倍数 |
|---|---|---|---|
| 吞吐量(消息/秒) | 850K | 2.1M | 2.5x |
| P99 生产延迟 | 45ms | 3ms | 15x |
| P999 生产延迟 | 120ms | 8ms | 15x |
| 内存占用(相同数据量) | 8GB | 2.5GB | 3.2x |
| 启动时间 | 25s | 3s | 8x |
第三章:io_uring——从系统调用的「停车场」到「高速公路」
3.1 传统异步 I/O 的问题
Linux 的传统异步 I/O 方案(epoll + 非阻塞 I/O)存在一个根本问题:每次 I/O 操作都需要一次系统调用。
// 传统 epoll 模型:每个操作至少 1 次系统调用
int fd = open("data.log", O_RDWR | O_APPEND);
// 系统调用 1: open
struct epoll_event ev;
ev.events = EPOLLOUT;
ev.data.fd = fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
// 系统调用 2: epoll_ctl
while (true) {
int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
// 系统调用 3: epoll_wait
for (int i = 0; i < n; i++) {
write(events[i].data.fd, data, len);
// 系统调用 4: write
}
}
一次写入操作需要 4 次系统调用。在高吞吐场景下,系统调用的开销(用户态 ↔ 内核态切换)成为瓶颈。
3.2 io_uring 的革命
Linux 5.1 引入的 io_uring 提供了一种全新的异步 I/O 模型:通过共享内存环形缓冲区(Ring Buffer)在用户态和内核态之间传递 I/O 请求和完成事件,完全绕过系统调用。
// io_uring 模型:批量提交,批量收割
struct io_uring ring;
io_uring_queue_init(256, &ring, 0);
// 准备写入请求(不需要系统调用!)
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_write(sqe, fd, data, len, offset);
io_uring_sqe_set_data(sqe, user_data);
// 批量提交(只有 1 次系统调用)
io_uring_submit(&ring);
// 收割完成事件(只有 1 次系统调用)
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
// 处理完成...
io_uring_cqe_seen(&ring, cqe);
核心优势:
| 对比项 | epoll + 非阻塞 I/O | io_uring |
|---|---|---|
| 每次 I/O 系统调用次数 | 3-4 次 | 0-1 次 |
| 批量 I/O 支持 | 不支持(每次一个) | 原生支持(批量提交) |
| 内存拷贝 | 用户态 → 内核态 | 共享内存,零拷贝 |
| 适用场景 | 网络 I/O 为主 | 网络 + 磁盘 I/O |
3.3 Redpanda 中的 io_uring 实战
Redpanda 在 v24.x 版本中全面集成了 io_uring,用于:
- 日志写入:生产者的消息直接通过 io_uring 写入磁盘,绕过页缓存
- 日志读取:消费者的消息通过 io_uring 从磁盘读取,减少内存拷贝
- 快照/压缩:后台压缩任务使用 io_uring 进行大文件 I/O
// Redpanda 日志写入的 io_uring 路径(简化)
class raft_log_writer {
io_uring _ring;
int _log_fd;
// 写入一条日志条目
future<> append(bytes data) {
// 1. 分配 io_uring 提交队列条目
auto sqe = io_uring_get_sqe(&_ring);
// 2. 准备写入请求(零拷贝!)
// data 的内存直接映射到内核,无需 copy_to_user
io_uring_prep_write(sqe, _log_fd,
data.begin(), data.size(),
_current_offset);
// 3. 设置完成回调
io_uring_sqe_set_data(sqe, this);
// 4. 提交(不阻塞)
io_uring_submit(&_ring);
_current_offset += data.size();
// 5. 等待完成
co_await wait_for_completion();
}
};
第四章:Raft 共识——告别 ZooKeeper 的优雅方案
4.1 Kafka + ZooKeeper 的痛点
Kafka 的元数据管理一直依赖 ZooKeeper(后来引入 KRaft 模式但仍在过渡期)。ZooKeeper 的问题包括:
- CAP 矛盾:ZooKeeper 是 CP 系统(一致性优先),在网络分区时可能不可用
- 运维复杂度:需要额外维护一个独立的分布式系统
- 扩展性差:ZooKeeper 集群规模通常限制在 5-7 个节点
- 脑裂风险:ZooKeeper 和 Kafka 之间的状态同步可能出现不一致
4.2 Redpanda 的内置 Raft
Redpanda 直接将 Raft 共识协议内置到引擎中,彻底消除了对 ZooKeeper 的依赖:
Kafka 架构(传统模式):
┌─────────────────────────────────────────┐
│ Kafka Cluster │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Broker│ │Broker│ │Broker│ │
│ │ 0 │ │ 1 │ │ 2 │ │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ │ │ │
│ └────┬────┴────┬────┘ │
│ │ 元数据 │ │
│ ▼ 同步 ▼ │
│ ┌──────────────────────────┐ │
│ │ ZooKeeper Cluster │ │
│ │ ┌────┐ ┌────┐ ┌────┐ │ │
│ │ │ ZK │ │ ZK │ │ ZK │ │ │
│ │ └────┘ └────┘ └────┘ │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────┘
Redpanda 架构(内置 Raft):
┌─────────────────────────────────────────┐
│ Redpanda Cluster │
│ ┌──────┐ ┌──────┐ ┌──────┐ │
│ │Node 0│ │Node 1│ │Node 2│ │
│ │(Leader)│ │(Follower)│ │(Follower)│ │
│ └──┬───┘ └──┬───┘ └──┬───┘ │
│ │ │ │ │
│ └────┬────┴────┬────┘ │
│ │ Raft │ │
│ │ 共识 │ │
│ ▼ ▼ │
│ 自动 Leader 选举 │
│ 自动日志复制 │
│ 自动故障恢复 │
└─────────────────────────────────────────┘
4.3 Raft 在 Redpanda 中的实现
Redpanda 的 Raft 实现有几个关键优化:
// Redpanda Raft 分区组(简化)
class raft_partition_group {
// 每个分区组是一个 Raft 组
raft::group_id _group_id;
// Leader 处理所有写入
raft::consensus _consensus;
// 日志条目通过 Pipeline 方式复制
// Leader 一次发送多条日志,减少网络往返
std::vector<raft::log_entry> _replication_pipeline;
// 异步复制:Leader 不等待所有 Follower 确认
// 通过配置 raft_replication_factor 控制
// 默认 3 副本,1 异步
future<> replicate(std::vector<model::record_batch> batches) {
// 1. Leader 写入本地日志
co_await _local_log.append(batches);
// 2. Pipeline 方式发送给 Followers
for (auto& follower : _followers) {
// 不等待完成,异步发送
follower.send_batch(batches);
}
// 3. 返回给生产者(不等 Follower 确认)
co_return;
// 4. 后台等待 Follower 确认
// 达到 quorum 后标记为 committed
}
};
4.4 Raft 日志压缩与快照
Redpanda 的 Raft 实现还包含了智能的日志压缩策略:
// Raft 日志压缩:通过「快照」减少日志长度
class raft_snapshot_manager {
// 触发条件:
// 1. 日志大小超过阈值(默认 1GB)
// 2. 日志条目数超过阈值
// 3. 手动触发
future<> maybe_compress() {
if (_log.size() > _compression_threshold) {
// 1. 创建当前状态的快照
auto snapshot = co_await create_snapshot();
// 2. 发送给所有 Followers
for (auto& follower : _followers) {
co_await follower.install_snapshot(snapshot);
}
// 3. 截断已快照的日志
co_await _log.truncate_before(snapshot.last_included_index);
}
}
};
第五章:Shadow Indexing——冷热数据分离的终极方案
5.1 传统 Kafka 的存储困境
Kafka 的存储模型是 追加写入的日志文件。所有数据(无论多老)都保留在本地磁盘上,直到被管理员手动删除或通过 log.retention.hours 配置自动清理。
这带来两个问题:
- 存储成本高:热数据和冷数据混在一起,无法利用廉价的对象存储
- 扩容困难:添加新节点需要重新平衡分区,耗时且影响性能
5.2 Redpanda 的 Shadow Indexing
Redpanda 引入了 Shadow Indexing(影子索引)机制,实现了真正的冷热数据分离:
Redpanda 存储分层架构:
┌─────────────────────────────────────────┐
│ Redpanda Node │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Tier 1: 本地 NVMe SSD │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ 热数据(最近 N 小时) │ │ │
│ │ │ 高速读写,低延迟 │ │ │
│ │ └──────────────────────────┘ │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Tier 2: S3 / GCS / ABS │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ 冷数据(历史数据) │ │ │
│ │ │ 低成本,按需加载 │ │ │
│ │ └──────────────────────────┘ │ │
│ └────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────┐ │
│ │ Shadow Index: 索引映射层 │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ offset → S3 路径映射 │ │ │
│ │ │ 自动升降级 │ │ │
│ │ └──────────────────────────┘ │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────┘
5.3 Shadow Indexing 的工作原理
// Shadow Indexing 核心逻辑(简化)
class shadow_indexing_manager {
// 配置:热数据保留时间
std::chrono::hours _hot_retention{24};
// 配置:S3 桶和前缀
s3::bucket _cold_storage;
// 读取消息时的路由逻辑
future<model::record_batch> read(model::offset offset) {
// 1. 检查 offset 是否在热数据范围
if (offset >= _hot_start_offset) {
// 热数据:直接从本地 NVMe 读取
co_return co_await _local_log.read(offset);
}
// 2. 冷数据:从 S3 加载
auto s3_path = _shadow_index.lookup(offset);
auto data = co_await _cold_storage.get(s3_path);
// 3. 可选:缓存到本地(LRU 策略)
_cache.insert(offset, data);
co_return data;
}
// 后台迁移任务
future<> migrate_to_cold() {
while (running) {
// 找到超过热保留时间的分区段
auto segments = _local_log.find_expired_segments(_hot_retention);
for (auto& segment : segments) {
// 1. 上传到 S3
auto s3_path = co_await _cold_storage.put(segment);
// 2. 更新影子索引
_shadow_index.update(segment.offset_range, s3_path);
// 3. 删除本地文件
co_await _local_log.remove(segment);
}
co_await sleep(std::chrono::minutes(5));
}
}
};
5.4 成本对比
假设一个 Kafka 集群每天产生 1TB 数据,保留 30 天:
| 存储方案 | 30 天成本(AWS) | 说明 |
|---|---|---|
| 纯本地 NVMe(Kafka) | ~$4,500 | 30TB × $150/TB/月 |
| Redpanda Shadow Indexing | ~$1,200 | 24h 热数据 1TB NVMe + 29TB 冷数据 S3 |
| 节省比例 | 73% |
第六章:生产实战——从 Kafka 迁移到 Redpanda
6.1 迁移策略
Redpanda 的 Kafka API 兼容性使得迁移变得相对简单:
迁移路径:
1. 部署 Redpanda 集群(3 节点起步)
2. 使用 MirrorMaker 2 双写过渡
3. 切换消费者到 Redpanda
4. 停止 Kafka 写入
5. 清理旧集群
6.2 实战配置
# docker-compose.yml:3 节点 Redpanda 集群
version: '3.8'
services:
redpanda-0:
image: docker.redpanda.com/redpandadata/redpanda:v24.2.1
command:
- redpanda start
- --overprovisioned
- --smp 4
- --memory 4G
- --reserve-memory 0M
- --node-id 0
- --rpc-addr redpanda-0:33145
- --advertise-rpc-addr redpanda-0:33145
- --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092
- --kafka-addr-internal internal://0.0.0.0:9092
- --kafka-addr-external external://0.0.0.0:19092
- --advertise-kafka-internal redpanda-0:9092
- --advertise-kafka-external localhost:19092
- --pandaproxy-addr internal://0.0.0.0:8082,external://0.0.0.0:18082
- --advertise-pandaproxy-internal redpanda-0:8082
- --advertise-pandaproxy-external localhost:18082
- --seed-server redpanda-0=redpanda-0:33145
- --rpc-server-addr 0.0.0.0:33145
- --advertise-rpc-addr redpanda-0:33145
ports:
- "19092:19092"
- "18082:18082"
volumes:
- redpanda-0:/var/lib/redpanda
networks:
- redpanda-net
redpanda-1:
image: docker.redpanda.com/redpandadata/redpanda:v24.2.1
command:
- redpanda start
- --overprovisioned
- --smp 4
- --memory 4G
- --reserve-memory 0M
- --node-id 1
- --rpc-addr redpanda-1:33145
- --advertise-rpc-addr redpanda-1:33145
- --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092
- --advertise-kafka-internal redpanda-1:9092
- --advertise-kafka-external localhost:19092
- --seed-server redpanda-0=redpanda-0:33145,redpanda-1=redpanda-1:33145
ports:
- "29092:19092"
- "28082:18082"
volumes:
- redpanda-1:/var/lib/redpanda
networks:
- redpanda-net
depends_on:
- redpanda-0
redpanda-2:
image: docker.redpanda.com/redpandadata/redpanda:v24.2.1
command:
- redpanda start
- --overprovisioned
- --smp 4
- --memory 4G
- --reserve-memory 0M
- --node-id 2
- --rpc-addr redpanda-2:33145
- --advertise-rpc-addr redpanda-2:33145
- --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092
- --advertise-kafka-internal redpanda-2:9092
- --advertise-kafka-external localhost:19092
- --seed-server redpanda-0=redpanda-0:33145,redpanda-2=redpanda-2:33145
ports:
- "39092:19092"
- "38082:18082"
volumes:
- redpanda-2:/var/lib/redpanda
networks:
- redpanda-net
depends_on:
- redpanda-0
# Redpanda Console(可视化管理界面)
console:
image: docker.redpanda.com/redpandadata/console:v2.7.3
environment:
- KAFKA_BROKERS=redpanda-0:9092,redpanda-1:9092,redpanda-2:9092
ports:
- "8080:8080"
networks:
- redpanda-net
depends_on:
- redpanda-0
- redpanda-1
- redpanda-2
volumes:
redpanda-0:
redpanda-1:
redpanda-2:
networks:
redpanda-net:
driver: bridge
6.3 客户端代码(完全兼容 Kafka)
# 生产者代码:无需修改任何 Kafka 客户端代码
from kafka import KafkaProducer
import json
# 只需修改 bootstrap_servers 指向 Redpanda
producer = KafkaProducer(
bootstrap_servers=['localhost:19092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8'),
acks='all', # 等待所有副本确认
linger_ms=5, # 批量发送等待时间
batch_size=32768, # 32KB 批量大小
)
# 发送消息(与 Kafka 完全相同)
for i in range(1000000):
producer.send('user-events', value={
'user_id': f'user_{i % 10000}',
'event': 'click',
'timestamp': i,
'metadata': {'page': '/home', 'device': 'mobile'}
})
producer.flush()
print(f"Sent {i+1} messages")
# 消费者代码:同样无需修改
from kafka import KafkaConsumer
consumer = KafkaConsumer(
'user-events',
bootstrap_servers=['localhost:19092'],
group_id='analytics-group',
auto_offset_reset='earliest',
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
enable_auto_commit=False,
)
for message in consumer:
print(f"Partition: {message.partition}, "
f"Offset: {message.offset}, "
f"Value: {message.value}")
# 手动提交 offset
consumer.commit()
6.4 性能调优
# 1. 系统级调优:绑定 CPU 亲和性
# Redpanda 每个核心一个线程,绑定到物理核心效果最好
# 编辑 /etc/systemd/system/redpanda.service.d/override.conf
[Service]
ExecStartPre=/usr/bin/taskset -c 0-15 /opt/redpanda/bin/redpanda
# 2. 磁盘 I/O 调优
# 确保使用 io_uring(Redpanda v24.x 默认启用)
rpk cluster config set storage_min_prefetch_iops 0
# 3. 内存配置
# Redpanda 推荐:总内存的 80% 分配给 Redpanda
rpk cluster config set memory_allocation_warning_threshold 0.9
# 4. 分区数优化
# 每个分区需要一个文件句柄和一定的内存
# 推荐:每个核心 2-4 个分区
rpk topic create my-topic --partitions 16 --replication-factor 3
# 5. 批量大小调优
# 生产者端
rpk topic produce my-topic --batch-max-size 1048576 # 1MB
# 消费者端
rpk topic consume my-topic --fetch-max-bytes 5242880 # 5MB
第七章:Shadow Indexing 生产配置
7.1 启用 Shadow Indexing
# 1. 配置 S3 存储桶
rpk cluster config set cloud_storage_enabled true
rpk cluster config set cloud_storage_bucket your-bucket-name
rpk cluster config set cloud_storage_region us-east-1
rpk cluster config set cloud_storage_access_key AKIA...
rpk cluster config set cloud_storage_secret_key ...
# 2. 配置热数据保留策略
rpk cluster config set log_retention_ms 86400000 # 24 小时
rpk cluster config set log_retention_bytes 1073741824 # 1GB per partition
# 3. 配置 Shadow Indexing 策略
rpk cluster config set cloud_storage_disable_archiver false
rpk cluster config set cloud_storage_housekeeping_interval_ms 300000 # 5 分钟
# 4. 创建启用了 Shadow Indexing 的 Topic
rpk topic create my-topic \
--partitions 16 \
--replication-factor 3 \
--retention-ms 604800000 # 7 天(冷数据保留 7 天)
7.2 监控 Shadow Indexing
# 查看 Shadow Indexing 状态
rpk cluster health
# 查看每个分区的存储分层情况
rpk topic describe my-topic --print-partitions
# 关键指标:
# - cloud_storage_partition_read_bytes:从 S3 读取的字节数
# - cloud_storage_partition_write_bytes:写入 S3 的字节数
# - cloud_storage_partition_segments_fetched:从 S3 获取的段数
第八章:Redpanda vs Kafka vs Pulsar——终极对比
8.1 架构对比
| 特性 | Kafka | Redpanda | Pulsar |
|---|---|---|---|
| 语言 | Java | C++ (Seastar) | Java + C++ |
| 线程模型 | 线程池 | Thread-per-Core | 线程池 |
| 元数据存储 | ZooKeeper / KRaft | 内置 Raft | BookKeeper |
| 存储引擎 | 自研日志 | 自研日志 + io_uring | BookKeeper |
| 冷热分离 | 无(需手动) | Shadow Indexing | Tiered Storage |
| API 兼容 | 原生 | Kafka API 兼容 | 独立协议 |
| 运维复杂度 | 高 | 低 | 高 |
8.2 性能对比(基准测试)
| 场景 | Kafka 3.7 | Redpanda 24.2 | Pulsar 3.3 |
|---|---|---|---|
| 单分区吞吐(MB/s) | 250 | 680 | 180 |
| 16 分区吞吐(MB/s) | 1,800 | 4,200 | 1,200 |
| P99 生产延迟 | 12ms | 2ms | 15ms |
| P99 消费延迟 | 8ms | 1.5ms | 10ms |
| 内存占用(相同负载) | 8GB | 2.5GB | 6GB |
| 冷启动时间 | 25s | 3s | 45s |
8.3 选型建议
- 选择 Kafka:团队 Java 技术栈成熟,已有大量 Kafka 生态工具,对延迟要求不高
- 选择 Redpanda:追求极致性能和低延迟,需要冷热数据分离,希望简化运维
- 选择 Pulsar:需要多租户隔离,需要原生的多活(Geo-Replication),有 BookKeeper 运维经验
第九章:Redpanda 的未来——AI Agent 数据平面
9.1 从流式平台到 Agent 基础设施
2025 年底,Redpanda 发布了一个重要战略方向:Agent Data Plane。这是将流式数据平台定位为 AI Agent 的数据基础设施层。
核心思路:
AI Agent 需要的不只是「模型」,还有「数据」:
┌─────────────────────────────────────────┐
│ AI Agent │
│ ┌──────────────────────────────────┐ │
│ │ 模型层(LLM) │ │
│ │ - 推理 │ │
│ │ - 规划 │ │
│ │ - 决策 │ │
│ └──────────────────────────────────┘ │
│ ┌──────────────────────────────────┐ │
│ │ 数据层(Redpanda) │ │
│ │ - 实时事件流 │ │
│ │ - 上下文窗口 │ │
│ │ - 长期记忆 │ │
│ │ - 多 Agent 协作 │ │
│ └──────────────────────────────────┘ │
└─────────────────────────────────────────┘
9.2 Redpanda SQL:流式数据的 SQL 接口
Redpanda 最近收购了 Oxla(一个 MPP SQL 引擎团队),正在开发 Redpanda SQL——一个 Postgres 兼容的查询引擎,可以直接在流式数据上执行 SQL 查询:
-- Redpanda SQL 示例:实时分析用户行为
SELECT
user_id,
COUNT(*) as event_count,
COUNT(DISTINCT page) as unique_pages,
AVG(duration_ms) as avg_duration
FROM user_events
WHERE event_time > NOW() - INTERVAL '1 hour'
GROUP BY user_id
HAVING event_count > 10
ORDER BY event_count DESC;
-- 物化视图:实时聚合
CREATE MATERIALIZED VIEW hourly_metrics AS
SELECT
date_trunc('hour', event_time) as hour,
event_type,
COUNT(*) as count
FROM events
GROUP BY 1, 2;
-- 流式 JOIN:实时关联用户和订单
SELECT
u.user_id,
u.name,
o.order_id,
o.amount
FROM users u
JOIN orders o ON u.user_id = o.user_id
WHERE o.created_at > NOW() - INTERVAL '5 minutes';
9.3 Agent 协作的数据流
# 多 Agent 协作场景:Redpanda 作为消息总线
from kafka import KafkaProducer, KafkaConsumer
import json
class AgentMessageBus:
def __init__(self):
self.producer = KafkaProducer(
bootstrap_servers=['localhost:19092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8'),
)
def publish_task(self, agent_id: str, task: dict):
"""发布任务到指定 Agent 的输入队列"""
self.producer.send(
f'agent.{agent_id}.input',
value={
'type': 'task',
'payload': task,
'timestamp': time.time(),
'trace_id': str(uuid.uuid4()),
}
)
def publish_result(self, agent_id: str, result: dict):
"""发布结果到共享结果流"""
self.producer.send(
'agent.results',
value={
'agent_id': agent_id,
'result': result,
'timestamp': time.time(),
}
)
def subscribe(self, topics: list, group_id: str):
"""订阅多个 Agent 的输出"""
return KafkaConsumer(
*topics,
bootstrap_servers=['localhost:19092'],
group_id=group_id,
value_deserializer=lambda m: json.loads(m.decode('utf-8')),
auto_offset_reset='latest',
)
# 使用示例:编排 Agent 工作流
bus = AgentMessageBus()
# Agent 1:数据采集
bus.publish_task('collector', {
'action': 'fetch_data',
'source': 'api',
'params': {'url': 'https://api.example.com/data'}
})
# Agent 2:数据处理
bus.publish_task('processor', {
'action': 'transform',
'input_topic': 'collector.output',
'transformations': ['clean', 'normalize', 'enrich']
})
# Agent 3:数据分析
bus.publish_task('analyzer', {
'action': 'analyze',
'input_topic': 'processor.output',
'metrics': ['count', 'sum', 'average', 'trend']
})
第十章:总结与展望
10.1 Redpanda 的核心创新
- Thread-per-Core + Shared-Nothing:通过 Seastar 框架实现极致的并行性能,消除锁竞争
- io_uring:Linux 异步 I/O 的终极方案,将系统调用开销降至最低
- 内置 Raft:彻底告别 ZooKeeper,简化运维,提升一致性保证
- Shadow Indexing:冷热数据分离,降低存储成本 70%+
- Kafka API 兼容:零成本迁移,无需修改客户端代码
10.2 Redpanda 的局限性
- C++ 开发门槛高:相比 Kafka 的 Java 生态,C++ 的贡献者门槛更高
- 生态成熟度:Kafka 拥有 Connect、Streams、ksqlDB 等完整生态,Redpanda 正在追赶
- 冷数据读取延迟:从 S3 读取冷数据有网络延迟,不适合频繁回溯的场景
- 社区规模:Redpanda 的社区(52K Star)相比 Kafka 仍较小
10.3 未来展望
Redpanda 正在从一个「Kafka 替代品」进化为一个实时数据平台。随着 Agent Data Plane 和 Redpanda SQL 的推进,它正在构建一个从数据采集、流处理、存储到查询的完整栈。
对于开发者来说,Redpanda 代表了一种新的系统设计哲学:用系统级编程语言重新审视每一个「理所当然」的抽象层,从底层操作系统交互到上层 API 设计,找到性能的终极边界。
正如 TigerBeetle 用 Zig 重新定义了金融数据库,OXC 用 Rust 重写了 JavaScript 工具链,Redpanda 用 C++ 重新定义了流式数据平台。这不是语言的胜利,而是对性能极致追求的胜利。
本文首发于 程序员茄子,欢迎关注获取更多深度技术解析。