云计算核心算法全景:从负载均衡到分布式调度
云计算发展到今天已经从“把物理机切成虚拟机卖”进化成了“把算力变成像水电一样的基础资源”。但我这几年在云平台团队做性能优化最深的感受是真正决定一个云产品能不能“稳、省、快”的往往不是芯片、也不是网卡而是藏在资源调度、数据存储、网络转发背后的那一层算法。算法在云计算体系里属于承上启下的位置——上面承接业务对资源无限扩张的诉求下面压制物理设备的单机性能上限做得不好再多机器也白搭。这篇文章是云计算系列里专门讲算法的一篇。我不打算把算法教材里的定义再抄一遍而是想从“一个请求从用户点下按钮到最终返回结果”这条链路出发把云平台里真正在跑的算法过一遍负载均衡怎么分流量、调度器怎么挑机器、数据存三副本还是纠删码、分布式事务靠什么算法保证一致、云上跑AI训练时怎么分卡。适合刚入行的运维和后端开发也适合准备架构师面试、想查漏补缺的同学。1. 云计算算法全景图一条请求背后的算法链路1.1 为什么云上的算法和课本算法不一样课本教算法时默认的前提是“一个进程、一份数据、一台永远不会宕机的机器”。在这个前提下面最短路就是Dijkstra排序就是快排字符串匹配就是KMP思路清晰、结果确定。但云上面对的是完全不同的约束成千上万的并发请求、随时可能宕机的物理节点、来路不明的攻击流量以及“客户突然说周末要扩容5000核”这种非技术变量。所以云上的算法普遍有两个特点。一是容错优先。正确性不是第一位可用性才是。很多场景下算法会主动做“有损降级”比如分布式一致性算法里网络分区时要么牺牲可用性换一致性要么牺牲一致性保可用性必须二选一再比如监控系统丢几个采样点不会报警但要保证链路整体可观测。教科书里那一套“永远返回正确结果”的理想在云平台上往往要打折。二是规模倒逼复杂度。单机上的最优解在几万台服务器组成的集群里往往不成立。比如排序单机上快排确实快但分布式排序要考虑数据倾斜、网络传输、任务粒度核心不是“怎么比大小”而是“怎么减少数据跨机器搬动”。再比如哈希单机上就是一个散列函数云上要考虑节点增删时数据迁移量最小化这才有了一致性哈希的用武之地。用一个生活化的类比云计算算法更像是城市交通规划而不是导航软件。导航只给一辆车找最快路线交通规划要让全城几万辆车整体不堵死个别车迟到可以接受。云平台上的算法大多数是后者目的是“全局稳定”不是“单点最优”。理解这一点很多选型就不会再迷糊。1.2 一条请求背后经过的算法节点把一次“在云控制台上创建一台云主机”的操作拆开看你会发现一条请求背后至少经过五六类算法。请求先到入口网关。网关上的负载均衡算法决定把请求转发给哪个后端实例常见的有加权轮询、最小连接数、一致性哈希。进入业务服务前还要过鉴权。Token校验和签名验证底层是对称加密、非对称加密和哈希算法在跑。这部分虽然用户感知不到但每次请求都会发生性能敏感时会影响整体延迟。再往下是业务逻辑层通常要查数据库或缓存。这里又会遇到缓存淘汰算法、索引结构B树、分布式事务的一致性算法。如果请求是“创建云主机”还要进入调度器调度器收集整个集群所有物理机的实时资源信息用某个评价函数给每台机器打分、排序最后挑出目标机器。如果涉及数据落盘存储系统开始工作决定数据分片到哪个节点、存几个副本、要不要启用纠删码、副本之间怎么同步。这一步背后是数据分布算法和一致性协议。所以一次看起来“简单”的操作背后是负载均衡算法、加密算法、缓存淘汰算法、调度算法、数据分布算法、一致性算法的组合拳。这也是为什么云平台的架构师常说做一个云产品主要不是写业务代码而是做算法选型和参数调优。业务代码有bug容易修选错算法往往要推到重来。2. 资源调度与负载均衡云平台的“红绿灯系统”2.1 负载均衡算法从轮询到一致性哈希负载均衡是云平台入口处最常见的算法场景。无论是硬件负载均衡设备、软件负载均衡服务还是云原生网关本质上都是把流量分发给后端多个实例避免一台机器被打爆。最简单的是轮询也就是按顺序轮流分发请求。后端实例配置一样、请求处理时长都差不多时轮询效果还行。但现实是后端实例的规格经常不一样于是有了加权轮询给高规格实例配大权重比如8C16G的权重设成42C4G的设成1流量就按比例分配。加权轮询是静态配置无法感知后端当前的真实负载所以又有了最小连接数算法——新请求发给当前活跃连接数最少的实例适合长连接场景。这里单独说一下一致性哈希。它最初为了解决分布式缓存的问题假设有N个缓存节点对请求的key做哈希取模映射到某个节点。问题在于一旦节点数从N变成N1几乎所有的key都会重新映射缓存会大面积失效请求直接打到数据库瞬间可能把数据库打爆。一致性哈希把哈希值空间看成一个环节点和key都哈希后放到环上key顺时针找到的第一个节点就是目标节点。这样增加或减少一个节点时只有环上相邻的一小段key需要重新映射大部分数据不动。再加上“虚拟节点”机制——每个物理节点在环上放多个虚拟位置可以缓解节点少时数据分布不均匀的问题。我在实际项目里见过不少把一致性哈希当万能负载均衡用的这其实是个误区。如果后端是无状态服务一致性哈希反而可能导致流量倾斜某个key特别热对应的实例就成了热点。对这种场景加权轮询或最小连接数更合适。一致性哈希适合的是有状态的中间件、缓存分片、网关路由这类需要“请求稳定落到同一节点”的场景。选型之前先想清楚一个问题你是要状态亲和还是只要分发均匀。2.2 调度器核心算法怎么从一万台机器里挑一台负载均衡解决的是“请求到了网关发给谁”而云平台的调度器解决的是“新创建的虚拟机或容器放在哪台物理机上”。这个问题在单机时代不存在但在一个上万台物理机的大集群里调度算法直接决定了资源利用率、业务稳定性和排障成本。主流的做法是“过滤加打分”两阶段。过滤阶段先把明显不合适的机器剔除。常见过滤条件包括剩余CPU内存是否满足需求、端口是否冲突、标签是否匹配、节点是否被标记为不可调度、有没有污点导致某些任务不能部署。这一步属于“和”关系任何一个条件不满足就直接排除。打分阶段对剩余节点做多维评估按综合得分排序。常见的打分维度有CPU剩余量、内存剩余量、镜像是否已存在于本地、数据本地性、跨可用区分布要求、实例间亲和性等。调度器最后把任务放到得分最高的节点上。工程实现上有一个容易踩的坑不是CPU利用率最低的机器就一定是好选择。如果调度器总是挑最低负载的机器就会引发“羊群效应”——新任务连续压到同一台机器上这台机器迅速变成热点而其他机器一直闲着。所以好的调度器会加入“资源碎片”惩罚项一台机器剩余4核一个任务要3核放上去之后只剩1核这1核几乎无法再利用这就是碎片相反另一台机器剩余8核任务放上去后剩5核还能继续承载其他任务后者得分反而更高。我调过一个调度器最初的打分函数只看CPU剩余率结果集群里某些机器负载长期超过80%其他机器负载只有20%还以为是节点容量不够其实纯粹是调度策略的问题。后来在打分函数里加入了“碎片惩罚”和“同类实例互斥”项分布慢慢就均匀了。2.3 实战参数一个调度打分函数的示例基于常见的工程实践一个简单的打分函数可以这样设计def score(node): score 0 # 资源水位维度CPU权重最高 score (1 - node.cpu_usage) * 40 # 内存水位维度 score (1 - node.mem_usage) * 30 # 磁盘剩余维度 score node.disk_free_weight * 15 # 本地性奖励镜像已经在节点上省去拉镜像时间 if node.has_local_image: score 10 # 反亲和惩罚已经有太多同类实例的降权避免热点 same_type_count node.count_running_instances(task.app) score - same_type_count * 5 return score这只是一个示意权重值在不同场景下要重新调。我的经验是在线业务场景下调度器更看重均衡性打分时CPU和内存的平滑均值比瞬时值更可靠因为瞬时值可能是监控系统采集抖动导致的。离线大数据场景下调度策略反过来追求装箱Binpack尽量把任务塞满少量机器让空闲机器可以关机省电在线业务追求分散离线追求聚集这是两种截然不同的策略。新购一批高性能机器时不要立刻把新任务全调度过去。新机器上线的初始阶段往往伴随镜像预热、系统初始化等额外负载直接压满容易踩到未知问题。我习惯让新机器先进入“观察期”跑几天业务再放开调度权重。调度器一定要留手动干预的入口。比如“某台机器有坏盘但是还没被系统识别出来”调度器照常调度上去实例起来就失败。所以生产环境里要支持对单节点设置临时禁止调度。3. 云存储与数据访问路径上的算法设计3.1 三副本与纠删码的经济账云存储最核心的问题是怎么保证数据不丢。传统做法是复制一份数据同时写三个副本三个副本分布在不同机架甚至不同可用区任何一份损坏都可以从另外两份恢复。三副本的优点是实现简单、读取性能好缺点也明显3倍的存储成本摆在那里写放大严重。还有一个选择是纠删码。纠删码把数据切成k份原始数据块再经过编码生成m份校验块总共km份数据任意丢失m份以内都可以通过数学计算恢复原文。常用的如RS(6,3)6个数据块加3个校验块存储放大只有1.5倍还能容忍任意3个块同时损坏。从成本和可靠性角度算纠删码明显优于三副本。但纠删码不是免费的午餐。编码需要计算数据恢复需要读取多个块做解码网络开销和延迟都比直接读副本高。所以云厂商的典型做法是热数据用多副本冷数据用纠删码。比如对象存储里的低频访问、归档存储基本都是纠删码云硬盘因为要求低延迟主流还是多副本或者分布式复制协议。我见过不少团队对存储系统做低成本化改造方案就是无脑上纠删码结果热数据场景读延迟飙升最后又改回去。存储算法的选型要结合访问模式读多写少不是问题读时延敏感就是问题写多读少反而可以因为编码只发生在写入时。3.2 数据去重与压缩备份系统里的隐藏套路云上很多场景天生就有大量重复数据虚拟机备份、容器镜像、日志文件、数据库归档。为了省存储空间去重算法很常见。最简单的分块方式是固定大小分块把文件按固定大小切成块计算每块的哈希重复的块只存一次。问题也很明显如果在文件中间插入或删除一个字节后面所有块的分界都会偏移结果一个1字节的改动导致整个文件后面的块全部变成新块去重率立刻崩掉。更好的方案是内容定义分块也就是CDC。CDC不再按固定大小切块而是用滑动窗口在数据流里计算哈希当哈希满足某个条件时就把这里作为分块边界。这样即使文件中间插入一个字节影响的范围也很小只有那一小段数据会重新分块后面大部分块不受影响。实现CDC常用Rabin-Karp滚动哈希计算效率高。代价是CPU开销比固定分块高。我的实践经验是备份系统上做去重不要“一把梭”。热数据通常不建议去重因为去重会带来额外的哈希计算和索引查询延迟热数据访问路径上不能加这些开销冷数据可以“先压缩再去重”。压缩算法选型上LZ4速度最快适合对性能敏感的场景Zstd在压缩比和速度之间平衡最好是目前的主流推荐Gzip兼容性最好很多老系统里还在用但速度已经明显落后。3.3 缓存淘汰算法LRU、LFU与ARC的真实场景选择缓存是云上最常见的性能优化手段。Redis、本地Cache、CDN节点所有的缓存系统都要回答一个问题空间不够时到底淘汰哪些数据LRU是应用最广的算法每次访问一个key把它移到链表头部淘汰时从尾部开始删。LRU实现简单、时效性好但它有个著名的“缓存扫描”问题——如果一次性读取了大量冷数据这些冷数据会把缓存的头部位置全部占满真正的热点数据反而被挤到尾部淘汰掉。LFU按访问频率淘汰某个key在历史上一段时间内被访问次数多就留着适合处理周期性热点。但LFU实现复杂需要维护访问计数还要防止“老数据霸占空间”——一个曾经很热、现在已经没人访问的key因为计数高而永远不淘汰。ARC在LRU和LFU之间做动态平衡本质上是把缓存分成两部分根据访问模式动态调整LRU和LFU区域的占比。ARC效果好实现复杂度更高多数业务系统直接用现成的实现比如数据库和操作系统里会用到。这里分享一个真实案例。某个消息队列的消费端加了本地缓存用的还是最简单的LRU结果每天凌晨跑批任务会一次性读取大量历史数据把缓存热点全部挤掉导致白天业务高峰时缓存命中率骤降到不到10%。后来把缓存策略改成了类似MySQL Buffer Pool的新旧分区LRU新读入的数据先进旧区只有被再次访问才提升到新区扫描大量冷数据时只会污染旧区不会挤掉真正的热点。改完之后命中率稳定回升。4. 分布式计算引擎与一致性算法落地4.1 MapReduce与Shuffle被忽略的性能杀手说到云计算里的算法绕不开分布式计算框架。以MapReduce为代表的计算模型把一个大任务拆成Map和Reduce两个阶段Map阶段把数据拆成键值对并做初步处理Reduce阶段按相同的key做聚合归并。模型本身不难理解真正隐秘的代价在中间环节——Shuffle。Shuffle发生在Map任务结束、Reduce任务开始前的阶段包括对Map输出做分区、排序、合并然后通过网络传输给对应的Reduce节点。数据量大时Shuffle期间的磁盘读写和网络传输开销非常可观。很多离线任务表面上看起来CPU没跑满实际时间都耗在Shuffle的路上了。一些经验性的优化手段选择更紧凑的序列化格式减少数据体积Shuffle传输量直接下降调整Reduce任务的分区数让数据尽量均匀避免某个Reduce处理了90%的数据其他Reduce闲着等控制小文件数量。小文件本身不是Shuffle的问题但元数据膨胀会拖累整个计算集群的稳定性能不用Shuffle就不用Shuffle比如用Broadcast Join代替Reduce Join小表直接广播到每个计算节点省掉一次全量传输4.2 分布式一致性算法Raft不是“所有节点同意”分布式系统里多副本数据要保持一致靠的是共识算法。最常听到的两个是Paxos和Raft。Paxos理论性强但工程实现复杂Raft把共识问题拆成了领导选举、日志复制、安全性三个子问题更容易实现和理解现代分布式中间件里大量使用比如某些键值存储、配置中心、协调服务都基于Raft。Raft里最容易产生误解的点是“多数派提交”。一条日志要复制成功不需要所有节点都确认只要大多数节点确认就算提交。三节点集群2个节点确认即可五节点集群3个节点确认即可。这意味着三节点Raft可以容忍1台节点故障2台故障就停摆五节点可以容忍2台故障3台故障停摆这样的设计是数学上的最优如果要保证任何情况下都不丢数据、不错数据就必须满足“任意两个多数派之间有交集”那多数派的最小规模就是n/21。部署时我还有三个体会不要把Raft的所有节点放在同一个机架上机架断电等于整个集群没了跨地域部署Raft要小心时延每条日志提交都要经过领导节点到多数节点的一次往返物理距离越远提交时延越高网络抖动会引发频繁的领导选举而领导选举期间集群处于不可写状态。生产环境里我遇到过每秒一次leader切换的情况最后的根因是交换机某条链路不稳定触发大量心跳超时4.3 云上AI训练与异构算力调度最近这两年云平台上一个很重的算力场景是大模型训练和推理。GPU集群的调度算法跟传统CPU内存调度有很大区别。传统调度看的是“机器还剩多少CPU和内存”GPU调度还得看显存、卡型、卡间通信拓扑。先说卡间通信。一台GPU服务器里面有多张GPU卡卡间走的是NVLink这类高速互联带宽远高于网卡。同一训练任务的多张卡如果尽量放在同一台服务器上通信开销就小如果分散在多台服务器就得走网络训练效率会下降。调度器为此会把“节点亲和性”作为打分项优先把同一训练任务的卡聚在一台机器上。再说显存碎片问题。GPU显存一旦分配就很难搬移反复创建销毁任务后显存碎片会让明明还有空闲显存的机器无法承接新任务。常见思路是对训练任务用装箱策略尽量集中分配减少碎片对推理服务则反过来用均匀分布策略降低单点故障波及面。还有一个容易被忽略的是Gang Scheduling。一个训练任务需要16张卡只有等到16张卡全部就绪才能启动如果调度器只凑到8张就启动任务会一直等待剩余8张这8张卡反而白白占用可能卡住后面的任务。所以GPU调度器通常会实现“全有或全无”的调度语义宁可让任务排队也不做半吊子分配。5. 云安全与可观测性背后的算法逻辑5.1 密码学算法在云上的应用形态云平台身份认证和传输加密背后是一整套密码学算法。对称加密算法如AES-GCM兼顾速度和完整性校验是目前传输加密的主流选择非对称加密算法如RSA、ECC大量用于证书签名和密钥交换哈希算法如SHA-256、SHA-3用于数据完整性校验和密码存储。我在这里只强调一个原则不要自己发明加密协议。业界有成熟的标准TLS负责传输加密信封加密负责数据加密硬件安全模块负责密钥保护。对象存储的服务端加密一般都采用信封加密方案用主密钥加密数据密钥数据密钥再去加密实际数据。主密钥不直接碰明文数据定期轮换主密钥也不影响存量数据的解密。这套方案既满足合规要求又能处理大数据量的加密性能问题。5.2 异常检测与访问控制里的算法云安全平台里的算法形态和前面说的调度、存储算法不太一样这里更多是统计分析、机器学习模型和规则引擎的组合。举个例子某个账号平时每天凌晨1点登录突然有一天凌晨4点从另一个地区登录这种异常行为靠规则就能识别某个接口的请求量突然从每分钟100次涨到每分钟10万次这种靠统计基线也能发现。基线检测的做法就是按时间段统计请求量、失败率、登录次数的均值和方差当前值偏离均值超过一定倍数就触发告警。到了恶意流量识别、Web攻击拦截这个层面规则引擎会显得不够用这时通常会上树模型或梯度提升类模型把请求特征长度、路径、UA、频率分布作为输入输出是否为恶意请求的分数。工程上的难点不是模型准确率而是误报率控制。安全告警阈值设得太低运维团队一天收到几千条误报很快就麻木了真正的风险反而被淹没阈值设太高又可能漏报。我的实践思路是先用高召回率的模型生成候选异常再叠加规则过滤和人工确认闭环每周用确认结果更新一次特征权重让模型跟着业务变化走。5.3 链路追踪中的采样与聚合微服务架构下一次用户请求会经过十几个服务每个服务都产生日志和调用链数据。如果全部上报到链路追踪系统存储成本高得吓人。所以采样算法是链路追踪里的基础。最简单的采样是概率采样比如每100条请求中采1条。问题在于低频错误可能恰好没被采到。工程里常用的是“错全采、对按比例采”有错误的链路100%上报正常链路按低比例采样。这样一个系统即使采样率只有1%也能保证几乎所有的线上错误都被追踪系统捕获。在把这些跨度聚合为调用树时还有一个算法上的取舍TraceID聚合之后如何快速定位根因。实际系统的做法一般是把调用树记录下来然后按“服务名错误类型”做分桶统计计算每个服务的错误率和耗时占比再按“最慢的调用链优先展示”。这块算法不需要多复杂真正需要下功夫的是把采样率、存储成本和排查效率调到平衡。6. 常见误区和故障排查实录6.1 云计算算法常见误区速查表误区真相建议一致性哈希万能所有负载均衡都该用一致性哈希适合有状态路由无状态服务用轮询或最小连接更好先确认“状态亲和”是不是刚需调度器挑负载最低的机器最合理会造成热点聚集和资源碎片用打分模型兼顾均衡、本地性、反亲和三副本就是最安全最合理的存储方案成本高且不是所有数据都需要三副本热数据多副本、冷数据纠删码共识算法要所有节点都确认才提交多数派确认即可三节点容忍1故障五节点容忍2故障缓存淘汰用LRU就行扫描场景会把热点全部挤掉考虑新旧分区LRU或LFU/ARC安全告警阈值越低越安全误报疲劳最终导致漏报高召回模型加人工反馈闭环6.2 实操记录一次调度抖动的完整排查过程之前我负责过一个容器集群某天凌晨突然收到告警一批新建实例启动之后还没有流量进来CPU已经被拉到了50%以上。排查过程是这样的第一步先看新建实例都落在哪些物理机上。结果显示这一批实例几乎全部落到了同一台物理机上。一台8核的机器上同时起了十几个实例每个实例启动阶段的初始化进程都很消耗CPU叠加之后机器直接过载。第二步看调度日志。调度器打分的时候“剩余CPU最多”这一项的权重设置得太高导致所有新任务都偏向这台CPU空闲率最高的机器。其他机器负载不低分数低于是被忽略了。第三步确认这台机器本身有问题。查看监控发现这台机器上有一个周期性任务每隔几个小时会短暂拉高CPU但持续几秒后就降回去了。调度器采样时刻恰好采集到了低水位导致它被误判成“空闲”。修复手段有两个一是把调度策略改成均衡优先而不是单纯低负载优先二是对单台物理机上同类实例数加了上限限制不允许所有实例堆到同一台机器。改完后重新拉一批实例CPU水位恢复正常。这个案例说明调度算法不是一锤子买卖采样周期、权重配置、实例类型都会影响最终效果。生产环境一定要有压测和灰度验证让调度策略调整先在低风险集群跑一段时间再全量放开。6.3 排查调度和算法问题时常用的调试手段实际排障时我会按链路逐层验证负载均衡层查看网关日志里的upstream地址对比同一来源IP的请求是不是被分到了多个后端测试一致性哈希配置在扩缩容后有没有引发大量key迁移调度器层使用调度器的模拟调度或dry-run模式不真实创建实例就能输出每台节点的打分结果对照分数找异常存储层查看数据分布均衡度和恢复任务耗时如果某个节点上的分片数明显偏多要检查哈希函数和扩容策略一致性协议层抓包看重传率、心跳间隔、leader切换次数网络抖动在Raft集群里会直接表现为频繁的leader变更最后分享一个小经验云计算里的算法问题80%不是算法本身错而是参数和业务场景不匹配。调度策略偏重均衡还是装箱缓存策略选LRU还是LFU一致性哈希还是最小连接数这些本质上是一道“场景匹配题”。你只要把业务访问模式摸清楚再做两次灰度验证大多能选到合适的方案。如果一开始就追求某个“业界最强算法”反而容易把简单问题搞复杂。