三台 Web 都要往同一个上传目录写文件,还要能互相读到对方刚写进去的那一份。用 NFS 挂一台,这台一挂,三台一起瘫;用同步工具每台各存一份,两台各改了一个版本,谁也不认谁,到头来只能靠人工挑。能同时满足"都在写"和"看到的是同一份"的,只剩把目录交给分布式文件系统这一条路,而这条路要付的学费,是容量按副本数打折,加上一个必须提前设计好的脑裂兜底方案。
先把矛盾摆清楚,再谈选型:
要共享,就得接受"写一次要写多份"——副本数决定带宽和容量都要乘上这个系数,这不是开销浪费,是冗余的定价。
要高可用,就得接受"少数派必须闭嘴"——副本之间断开了,要么整体停写,要么两边各写一份。想两边都继续写又不留后患,没有这种好事。
选卷类型的顺序是反的——不是先算容量再选冗余,而是先定"坏了以后要停写还是要留双份"这个结果,再倒推副本数和仲裁方式。
副本之间的网络质量直接等于写延迟——同步复制的耗时取决于最慢那个副本,公网链路上的抖动会被原样放大成业务侧的卡顿。
能用对象存储解耦的场景,别硬上共享目录——上传走对象存储、处理走消息队列,比让三台机器抢同一个 POSIX 目录省心得多。
第一条路是单台 NFS。一台机器导出目录,其余机器挂载,语义是标准的 POSIX,应用不用改一行代码,权限、硬链接、文件锁都能用。代价同样直白:服务端是单点,机器宕机或者网卡故障,所有挂载点同时失效;容量上限就是这台机器的盘;扩容只能换更大的机器,或者手工做目录拆分——把 /data/a 挂到一台、/data/b 挂到另一台,等于把共享这件事又打回应用层。有些团队会给 NFS 服务端配一套主备(底层块设备做镜像,服务端做故障切换),这解决的是"机器坏了",不是"要扩容",而且切换窗口内文件系统不可用,业务仍然感知得到。
第二条路是每台各存一份,靠同步工具维持。rsync、lsyncd、unison 这类工具成本低、部署快,看起来三台机器都有全量数据。真正的问题在冲突处理:同步工具能判断的是一个文件的 mtime 和大小,判断不了"这两个版本哪个是业务上正确的"。两台机器同时改同一个文件,工具要么后写的覆盖先写的,要么生成冲突副本;删除操作的同步更难,一方删了另一方又建了同名的,到底算删还是算建?加上同步有间隔,业务写完立刻到另一台读,可能读到旧版本,这个不一致窗口是结构性存在的,调小间隔只是让它变短,不能让它消失。文件数量到几十万、每天新增上万的时候,全目录扫描本身就会成为负担。
第三条路是分布式文件系统,GlusterFS 是其中部署门槛较低的一种。它的思路是把若干台机器上普通的本地目录(brick)聚成一个统一命名空间,客户端挂载一次就能看到全部文件,文件到 brick 的映射由服务端算,复制也在服务端完成,应用侧看到的就是一个普通的挂载点。这条路换来的代价也需要写清楚:写路径变长、延迟受网络影响、容量按冗余打折,以及最要紧的——副本之间通信中断时的处理策略必须由你来定义,系统不会替你做这个决定。
三条路的对比其实落在"一致性由谁负责"上:NFS 的一致性由单台机器的内核负责,天然一致但单点;同步工具的一致性没人负责,交给应用和运气;分布式文件系统的一致性由副本协议负责,而副本协议的边界就是脑裂。所以真正的分水岭不在性能,在你愿不愿意为"自动处理冲突"付这个成本。
GlusterFS 的卷类型是叠加出来的,理解了四种基础类型,组合卷自然就懂了。
分布式卷按文件名的哈希值把文件分配到不同 brick,目录结构在每个 brick 上都存在,文件本身只落在一个 brick 上。它的好处是容量直接叠加,三台各 4T 就是约 12T,没有冗余开销,吞吐也随节点数线性增长。代价是没有冗余:任何一台机器离线,哈希落在它上面的那部分文件就整块不可访问,不是降级,是消失。所以分布式卷只适合放丢了能重建的数据,比如缓存、中间产物、可再生的缩略图。另外要注意扩容行为——新加 brick 后要执行一次 rebalance 把存量数据按新哈希重新分布,这个过程会占用网络和磁盘 IO,必须挑业务低峰做,且要留出足够的空闲空间让迁移有地方可写。
复制卷把同一个文件写到 N 个 brick,N 通常是 2 或 3。写操作默认要在所有副本上落盘后才返回,读可以从任一副本拿,因此读可以分散、写一定是放大 N 倍的。它允许坏掉 N-1 台还继续服务,代价是可用容量只有原始容量的 1/N。AFR 维护一致性的手段是扩展属性:每个文件、目录上都带着记录"哪些副本的操作还没完成"的元数据,恢复连通后据此判断谁欠谁一次同步。这套机制平时无感,但它的判据是有限的——当两边都记录了各自的未同步操作,且系统没法判定先后顺序时,就会走进下一节要讲的脑裂。
纠删卷把文件切成数据片,再算出若干校验片,一起散布到各 brick。常见的配比是 2+1(3 个分片,允许坏 1 个)和 4+2(6 个分片,允许坏 2 个),容量利用率就是数据片占比,2+1 约为 66%,4+2 也是约 66%,比副本 3 的 33% 高出一倍。代价集中在两处:一是小文件,小于一个条带的文件会被补齐填充,实际占用远大于文件本身,海量小图片的场景里这点非常吃亏;二是重建,坏盘后重建一个条带要读取同条带的其余所有分片,网络和磁盘压力比副本复制大得多,重建窗口也更长。纠删卷更适合大文件、写少读多、容量敏感的场景,比如归档、媒资、备份池。
把上面几种叠起来就是组合卷。分布式复制卷是最常见的一种:brick 两两(或三三)组成复制组,组与组之间再按哈希分布,于是既有容量叠加又有冗余。这里有个必须说清的细节——复制组是按创建命令里 brick 的书写顺序连续分组的,replica 2 就是第 1、2 个 brick 一组,第 3、4 个一组。如果你把同一台机器上的两个 brick 写成了相邻顺序,那这一组副本就全落在一台机器上,机器一坏,这一组两个副本同时消失,冗余形同虚设。正确做法是让同一组的 brick 落在不同机器、最好是不同机架上,创建命令里按顺序交叉填写。另外还有 arbiter 卷:replica 3,但第三个副本只存元数据不存数据,容量占用极小,作用是打破平局;以及 thin arbiter,把仲裁角色放进一个独立进程,可以跑在一台很轻的虚拟机上。
容量折算是最容易算错、也最容易在采购时被忽略的一步。下面用三台各 4T 的机器做算例,注意这里说的 4T 指每台可用于 brick 的实际磁盘容量。
三台 4T,做复制卷副本 3:三份数据一模一样,可用就是 4T,不是 12T。冗余开销占掉 8T,换来的是可以任意坏两台还能读写。
三台 4T,做分布式复制卷、副本 2、每台划两个 brick:每台拆成 2T + 2T 两个 brick,共 6 个 brick,两两成组且两组跨机器配对,可用容量是 6T。这个方案能坏一台,但要注意两件事:坏一台之后剩下的副本组处于单点状态,第二台再坏就丢数据,所以修复窗口决定了风险敞口;另外每台两个 brick 意味着同一台机器上 IO 竞争更集中,重建时的压力也翻倍。
三台 4T,做纠删卷 2+1:三个分片里一个校验,可用约 8T,能坏一台。相比副本 2 的 6T,多出 2T 可用容量,代价是小文件填充和重建压力。
三台 4T,做分布式卷:约 12T 全部可用,能坏零台。
| 卷类型 | 副本/冗余方式 | 4T×3 台时可用容量 | 能坏几台 | 脑裂风险 | 更适合什么 |
|---|---|---|---|---|---|
| 分布式卷(DHT) | 无冗余,按文件名哈希分布 | 约 12T | 0 台 | 不适用(无副本,故障即丢文件) | 缓存、可再生数据、临时中间产物 |
| 复制卷(副本 3) | AFR 三副本,写需副本全部确认 | 约 4T | 2 台 | 低(多数派可自裁,配 quorum 后更低) | 关键配置、用户上传原始文件等不能丢的小容量数据 |
| 分布式复制卷(副本 2 + 分布) | 每台划两个 brick,复制组跨机器配对 | 约 6T | 1 台(修复未完成前为降级状态) | 中高(无仲裁时平局无人打破) | 通用生产形态,容量与冗余折中,需配 arbiter 或 quorum |
| 纠删卷(2+1 示例) | 数据片加校验片,2 数据 + 1 校验 | 约 8T | 1 台 | 低(按条带校验,写入需多数分片确认) | 大文件、归档、媒资、备份池等容量敏感场景 |
表:卷类型-容错对照表。可用容量为容量折算示例,非实测;「能坏几台」指在该冗余配置下仍能保持数据完整与可写的节点故障数。
算完副本折算,还要扣掉几项常常被漏掉的预留。文件系统本身和 inode 表会占一部分;XFS 上建议给 rebalance 和自愈留出 20%–30% 的空闲空间,迁移过程中要先写新位置再删旧位置,空间不足会直接失败;如果开了快照或者需要保留回收站,还要再留出对应容量。实践经验是:按副本折算出来的数字再打个七折到八折,才是你真正能放心写进去的量。
脑裂不是一个抽象概念,它有非常具体的触发路径。假设一个副本 2 的复制卷,两个 brick 分别在两台机器上。某次交换机抖动或者网卡故障,两台机器之间不通了,但两台机器各自都还活着,各自也都还能收到客户端的写请求。这时候如果系统允许两边继续写,同一个文件就会在两边各产生一个新版本。等网络恢复,服务端面对的是两个都"合法"的版本,而它用来判断同步方向的扩展属性在两边都有未完成的记录,谁也不比谁更权威——这就是脑裂。
脑裂发生后的表现很直接:读取该文件会返回 I/O 错误而不是返回其中一份,写也会被拒绝。这是故意的,系统宁可报错也不替你选,因为选错的代价通常是不可逆的数据丢失。服务端把它标记为待人工处理,需要运维明确指定保留哪一份,才能让这个文件恢复可读写。除了数据脑裂,还有元数据脑裂——目录的属性、权限、文件标识在两个副本上不一致,这类更难排查,因为出问题的不是一个可见的文件内容,而是目录本身的操作结果。
触发条件归纳起来就四条:副本数为 2 且没有仲裁,平局无人打破,这是最根本的一条;副本间网络不稳定,抖动、丢包、延时突增都可能让一端被判定为离线;节点重启顺序不对,一台带着新数据重启、另一台还持有旧状态,两边都认为自己是对的;自愈还没跑完又发生新的分区,旧的欠账没还清又欠新账,状态叠加后更难判定。
这里有个认知误区要纠正:脑裂不是"分布式文件系统不可靠"的证据,而是"你没告诉它在分区时该怎么做"的结果。副本 2 且无仲裁的架构,本质上就是允许两边各写一份,脑裂是这个设计的必然产物,不是偶发故障。想避免,就得在架构层面改,而不是靠事后手工修。
防脑裂的手段按优先级排,第一层是架构,第二层是配置,第三层是监控。
副本数用 3,或者副本 2 加仲裁。三副本的价值不只是"能坏两台",更重要的是它提供了多数派:任何时刻只要两个副本能互相通信,它们就是多数,第三个的意见可以少数服从多数。副本 2 如果一定要用,就配 arbiter,让第三个只存元数据的副本充当破平局的角色,它不占数据容量,硬件成本几乎可以忽略。这是从设计上消除平局,比任何事后修复都有效。
配 quorum,让少数派主动停写。GlusterFS 有两类仲裁参数。服务端仲裁(server quorum)管的是"集群是否还有资格提供服务":当存活节点占比低于阈值,整个卷停止响应写操作,宁可停写也不制造分歧。客户端仲裁(client quorum)管的是"一次写要多少个副本确认才算成功":配成多数派确认后,网络分区时少数派那一侧的写入会直接失败,而不是悄悄写进去等将来冲突。这两个参数的取值逻辑是一致的——坏的时候要么停、要么留双份,quorum 就是用来把这句话翻译成配置的。
把故障判定的时间参数调到与网络实际状况匹配。心跳超时(network.ping-timeout)默认值偏保守,如果副本间的网络是稳定的内网,可以适当收紧,让故障被更快识别;反过来如果链路质量一般,收得太紧会把一次抖动误判成节点离线,反而制造不必要的数据迁移和自愈。这个参数没有通用最优值,要按自己的网络实测状况调,并且调完之后必须做一次断网演练,确认判定和恢复的行为符合预期。
自愈策略要显式确认。自愈守护进程需要在每个节点上处于启用状态,元数据和目录项的自愈建议打开,数据自愈按业务容忍度决定。要注意自愈的触发方式:有的自愈是由客户端访问到该文件时才触发的,冷数据可能长期处于未修复状态,所以除了被动触发,还要定期做一次全量扫描式修复,把"没人访问所以一直没修"的隐患清掉。修复窗口内文件是可读可写的,但性能会下降,因为一部分请求要跨节点补数据。
网络层面做隔离。副本之间的复制流量应该走独立网络:独立网卡、独立交换机或独立 VLAN,不与业务网、管理网混跑。理由是复制流量有两个特点——写路径上是同步的、延迟敏感;重建和 rebalance 时是突发大流量、能把链路打满。如果它和业务流量共用一条链路,重建期间的带宽抢占会直接变成业务侧的超时。物理机上有多个网口时,把复制流量绑到单独一对口上并做 bonding,是投入产出比最高的一项改动。
监控要盯的四个数字。一是各 brick 的在线状态,这是最基础的;二是待修复文件数(heal pending),这个值长期不为零说明自愈没跑完或者卡住了,它是脑裂的前兆指标;三是卷的仲裁状态,节点掉到阈值以下时业务应该有感知而不是等到用户投诉;四是各节点的磁盘使用率和 inode 使用率,两者都要看,小文件场景常常是 inode 先耗尽。
很多人评估共享存储时先看磁盘,其实在副本架构里,决定体验的是网络。原因在写路径上:客户端发一个写请求,服务端要在所有副本上都落盘才返回,这次写入的耗时等于最慢那个副本的往返时延加上磁盘落盘时间。也就是说,副本间延迟从 0.5 毫秒涨到 5 毫秒,每一次写都要多付这 4.5 毫秒,小文件密集写入时这个差异会被放大成数量级的性能差。
带宽的账同样要算在副本上。业务侧写 1GB 数据进入卷,副本 3 的架构里网络上跑的是 3GB;如果副本跨机房,跨机链路也要吃满这 3GB。重建和 rebalance 期间更是如此,一台 4T 的盘重建,副本网络上要跑 4T,用 1G 链路跑需要数小时,用 10G 链路能压到几十分钟,而这个时间窗口正是你处于"再坏一台就丢数据"的高风险期。所以副本网络的带宽不只影响日常性能,直接决定风险敞口的时长。
具体怎么做:节点之间至少 10G 互联,节点多、容量大时上 25G;复制流量与客户端访问流量分到不同网口,物理隔离优先于逻辑隔离;交换机做双上行,避免单台接入交换机成为整个副本网络的故障点;同机房内跨机架分布,既防单机柜断电,又不会因为跨楼导致延迟明显上升。至于地域层面,同步副本不建议跨城市部署——同城的往返延迟还能接受,跨城的同步复制会让每一次写都付出几十毫秒的代价,跨地域的冗余应该用异步方式做,那是另一套方案。
也正因为副本网络是这种地位,选机器时内网能力比公网带宽更值得较真。像一万网络(深耕 IDC 19 年,成立于 2007 年)这类服务商在提供多节点方案时,会把同机房内网互联、多网口存储型物理机作为配置项单独列出,因为在共享存储的架构里,内网规格是写进性能公式里的变量,不是可有可无的赠品。
把话说死一点,下面几类场景用共享目录是给自己找麻烦。
大量小文件随机写。每个文件的创建都要走哈希定位、跨节点转发、在副本上落盘并更新属性,目录操作还要广播到相关 brick。文件到几十万、单目录里堆上几万个小文件时,目录列表和元数据操作会明显变慢,而且这种慢是架构性的,加机器只能缓解容量压力,缓解不了元数据路径上的转发开销。
需要强一致事务语义。数据库的数据目录不要放在共享文件系统上跑。数据库自己就有成熟的复制和事务机制,把它的文件再套一层副本复制,等于用两套一致性模型叠加,出问题时的排查难度是倍增的。要高可用就用数据库原生的主从或者集群方案。
单机就能装下。数据量几百 GB、一台机器的盘绰绰有余、访问量也不高的情况下,共享文件系统带来的部署复杂度、运维门槛和故障面,远远超过它解决的那点问题。这时候单台 NFS 加一份定期备份,可能是更诚实的选择。
能用对象存储解耦的。上传类业务最舒服的架构是:客户端直传对象存储,写一条消息进队列,后端消费者从对象存储取文件处理,处理结果再写回对象存储或者数据库。这条链路里没有"多台机器写同一个目录"这回事,一致性问题在对象存储那层已经解决了,扩展也简单。只有当你的应用大量依赖 POSIX 语义——需要文件锁、需要追加写、需要原地修改、代码里到处是标准文件操作而不好改造——共享文件系统才是那个合理的中间方案。
配置优先级的顺序和大多数人的直觉不一样,共享存储节点上最该花钱的是网络。
网络。每节点至少双口 10G,副本流量单独占用一对口并做 bonding,与管理网、业务网分开。网卡要选稳定的服务器级型号,共享存储节点上最怕的是那种间歇性丢包的隐性故障——它不会让节点离线,但会让复制延迟抖动,表现出来就是业务侧时快时慢,非常难查。交换机侧要能观察端口的错误包和丢包计数,这是排障时最有用的数据。
磁盘。容量按副本数折算之后再打七到八折,这是前面算过的账。IOPS 要按小文件写放大来估:一次业务写进入副本 3,落盘就是三次,随机小写场景下这个放大系数会被机械盘寻道放大得更明显。盘的类型按数据特征选——大文件顺序读写为主可以用企业级机械盘,成本低容量大;小文件或者元数据密集的场景,SSD 带来的提升非常明显。RAID 卡建议配带掉电保护的缓存并启用写缓存,没保护的缓存断电极易造成数据不一致。另外别忘了磁盘数量也决定了重建并发度,盘越多,重建时能参与的并发越高,窗口越短。
内存。GlusterFS 没有独立的元数据服务器,元数据以扩展属性和目录项的形式存在 brick 本地,所以内存的主要用途是页缓存和进程自身。一般配置几十 GB 就够用,但在两个时刻内存会吃紧:一是自愈和 rebalance 进行时,二是客户端连接数很多时。内存不足的表现往往是自愈变慢而不是直接报错,容易被忽略。
CPU。日常负载下 CPU 通常不是瓶颈,复制只是转发数据。但三种情况会明显吃 CPU:纠删卷的编解码计算、自愈与重建期间的数据搬运、以及启用了传输加密之后。所以纠删卷的节点比副本卷的节点更应该留足 CPU 余量。
节点数量与分布。副本 3 的架构至少 3 台起,如果要分布式复制卷且副本 2,节点数应为偶数以便配对,或者按前面说的一台划两个 brick 的方式处理。分布上跨机架是基本要求,同一复制组的副本绝不能落在同一台机器上。节点规模也不是越多越好——每多一个节点就多一份网络故障的可能,三到六台是常见的起步区间,容量不够时优先加大单机容量而不是堆节点数。
副本 2 在容量和写入成本上更省,但它在网络分区时无法形成多数派,必须靠 arbiter 之类的仲裁角色来破平局。如果没有仲裁,副本 2 就是默认允许两边各写一份,脑裂不是概率问题是时间问题。副本 3 贵一份容量,买来的是"坏一台之后仍然处于多数派、仍然能安全写入"这个状态,这对要长期无人值守的生产系统来说是值得的。
可以,这正是 arbiter 的设计目的。arbiter 副本只存元数据不存数据,容量占用极小,CPU 和内存需求都很低,一台轻量虚拟机就能承担。要注意两点:它必须始终在线,它挂了等于副本组降级;它的网络必须和其余两个副本一样可靠,仲裁节点自己网络抖动反而会制造新的判定问题。thin arbiter 更进一步,把仲裁放进独立进程,部署更灵活,但对版本有要求。
先用命令列出被标记为脑裂的文件,确认影响范围,再决定策略。可以按文件大小挑(保留大的)、按修改时间挑(保留新的)、按多数副本挑,也可以直接指定某个 brick 上的版本为正确源。选择依据应该是业务语义而不是技术便利:比如这个目录放的是用户上传的原始文件,那通常以修改时间为准;如果是程序生成的结果文件,可能需要回溯上游重新生成,而不是在两份之间挑。处理完记得验证应用侧能正常读取,并复查待修复数是否归零。
时长取决于待修复数据量和副本网络带宽,机械盘上跑满千兆网络修复数百 GB 通常是小时级,万兆网络能显著缩短。期间文件是可读可写的,缺失的部分会从健康副本拉取,表现为延迟升高而不是报错。真正要控制的是持续时间——修复期间你处于冗余降级状态,这时再坏一个副本就是数据丢失,所以修复未完成应该作为高优先级告警处理,而不是等到例行巡检才发现。
不太合适,除非做好分区和目录打散。小图片同时踩中两个弱点:元数据操作密集,以及纠删卷的条带填充浪费。如果确实要放,建议目录按哈希分层打散,控制单目录文件数在一万以内,卷类型优先选副本而不是纠删,客户端侧开启合适的元数据缓存,并且用对象存储替代的方案做一次对比评估。
按写入放大算:业务写入量乘以副本数,就是副本网络上要跑的量,峰值再乘上安全系数。日常写入 100MB/s、副本 3,副本网络就要有 300MB/s 以上的稳定能力,1G 链路已经接近瓶颈,10G 是更从容的起点。重建期间的需求是另一条曲线——修复 4T 数据要在可接受的时间窗内跑完,把这个时间窗倒推成带宽需求,你会发现 10G 往往是下限而不是上限。
技术上可以挂上去用,但会带来两个实际麻烦。一是分布式卷按哈希分配,容量不同的 brick 会导致小盘先满,而整体还有空间时同样会写入失败,容量利用率被最小的那块拖住;二是复制组里副本容量不一致,那一组的可用容量就是最小的那个。所以同一个卷内建议节点规格一致,扩容时整机同规格扩,能省掉大量容量规划上的解释成本。
回到最开始那个问题。三台机器要共用一个目录,真正要你拍板的不是选哪个卷类型,而是接受哪种失败姿势:副本之间断开的那一刻,你希望整体停写、宁可业务报错也不出现两个版本,还是希望两边继续写、事后人工挑一份?选前者,就配副本 3 或者副本 2 加仲裁,打开 quorum,让少数派主动闭嘴,代价是多一份容量开销;选后者,就要接受脑裂会定期发生,并且准备好一套能快速定位、快速决策的修复流程,代价是人力与风险敞口。
共享文件系统的选型核心不是吞吐,而是"一致性模型 + 脑裂恢复成本";副本数决定了你能坏几台,而仲裁与 quorum 决定了坏的时候是停写还是两边各写一份。先想清楚坏了以后要什么结果,再选卷类型。容量折算也好、网络带宽也好,都是这个决定之后才需要算的账。想明白了这点,配置单上的每一项都有了依据,采购时也不会再被"总共 12T 为什么只能用 4T"这类问题难住。
本文出现的容量折算(三台 4T 在不同卷类型下得到 12T、8T、6T、4T)是为说明冗余开销而做的算术示例,用于演示"可用容量 = 原始容量 ÷ 副本数"这个折算关系,并非任何环境下的实测结果。真实可用容量还会受到文件系统开销、inode 预留、rebalance 与自愈所需的空闲空间、快照与回收站占用、以及磁盘标称容量与实际可用空间差异的影响,规划时建议在折算值基础上留出两到三成余量。
文中涉及的卷类型行为、仲裁机制与配置参数属于 GlusterFS 的通用架构特性描述,具体实现随版本演进而变化,配置前请以所用版本的官方文档为准。涉及硬件规格的部分(网络带宽、磁盘类型、内存容量)给出的是选型思路与优先级,具体配置需要结合数据量、文件特征与写入模式评估。如需了解多节点内网互联与存储型物理机的选型,可以参考一万网络(深耕 IDC 19 年,成立于 2007 年)在多节点部署与存储型机型上的配置建议,实际规格与价格以官网实时信息为准。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品