ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

分布式计算系统入门:核心概念、CAP定理与技术地图

分布式计算系统入门:核心概念、CAP定理与技术地图 很多想入行分布式系统的新同学第一次找我聊天就问我该先学 Hadoop 还是 Spark每次听到这种问题我都想拦一下你连分布式计算系统到底难在哪都没搞清楚直接冲到框架层面多半会被绕晕学完只会调参数换个场景就抓瞎。我打算写一个系列从最基础的认知开始讲这一篇就是整个系列的第一章——不碰任何具体框架源码只把分布式计算系统这个概念的骨架搭起来它解决什么问题、由哪些要素组成、为什么在某些地方会出我们想都想不到的故障以及你在脑海里应该建立一张什么样的技术地图。这个系列适合谁呢主要是两类人一类是刚接触后端或大数据的工程师知道几个分布式框架的名字但说不清它们之间的差别另一类是工作两三年、天天在用 Kafka 和 Redis却总觉得这些系统是黑盒的开发者。读完这一章你未必能立刻写出一个分布式系统但再看任何分布式组件时你会有一种它在这个地图上的哪个位置、它替我把什么麻烦扛下来了的感觉。1. 定义之争多台机器连在一起就算分布式计算系统吗1.1 先排除三个看起来像的反例我在面试实习生的时候经常先问一个热身问题你们公司项目部署了十台服务器前面挂个负载均衡这算不算分布式计算系统得到的回答经常是算。在深入了解概念之前这个答案是合理的但从严格定义出发它只算一个分布式部署的 Web 服务距离分布式计算系统还差着一层。先看三个反例帮你把边界划清楚。第一个反例一台机器上的多核并行程序。很多人用 OpenMP 写多线程让 16 个核同时算矩阵乘法速度确实快了很多。但这属于并行计算不属于分布式计算。关键区别在于所有线程共享同一块物理内存它们之间通过内存来通信不需要网络、不需要考虑另一台机器宕机后怎么办。分布式系统里没有这块共享内存节点之间只能靠网络消息传递复杂的源头就从这里开始了。第二个反例一台主机挂几十个终端。上世纪六七十年代的大型机就是这么干的几十个用户用哑终端连到一台主机。计算、存储全都在主机上终端只是个输入输出设备。这甚至不能算分布因为计算能力根本没有离开主机。第三个反例负载均衡后面的无状态集群。十台 Web 服务器跑同样的代码Nginx 把请求轮流分发过去某台挂了其他机器继续干活。这当然是分布式部署也有容错能力但如果这些服务器之间完全不共享状态、不协作完成一个任务学界更愿意叫它集群而非典型的分布式计算系统。这里我不是在抠字眼而是想让你意识到负载均衡解决的只是请求往哪儿发真正的分布式计算还涉及任务怎么拆分结果怎么汇总多个节点怎么达成一致这比资源调度复杂得多。1.2 三个硬性标志自治、协作、透明的错觉那么什么样的系统才算是严格意义上的分布式计算系统比较通用的定义来自分布式系统领域的经典教材分布式系统是一组自治的计算机通过网络互联彼此协作完成共同的目标且对用户而言表现得像一个单一系统。拆开看三个核心要素。第一节点自治。每个节点是一台有独立 CPU、内存、硬盘、操作系统的机器有自己的生命周期。它可能随时加入、退出、崩溃没有任何一个节点能直接访问另一个节点的内存。这就意味着节点之间不能像单机程序那样改一个全局变量就通知所有人所有信息交换都必须通过网络消息完成。第二通过消息传递协作。这是分布式计算的底层事实。消息传递有两个麻烦一是有延迟而且延迟不可预测二是可能丢失、重复、乱序。单机程序里函数调用要么成功要么报错结果明确分布式场景里你发出去一个消息根本不知道对方收到没有也不知道它什么时候处理完。所有分布式系统的设计技巧本质上都是围绕消息不可靠这个事实在想办法。第三对外呈现单一系统的错觉。用户不应该感知到他访问的数据分散在几十台机器上他要的是一个统一的入口、统一的接口。当然现实世界里完全透明几乎做不到CAP 定理和各种工程权衡让系统在某些场景下不得不暴露出内部不一致。但这个单一系统的错觉仍然是分布式计算系统追求的目标只是程度深浅的问题。1.3 认知误差分片容易协调难新手最容易犯的错误是把分布式计算想象成把数据切成 N 块分配给 N 台机器大家同时算最后合并结果。这个画面没错但太理想化了。分片sharding只解决了横向扩展容量的问题真正麻烦的是协调coordination哪台机器负责哪一块任务执行到一半负责的节点挂了怎么办两个节点同时想写同一份数据谁先谁后怎么保证每个节点看到的数据是一致的举一个特别简单的类比让十个人一起写一篇一万字的报告可以每人写一千字然后拼接这是分片。但如果要求十个人协作写一章还得保证风格统一、事实一致、中途有人退出也能按时交付难度立刻上升一个数量级。分布式系统的复杂度全部藏在协作里而不是切分里。所以第一章先立一个结论分布式计算系统的核心挑战不是怎么把任务切开而是怎么让一群时而在线、时而不在线的节点在网络随时可能出问题的情况下仍然协作得像一个整体。2. 为什么要把单机拆成多机性能、容量、可用性三座大山2.1 单机的物理极限和商业账本你可能想问既然分布式这么麻烦那为什么早期的大型软件很多都跑在单机上因为单机曾经够用。但随着数据量和用户量增长单机会撞上三堵墙。第一堵墙是算力墙。单颗 CPU 的主频在 2010 年前后就基本摸到物理极限了继续堆主频功耗和散热都受不了。算力扩展只能朝多核和多机两个方向走。而多核受限于内存带宽和总线架构扩展性远不如直接加机器来得直接。第二堵墙是容量墙。单台服务器的磁盘容量再大也赶不上互联网公司每天产生的数据量。大数据时代数据量是以 PB 级计的单机根本放不下必须把数据散到成千上万台机器上。第三堵墙是可用性墙。单机意味着单点故障硬盘坏了、内存松了、机房断电整个服务就停了。用两台机器互相备份一台出问题另一台顶上可用性才能提升。这就是高可用最基本的逻辑。换句话说分布式是被逼出来的。不是因为大家喜欢复杂而是单机这条路在某个规模上一定会走到头。2.2 垂直扩展与水平扩展的选择业内把加资源的方式分成两类垂直扩展scale up和水平扩展scale out。两者不是简单二选一而是有一个非常现实的决策逻辑。维度垂直扩展Scale Up水平扩展Scale Out做法把单台机器的 CPU、内存、磁盘换大增加更多机器组成集群上限受单机硬件规格限制有天花板理论上可以无限加机器成本高性能硬件价格指数级上升普通硬件价格便宜但协调成本上升运维简单几乎不改造代码复杂要处理网络、一致性、容错适用阶段早期、数据量可控时数据量和访问量到一定规模后我的建议是能垂直扩展就垂直扩展因为它便宜、简单、不容易出事直到单机成本已经无法接受或者停机窗口无法容忍再考虑水平扩展。很多团队犯的错是过早分布式化系统只有几千个用户就引入了一整套微服务和消息队列。分布式不是荣誉勋章是不得已而为之的工程决策。2.3 分布式计算系统要接住的三大类问题拆开看分布式计算系统要解决的问题可以归成三类你在后续学习任何一个框架时都能对号入座。计算拆分问题一个巨大的计算任务怎么切成小片分配给多台机器并行执行再把结果合并典型如 MapReduce 里的 Map 阶段和 Reduce 阶段。数据存取问题海量数据放在多台机器上客户端怎么知道数据在哪写入时如何分片和复制某台机器挂了数据会不会丢典型如 HDFS、Cassandra、TiKV 的设计。协同一致问题多个节点怎么互斥地访问共享资源怎么选出一个领导节点怎么保证多个副本的数据最终达成一致典型如 ZooKeeper、etcd、Raft 算法做的事。大多数分布式系统都是这三类问题的组合。Spark 偏重计算拆分HDFS 偏重数据存取Paxos/Raft 偏重协同一致。看你系统缺哪块就找对应的框架。3. 分布式系统的坏消息八条一厢情愿的假设我见过很多刚接触分布式的工程师设计系统时默认网络是可靠的、机器是从不宕机的、传输是瞬间完成的。这些默认认知几乎全错。分布式领域有一份流传了三十年的经典清单叫分布式计算的谬误Fallacies of Distributed Computing是 SUN 的工程师在 1994 年总结的。我第一次看到时觉得这太夸张了后来自己踩过坑、看过生产事故才明白每条背后都是血淋淋的现实。3.1 网络不是可靠的也不是瞬时的这是最容易理解但最容易被忽略的一条。很多人在本地开发时从不考虑网络问题因为本机回环地址几乎不丢包、延迟趋近于零。可一旦上了真实环境跨机房、跨地域的链路随时可能抖动、中断、丢包。有同事曾经跟我吐槽他要等一个服务 A 调用服务 B 的响应代码里设了 5 秒超时。结果某个下午网络抖动持续了半分钟所有调用全部超时系统把大量请求标记为失败业务方炸了锅。后来我们改成了指数退避加重试并把超时阈值调整到与业务容忍度匹配问题才缓解。这个案例想说明的是设计分布式系统时必须假设任何一条网络请求都可能失败且失败可能不是立即的而是永远不返回。这才是真实世界的行为模式。3.2 带宽无限拓扑不变只要管理员一个后面几条我快速过一下但每一条都值得你以后设计系统时拿出来对照。带宽是无限的听起来离谱可不少系统上线时才发现传输日志、同步数据把机房带宽打满了。做数据量估算时必须把节点间同步、备份、对账的消息量都算进去而不是只算用户流量。网络拓扑不会改变实际上容器迁移、机器下线、网段调整每天都在发生。这也是为什么现代分布式系统都要做服务发现而不是把 IP 写死在配置文件里。只有一个管理员在大型公司里不同团队管理不同层级的机器策略网络团队管防火墙运维团队管配置安全团队管权限。你以为改个配置就完事实际要经过层层审批。设计系统时要考虑运维权限的边界否则上线时会卡在流程上。传输成本为零以及网络是同构的这两条也常被忽略。前者提醒你消息序列化和网络 IO 不是免费的高频小消息照样能把 CPU 烧光后者提醒你不同机房的机器配置可能完全不同有的 CPU 强、有的磁盘慢做负载均衡时不能假设所有节点等速。3.3 把架构评审变成一场假设审计既然清单里每一条都可能误导你那我建议你在做任何分布式设计评审时专门拿出一页来逐条检查自己的假设假设网络可靠——超时、重试、熔断都算好了吗假设延迟为零——远程调用有预算吗耗时会累积在哪里假设带宽无限——同步和复制消息占带宽的峰值算过吗假设拓扑不变——机器下线会不会影响路由假设只有一个管理员——你的配置谁有权改改错了谁来背锅假设传输成本为零——序列化开销、协议开销评估过吗假设同构——最慢的节点会不会拖垮整体假设网络是安全的——节点之间的认证与加密做了吗顺着这八条去评审很多设计缺陷在写代码前就会暴露。这一章是最值得反复回看的因为后面所有章节本质上都是在这八个不成立假设的基础上重建系统的可靠性。4. 第一章的概念地图节点、网络、时钟与状态4.1 三大基本元素与它们的坑为了后续讲具体系统时大家用同一套词汇这里先把分布式计算系统的三个基本元素拎出来节点Node、网络Network、和时间Clock。节点就是一台台计算机它负责执行计算、保存状态。节点的故障模式比想象中复杂它不只是正常或崩溃两种状态还可能处于假死——进程还在但没有响应、磁盘卡死、网络断开。这种状态最可怕因为其他节点无法判断它到底死没死只能靠超时来猜。而超时又引出了第三个元素——时间。网络负责消息传递但这张网又慢又不稳。节点间通信的路径、路由、拥塞都不可控。在设计系统时你要区分两类网络事实一类是消息可能永远丢失另一类是消息可能延迟很久才到。这两类在工程上的应对办法不同前者靠重试后者靠超时和幂等设计。时钟是分布式系统最隐蔽的坑。单机系统里所有进程共享一个时钟判断事件先后很容易。分布式系统里没有全局时钟每台机器的本地时钟还可能存在秒级偏差。即使你用 NTP 同步过时钟漂移和闰秒也会带来偏差。所以排序问题在分布式系统里变得非常棘手事件 A 和事件 B 哪一件先发生如果两台机器各自记录了日志你根本无法通过比较本地时间戳来还原真实顺序。也因此才有了逻辑时钟、向量时钟这些设计它们是 Lamport 大神为了给分布式事件排序建立严谨模型而提出的。这一章只需要知道全局时钟不存在这个事实即可具体算法后面会专门展开。4.2 有状态与无状态的分野用任意一个分布式系统时你都要先看清它的节点是有状态还是无状态的。无状态节点不持有业务数据随时可以被替换请求打到哪台机器都行。网页服务器就是典型它本身不存用户数据数据库和缓存另有专人在管理。无状态节点好伸缩扩缩容只要改变副本数量。有状态节点持有数据比如数据库、缓存、消息队列。有状态节点的难点是数据在哪个节点上节点挂了数据会不会丢多个副本之间怎么保持同步几乎所有的分布式算法——分片、复制、一致性协议——都是在解决有状态节点的问题。我后文讲主流系统时会反复提到这个分野。无状态系统是在做计算平移有状态系统才是在做数据协同。分布式真正的硬骨头都在后者。4.3 同步与异步两种世界观分布式系统还有一个基本分类同步系统和异步系统。这不是指编码里的 async/await 语法而是对系统行为的一种假设。同步模型假设消息传递有一定的时间上界进程以锁步的方式执行一个节点发消息后等待时间是有限的。这个模型太好推理了可惜现实世界几乎不存在。实际网络里一个包可能几毫秒就到也可能堵上几十秒。异步模型更贴近现实但代价是不能靠等待一段时间来判断一个节点是否故障。如果一个节点没响应你不知道它是死了还是只是消息在网络上堵住了。这就是分布式系统里著名的两将军问题延伸出来的困境靠消息传递永远无法完美确认对方状态。为了在这个困境下还能做决策工程师发展出了多数派投票租约心跳等机制。不必现在深究但请记住只要系统基于异步网络所有看似简单的判断都会变得困难。5. 一致性、分区与 CAP被误解最多的三件事5.1 CAP 的准确表述如果只记住分布式系统的一个理论那大概率是 CAP 定理。正因为太出名误解也最多。CAP 说的是一个分布式系统在网络分区发生时只能在一致性Consistency和可用性Availability之间二选一。注意网络分区指的是节点之间彻底失去联系比如机房间的光缆断了两边还在各自运行但彼此无法通信。这个前提非常关键。C一致性所有节点在同一时刻看到的数据是相同的。准确说是线性一致性任何读操作都能读到最近一次写操作的结果。A可用性任何非故障节点总能对请求产生响应。所谓总能意味着哪怕节点之间失联每个节点也都能继续应答。P分区容忍性系统在网络分区发生时仍能继续运行。CAP 定理的结论是在网络分区发生时系统必须选择放弃 C 或者放弃 A。因为分区之后两边不知道对方的状态如果坚持一致性就必须拒绝一部分请求放弃可用性如果坚持可用性两边就会各自写入数据产生不一致。5.2 为什么CA几乎不存在网上流传着三选二的说法你可以选 CA、CP 或 AP。这个说法误导了很多人好像 CA 是一个可以实现的选择。真相是P 不是可选可不选的而是分布式系统里必须接受的现实。只要系统部署在多台通过网络互联的机器上分区就可能发生。你不可能设计一个不允许分区的系统就像你不能设计一个不允许地震的建筑物。所以现实选择只有两种CP 系统分区发生时牺牲可用性拒绝部分请求保证一致。典型例子是 ZooKeeper、etcd它们在失去多数派时会停止服务宁可不可用也不给出错误数据。AP 系统分区发生时各自继续服务保证可用但可能出现数据不一致之后再靠后台同步收敛到一致。典型例子是 Cassandra、Dynamo。平时没有分区时系统可以同时提供一致性和可用性这也是为什么很多系统平时表现良好一遇上网络故障才原形毕露。CAP 定理的真正含义是警告你故障时刻的选择是你在设计阶段就要想清楚的。5.3 从 CAP 到 PACELC、共识与最终一致CAP 只是一个起点。后来有人把它扩展成 PACELC更全面地概括实际系统的权衡如果分区Partition发生你选择可用性和一致性如果分区没有发生你仍然要在延迟Latency和一致性Consistency之间取舍。为什么因为保证强一致需要节点间实时同步、多数派确认这必然增加延迟而很多业务为了低延迟宁愿接受短暂的不一致。顺着这个思路往下走你会碰到两个更基础的概念。最终一致指的是如果没有新的写操作系统经过一段时间的后台同步后所有副本会收敛到同一个值。最终一致不是什么都不管的借口它必须配合冲突解决策略比如最后写入者胜或版本向量合并否则收敛可能永远完不成。共识Consensus则是更强的需求多个节点必须对一个值达成一致比如谁是领导者某个提交是否生效。Raft、Paxos 就是解决这个问题的算法。可以这样说分布式系统里最核心、最困难的一个问题就是共识问题。这一章先记住它的存在。6. 一张图看懂主流分布式系统的分工学完理论再看具体技术栈就容易了。我按计算模式和系统角色两个维度帮你梳理一张地图覆盖目前最常见的分布式系统。6.1 三种计算模式批处理、流处理与交互式查询批处理处理的是已经完整存在的数据全集任务持续数分钟到数小时。典型是 Hadoop MapReduce 和 Spark 的离线任务。它们的共性是没有用户实时等待所以更看重吞吐量而非延迟。流处理处理的是源源不断到达的实时数据任务以秒级甚至毫秒级延迟持续运行。典型是 Flink、Kafka Streams。流处理的难点在于事件乱序、延迟和状态管理。交互式查询面向用户或分析师要求亚秒级/秒级返回结果。典型是 ClickHouse、Doris 这类 OLAP 数据库以及更通用的分布式 OLTP 数据库TiDB、CockroachDB。它们对延迟和一致性非常敏感。同一个业务系统可能同时需要这三种模式白天接收实时交易交互式查询 流处理晚上批量生成报表批处理。因此你不该问哪个框架最好而该问我当前的业务属于哪种计算模式。6.2 分布式系统的角色分工按角色分工主流系统大致如下角色典型系统解决的核心问题分布式计算引擎Hadoop MapReduce、Spark、Flink任务拆分、调度、结果聚合分布式文件系统HDFS、Ceph、MinIO海量文件存储、副本管理分布式数据库/存储Cassandra、TiDB、TiKV、Doris数据分片、复制、事务与会话分布式消息队列Kafka、Pulsar解耦生产者和消费者、削峰填谷、持久化分布式协调服务ZooKeeper、etcd分布式锁、领导者选举、配置管理资源调度Kubernetes、YARN、Nomad容器编排、资源分配、故障重启你注意看这个表格每个角色解决的核心问题都是前面几章提到的底层要素的组合。分布式文件系统解决的是数据存取和数据复制消息队列解决的是网络不可靠下的异步通信协调服务解决的是共识和领导者选举。这个角色—核心问题的对应关系比我列一百个框架名字更有用。7. 学习建议第一章结束后你应该带走什么7.1 三个必须完成的思维转变从单机思维转向分布式思维有三道坎。第一道坎从程序是指令序列到程序是节点间协作协议。单机程序控制流是确定的if 条件成立就执行某个分支分布式程序里每个节点都在独立推进它们的交互靠消息而消息是不可靠的。你写的不再是执行流程而是在所有可能的乱序、超时、失败情况下依然能收敛到正确结果的协议。第二道坎从确定性调试到可能性思考。单机程序出 bug可以断点调试一步步稳定复现。分布式系统里的 bug 往往是概率性的一百次请求里有一次超时某个节点偶尔宕机日志顺序偶发错乱。你没法靠打断点来调试只能靠日志、指标、追踪来观察系统行为模式。这就要求你在一开始就考虑可观测性而不是等项目出问题才想起打日志。第三道坎从追求完全正确到接受最终一致但可收敛。很多业务场景不需要强一致比如社交媒体的粉丝数、购物车的库存数量短暂地显示旧数据用户根本感知不到。你要学会判断业务的一致性需求是什么等级再去选择对应的系统设计而不是一律要求强一致。7.2 三个可以动手验证的小实验光看理论不落地过两周准忘。我建议你也这样动手验证一遍实验一一台机器 vs 两台机器的网络行为差异。准备两台机器哪怕是两台云主机在 A 上 ping B观察延迟波动再在单机上 ping 回环地址对比延迟。你会非常直观地感受到网络是一个不稳定的系统而这将是分布式系统的常态。实验二模拟单点故障。用 Docker Compose 起一个 Nginx 两个后端服务然后手动 kill 掉其中一个后端容器观察业务侧的表现。你会发现请求开始出现失败直到负载均衡器把故障节点摘掉。这大概是最简单的分布式容错实验了但足够让你理解故障检测和服务发现在系统中的分量。实验三体验分区带来的不一致。如果你身边有一套带复制的存储比如 Redis Cluster 或关系数据库的主从可以强行把主节点与从节点断网例如用 iptables 断开两个 IP 的通信再往主节点写入数据观察从节点读取是否一致以及恢复网络后数据怎么收敛。这个过程会把你对 CAP 的抽象理解变成具体的感受。这类实验比较危险建议一定要在测试环境做不要碰生产环境。这三件事做完比单纯读十篇论文管用。我自己当年就是从ping 都能丢包这个现象开始理解分布式系统的。7.3 给新手排掉三个常见误区最后排三个我反复见到的认知误区。误区一分布式系统就是性能更快。不一定。节点数量增加协调和同步的开销也增加。当任务本身不适合并行化时分布式甚至比单机更慢。分布式换来的是扩展性、容错性和资源池化而不是盲目地速度提升。误区二最终一致 随便什么时候读都一致。最终一致强调的是一个收敛过程不是任何时刻任何节点都立刻能看到最新数据。业务上如果对实时性有要求一定要在架构上做区分不能寄希望于底层最终一致能兜底。误区三用了消息队列就是分布式了。消息队列只是分布式系统中的一个组件把系统从同步调用改成异步解耦不代表整个系统就具备了分布式计算的能力。判断一个系统是不是真正分布式要看它是否在多个节点上拆分计算、共享状态、并处理节点故障和一致性问题而不是看它用了多少中间件。第一周学完这一章我的期望只有一个你能指着公司里任何一个系统说出它属于哪一类分布式角色、它依赖哪些底层要素、它在 CAP 的权衡中选择站哪边。有了这张地图后面不管学习 Raft 算法、MapReduce 原理还是具体框架源码你都知道该把它们挂在地图的哪一个格子里。
返回列表