服务器跑了一年多,同一块固态盘,刚上线那会儿写文件干净利落,现在动不动就来一段几百毫秒的卡顿,业务方第一反应往往是"盘是不是要坏了"。这种报障我们接得不算少,真到机器上一查,绝大多数时候盘好得很,是盘以为自己还满着——它不知道你删掉的那批文件已经作废了,于是每次写入都先干一遍搬家活儿,延迟就是这么被搬出来的。把它从这个误解里拉出来,速度通常就回来了。
这篇不谈文件系统选型,也不展开内核脏页回写的那些参数,那些是另一篇的事。这里只讲一件事:SSD 的回收通路。从为什么 NAND 天生会慢,一直讲到 RAID 卡、加密层、虚拟化这几层谁会把这条指令吃掉。
discard 每次删除即时下发,省心但可能造成写入抖动;fstrim 把开销集中到可控窗口,更适合有明确低峰期的业务。机械盘想改一个扇区,磁头直接盖上去就行,写就是写。固态盘不行,NAND 闪存不能就地覆写:想改已经写过的地方,得先把那一整块擦成空白,再重新写进去。问题是,写入的最小单位叫页,擦除的最小单位叫块,而一个块里塞了很多个页,两者大小差着量级。
这就带来一个很别扭的局面:你只想改一个页的内容,但擦除必须以块为单位,所以盘必须先把这个块里其他还有用的页搬到别处,腾出地方擦干净,再把新数据连同搬走的老数据一起写回去。你写 1 份数据,盘内部实际搬了很多份,这个多出来的部分就是写放大。写放大不是 bug,是 NAND 的物理属性,只能缓解,不能消灭。
盘内部当然有垃圾回收来收拾残局,它会挑那些"有效页较少"的块,把残余的有效页挪走,整块擦掉变成空白块,留给后续写入。这个动作本身要占用盘的带宽和主控算力,而且必须在新写入进来的时候同步完成——所以你越急着写,它越手忙脚乱。
一块刚开始用的盘,出厂时全是空白块,垃圾回收随随便便就能找到干净的块,写入延迟低得让人忘记它的存在。用久了之后,盘几乎被写满过一轮,空白块要靠即时回收产生,每一次写入都可能要先触发一次搬迁,延迟和吞吐就一起往下掉。这就是"越用越慢"的完整链条。
关键在这儿:删除是一个文件系统层的动作,盘压根不参与。
你在服务器上执行删除操作,文件系统做的事是改自己的元数据——把这个文件占用的那些逻辑位置标记成"空闲",目录项一摘,inode 引用数减到零,完事。这个过程极快,因为根本没碰存储介质里的数据本身。
对固态盘而言,它看到的世界一直是这样的:那些已经被你删掉的文件所占的物理页,它仍然认为是有效数据。因为从头到尾没有人告诉过它"这些逻辑块地址不需要了"。于是它忠实地在垃圾回收时把这些垃圾搬来搬去,一遍又一遍,白干。
用量这个类比很好懂:文件系统像图书管理员,读者退书后他在目录卡上划掉一行;固态盘像后仓搬运工,他不知道书已经退了,仍然按"架上有这本书"来规划仓位,于是空间永远腾不出来。两边的认知脱节,就是所有问题的起点。
TRIM 就是用来把这两个世界的认知对齐的。它是一条由主机发给盘的通知:这些逻辑块地址,我不再需要了,你内部的对应页可以当垃圾处理。接口名字随总线不同略有差异——ATA 上叫 TRIM,NVMe 上叫 Deallocate,本质是同一个东西。
收到这条通知之后,盘可以把这些页从"有效数据"名单里剔除,垃圾回收在下一次整理时就能直接跳过它们,不用再搬。TRIM 本身不擦除任何东西,它只是让盘的垃圾回收不必再做无用功。这一点很多资料写得含糊,导致有人以为执行完就该立刻变快——不是的,它改变的是后续写入时的工作量。
明确一下 TRIM 和垃圾回收的分工:垃圾回收是盘自己的事,它永远在跑,负责制造空白块;TRIM 是主机给的情报,负责让垃圾回收知道哪些是废数据。没有 TRIM,垃圾回收照样干活,只是干得又慢又冤;有了 TRIM,垃圾回收的每一次搬运都打在有用的地方。
所以回收通路通不通,直接决定了盘在长期高水位运行后的表现。一块从头到尾没收到过回收通知的盘,用上一段时间后表现可能明显劣于同型号、同样写入量、但回收指令一路通畅的盘——硬件一模一样,差别全在这条链路上。
知道要发这条指令之后,下一个问题是什么时候发。工程上就两条路,各有脾气。
第一条是挂载选项 discard,行业内习惯叫连续丢弃。它的行为很简单:每删一个文件,立刻下发一次回收指令。好处是完全不用管,系统自己就把事办了,也不存在"忘了跑"的风险。坏处是每一次删除都多一次盘内操作,在一些盘和阵列组合上,会带来肉眼可见的写入抖动——尤其是那些批量删除临时文件、轮转日志的场景,删除动作密集的时候,抖动会被放大。
第二条是周期性的 fstrim。由命令或者 systemd 的 fstrim.timer(常见的默认节奏是每周一次)统一触发,把一段时间内攒下来的所有释放批量下发。好处是开销被集中到一个你能控制的窗口,放在业务低峰跑,用户基本无感。代价就是窗口内的盘内活动会比较明显,IO 压力在那段时间抬头,所以窗口选错位置等于给自己找事。
我的立场比较明确:生产服务器上,我更偏向定时丢弃。理由是它的开销形状可预期:你清楚它什么时候来、大概多大;而连续丢弃的开销是散落在每一次删除上的,排查抖动的时候非常难归因。除非你的业务删除极少、而且没有明显的低峰窗口可以把维护动作塞进去,否则没必要赌那个「不少盘都说没事」的概率。
| 方式 | 触发时机 | 开销形态 | 适用负载 | 主要风险 | 判据 |
|---|---|---|---|---|---|
| 挂载选项 discard | 每次删除文件即时下发 | 碎片化、高频次的盘内操作,跟随删除行为随机分布 | 删除稀疏、读写平稳的通用型业务 | 写入延迟抖动难归因,批量删除时尤其明显 | 低峰期抽样对比开关前后的写入延迟尾部 |
| fstrim.timer 每周 | systemd 计时器周期性批量触发 | 一次性集中下发,窗口内盘内活动较集中 | 有明确低峰窗口的绝大多数生产服务器 | 窗口选址不当会和业务高峰重叠 | fstrim -v 输出的丢弃字节数,配合窗口内延迟 |
| 手动 fstrim(维护窗口) | 运维手工执行,配合变更窗口 | 完全由人控制,可事前停写或切流量 | 数据库主从、有切换能力的集群节点 | 依赖人记得,漏跑即失效 | 执行记录挂在变更单里,可追溯 |
| 云盘(guest 内执行) | 后端自行回收,虚拟盘多不接受 guest 指令 | 多数情况下无实质动作,窗口内几乎无额外开销 | 云主机本地盘之外的大多数云盘形态 | 把 0 字节误判成"回收失败"而反复折腾 | lsblk --discard 的粒度与上限是否非零 |
| 硬件 RAID 卡后置 | 取决于阵列卡是否透传 | 不透传时下发动作被静默吞掉 | 传统阵列卡组建的本地盘组 | 上层显示支持,实际从未落到成员盘 | 查厂商文档是否写明支持,别只看工具输出 |
| 完全不处理 | 靠盘自带垃圾回收硬扛 | 写入时现发现打扫,延迟随使用时间走高 | 只适合低水位、写入量很小的盘 | 长期高水位的机器上性能持续劣化 | 同样的写入模式,延迟曲线是否随时间抬升 |
这里得泼一盆冷水。回收指令是维护动作,不是一按就提速的魔法开关。它的作用是"把空间还给盘",让盘内部少做无用搬运,如此而已。指望在刚上线的新盘上开一下就有肉眼可见的提升,那注定要失望。
哪些场景差别不大?空盘、写入量很小的系统盘、长期低水位的静态文件存储,回收做不做几乎没有可感知的区别——因为压根就没积累多少需要回收的东西。反过来才对:长期高水位运行、频繁删写的盘,比如跑消息队列的、跑日志收集的、做 CI 构建缓存的、跑临时表空间的,这类才是必须把这条链路查清楚的对象。
还有一个容易混淆的点:回收做得好不好,看的是延迟和写放大,不是盘的寿命。有人跑完回收指令就去翻 SMART,想找"有效证据",找错了地方。累积写入量这个数字不会因为一次回收而变小,它记录的是历史;真正该盯的是之后的写入延迟分布。
怀疑回收没到位,别急着改配置,先把两件东西查明白。
第一个是 lsblk --discard。这条命令列出每个块设备的丢弃能力,重点看两列:DISC-GRAN 是粒度,也就是单次回收指令的最小对齐单位;DISC-MAX 是单次能处理的最大字节数。这两列如果显示 0,说明这条路径上不支持丢弃,后面的讨论就无从谈起。非零值意味着协议层面通了,但注意,它只代表这一层宣称自己支持。
下面这段是示意输出,具体数值以你在自己机器上跑出来的为准:
NAME DISC-ALN DISC-GRAN DISC-MAX DISC-ZERO nvme0n1 0 512B 2T 0 ├─nvme0n1p1 0 512B 2T 0
第二个是 fstrim -v /。它会打印本次实际丢弃了多少字节,这是最直接、也最有说服力的"到底有没有生效"的证据。一个长期没做回收的盘,第一次跑出来的数值往往大得吓人,那是这段时间内累积的释放量;之后周期跑的时候,数字应该稳定在一个和你日常删除量匹配的量级上。
如果跑出来是 0 字节,别急着下结论,它至少有几种完全不同的含义:一是这段时间确实没删东西,无垃圾回收,正常;二是刚跑过没多久,窗口太短没攒下量;三是路径上某一层不支持或者不透传,指令被吞了;四是后端自己做了回收,guest 内看不到任何字节。前两种不用管,第三种要去查层级,第四种在云主机上属于正常现象。分清楚是哪一种,比数值本身重要。
我的习惯是把 fstrim -v 的输出记进日志,而不是只在终端看一眼。攒上几周的数据,你就能看出这台机器的删除行为是不是有周期性,窗口排在哪天最合适,以及有没有某一天突然数字异常大——那通常意味着业务在批量清理,值得聊一聊。
这是整篇文章里最容易被跳过、也最容易翻车的一节。回收指令必须一路传下去,从文件系统到块设备层,再到物理盘。中间只要有一层不支持或者不透传,上层工具报告给你的"支持"就是假的——你以为发下去了,实际没有任何一颗 NAND 收到。
硬件 RAID 卡是头号嫌疑。很多传统阵列卡压根不透传这条指令,它们在操作系统面前把自己伪装成一个大容量硬盘,指令到它这里就结束了。怎么确认:查厂商文档里有没有明确写支持,不能只看 lsblk 的输出——有些卡会让上层"看起来支持",实际上往下一层什么都没发生。要不要处理:如果不透传,你能做的选择不多,要么接受盘组自己慢慢扛,要么调整方案,比如把需要频繁删写的负载挪到直连的盘上。
加密层(LUKS/dm-crypt)默认不透传。这是设计上的保守选择,因为透传回收会泄露一些关于哪些块在用、哪些没用的信息。要打开得显式配置 allow_discards。这里的取舍是明确的:开了会有信息泄露的顾虑,不开就得接受盘内部的搬运效率下降,按你的安全口径定,没有标准答案。默认状态下它是关的,很多人不知道这点。
LVM / thin provision 又是一个单独的坑。薄卷(thin pool)自己有一套的丢弃处理逻辑,回收指令走到薄卷层要按它的规则走一遍,配置不对就停在这一层。凡是用到薄 provisioning 的环境,这一层必须单独验。
虚拟化与云盘后端的情况最特殊,单独在下一节讲。
排雷的顺序我建议这样走:先看物理盘本身的能力,再一层一层往上验,每加一层就重跑一次检查。哪一层开始数值变了,断点就在那儿。从下往上查比从上往下查要快得多,因为你能确定底层基础是好的。
这一节的结论很简单也很反直觉:很多云盘根本不需要、也不接受虚拟机内部的回收指令,因为后端自己在做空间回收。你在 guest 里跑 fstrim,出来 0 字节,这是完全正常的,不代表盘坏了,也不代表你配置错了。
为什么?云盘对虚拟机呈现的通常是一个逻辑卷,后面挂着的是一整套分布式存储。你在里面删除文件,后端有它自己的回收机制来处理对应的物理空间。这条路径上,虚拟机内部的通知传到 hypervisor 层往往就终止了,后面的事由存储集群自己调度。这时候执着于让 fstrim 跑出非零数字,是在追一个不存在的目标。
那什么时候才需要较真?三条线索可以参照:一是持续观察到写入延迟明显劣化,且排除了网络、CPU、邻居干扰这些因素;二是这台机器跑的是写密集负载,LSM 类存储、binlog、WAL 这类频繁覆盖写的组件,对写放大最敏感,它们所在的那块盘,回收通路和预留空间都值得确认;三是同一批机器里只有个别的表现异常,这种对比最能排除共性配置的干扰。
遇到第一种情况,正确的动作不是自己去改一堆参数试,而是先确认这块盘的类型和后端行为。本地盘和云盘的处理方式完全不同,搞反了纯属白干。
遇到云盘上 fstrim 一直返回 0 的情况,别自己瞎猜,直接开工单问一万网络的工程师——深耕 19 年(成立于 2007 年)积累下来的那些盘型脾气,问一句比查半天快。
回收指令解决的是"通知"问题,还有一个同等重要的问题是:盘手里有没有周转余地。
预留空间(OP)这个概念得拎出来说。厂商出厂时就留了一部分用户看不到的空间,专门给垃圾回收周转用。企业盘预留通常比消费盘给的足,这是两者在长期写入表现上拉开差距的重要原因之一,也是同样标称容量、价格差一截的地方花在哪儿了。选购的时候别只盯着标称容量和顺序读写数字。
用户层面自己也能做一件事:不要把分区分满。给盘留一部分始终不被写到的容量,等于手动加了一层预留空间。做法很简单,分区的时候少分一点,或者用保留块的方式留出来。代价是可用容量变小,收益是垃圾回收始终有空白块可用,不用每次写入临时现腾。这笔账在高写入负载上非常划算,那种"每一 GB 都要榨干"的分配思路在数据库机器上是要吃亏的。
日志与临时文件分离看着是老生常谈,落到实处的人不多。把高频删写的数据(日志、临时文件、缓存、队列)放到单独的盘或者单独的分区上,它的写放大就不会影响到存核心数据的那块盘。这不是洁癖,是隔离故障域——在不少事故场景里,日志盘写穿导致的抖动,往往被误判成"数据库出问题了"。
写密集负载在选盘的时候就要多想一步。承受高频覆盖写、delete 密集的机器,选盘时应该优先考虑耐久度和预留空间给得足的盘型,而不是只看标称容量。这类盘子贵出来的部分,买的就是长期的延迟稳定性。具体盘型的参数与可用性,以签约时最新报价与合同为准,别对着宣传页上的浮点数字做决定。
坑一:把"感觉变慢"直接当成硬件故障。坑在哪——很多人第一时间申请换盘,换完头几天确实快,过几个月又慢回去,因为根因从来没动过。怎么避——先查回收通路,lsblk --discard 和 fstrim -v 两条命令几分钟出结果,把这步做完再谈换盘。
坑二:只看工具报告支持就收工。坑在哪——中间任何一层不透传,上层显示的都是假象。怎么避——从物理盘开始由下往上一层一层验,每加一层重跑一次,还要拿厂商文档交叉确认,尤其是硬件阵列卡。
坑三:在加密层上想当然。坑在哪——LUKS/dm-crypt 默认不透传,很多人默认它是通的。怎么避——明确知道需要显式打开才有效,并且清楚打开它意味着什么,这是安全口径的选择,别稀里糊涂地改。
坑四:在云主机里追一个不存在的数字。坑在哪——后端已经替你回收了,guest 内必然是 0,有人为了让它非零反复折腾,甚至怀疑配置。怎么避——先搞清楚盘的类型,明白 0 字节在这种场景下的正当含义,把精力放到延迟曲线上。
坑五:把回收当性能开关,一劳永逸。坑在哪——开了就不管了,既不排周期也不看输出,几年后回到原点。怎么避——把它排进例行维护,把输出记进日志,让它变成一件有记录、可追溯的常规动作。
Q1:discard 和 fstrim 要不要同时开?大多数情况下不必,我倾向选一个。连续丢弃是"每次删除都触发一次",碎片化地散落在整个运行期间;周期性丢弃把同样的总量攒起来一次下发,窗口可控。两边都开的结果是:零星的小额下发依然存在,同时低峰期还会再来一次批量动作,重试同一件事,收益有限而抖动源变多。真正需要取舍的场景是那些删除极其稀疏、又没有明确低峰窗口的机器——那种可以考虑连续丢弃省心。除此之外,定时的方式更好归因、更好排班,也更容易向业务方解释"这段时间为什么 IO 高"。
Q2:为什么 fstrim 跑完还是慢?先看跑出来的字节数。如果是 0,说明这条通路上压根没有东西被回收,要么这一层不支持、要么被中间层吃掉了、要么后端自己处理了,问题在链路而不是在执行。如果有量但还是慢,那多半是回收通路已经通了,慢的来源在别处:写放大本身就有物理下限,盘长期在高水位跑、预留空间不足、或者负载模式就是持续覆盖写,这些都会让延迟居高不下。还有一种常被忽略的情况——累积损伤已经形成,长期高强度写入之后 NAND 的状态和刚出厂不同,这时候再看短期内那点回收效果就不现实了。对症下药的前提是先分清是链路问题还是容量问题。
Q3:硬件 RAID 卡后面要不要管 TRIM?要管,但管的重点不一样。先查这张卡到底透不透传,这一步看厂商文档,不能只看系统里的工具输出——某些卡会让上层显示支持,实际什么都不传下去。如果不透传,你在操作系统层面做再多调整也没有意义,成员盘永远收不到通知,只能在自己的垃圾回收里硬扛。这时候能做的取舍很有限:要么接受这块盘组的长期表现,要么把删除密集的负载挪出去,挪到直连、通路干净的盘上。反过来,如果卡明确支持透传,那就正常往下走:确认能力、排周期、看输出。这张卡的脾气在选型阶段就该问清楚,而不是上线半年后再翻文档。
Q4:云盘需要定时 fstrim 吗?多数情况下不需要专门管。云盘的后端存储自己有一套空间回收机制,虚拟机内部那条通知往往传不到物理层,也无需传过去。所以你在里面跑了命令、出来 0 字节,这不是故障信号,把它当成"执行失败"反复排查是浪费时间。真正值得花精力的是观察延迟分布:有没有随时间抬升的趋势、写延迟的尾部是不是在恶化。这些才是判断磁盘状态的有效输入。什么时候该找服务商确认?当你在排除掉其他因素之后,仍然看到持续的写入劣化,或者跑的是对写放大格外敏感的负载,那就不妨开口问一句这块盘的类型和回收策略。
Q5:TRIM 会不会缩短盘的使用寿命?方向恰恰相反。回收指令的目的就是减少盘内部无意义的搬运,它告诉盘哪些数据是废的,垃圾回收就不用再搬动这些垃圾。搬得少,实际写进 NAND 的量就少,写放大随之下降。这才是它对寿命的正面影响。真正的顾虑在别处:不要在高峰期集中跑大批量的回收动作,那会在窗口内制造一次明显的盘内活动,让业务延迟受影响,这是运维节奏的问题,不是耐久问题。另外要区分清楚两件事——判断回收做得好不好,看的是延迟和写放大;盘的磨损状况,得去看 SMART 里那些和 NAND 磨损相关的属性(已用寿命百分比、可用备用块、主机累计写入量),两者不是一回事。
Q6:多久跑一次合适?没有放之四海皆准的数字,要看你的删除节奏。systemd 常见的默认节奏是每周一次,对大多数通用服务器够用,低峰窗口也好排。判断标准很朴素:如果每次跑出来的字节数都很小,说明攒不够东西,周期可以拉长;如果每次都是一大坨,而且执行期间能观察到 IO 压力,说明堆积量太大,该缩短周期或者换个更大的窗口。删除密集的机器(比如有大量临时文件、日志轮转、构建缓存的)可以排得更勤一些;长期只读、几乎不删东西的机器,一个月一次都嫌多余。跑之前务必确认执行时间和业务低峰对齐,这一步比频率本身重要得多。
把上面这些串起来,遇到"SSD 越用越慢"的报障,我的处理顺序是这样的:先看有没有坏,SMART 里和 NAND 磨损相关的那些属性(已用寿命百分比、可用备用块、主机累计写入量)过一遍;再验证回收通路,lsblk --discard 看粒度和单次上限是否为零,fstrim -v 看实际丢弃字节数;然后逐层往下查,阵列卡、加密层、薄卷、虚拟化后端,哪一层断了就停在哪一层;最后才讨论硬件,把排周期、查职守、要不要换盘这类问题留给有据可依的判断。
这套流程走下来,你会发现大部分"该换盘了"的结论其实站不住。真正需要动硬件的,是 SMART 里明确显示磨损接近阈值、或者写放大已经压不下去的情况,那是另一个故事了。
如果查到中间层就断了、或者对着 lsblk 的输出看不出所以然,别硬扛。把你在每一层跑出来的原始输出原样贴给工程师,比转述十句"好像不支持"有用得多——判据清楚,答复也会快。有工单通道的服务上这一步很省事:截个图丢过去,附上盘型、阵列卡型号、是否有加密层这些背景,剩下的交给熟悉这些组合的人。
回收这件事的终点不是"配好了",而是"它在持续发挥作用"。配的时候花的时间不多,难的是之后的观察,所以我建议把它当作一件有记录的常规动作来养:每次执行的输出存进日志,攒够几周就能看出删除行为的周期性,窗口排在哪天最合适也就清楚了;盯写入延迟的分布而不是平均值,平均值会掩盖掉那些最影响体验的长尾,你会发现坏的那几次总出现在特定时段;留意那些 fstrim 输出异常大的日子,那通常意味着业务侧在批量清理,值得聊一下能不能错开。
写放大短期内其实不太好直接量化,能抓的抓手是它的间接表现:同样的写入模式,延迟是否随时间抬升。这个趋势线比任何一个单点数值都可靠。如果修整回收节奏之后曲线还是稳步往上走,说明物理层面的余地已经不大了,该考虑预留空间是不是给少了、或者这块盘是不是该让它退役了。
换盘的判断线我说得直接一点:不是"感觉变慢",而是几条硬指标凑齐——磨损属性接近阈值、可用备用空间被消耗、回收通路确认是通的、延迟劣化趋势在调整后依然延续。前三条成立、第四条也成立,那就别犹豫;如果第四条不成立,先回头查链路和容量规划,别急着把盘换掉。
来源说明:本文所述为通用的固态存储工作原理与常见运维工具的行为说明,具体到某台机器上,需以所用的内核版本、文件系统类型、阵列卡或云盘后端的官方文档为准。涉及设备选配与业务的,具体以签约时最新报价与合同为准。
Copyright © 2013-2020 idc10000.net. All Rights Reserved. 一万网络 科技有限公司 版权所有 深圳市科技有限公司 粤ICP备07026347号
本网站的域名注册业务代理北京新网数码信息技术有限公司的产品