大家好,我是数据库小学妹👋 我踩过的坑,你别再踩。
前段时间我跟过一个系统改造。原来的库到了读写瓶颈,方案评审会上分布式几乎全票通过。理由很统一,单机有天花板,分布式能水平扩展。半年后系统上线,瓶颈没消失,还多了一个。一条原来在单表上跑 50 毫秒的关联查询,切完之后要跨三个分片,变成了 340 毫秒。
以前我一度把分布式当成先进性的代名词,直到那次之后才明白,这其实是一次交换。拿跨分片协调的代价,换水平扩展的能力。这个交换划不划算,取决于两件事。数据能不能被干净地切开,热点能不能被打散。这两个答案,比"哪个更先进"重要得多。
先把三个词分开
分布式和集群,很多人当同义词用。这是选型走偏的第一步,因为它们的扩展对象完全不同。
集群是同一份数据存多份,对外还是一个库。它扩展的是可用性和只读吞吐。写入点还是那一个,主库扛不住,加再多从库也没用。无共享分布式是把数据切开,每个节点各管一段,节点之间数据不重叠,扩展的是写入能力和总容量。还有一类常被漏掉的形态夹在中间,共享存储集群。多个计算节点共享同一份存储,都能读写。
区分它们只要问两个问题:写入点有几个,数据切没切。这两问的答案,基本决定了后面所有的取舍。
| 形态 | 数据关系 | 写入点 | 扩展的是什么 |
|---|---|---|---|
| 主从集群 | 同一份数据多副本 | 一个 | 可用性、只读吞吐 |
| 共享存储集群 | 一份数据,多节点共享 | 多个,靠全局协调 | 计算能力与内存池 |
| 无共享分布式 | 数据切分,各管一段 | 多个,各自独立 | 写入能力与总容量 |
这三种形态不是互斥的选项。现在不少国产库三条路线都在做,金仓是其中一家,选型时按业务挑就行,不用在阵营之间二选一。
共享存储集群:靠缓存融合撑起多节点读写
这条路线的核心矛盾在缓存上。同一份数据页,每个节点的内存里都可能存了一份。节点 A 把某页改了,节点 B 内存里那份就过期了。两边都在写的时候,谁说了算。
解决靠两层机制,一层是页的全局所有权。某个节点要读一页,先看这页最新的版本在不在自己内存里。不在,就得跨节点把那页传过来,而不是回存储读。要写一页,得先拿到这页的排他所有权。另外一层是全局锁服务。行锁和表锁不能各家各管,得由统一的服务授予和释放,不然节点之间互相不知道对方锁了什么。
这两层机制说明一件事,节点之间的交互走私有互联网络,不走存储。互联的带宽和延迟,就成了这条路线的天花板。它扩的是计算节点和总内存,存储那层不变。它能比单机扛得多,但不是无限的。规模上来之后,先饱和的是互联,不是存储。
无共享分布式:靠分片和共识撑起水平扩展
数据切开之后,节点之间不重叠。好处是不需要缓存融合,不用互相传页,也没有全局锁的争用。代价转移到了三个地方。
第一处是跨分片事务。一次写入落在两个分片上,就要走两阶段提交。准备阶段一轮跨网络往返,提交阶段日志要落盘。协调者如果中途故障,还得走恢复流程,把悬挂的事务收掉。这条链路上的每一次往返、每一次落盘,都会进提交延迟。
第二处是副本一致。要保证一份数据有多个副本还不乱,得靠多数派日志复制这类共识机制。写入要等到多数派确认才算成功。延迟由最快的那个多数派决定。副本数越多,跨得越远,这个延迟就越高。多数派不可达时,保一致就得牺牲可用性。这是这类机制的固有取舍,跟实现质量没关系。
第三处是跨片查询。原来一条 SQL 搞定的事,切开之后要分发到多个分片。中间结果汇总回来,聚合、排序、分页还得在协调层再做一遍。数据量一大,这一步的开销比查询本身还显眼。
瓶颈不在同一个地方
把两条路线的瓶颈摆在一起,选型思路才清楚。只看"能不能水平扩展"看不出差别,这也是我做下面这张表的原因。
| 对比维度 | 共享存储集群 | 无共享分布式 |
|---|---|---|
| 数据副本 | 一份,在共享存储上 | 每分片多副本 |
| 内存一致性 | 跨节点传页,靠缓存融合 | 不需要,节点数据不重叠 |
| 锁协调 | 全局锁服务,行锁跨节点授予 | 节点内本地锁,跨片靠分布式事务 |
| 主要瓶颈 | 私有互联的带宽与延迟 | 跨分片协调与共识延迟 |
| 热点敏感点 | 热点集中则互联被打满 | 热点分片则该分片成瓶颈 |
| 扩展方向 | 加计算节点,存储不变 | 加节点,存储与计算同扩 |
| 前置设计成本 | 低,应用无感 | 高,分片键是核心决策 |
| 运维复杂度 | 中,拓扑简单 | 高,节点多、扩缩容要搬数据 |
一个反直觉的结论:热点两种路线都解决不了
很多人上分布式是为了扛住高并发。这里有个容易漏掉的点。压力大和压力集中,是两个问题。架构扩的是前者,扩不动后者。
热点集中在少数几行或者少数几个账户,共享存储集群这边会看到全局锁争用。跨节点传页跟着一起暴增,互联被打满,再加节点只会更挤。无共享分布式那边,热点如果全落在一个分片键段上,那个分片就成了瓶颈。加别的节点一点用没有。两种路线都不会自动消解热点。热点治理是另一件事,要靠在业务层拆分热点键、排队、异步化来解决。
所以"能水平扩展"和"应该水平扩展"之间,隔着一个问题。你的压力是摊开的,还是压在少数几个键上。
我现在的判断顺序
吃过那次亏,我把选型拆成四步,每一步都要有能量化的答案。
第一步,找分片键候选。分片键要能覆盖绝大多数访问。我一般会统计跨片访问的比例。这个数超过两成就很危险,说明业务的主访问路径根本不落在一个维度上。没有合格的分片键,分布式方案直接出局,不用往下谈。
-- 跨分片访问占比:一次事务触达的分片数大于 1 的比例
SELECT
SUM(CASE WHEN shard_cnt > 1 THEN 1 ELSE 0 END) / COUNT(*) AS cross_shard_ratio
FROM (
SELECT tx_id, COUNT(DISTINCT shard_id) AS shard_cnt
FROM tx_shard_trace
GROUP BY tx_id
) t;
第二步,看热点分布。热点集中度也是能算的。取访问量最高的前 20 个业务键,看它们占总访问的比例。这个比例高,说明热点集中,两条路线都得先做热点治理,再谈架构。比例低,说明流量天然分散,分布式才谈得上收益。
-- 热点集中度:访问量最高的前 20 个业务键占总访问量的比例
WITH k AS (
SELECT biz_key, COUNT(*) AS cnt
FROM biz_access_log
GROUP BY biz_key
)
SELECT
SUM(CASE WHEN rk <= 20 THEN cnt END) / SUM(cnt) AS hotspot_ratio
FROM ( SELECT biz_key, cnt, ROW_NUMBER() OVER (ORDER BY cnt DESC) AS rk FROM k ) t;
第三步,算延迟预算。跨分片事务和多数派复制都会把网络延迟加进提交路径。跨城部署的时候,这个数字要先算出来,再拿去对 SLA。算不进 SLA 的方案,架构再漂亮也不能用。
第四步,看运维能力。无共享要管的节点数是共享存储集群的好几倍。扩缩容要搬数据,分布式死锁的排查也比单机复杂。团队没有对应的运维储备,架构选得再对也会出事。
避坑清单
分布式和集群别混着讲。方案评审时这两个词混用,很容易把"加从库"当成"上分布式"。最后扩容方案和实际瓶颈对不上号。我现在会在方案里明确写清楚,写入点到底有几个。
分片键要在上线前定死,别指望上线后调。它决定了数据落在哪,改它等于全量搬数据加重建索引。我见过上线三个月后想换分片键的项目。最后是新建一套库做双写迁移,成本比原方案高得多。
最后一条是我自己搞错的。我一开始以为分片数越多扩展性越好,把订单表切成了 64 片。后来才发现片数多不等于吞吐高。每个分片都要维护副本,每个跨片查询都要多一次协调。片数要跟节点数和增长预期匹配,不是越大越好。
写在最后
架构选型里最贵的一句话是"分布式是趋势"。趋势是从整体说的,你的系统只有一件具体的事,就是它的数据长什么样、被谁访问、热点在哪。
我现在的顺序很固定。先问数据能不能干净地切开,再问热点是集中的还是分散的。两个都过关,分布式是合理选择。只要有一个不过关,就该考虑共享存储集群。或者干脆把单机做扎实,那也是更划算的答案。这样做出来的取舍,扩展性才花在刀刃上。
你手上的系统,分片键是按什么维度定的?评论区聊聊。
我是数据库小学妹,帮你少走弯路少踩坑,咱们下篇见👋