资讯动态

第1讲:分布式系统基础与 CAP 实战

发布时间:2026/10/24 4:07:46 来源:尧图企业网站定制
一、CAP 定理的直觉理解CAP 定理说一个分布式系统最多只能同时满足以下三者中的两个CConsistency一致性所有节点在同一时刻看到相同的数据AAvailability可用性每个请求都能收到一个非错误的响应PPartition Tolerance分区容错性系统中任意节点间的网络中断系统仍能正常工作关键洞察P 不是可选项是必选项。只要系统分布在网络上网络就可能断开。所以实际是在CP​ 和AP​ 之间做选择。正常情况 Client ──→ [Node A] ──网络── [Node B] 数据: x1 数据: x1 分区发生 Client ──→ [Node A] ╳╳╳╳ [Node B] 数据: x1 数据: x1 ↑ 此时 Client 写入 x2 ↓ 分区恢复后B 不知道 x 变了CP 系统如 etcd、ZooKeeper的选择分区发生时牺牲可用性拒绝写入保证所有节点数据一致你访问的是哪个节点不重要重要的是数据一样AP 系统如 DNS、Cassandra的选择分区发生时牺牲一致性允许写入保证始终能响应但不同节点可能给出不同的答案二、核心数据结构// distributed/cap/types.go package cap import ( fmt sync time ) // NodeState 节点状态 type NodeState int const ( StateHealthy NodeState iota // 健康 StatePartitioned // 被分区隔离 StateRecovering // 分区恢复中 ) func (s NodeState) String() string { switch s { case StateHealthy: return healthy case StatePartitioned: return partitioned case StateRecovering: return recovering default: return unknown } } // ConsistencyLevel 一致性级别 type ConsistencyLevel int const ( StrongConsistency ConsistencyLevel iota // 强一致性 (CP) EventualConsistency // 最终一致性 (AP) ) // KVEntry 键值对条目 type KVEntry struct { Key string json:key Value string json:value Version int64 json:version UpdatedAt time.Time json:updated_at NodeID string json:node_id } // ReadResult 读取结果 type ReadResult struct { Entry *KVEntry Success bool Error error } // WriteResult 写入结果 type WriteResult struct { Success bool Error error } // PartitionSimulator 网络分区模拟器 type PartitionSimulator struct { mu sync.RWMutex partitions map[string][]string // nodeID - []blockedNodeIDs isPartition bool } func NewPartitionSimulator() *PartitionSimulator { return PartitionSimulator{ partitions: make(map[string][]string), } } // IsolateNode 隔离节点 func (ps *PartitionSimulator) IsolateNode(nodeID string) { ps.mu.Lock() defer ps.mu.Unlock() ps.isPartition true ps.partitions[nodeID] []string{*} // 阻止与所有节点通信 fmt.Printf([PARTITION] Node %s is now isolated\n, nodeID) } // CanCommunicate 检查两个节点能否通信 func (ps *PartitionSimulator) CanCommunicate(from, to string) bool { ps.mu.RLock() defer ps.mu.RUnlock() if !ps.isPartition { return true } blocked, exists : ps.partitions[from] if !exists { return true } for _, b : range blocked { if b * || b to { return false } } return true } // HealAll 恢复所有分区 func (ps *PartitionSimulator) HealAll() { ps.mu.Lock() defer ps.mu.Unlock() ps.isPartition false ps.partitions make(map[string][]string) fmt.Println([PARTITION] All partitions healed) }三、CP 模式实现强一致性// distributed/cp/store.go package cp import ( fmt log sync sync/atomic time distributed/cap ) // CPStore 强一致性存储CP 系统 // 特点写入必须同步到多数节点才返回成功 // 分区时牺牲可用性拒绝写入 type CPStore struct { nodes map[string]*CPNode quorumSize int sim *cap.PartitionSimulator } type CPNode struct { ID string data map[string]*cap.KVEntry version int64 mu sync.RWMutex } func NewCPStore(nodeIDs []string, sim *cap.PartitionSimulator) *CPStore { nodes : make(map[string]*CPNode) for _, id : range nodeIDs { nodes[id] CPNode{ ID: id, data: make(map[string]*cap.KVEntry), } } return CPStore{ nodes: nodes, quorumSize: len(nodeIDs)/2 1, // 多数派 sim: sim, } } // Write 强一致性写入 func (s *CPStore) Write(key, value string) *cap.WriteResult { log.Printf([CP] Writing key%s value%s (quorum%d), key, value, s.quorumSize) entry : cap.KVEntry{ Key: key, Value: value, Version: time.Now().UnixNano(), UpdatedAt: time.Now(), } // 收集所有节点的写入响应 successCount : int32(0) var wg sync.WaitGroup var mu sync.Mutex var errors []string for nodeID, node : range s.nodes { // 检查分区 if !s.sim.CanCommunicate(coordinator, nodeID) { log.Printf([CP] Cannot reach node %s (partitioned), nodeID) mu.Lock() errors append(errors, fmt.Sprintf(node %s unreachable, nodeID)) mu.Unlock() continue } wg.Add(1) go func(id string, n *CPNode) { defer wg.Done() n.mu.Lock() n.data[key] entry n.version entry.Version n.mu.Unlock() atomic.AddInt32(successCount, 1) log.Printf([CP] Node %s acknowledged write, id) }(nodeID, node) } wg.Wait() // 检查是否达到多数派 if int(successCount) s.quorumSize { log.Printf([CP] Write FAILED: got %d acks, need %d, successCount, s.quorumSize) return cap.WriteResult{ Success: false, Error: fmt.Errorf(write quorum not met: got %d/%d, successCount, s.quorumSize), } } log.Printf([CP] Write SUCCESS: %d/%d nodes acknowledged, successCount, len(s.nodes)) return cap.WriteResult{Success: true} } // Read 强一致性读取 func (s *CPStore) Read(key string) *cap.ReadResult { log.Printf([CP] Reading key%s (quorum%d), key, s.quorumSize) type nodeResponse struct { nodeID string entry *cap.KVEntry version int64 } responses : make(chan nodeResponse, len(s.nodes)) var wg sync.WaitGroup for nodeID, node : range s.nodes { if !s.sim.CanCommunicate(coordinator, nodeID) { log.Printf([CP] Cannot reach node %s (partitioned), nodeID) continue } wg.Add(1) go func(id string, n *CPNode) { defer wg.Done() n.mu.RLock() entry : n.data[key] ver : n.version n.mu.RUnlock() if entry ! nil { responses - nodeResponse{nodeID: id, entry: entry, version: ver} } }(nodeID, node) } wg.Wait() close(responses) // 收集响应找版本最新的 var latestEntry *cap.KVEntry responseCount : 0 for resp : range responses { responseCount if latestEntry nil || resp.version latestEntry.Version { latestEntry resp.entry } } if responseCount s.quorumSize { log.Printf([CP] Read FAILED: got %d responses, need %d, responseCount, s.quorumSize) return cap.ReadResult{ Success: false, Error: fmt.Errorf(read quorum not met: got %d/%d, responseCount, s.quorumSize), } } if latestEntry nil { return cap.ReadResult{ Success: true, Entry: nil, // key not found } } log.Printf([CP] Read SUCCESS: key%s value%s version%d (from %d nodes), key, latestEntry.Value, latestEntry.Version, responseCount) return cap.ReadResult{Success: true, Entry: latestEntry} }四、AP 模式实现最终一致性// distributed/ap/store.go package ap import ( fmt log math/rand sync time distributed/cap ) // APStore 最终一致性存储AP 系统 // 特点写入总是成功分区时各节点独立写入 // 分区恢复后通过反熵Anti-Entropy合并冲突 type APStore struct { nodes map[string]*APNode sim *cap.PartitionSimulator conflictFn ConflictResolver } type APNode struct { ID string data map[string]*cap.KVEntry mu sync.RWMutex } // ConflictResolver 冲突解决策略 type ConflictResolver func(existing, incoming *cap.KVEntry) *cap.KVEntry func NewAPStore(nodeIDs []string, sim *cap.PartitionSimulator) *APStore { nodes : make(map[string]*APNode) for _, id : range nodeIDs { nodes[id] APNode{ ID: id, data: make(map[string]*cap.KVEntry), } } // 默认冲突解决取版本号大的Last Writer Wins defaultResolver : func(existing, incoming *cap.KVEntry) *cap.KVEntry { if existing nil { return incoming } if incoming.Version existing.Version { return incoming } return existing } return APStore{ nodes: nodes, sim: sim, conflictFn: defaultResolver, } } // SetConflictResolver 设置自定义冲突解决策略 func (s *APStore) SetConflictResolver(fn ConflictResolver) { s.conflictFn fn } // Write 最终一致性写入 func (s *APStore) Write(nodeID, key, value string) *cap.WriteResult { node, exists : s.nodes[nodeID] if !exists { return cap.WriteResult{ Success: false, Error: fmt.Errorf(node %s not found, nodeID), } } entry : cap.KVEntry{ Key: key, Value: value, Version: time.Now().UnixNano(), UpdatedAt: time.Now(), NodeID: nodeID, } node.mu.Lock() node.data[key] entry node.mu.Unlock() log.Printf([AP] Node %s wrote key%s value%s (version%d), nodeID, key, value, entry.Version) // AP 系统写入总是成功不等待其他节点确认 return cap.WriteResult{Success: true} } // Read 最终一致性读取可能读到旧数据 func (s *APStore) Read(nodeID, key string) *cap.ReadResult { node, exists : s.nodes[nodeID] if !exists { return cap.ReadResult{ Success: false, Error: fmt.Errorf(node %s not found, nodeID), } } node.mu.RLock() entry : node.data[key] node.mu.RUnlock() if entry nil { return cap.ReadResult{Success: true, Entry: nil} } log.Printf([AP] Node %s read key%s value%s (version%d), nodeID, key, entry.Value, entry.Version) return cap.ReadResult{Success: true, Entry: entry} } // AntiEntropy 反熵同步节点间交换数据以达成最终一致 func (s *APStore) AntiEntropy() { log.Println([AP] Starting anti-entropy sync...) nodeIDs : make([]string, 0, len(s.nodes)) for id : range s.nodes { nodeIDs append(nodeIDs, id) } // 随机配对同步 rand.Shuffle(len(nodeIDs), func(i, j int) { nodeIDs[i], nodeIDs[j] nodeIDs[j], nodeIDs[i] }) for i : 0; i len(nodeIDs)-1; i { fromID : nodeIDs[i] toID : nodeIDs[i1] // 检查分区是否恢复 if !s.sim.CanCommunicate(fromID, toID) { log.Printf([AP] Cannot sync %s ↔ %s (still partitioned), fromID, toID) continue } fromNode : s.nodes[fromID] toNode : s.nodes[toID] // 读取 from 的所有数据 fromNode.mu.RLock() fromData : make(map[string]*cap.KVEntry) for k, v : range fromNode.data { fromData[k] v } fromNode.mu.RUnlock() // 合并到 to toNode.mu.Lock() for key, incoming : range fromData { existing : toNode.data[key] resolved : s.conflictFn(existing, incoming) if resolved ! existing { toNode.data[key] resolved log.Printf([AP] Synced key%s: %s → %s (resolved by %s), key, safeValue(existing), safeValue(incoming), resolved.NodeID) } } toNode.mu.Unlock() log.Printf([AP] Synced %s → %s, fromID, toID) } log.Println([AP] Anti-entropy completed) } func safeValue(e *cap.KVEntry) string { if e nil { return nil } return fmt.Sprintf(%s%d, e.Value, e.Version) }五、CAP 对比实验// examples/cap_demo.go package main import ( fmt log time distributed/ap distributed/cap distributed/cap/types ) func main() { fmt.Println( CAP 定理实战演示 \n) nodeIDs : []string{node-a, node-b, node-c} sim : types.NewPartitionSimulator() // 1. 初始化 CP 和 AP 存储 cpStore : cap.NewCPStore(nodeIDs, sim) apStore : ap.NewAPStore(nodeIDs, sim) // 2. 正常情况下的写入和读取 fmt.Println(【场景1】网络正常时\n) fmt.Println(--- CP 系统 ---) result : cpStore.Write(name, Alice) fmt.Printf(写入结果: success%v, result.Success) if result.Error ! nil { fmt.Printf(, error%v, result.Error) } fmt.Println() readResult : cpStore.Read(name) if readResult.Success readResult.Entry ! nil { fmt.Printf(读取结果: key%s value%s\n\n, readResult.Entry.Key, readResult.Entry.Value) } fmt.Println(--- AP 系统 ---) result apStore.Write(node-a, name, Bob) fmt.Printf(写入结果: success%v\n, result.Success) readResult apStore.Read(node-a, name) if readResult.Success readResult.Entry ! nil { fmt.Printf(读取结果: key%s value%s\n\n, readResult.Entry.Key, readResult.Entry.Value) } // 3. 网络分区时的表现 fmt.Println(【场景2】网络分区发生\n) // 隔离 node-c sim.IsolateNode(node-c) fmt.Println(\n--- CP 系统分区后写入---) result cpStore.Write(age, 30) fmt.Printf(写入结果: success%v, result.Success) if result.Error ! nil { fmt.Printf(, error%v, result.Error) } fmt.Println() // CP 系统会失败因为只有 2/3 节点可达不满足多数派 fmt.Println(\n--- AP 系统分区后写入---) result apStore.Write(node-c, city, Beijing) // 在被隔离的节点上写入 fmt.Printf(写入 node-c 结果: success%v\n, result.Success) result apStore.Write(node-a, city, Shanghai) // 在正常节点上写入 fmt.Printf(写入 node-a 结果: success%v\n, result.Success) fmt.Println(\n--- AP 系统分区后读取---) readResult apStore.Read(node-c, city) if readResult.Success readResult.Entry ! nil { fmt.Printf(node-c 读取: city%s\n, readResult.Entry.Value) } readResult apStore.Read(node-a, city) if readResult.Success readResult.Entry ! nil { fmt.Printf(node-a 读取: city%s\n, readResult.Entry.Value) } // AP 系统两个节点读到不同值出现不一致 // 4. 分区恢复后的反熵同步 fmt.Println(\n【场景3】分区恢复\n) sim.HealAll() time.Sleep(100 * time.Millisecond) fmt.Println(--- AP 系统反熵同步 ---) apStore.AntiEntropy() fmt.Println(\n--- 同步后读取 ---) readResult apStore.Read(node-a, city) if readResult.Success readResult.Entry ! nil { fmt.Printf(node-a 读取: city%s (version%d, from%s)\n, readResult.Entry.Value, readResult.Entry.Version, readResult.Entry.NodeID) } readResult apStore.Read(node-b, city) if readResult.Success readResult.Entry ! nil { fmt.Printf(node-b 读取: city%s (version%d, from%s)\n, readResult.Entry.Value, readResult.Entry.Version, readResult.Entry.NodeID) } readResult apStore.Read(node-c, city) if readResult.Success readResult.Entry ! nil { fmt.Printf(node-c 读取: city%s (version%d, from%s)\n, readResult.Entry.Value, readResult.Entry.Version, readResult.Entry.NodeID) } // 三个节点最终达成一致 // 5. 总结 fmt.Println(\n) fmt.Println( CAP 实验结论) fmt.Println() fmt.Println(CP 系统etcd/ZooKeeper) fmt.Println( - 分区时拒绝写入保证一致性) fmt.Println( - 适合元数据、配置、分布式锁) fmt.Println() fmt.Println(AP 系统DNS/Cassandra) fmt.Println( - 分区时接受写入保证可用性) fmt.Println( - 分区恢复后通过反熵达成一致) fmt.Println( - 适合社交动态、CDN、IoT 数据) fmt.Println() }六、生产坑位#坑位现象解决方案1误解 CAP 为三选二​以为可以在 C、A、P 中任意选两个记住 P 是必选的实际是 CP vs AP 的权衡2假一致性​用了 CP 系统但客户端绕过协调者直接读副本所有读写必须经过共识层3分区检测延迟​网络抖动导致频繁进出分区状态增加心跳超时阈值使用自适应检测4反熵风暴​分区恢复后大量数据同步导致带宽打满增量同步限速分批次同步5时钟偏差导致 LWW 失效​节点间时钟不同步导致版本号不准NTP 同步逻辑时钟Lamport clockVector clock6读写未走同一节点​CP 系统中读请求分散到非 Leader 节点读到旧数据强制读 LeaderReadIndexLeaseRead七、本讲总结┌──────────────────────────────────────────────────────────────────────────┐ │ 第1讲 核心收获 │ │ │ │ ✅ 理解了 CAP 的本质P 是必选项实际是 CP vs AP 的权衡 │ │ │ │ ✅ 实现了 CP 存储 │ │ - 写入需要多数派确认 │ │ - 分区时拒绝写入保证一致性 │ │ - 适用于配置中心、分布式锁等场景 │ │ │ │ ✅ 实现了 AP 存储 │ │ - 写入总是成功保证可用性 │ │ - 分区时各节点独立写入可能出现不一致 │ │ - 分区恢复后通过反熵同步达成最终一致 │ │ │ │ ✅ 通过实验直观看到 │ │ - 分区时 CP 写入失败AP 写入成功但读到不同值 │ │ - 分区恢复后 AP 通过反熵达成一致 │ │ │ │ ✅ 覆盖了 6 个生产坑位与解决方案 │ │ │ └──────────────────────────────────────────────────────────────────────────┘八、下一讲预告第2讲《共识算法入门Paxos 的精髓》你将学到Basic Paxos 的三阶段Prepare → Promise → AcceptMulti-Paxos 的优化思路活锁问题及其解决方案从零实现一个迷你 Paxos 库Paxos 在生产系统中的真实应用 开发之余的小工具推荐在调试分布式系统的网络分区实验时经常需要生成和对比不同节点间的数据状态。zz365.top 的文本差异比较工具可以快速对比 CP 和 AP 模式下各节点的数据快照帮助你直观理解分区期间的数据分歧。其 JSON 格式化工具则可以将节点状态导出为结构化数据便于分析反熵同步的效果。所有处理均在浏览器本地完成。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑