Controller 与 KRaft:从 ZooKeeper 到内置共识
上一篇看到了副本机制的细节。这一篇解决元数据层的核心问题:谁来决定哪个 replica 是 leader,以及这个决定怎么达成共识。 这个问题容易和副本选举混淆。副本选举本身只是一个结果——“partition-0 的 leader 从 broker-1 变成 broker-2”。真正的问题是:谁有权做出这个决定,这个决定如何传播到集群中的每个 broker,以及当做出决定的那个节点自己挂了会发生什么。 本文只抓一个问题:Kafka 的元数据管理从外部依赖(ZooKeeper)演化到内置共识(KRaft)的完整路径。 ZooKeeper 模式下的 Controller 在 ZooKeeper 模式下,Kafka 集群中有且仅有一个 broker 担任 controller 角色。Controller 是通过 ZooKeeper 的临时节点(ephemeral node)竞选产生的: 123456789101112 ZooKeeper Ensemble ┌───────────────────┐ │ /co...
副本与 ISR:高可用的代价和折中
前六篇走完了生产和消费两端。这一篇进入 Kafka 的高可用核心:副本机制。 副本机制容易被简化成"多写几份"。更准确的说法是:Kafka 在每个 partition 级别维护一组副本,用 ISR(In-Sync Replicas)动态集合代替固定多数派投票,在可用性和持久性之间做出了一系列显式的折中。 本文只抓一个问题:一条消息从被 leader 接收到被所有 ISR 副本确认,中间经历了哪些步骤,以及这些步骤中的每一个参数如何影响数据安全。 副本模型 Kafka 的副本模型是 per-partition 的 leader-follower 结构: 12345678910111213141516171819202122Producer │ ▼┌──────────────┐│ Partition-0 ││ Leader (B-0) │ ◄── 所有读写都经过 leader│ [0][1][2][3] │└──────┬───────┘ │ FetchRequest (replicaId=1) ▼┌─────────...
Offset 管理:提交、重置与消费语义
上一篇解决了 partition 怎么分给 consumer。这一篇解决分到之后的核心问题:consumer 读到哪里了,怎么记住。 这个"记住"机制看起来简单——不过是记录一个数字(offset)。但"什么时候记"和"怎么记"的选择,直接决定了消息会不会丢、会不会重复处理。auto-commit 的默认行为经常被误解为"自动保证 exactly-once",实际上它给出的是 at-least-once 甚至更弱的语义。 本文只抓一个问题:offset 的提交策略如何影响消费语义。 三个 offset 理解 offset 管理需要区分三个位置: 123456789Partition Log:┌────┬────┬────┬────┬────┬────┬────┬────┬─────┐│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ ... │└────┴────┴────┴────┴────┴────┴────┴────┴─────┘ ...
Consumer Group 协议:分配、重平衡与静态成员
前面四篇走完了写入端。这一篇转向读取端的核心问题:一组 consumer 怎么瓜分一个 topic 的所有 partition。 这个问题经常被简化成"负载均衡",但 consumer group 协议解决的不是负载均衡——它解决的是 partition 所有权的分配与变更。分配发生在 group 成员变化时,而每次变更的代价远比想象中大。 本文只抓一个问题:consumer group 的分配协议是怎么运作的,重平衡为什么贵,怎么降低代价。 consumer group 的基本契约 Kafka consumer group 有一条硬约束:同一个 group 内,一个 partition 在任意时刻只能被一个 consumer 消费。 这条约束的直接后果: group 内 consumer 数量超过 partition 数量时,多出来的 consumer 空转,分不到任何 partition。 partition 的消费进度由唯一的 consumer 负责推进,不存在两个 consumer 同时消费同一 partition 的情况。 consumer 数量变...
幂等 Producer 与序列号:消息不重不丢的第一层
上一篇走完了 Producer 的发送管线。这一篇解决一个写入端的经典问题:网络超时后重试,消息会不会写两遍。 重试导致重复是分布式系统的常见症状。Producer 发出一个请求,broker 写入成功但响应在网络中丢失,Producer 不知道成功了于是重发——同一条消息在日志中出现了两次。Kafka 从 0.11 版本开始,在 broker 端引入了基于序列号的去重机制,称为 idempotent producer。 本文只抓一个问题:幂等 Producer 是怎么用 PID 和序列号在 broker 端做去重的,它的边界在哪里。 重复问题的根源 先看一个没有幂等保护时的场景: 1234567891011Producer Broker (Leader) | | |--- ProduceRequest(msg-A) ------->| | |--- 写入 partition log...
Producer 内部机制:攒批、分区与 acks
上一篇拆解了 partition 内部的存储结构。这一篇转向写入端——Producer 把一条消息发出去,到底经过了哪些步骤。 很多使用者把 producer.send() 当成一次网络调用。实际情况是,send() 只是把消息丢进了一个内存缓冲区,真正的网络 I/O 发生在另一个线程里。Producer 内部是一条两阶段管线:主线程负责序列化和分区选择,后台 Sender 线程负责攒批和网络传输。 本文只抓一个问题:一条消息从 send() 调用到 broker 确认,经过了哪些对象、哪些线程、哪些等待。 发送管线总览 Producer 内部的数据流可以压成下面的路径: 123456789101112131415161718192021222324252627282930Main Thread Sender Thread | | v | Interceptors ...
日志存储:Segment、Index 与零拷贝
上一篇看到了 Kafka 集群的全景:broker 存储日志,controller 管理元数据,topic 通过 partition 映射到磁盘目录。这一篇进入单个 partition 内部,看日志文件到底怎么组织。 一个 partition 不是一个巨大的文件。更准确的说法是:一个 partition 是一组按 offset 范围切分的 segment 文件,每个 segment 由数据文件、offset 索引文件和时间索引文件组成。 本文只抓一个问题:一条 produce 请求写入的字节如何变成磁盘上的 segment 文件,以及 fetch 请求如何通过零拷贝把这些字节送到网络上。 Segment 文件:日志的物理切片 一个 partition 的日志目录中,segment 文件按 base offset 命名。Base offset 是该 segment 中第一条记录的 offset,用 20 位数字左补零表示。 12345678910partition 目录: orders-0/├── 00000000000000000000.log ← segme...
生产集群运维——升级、备份、故障排查与容量规划
把 Kubernetes 集群跑起来是第一步,让它在生产环境中长期稳定运行才是真正的挑战。版本升级、etcd 备份恢复、节点维护、故障排查、容量规划——这些 Day-2 运维场景,每一个都有足以让集群中断服务的操作风险。Kubernetes 为这些场景提供了明确的工具和流程约定,但操作顺序和细节上的失误仍然是生产事故的主要来源。 版本升级是最高风险的操作之一。Kubernetes 的 API 在版本间存在兼容性约束(skew policy),错误的升级顺序——例如先升级 kubelet 而非先升级 control plane——会导致集群进入不一致状态,轻则调度异常,重则 API Server 无法与节点通信。etcd 备份看似简单,但备份时机(必须在升级前)、恢复流程(必须停止 API Server 再恢复)、验证方式(恢复后检查关键对象)如果有任何一步出错,都可能导致数据丢失或集群无法启动。 节点维护(drain + upgrade + uncordon)有 PodDisruptionBudget(PDB)这道安全网,但前提是 PDB 本身配置正确。容量规划需要理解"...
可观测性——Metrics、Logging、Tracing 的集群实践
一个运行在 Kubernetes 上的分布式系统,出问题时最难回答的问题是"发生了什么"。单机时代,登录服务器查看进程状态和日志文件,基本能还原现场。容器化之后,Pod 随时可能被调度到不同节点,崩溃后立即重启,原始日志消失,这套方法彻底失效。可观测性(Observability)是对这个问题的系统性回答:通过在系统内部埋点、采集、存储和展示三类信号——Metrics(指标)、Logging(日志)、Tracing(链路追踪)——让工程师在不登录服务器的情况下理解系统行为。 Kubernetes 本身对可观测性有明确的架构分工。采集点(kubelet 内嵌的 cAdvisor、node-exporter DaemonSet、应用 SDK)由各组件负责暴露,聚合层(Prometheus、metrics-server)负责拉取和存储,展示层(Grafana、Jaeger UI)负责查询和可视化。这种"采集点内建、存储和展示留给生态"的设计,让 Kubernetes 本身保持简洁,同时允许不同规模的集群选择不同的存储方案。 理解可观测性的关键区分...
Helm 与应用打包——从 YAML 到可复用制品
在 Kubernetes 上部署一个生产级应用,往往需要几十个甚至上百个 YAML 文件:Deployment、Service、ConfigMap、Ingress、RBAC 规则、HorizontalPodAutoscaler……这些文件之间存在依赖关系,不同环境(开发、预发、生产)需要不同参数,手工管理极易出错。Helm 正是为了解决这个问题而生:把一组相关的 Kubernetes 资源打包成一个可版本化、可参数化、可共享的制品(Chart),并提供安装、升级、回滚、卸载的生命周期管理。 Helm 的本质是"带版本历史的 YAML 打包器"。它不追踪资源的真实运行状态(那是 Operator 的职责),只记录"这次 install/upgrade 渲染出了哪些 manifest,交给 Kubernetes 执行"。Release 状态存储在集群内的 Secret 对象中(key 为 helm.sh/release.v1),每次升级都新增一个版本,rollback 本质上是重新 apply 旧版本的 manifest 集合。 理解 Helm...
