深浅色
分布式与中间件(背诵版)
1. CAP 与 BASE
- CAP:一致性、可用性、分区容错。网络分区必然存在,所以只能 CP 或 AP。
- BASE:基本可用、软状态、最终一致,是 AP 的工程实践。
- 例子:etcd / ZooKeeper 偏 CP(选主期间不可用);Eureka、Cassandra 偏 AP。
- 追问:为什么 P 必须选?-> 分布式系统的网络一定会分区,放弃 P 等于放弃分布式。
2. 分布式事务
- 2PC:协调者先 prepare 再 commit,问题是同步阻塞 + 协调者单点。
- TCC:Try-Confirm-Cancel,业务侵入大,但补偿逻辑自己掌控。
- Saga:长事务拆成链式本地事务,失败反向补偿,适合长流程。
- 本地消息表 / 事务消息:把消息和业务写在同一个本地事务里,最终一致,最常用。
- 追问:为什么实际项目里很少用 2PC?-> 阻塞、性能差、协调者挂了会卡住全局。
3. Kafka 存储与顺序性
- 分区 + 分段(Segment)+ 稀疏索引 + 顺序追加写,读时二分定位再顺序扫描。
- 零拷贝 sendfile 提升消费吞吐。
- 顺序性只在单分区内保证:按 key 分区(同 key 落同分区)即可保证同 key 有序。
- 追问:要全局有序怎么办?-> 只用一个分区,牺牲并发。
4. 消息不丢、不重
- 不丢:生产者 acks=all + 重试;broker 副本数 >= 3 且 min.insync.replicas >= 2;消费者关闭自动提交,处理完再提交 offset。
- 不重:消费端做幂等(唯一键 + 去重表、Redis SETNX、状态机判断)。
- 追问:Kafka 的 exactly once 能端到端吗?-> 事务 + 幂等生产者可做到,但通常还是靠消费端幂等兜底。
5. 限流算法
- 固定窗口:简单,但有临界问题(窗口边界可能双倍流量)。
- 滑动窗口:把窗口细分,更平滑。
- 漏桶:恒定速率流出,能削峰但无法应对突发。
- 令牌桶:按速率放令牌,桶里攒着就能应对突发,最常用(Go 用 x/time/rate)。
- 追问:分布式限流怎么做?-> Redis + Lua 原子计数,或在网关层统一限流。
6. 熔断、降级、隔离
- 熔断三态:关闭 -> 打开(错误率超阈值)-> 半开(放少量请求试探)。
- 降级:返回兜底数据、读缓存、返回默认值。
- 隔离:线程池隔离、信号量隔离,避免一个依赖拖垮全部。
- 追问:有超时了为什么还要熔断?-> 超时只保护单次调用,熔断能快速失败、避免雪崩。
7. 分布式 ID
- 雪花算法:1 位符号 + 41 位时间戳 + 10 位机器 ID + 12 位序列号,趋势递增,每节点每秒 4096 个。
- 时钟回拨:等待、用备用位、或直接报错。
- 其他:数据库号段模式(Leaf)、Redis INCR。
- 追问:为什么不用 UUID 当主键?-> 无序,会导致 MySQL 页分裂和索引膨胀。
8. 服务注册发现与 Raft
- 注册:启动时写入 etcd / Consul / Nacos,靠心跳续约。
- 发现:客户端定时拉取或订阅变更。
- Raft:Leader 选举 + 日志复制,多数派确认才算提交;选举超时随机化避免分裂投票。
- 追问:etcd 为什么选 Raft 而不是 Paxos?-> 更好理解和实现,工程上更容易做对。
9. 链路追踪
- 入口生成 trace id,跨服务透传(HTTP header / gRPC metadata),每段生成 span id。
- 关键是上下文透传和采样上报(避免全量)。
- 追问:trace id 怎么跨 goroutine 传?-> 放进 context。
10. 一致性哈希
- 节点和数据都映射到哈希环,数据顺时针找第一个节点。
- 解决节点增减时的大规模数据迁移;用虚拟节点解决数据倾斜。
- 追问:Redis Cluster 用一致性哈希吗?-> 不用,用固定 16384 个槽位。