-
-
Save zavakid/c19146ab2902832a34ab316a7cfeaa8e to your computer and use it in GitHub Desktop.
Thinkd Distributed Systems
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!doctype html> | |
| <html lang="zh-CN"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>Think Distributed Systems · 第七章 Partitioning 学习笔记</title> | |
| <style> | |
| :root{ | |
| --bg0:#0b1220; | |
| --bg1:#0f1b2e; | |
| --panel:#101a2b; | |
| --card:#0f1a2c; | |
| --card2:#0d1727; | |
| --text:#e7eefc; | |
| --muted:#a8b3c7; | |
| --line:rgba(255,255,255,.10); | |
| --accent:#8bd0ff; | |
| --accent2:#9ee6a8; | |
| --warn:#ffcc66; | |
| --danger:#ff7a90; | |
| --codebg:rgba(255,255,255,.06); | |
| --shadow:0 14px 40px rgba(0,0,0,.35); | |
| --radius:16px; | |
| --navW:320px; | |
| --contentW:1100px; | |
| } | |
| *{box-sizing:border-box;} | |
| /* 关键修复:避免滚动时出现“白色蒙版/发白”的合成问题。 | |
| 一些浏览器在 background-attachment: fixed + blur/backdrop-filter 叠加时会出现发白。 | |
| 解决:把背景放到一个固定的伪元素图层上(不参与滚动合成),并提供纯色兜底。 */ | |
| html{min-height:100%; background-color:var(--bg1);} | |
| body{min-height:100vh; margin:0; color:var(--text); | |
| font:15px/1.7 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"PingFang SC","Hiragino Sans GB","Noto Sans CJK SC","Microsoft YaHei",sans-serif; | |
| background:transparent; | |
| position:relative; | |
| overflow-x:hidden; | |
| } | |
| body::before{ | |
| content:""; | |
| position:fixed; | |
| inset:0; | |
| z-index:-1; | |
| pointer-events:none; | |
| background: | |
| radial-gradient(1200px 600px at 15% 0%, rgba(139,208,255,.20), transparent 60%), | |
| radial-gradient(900px 500px at 85% 15%, rgba(158,230,168,.14), transparent 55%), | |
| linear-gradient(180deg, var(--bg0), var(--bg1)); | |
| } | |
| /* iOS/Safari 等对 fixed 背景/滤镜更敏感,强制关闭滚动回弹带来的露底 */ | |
| @supports (-webkit-touch-callout: none){ | |
| html,body{overscroll-behavior-y:none;} | |
| } | |
| a{color:var(--accent); text-decoration:none;} | |
| a:hover{text-decoration:underline;} | |
| .app{ | |
| width:min(1480px, 96vw); | |
| margin:0 auto; | |
| display:grid; | |
| grid-template-columns: var(--navW) 1fr; | |
| gap:20px; | |
| padding:20px 14px 40px; | |
| min-height:100vh; | |
| } | |
| /* 左侧导航 */ | |
| .nav{ | |
| position:sticky; | |
| top:16px; | |
| align-self:start; | |
| height:calc(100vh - 32px); | |
| overflow:auto; | |
| border:1px solid var(--line); | |
| background:rgba(16,26,43,.70); | |
| backdrop-filter: blur(10px); | |
| border-radius:var(--radius); | |
| box-shadow:var(--shadow); | |
| padding:14px; | |
| } | |
| .brand{ | |
| display:flex; | |
| flex-direction:column; | |
| gap:6px; | |
| padding:10px 10px 12px; | |
| border-bottom:1px solid var(--line); | |
| margin-bottom:10px; | |
| } | |
| .brand .title{font-weight:800; letter-spacing:.2px; font-size:16px;} | |
| .brand .sub{color:var(--muted); font-size:12.5px;} | |
| .toc{ | |
| list-style:none; | |
| padding:0; | |
| margin:12px 0 0; | |
| } | |
| .toc li{margin:2px 0;} | |
| .toc a{ | |
| display:block; | |
| padding:7px 10px; | |
| border-radius:12px; | |
| color:var(--text); | |
| border:1px solid transparent; | |
| transition:background .15s ease,border-color .15s ease; | |
| font-size:13.5px; | |
| } | |
| .toc a:hover{background:rgba(255,255,255,.06); border-color:rgba(255,255,255,.08); text-decoration:none;} | |
| .toc a.active{background:rgba(139,208,255,.12); border-color:rgba(139,208,255,.30);} | |
| .nav .hint{ | |
| margin-top:12px; | |
| padding:10px 10px; | |
| border-radius:12px; | |
| border:1px dashed rgba(255,255,255,.18); | |
| color:var(--muted); | |
| font-size:12.5px; | |
| } | |
| /* 内容区 */ | |
| .content{ | |
| max-width:var(--contentW); | |
| } | |
| .hero{ | |
| border:1px solid var(--line); | |
| background:rgba(16,26,43,.55); | |
| backdrop-filter: blur(10px); | |
| border-radius:22px; | |
| box-shadow:var(--shadow); | |
| padding:22px 22px 18px; | |
| margin-bottom:18px; | |
| } | |
| .hero h1{margin:0 0 6px; font-size:26px; letter-spacing:.2px;} | |
| .hero p{margin:6px 0 0; color:var(--muted);} | |
| .grid2{ | |
| display:grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap:14px; | |
| margin-top:14px; | |
| } | |
| .card{ | |
| border:1px solid var(--line); | |
| border-radius:var(--radius); | |
| background:rgba(15,26,44,.62); | |
| box-shadow:0 10px 26px rgba(0,0,0,.25); | |
| padding:16px 16px; | |
| } | |
| .card h2{ | |
| margin:0 0 10px; | |
| font-size:18px; | |
| } | |
| .section{ | |
| margin-top:16px; | |
| border:1px solid var(--line); | |
| border-radius:22px; | |
| background:rgba(13,23,39,.58); | |
| box-shadow:var(--shadow); | |
| padding:18px 18px; | |
| } | |
| .section h2{margin:0 0 12px; font-size:20px;} | |
| .section h3{margin:16px 0 8px; font-size:16.5px;} | |
| .section p{margin:8px 0; color:rgba(231,238,252,.95)} | |
| .muted{color:var(--muted);} | |
| .pill{ | |
| display:inline-flex; | |
| gap:6px; | |
| align-items:center; | |
| padding:3px 10px; | |
| border-radius:999px; | |
| border:1px solid rgba(139,208,255,.35); | |
| background:rgba(139,208,255,.10); | |
| font-size:12.5px; | |
| color:var(--text); | |
| margin-right:6px; | |
| } | |
| .kv{ | |
| display:grid; | |
| grid-template-columns: 140px 1fr; | |
| gap:8px 12px; | |
| margin:10px 0 0; | |
| padding:12px; | |
| border-radius:14px; | |
| background:rgba(255,255,255,.05); | |
| border:1px solid rgba(255,255,255,.10); | |
| } | |
| .kv div:nth-child(odd){color:var(--muted);} | |
| .callout{ | |
| border-radius:16px; | |
| border:1px solid rgba(255,255,255,.14); | |
| background:linear-gradient(180deg, rgba(139,208,255,.10), rgba(255,255,255,.04)); | |
| padding:14px 14px; | |
| margin:12px 0; | |
| } | |
| .callout b{color:var(--accent);} | |
| .warn{ | |
| background:linear-gradient(180deg, rgba(255,204,102,.16), rgba(255,255,255,.04)); | |
| border-color:rgba(255,204,102,.28); | |
| } | |
| .danger{ | |
| background:linear-gradient(180deg, rgba(255,122,144,.14), rgba(255,255,255,.04)); | |
| border-color:rgba(255,122,144,.28); | |
| } | |
| .figure{ | |
| border:1px solid rgba(255,255,255,.12); | |
| background:rgba(255,255,255,.04); | |
| border-radius:16px; | |
| padding:14px; | |
| margin:12px 0; | |
| } | |
| .figure .cap{color:var(--muted); font-size:12.5px; margin-top:6px;} | |
| pre{ | |
| margin:10px 0; | |
| padding:12px 12px; | |
| border-radius:14px; | |
| background:var(--codebg); | |
| border:1px solid rgba(255,255,255,.12); | |
| overflow:auto; | |
| } | |
| code{font-family: ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace; font-size:13px;} | |
| .list{ | |
| margin:8px 0 0 18px; | |
| color:rgba(231,238,252,.92); | |
| } | |
| .list li{margin:4px 0;} | |
| .table{ | |
| width:100%; | |
| border-collapse:separate; | |
| border-spacing:0; | |
| overflow:hidden; | |
| border-radius:14px; | |
| border:1px solid rgba(255,255,255,.12); | |
| margin:10px 0; | |
| background:rgba(255,255,255,.04); | |
| } | |
| .table th,.table td{ | |
| padding:10px 10px; | |
| border-bottom:1px solid rgba(255,255,255,.10); | |
| vertical-align:top; | |
| } | |
| .table th{color:var(--muted); text-align:left; font-weight:650; background:rgba(255,255,255,.03)} | |
| .table tr:last-child td{border-bottom:none;} | |
| .footer{ | |
| margin-top:16px; | |
| padding:14px 4px 0; | |
| color:var(--muted); | |
| font-size:12.5px; | |
| } | |
| @media (max-width: 1080px){ | |
| :root{--navW:280px;} | |
| .app{grid-template-columns: var(--navW) 1fr;} | |
| } | |
| @media (max-width: 920px){ | |
| .app{grid-template-columns: 1fr;} | |
| .nav{position:relative; height:auto;} | |
| .content{max-width:100%;} | |
| .grid2{grid-template-columns:1fr;} | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="app"> | |
| <aside class="nav" id="nav"> | |
| <div class="brand"> | |
| <div class="title">第七章 · Partitioning</div> | |
| <div class="sub">学习笔记(含对话要点 + 章节主线)</div> | |
| </div> | |
| <ul class="toc" id="toc"> | |
| <li><a href="#sec-0">章节地图</a></li> | |
| <li><a href="#sec-1">7.1 百科全书隐喻</a></li> | |
| <li><a href="#sec-2">7.2 分区的定义与价值</a></li> | |
| <li><a href="#sec-3">两层映射:item→partition→node</a></li> | |
| <li><a href="#sec-4">7.4(再)分区与类型</a></li> | |
| <li><a href="#sec-5">水平 vs 垂直(可组合)</a></li> | |
| <li><a href="#sec-6">7.4.2 分配策略:item-based vs directory-based</a></li> | |
| <li><a href="#sec-7">衡量指标:variance 与 relocation</a></li> | |
| <li><a href="#sec-8">7.5 常见 item-based:range 与 hash</a></li> | |
| <li><a href="#sec-9">7.6 Repartitioning:为什么痛</a></li> | |
| <li><a href="#sec-10">7.7 Consistent hashing:兼顾均衡与少搬</a></li> | |
| <li><a href="#sec-11">7.8(再)均衡与 overpartitioning</a></li> | |
| <li><a href="#sec-12">工程落地清单</a></li> | |
| </ul> | |
| <div class="hint"> | |
| 提示:阅读本章时始终抓住两条线: | |
| <br/>① <b>item→partition</b>(分区规则) | |
| <br/>② <b>partition→node</b>(调度/均衡) | |
| </div> | |
| </aside> | |
| <main class="content"> | |
| <section class="hero" id="sec-0"> | |
| <h1>第七章学习笔记:Partitioning(分区)</h1> | |
| <p> | |
| 本章围绕“把一个逻辑对象拆成多个物理对象”展开:为什么要分区、怎么分区、如何评价分区方案、以及系统演进时如何再分区与再均衡。 | |
| 你会看到:<b>分区解决扩展性</b>,而下一章的 replication 解决可靠性。 | |
| </p> | |
| <div class="grid2"> | |
| <div class="card"> | |
| <h2>本章你需要掌握的“概念接口”</h2> | |
| <div class="kv"> | |
| <div>Partitioning</div><div>item → partition 的归属规则(决定数据属于哪片)</div> | |
| <div>Balancing</div><div>partition → node 的落点分配(决定哪台机器承载哪片)</div> | |
| <div>Variance</div><div>分布是否均匀(倾斜/热点程度)</div> | |
| <div>Relocation</div><div>拓扑变化时需要搬迁的 item 数量</div> | |
| <div>Repartitioning</div><div>改变 item→partition(通常更重、更痛)</div> | |
| <div>Rebalancing</div><div>改变 partition→node(更常做、可控)</div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h2>章节主线(读完应该形成的心智图)</h2> | |
| <p class="muted" style="margin-top:6px;"> | |
| <span class="pill">隐喻</span>百科全书分卷 | |
| <span class="pill">定义</span>逻辑对象 vs 物理对象 | |
| <span class="pill">两层映射</span>item→partition→node | |
| <span class="pill">策略</span>range/hash/consistent hashing/目录 | |
| <span class="pill">演进</span>repartitioning vs rebalancing | |
| <span class="pill">技巧</span>overpartitioning | |
| </p> | |
| <div class="callout warn" style="margin-top:12px;"> | |
| <b>关键提醒:</b>“静态”不一定意味着永不改变,也可能是“慢变化/低频变化”。只要改分区数属于管理动作(需要窗口期、迁移、回滚预案),它就可以被视为 static。 | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="section" id="sec-1"> | |
| <h2>7.1 百科全书与分卷:一个非常好用的隐喻</h2> | |
| <p> | |
| 百科全书是<b>逻辑对象</b>,每一本分卷是<b>物理对象</b>。逻辑上全书包含所有条目,物理上每卷只存互不重叠的一部分条目。 | |
| 分卷必须有规则(例如按首字母分配),否则查找会退化为“扫全书”。 | |
| </p> | |
| <div class="figure"> | |
| <div><b>图 7.1 / 7.2 的核心信息</b></div> | |
| <ul class="list"> | |
| <li>分区的本质:一个逻辑对象由多个互斥的物理对象共同表示。</li> | |
| <li>分区的目的:把需求与容量分散到多个资源上,避免单点资源瓶颈。</li> | |
| <li>分区的挑战:分布不均(uneven distribution)、需求不均(uneven demand)、跨分区引用(cross-reference)。</li> | |
| </ul> | |
| <div class="cap">你可以把“条目↔分卷”直接映射到“数据项↔分区”。</div> | |
| </div> | |
| <div class="callout"> | |
| <b>Aha:</b>分布式系统从定义上就已经“分区”了——每个组件只拥有自己的本地状态,这本身就是一种天然的 partition。 | |
| 进一步的“数据分区”则是为了突破单节点在存储/吞吐上的限制。 | |
| </div> | |
| </section> | |
| <section class="section" id="sec-2"> | |
| <h2>7.2 分区的定义与价值:扩展性优先</h2> | |
| <p> | |
| 分区(partitioning)用于提升可扩展性:把数据与请求压力分摊到多节点,从而突破单节点限制。 | |
| 复制(replication)用于提升可靠性:让物理对象代表“整体”。 | |
| 本章先讲分区,下一章讲复制。 | |
| </p> | |
| <div class="table-wrap"> | |
| <table class="table"> | |
| <thead> | |
| <tr><th>技术</th><th>解决的问题</th><th>核心代价</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><b>Partitioning</b></td> | |
| <td>扩展性(Scalability):突破单资源瓶颈</td> | |
| <td>跨分区访问、再分区/迁移复杂度</td> | |
| </tr> | |
| <tr> | |
| <td><b>Replication</b></td> | |
| <td>可靠性(Reliability):突破单点故障</td> | |
| <td>一致性、复制延迟、冲突与协调</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <div class="callout"> | |
| <b>实用定义:</b>“big data 是相对的”——任何放不进一个节点的数据(或处理不过来的请求)都需要分区。 | |
| 节点越弱,越容易“big data”。 | |
| </div> | |
| </section> | |
| <section class="section" id="sec-3"> | |
| <h2>两层映射:item→partition→node(整章最重要的结构)</h2> | |
| <p> | |
| 本章不断强调两个不同但相关的关系: | |
| </p> | |
| <ul class="list"> | |
| <li><b>Partitioning:</b>把数据项(item)分配到分区(partition)。</li> | |
| <li><b>Balancing:</b>把分区(partition)分配到节点(node)。</li> | |
| </ul> | |
| <div class="figure"> | |
| <div><b>图 7.4 / 7.5 / 7.9 的统一解释</b></div> | |
| <div class="kv"> | |
| <div>第一层</div><div>item → partition:决定“归属”,影响面大;更接近“数据模型/路由规则”。</div> | |
| <div>第二层</div><div>partition → node:决定“落点”,适合动态调整;更接近“调度/运维”。</div> | |
| </div> | |
| <div class="cap">理解这两层映射后,你就能区分 repartitioning 和 rebalancing 的代价差异。</div> | |
| </div> | |
| <div class="callout danger"> | |
| <b>经验结论:</b>尽量让 <b>item→partition</b> 稳定,把动态性放在 <b>partition→node</b>。 | |
| 这样系统扩缩容时更平滑,风险更可控。 | |
| </div> | |
| </section> | |
| <section class="section" id="sec-4"> | |
| <h2>7.4(再)分区与类型:static vs dynamic</h2> | |
| <p> | |
| <b>Repartitioning</b> 指把已分配过的 item 重新分配到不同 partition(典型触发:分区数量变化)。 | |
| 对 item-based 这类“把分区数写进函数”的方案来说,这一步通常非常痛。 | |
| </p> | |
| <h3>静态分区 vs 动态分区</h3> | |
| <ul class="list"> | |
| <li><b>Static partitioning:</b>分区数量固定,不能在线改;更简单,但缺乏弹性。</li> | |
| <li><b>Dynamic partitioning:</b>分区数量可在线改;更弹性,但复杂度高。</li> | |
| </ul> | |
| <div class="figure"> | |
| <div><b>表 7.1 的要点</b></div> | |
| <div class="kv"> | |
| <div>Elasticity</div><div>Static ✗;Dynamic ✓</div> | |
| <div>Complexity</div><div>Static 低;Dynamic 高</div> | |
| </div> | |
| <div class="cap">Dynamic 的复杂度主要来自“在线迁移 + 元数据一致性 + 故障恢复”。</div> | |
| </div> | |
| <div class="callout"> | |
| <b>Aha:No change vs slow change</b> | |
| <div class="muted">只要改分区数不是日常操作,而是需要规划、迁移、回滚、窗口期的管理动作,它就属于 static 的语境。</div> | |
| </div> | |
| </section> | |
| <section class="section" id="sec-5"> | |
| <h2>水平 vs 垂直:按“行”切还是按“列”切(可组合)</h2> | |
| <p> | |
| 最直观的模型来自关系型表:表由行(rows)与列(columns)组成。 | |
| </p> | |
| <h3>水平分区(Horizontal / Sharding)</h3> | |
| <p class="muted">按行切分:每个分区包含完整列集合,但只覆盖部分行/主键范围。</p> | |
| <h3>垂直分区(Vertical)</h3> | |
| <p class="muted">按列切分:每个分区覆盖全部行,但只包含部分列(字段集合)。</p> | |
| <div class="figure"> | |
| <div><b>组合策略(图 7.7):先垂直再水平</b></div> | |
| <ul class="list"> | |
| <li>先按类型拆:文本(profile data)与图片(BLOB)分开存。</li> | |
| <li>再分别水平分区:文本进多个 DB 分片;图片进多个对象/文件存储分片。</li> | |
| <li>好处:不同子系统可独立扩展(DB 的索引/查询 vs 存储的容量/带宽)。</li> | |
| </ul> | |
| <div class="cap">代价:跨存储的引用一致性、失败重试与“孤儿对象”清理需要系统性方案。</div> | |
| </div> | |
| </section> | |
| <section class="section" id="sec-6"> | |
| <h2>7.4.2 item→partition 分配策略:item-based vs directory-based</h2> | |
| <p> | |
| 当数据被分区后,第一件事是回答:<b>一个 item 属于哪个 partition?</b> | |
| 常见有两类: | |
| </p> | |
| <h3>Item-based assignment(无状态、粗粒度)</h3> | |
| <ul class="list"> | |
| <li>只依赖 item 自身特征(key、hash(key)、范围)。</li> | |
| <li>stateless:不需要目录表;实现与运维简单。</li> | |
| <li>缺点:不能把某个 item 指定到某个分区;热点 key 碰撞时难以“手动拆解”。</li> | |
| </ul> | |
| <h3>Directory-based assignment(有状态、细粒度)</h3> | |
| <ul class="list"> | |
| <li>依赖额外目录/查找表(assignment table)。</li> | |
| <li>stateful:能按 key 精准放置,便于隔离热点、做渐进迁移。</li> | |
| <li>缺点:目录自身可能成为瓶颈/单点,反而与“消除单点”的分区目标冲突。</li> | |
| </ul> | |
| <div class="callout"> | |
| <b>Aha:Coarse-grained vs Fine-grained</b> | |
| <div class="muted">item-based 更像“公式路由”;directory-based 更像“路由表”。粒度不同,意味着你处理热点与迁移时的手段完全不同。</div> | |
| </div> | |
| <table class="table"> | |
| <thead> | |
| <tr><th>维度</th><th>Item-based</th><th>Directory-based</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr><td>状态</td><td>无状态</td><td>有状态(目录)</td></tr> | |
| <tr><td>控制粒度</td><td>粗(改规则影响大量 key)</td><td>细(可单 key 调度/迁移)</td></tr> | |
| <tr><td>热点处理</td><td>易碰撞,难手动干预</td><td>可定点隔离/迁移热点</td></tr> | |
| <tr><td>系统依赖</td><td>少(计算即可)</td><td>多(目录一致性/可用性/缓存)</td></tr> | |
| </tbody> | |
| </table> | |
| </section> | |
| <section class="section" id="sec-7"> | |
| <h2>衡量指标:variance 与 relocation(让比较可量化)</h2> | |
| <p> | |
| 分配策略的适配度常用两个指标衡量: | |
| </p> | |
| <div class="kv"> | |
| <div><b>Variance</b></div> | |
| <div>item 在 partitions 之间是否均匀。variance 低意味着倾斜小、热点少。</div> | |
| <div><b>Relocation</b></div> | |
| <div>分区数量变化时需要搬迁多少 item。relocation 小意味着扩缩容更平滑。</div> | |
| </div> | |
| <div class="callout warn"> | |
| <b>常见张力:</b>很多简单策略只能二选一: | |
| 例如 <code>hash(key) % P</code> 往往 variance 很好,但 P 变化时 relocation 很差。 | |
| </div> | |
| </section> | |
| <section class="section" id="sec-8"> | |
| <h2>7.5 常见 item-based:range 与 hash(用 words 文件做实验)</h2> | |
| <p> | |
| 作者用 <code>/usr/share/dict/words</code>(235,976 个单词)做实验,把每个单词分配到 5 个分区。 | |
| 理想均匀是每个分区约 47,195 个条目。 | |
| </p> | |
| <h3>Range partitioning(按 key 范围)</h3> | |
| <p class="muted">示例:按首字母分组(ABCDE / FGHIJ / ...)。</p> | |
| <div class="figure"> | |
| <div><b>实验结果(作者机器)</b></div> | |
| <pre><code>0: 67730 | |
| 1: 33203 | |
| 2: 35829 | |
| 3: 73432 | |
| 4: 25782</code></pre> | |
| <div class="cap">明显不均衡:分区 3 与 0 是热点。原因是输入分布(首字母频率)天然不均匀。</div> | |
| </div> | |
| <h3>Hash partitioning(按 hash 值)</h3> | |
| <pre><code>def placement(key): | |
| return hash(key) % 5</code></pre> | |
| <div class="figure"> | |
| <div><b>实验结果(作者机器)</b></div> | |
| <pre><code>0: 47219 | |
| 1: 47187 | |
| 2: 47362 | |
| 3: 47175 | |
| 4: 47033</code></pre> | |
| <div class="cap">分布非常均匀(variance 很好)。但范围查询会变成跨分区 scatter-gather。</div> | |
| </div> | |
| <div class="callout"> | |
| <b>工程提醒:</b>很多语言的内置 <code>hash()</code> 可能跨进程/版本不稳定;生产系统通常使用稳定哈希(固定种子的 xxHash/Murmur/CRC 等)。 | |
| </div> | |
| </section> | |
| <section class="section" id="sec-9"> | |
| <h2>7.6 Repartitioning:分区数变化时为什么“搬家”很痛</h2> | |
| <p> | |
| 作者用脚本比较从 5 分区变 6 分区时,多少 item 仍在原分区(same),多少需要迁移(diff)。 | |
| 这就是 relocation 的直接量化。 | |
| </p> | |
| <h3>Range 的再分区(整体重划边界)</h3> | |
| <div class="figure"> | |
| <pre><code>Same: 114790 | |
| Diff: 121186 | |
| (48.6% remain, 51.4% relocate)</code></pre> | |
| <div class="cap">整体重划边界导致近一半条目迁移。若只对热点范围“局部拆分”,迁移可更局部化。</div> | |
| </div> | |
| <h3>Hash 的再分区(%P)</h3> | |
| <div class="figure"> | |
| <pre><code>Same: 39443 | |
| Diff: 196533 | |
| (16.7% remain, 83.3% relocate)</code></pre> | |
| <div class="cap">改变取模基数相当于“全体重新分桶”,迁移量巨大。</div> | |
| </div> | |
| <div class="figure"> | |
| <div><b>表 7.2:最小 variance vs 最小 relocation</b></div> | |
| <div class="kv"> | |
| <div>Key range</div><div>variance ✗;relocation ✗</div> | |
| <div>Hashing (%P)</div><div>variance ✓;relocation ✗</div> | |
| </div> | |
| </div> | |
| <div class="callout danger"> | |
| <b>结论:</b>如果你的系统需要频繁扩缩容,单纯的 <code>%P</code> 哈希通常会把你逼向更高级的策略(consistent hashing / 目录 / overpartitioning)。 | |
| </div> | |
| </section> | |
| <section class="section" id="sec-10"> | |
| <h2>7.7 Consistent hashing:同时减少倾斜与搬迁</h2> | |
| <p> | |
| 一致性哈希的目标是同时做到:variance 小 + relocation 小。 | |
| 其关键效果是:从 m 增加到 m+1 个分区时,平均只需要迁移大约 <b>1/(m+1)</b> 的 items(等价于新增分区获得的份额)。 | |
| </p> | |
| <div class="figure"> | |
| <div><b>作者实验(5→6)</b></div> | |
| <pre><code>Same: 192243 | |
| Diff: 43733 | |
| (81.5% remain, 18.5% relocate)</code></pre> | |
| <div class="cap">18.5% 接近 1/6≈16.7% 的量级,说明迁移被局部化了(不再全局洗牌)。</div> | |
| </div> | |
| <h3>用“接口/规格”思维理解一致性哈希(不纠缠实现)</h3> | |
| <div class="callout"> | |
| <b>Aha:Thinking about algorithms</b> | |
| <div class="muted">从系统层面关注效果与契约:给定拓扑变化,一致性哈希应该带来什么可观察的性质。</div> | |
| </div> | |
| <div class="kv"> | |
| <div>路由接口</div><div><code>place(key, ring) → partition/node</code>(同 key 在同 ring 下稳定落点)</div> | |
| <div>迁移接口</div><div><code>relocation(old, new) ≈ Δcapacity_share</code>(新增一个分区只搬走一小段)</div> | |
| <div>均衡接口</div><div><code>variance(ring) → skew</code>(通常配合虚拟节点 vnodes 降低抖动)</div> | |
| </div> | |
| <div class="callout warn"> | |
| <b>实践提醒:</b>一致性哈希的均衡性往往需要虚拟节点(vnodes)增强;否则各节点区间长度可能有波动。 | |
| </div> | |
| </section> | |
| <section class="section" id="sec-11"> | |
| <h2>7.8(再)均衡与 overpartitioning:让扩缩容更平滑</h2> | |
| <p> | |
| <b>Balancing</b> 是 partition→node 的分配,<b>Rebalancing</b> 是在需求变化/节点变化后重新分配分区落点。 | |
| 这通常比 repartitioning(改 item→partition)更常做、更可控。 | |
| </p> | |
| <h3>Overpartitioning(过度分区)</h3> | |
| <p> | |
| 过度分区的做法是:让分区数显著多于节点数,从而每个节点承载多个分区。 | |
| 这样你获得更细的调度粒度。 | |
| </p> | |
| <div class="figure"> | |
| <div><b>图 7.10 的要点</b></div> | |
| <ul class="list"> | |
| <li>分区数=节点数:一对一绑定,缺乏弹性,难以细调。</li> | |
| <li>分区数>节点数:一对多承载,可通过迁移分区来平滑扩缩容。</li> | |
| <li>上限:分区数决定可加入的最大节点数;分区都分完后,再加节点无法提升并行度。</li> | |
| </ul> | |
| </div> | |
| <table class="table"> | |
| <thead> | |
| <tr><th>做法</th><th>收益</th><th>代价</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr> | |
| <td><b>Rebalancing</b></td> | |
| <td>适配节点增减、需求波动;迁移边界清晰</td> | |
| <td>需要可靠的元数据与迁移机制;控制抖动</td> | |
| </tr> | |
| <tr> | |
| <td><b>Overpartitioning</b></td> | |
| <td>更细粒度调度;扩缩容更平滑;故障恢复更分散</td> | |
| <td>元数据更大;每分区开销(日志/索引/连接/线程)上升</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </section> | |
| <section class="section" id="sec-12"> | |
| <h2>工程落地清单:如何设计“够用”的分区策略</h2> | |
| <h3>一、先回答“你的系统更怕什么”</h3> | |
| <ul class="list"> | |
| <li><b>更怕热点/倾斜?</b>优先控制 variance(哈希/一致性哈希/目录/热点隔离)。</li> | |
| <li><b>更怕扩缩容搬迁成本?</b>优先控制 relocation(一致性哈希/目录式渐进迁移/固定分区+再均衡)。</li> | |
| <li><b>更怕范围查询成本?</b>优先考虑 range 或二级索引/聚合方案。</li> | |
| </ul> | |
| <h3>二、优先选择“把动态性放在 partition→node”</h3> | |
| <div class="callout"> | |
| 典型稳妥路径:<b>一开始 overpartitioning</b>(分区数显著大于节点数) + <b>后续主要靠 rebalancing</b> 扩缩容。 | |
| 只有当分区数上限卡住、或热点严重到需要拆分,才进入 repartitioning。 | |
| </div> | |
| <h3>三、选择策略时的快速对照</h3> | |
| <table class="table"> | |
| <thead> | |
| <tr><th>策略</th><th>variance</th><th>relocation(扩容)</th><th>范围查询</th><th>实现复杂度</th></tr> | |
| </thead> | |
| <tbody> | |
| <tr><td>Range</td><td>受输入分布影响,可能差</td><td>取决于边界变更方式,整体重划会大</td><td>好</td><td>中</td></tr> | |
| <tr><td>Hash (%P)</td><td>好</td><td>很差(接近全局洗牌)</td><td>差</td><td>低</td></tr> | |
| <tr><td>Consistent hashing</td><td>通常好(vnodes 可更好)</td><td>好(≈新增份额)</td><td>一般</td><td>中</td></tr> | |
| <tr><td>Directory-based</td><td>可很好(按负载调度)</td><td>可很好(渐进迁移)</td><td>取决于目录与索引</td><td>高(目录自身要 HA/一致性)</td></tr> | |
| </tbody> | |
| </table> | |
| <h3>四、读者自测题(帮助巩固)</h3> | |
| <ul class="list"> | |
| <li>你的系统里,<b>item→partition</b> 与 <b>partition→node</b> 分别由哪些组件负责?它们的元数据如何传播?</li> | |
| <li>系统扩容时,你更倾向于做 <b>repartitioning</b> 还是 <b>rebalancing</b>?为什么?</li> | |
| <li>你能给出一个“热点碰撞”的例子吗?在 item-based 下你会怎么缓解?目录式又会怎么做?</li> | |
| <li>如果你需要范围查询 + 少搬迁,你会选择什么组合策略?</li> | |
| </ul> | |
| <div class="footer"> | |
| 备注:本页面根据对话内容与《Think Distributed Systems》第七章要点整理。 | |
| 你可以继续把第七章后续段落贴上来,我也可以把本页扩展成“带练习/带对照示例/带你们系统映射”的版本。 | |
| </div> | |
| </section> | |
| </main> | |
| </div> | |
| <script> | |
| // 平滑滚动 | |
| document.querySelectorAll('.toc a').forEach(a => { | |
| a.addEventListener('click', (e) => { | |
| const href = a.getAttribute('href'); | |
| if (!href || !href.startsWith('#')) return; | |
| e.preventDefault(); | |
| const el = document.querySelector(href); | |
| if (!el) return; | |
| el.scrollIntoView({ behavior: 'smooth', block: 'start' }); | |
| history.replaceState(null, '', href); | |
| }); | |
| }); | |
| // 当前章节高亮 | |
| const links = Array.from(document.querySelectorAll('#toc a')); | |
| const sections = links | |
| .map(a => document.querySelector(a.getAttribute('href'))) | |
| .filter(Boolean); | |
| const io = new IntersectionObserver((entries) => { | |
| const visible = entries | |
| .filter(e => e.isIntersecting) | |
| .sort((a,b) => b.intersectionRatio - a.intersectionRatio)[0]; | |
| if (!visible) return; | |
| const id = '#' + visible.target.id; | |
| links.forEach(a => a.classList.toggle('active', a.getAttribute('href') === id)); | |
| }, { root: null, threshold: [0.25, 0.5, 0.75] }); | |
| sections.forEach(s => io.observe(s)); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment