news 2026/9/16 3:40:03

磁盘%util高不代表磁盘坏了:I/O性能排查实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
磁盘%util高不代表磁盘坏了:I/O性能排查实战

凌晨两点四十五分,告警群里弹出一条消息:“MySQL 主库磁盘 I/O %util 已连续 15 分钟超过 95%,请立即排查。”相信做过运维或 DBA 的朋友对这条告警一点都不陌生。你可能也会像我第一次遇到时一样,先冲到机柜前,准备换硬盘、加缓存、上 SSD,结果折腾一晚上,发现真正的问题根本不是磁盘本身。

%util 这个指标,是我排查性能问题这些年里见过最多、也被误解最多的一个。它看起来很直观——“util 不就是利用率吗,99% 那就是满了”——但实际上它的统计口径、适用场景和真实含义都跟直觉不太一样。如果你正在被“磁盘 %util 特别高”折磨,又或者你只是拿到了一台新服务器想做基线体检,这篇文章帮你把这条排查链路完整走一遍:指标怎么读、现场怎么看、进程怎么揪、优化怎么排优先级。


1. 先别急着给磁盘判死刑:%util 到底是怎么算出来的

1.1 从 iostat 的一行输出说起

最常用的观测工具就是 sysstat 包里的 iostat。很多人在排查时习惯敲iostat -x 1,然后盯着%util那一列看。我见过不少同事,看到某个盘的%util到了 90% 以上,立刻判断“磁盘瓶颈,必须扩容/换盘”,然后申请预算、走采购流程,结果硬件换了问题还在。根源在于:%util不等于通常意义上的“利用率”。

先看一条典型输出:

Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s r_await w_await aqu-sz %util sda 520.0 380.0 8320.0 24320.0 0.0 120.0 68.5 215.6 21.40 99.90

只看%util,这块盘确实是 99.9%,跟“满负荷”三个字非常贴切。但如果往下看r_await68.5 毫秒、w_await215.6 毫秒、队列长度aqu-sz21.4,这几个数字透露出来的信息比%util本身重要得多。等到后面定位阶段,你会发现完全不同的场景可能都会落在“%util 很高”这个表象上,所以必须搞清楚这个数字的统计口径。

1.2 %util 的真实身份:繁忙时间占比,不是物理利用率

%util的官方定义是:采样周期内,设备“有 I/O 请求在处理”的时间比例。它的原始计算逻辑是(设备繁忙时间) / (采样总时间)。换句话说,它统计的是“这块设备上有没有至少一个请求在排队处理”,而不是“这块磁盘的机械臂或者电子元件实际跑满了多少”。

这就带来第一个关键结论:只要设备上一直有请求在排队,%util 就会是 100%,哪怕这些请求本身很轻、吞吐量很低。你写一个程序,每秒钟往磁盘上打 1000 个 4KB 的随机小请求,%util 直接拉满;但相同时间内持续读大文件,吞吐可能几十 MB/s,%util 反而不一定满。所以在看这个指标之前,要先想清楚:它衡量的是“有没有活干”,衡量不了“干了多少活”。

第二个关键结论来自 sysstat 的官方说明:对于串行处理请求的设备(比如单块旋转硬盘),%util 接近 100% 确实意味着接近饱和;但对于并行处理请求的设备,比如磁盘阵列和现代的 SSD,这个数字并不能反映设备的性能上限。SSD 内部有多个通道和多颗闪存颗粒,NVMe 更是支持多队列并发。设备可以一边处理一个请求,一边接着接收新的请求,所以在它眼里“繁忙时间”占比很高,但硬件本身还有几十倍的吞吐余量。这也就是为什么 SSD 和 NVMe 上经常出现“%util 100% 但系统一点都不卡”的反直觉现象。

1.3 为什么 SSD 和 RAID 上的 %util 经常骗人

再往深一层说,磁盘阵列和 SSD 的“并行”本质是不同的。RAID 0/10 把数据条带化到多块物理盘上,一个请求被切分到多块盘同时执行;SSD 则是靠内部的多 Plane、多 Die 并发。无论是哪种,在系统监控层面,操作系统看到的是整块逻辑设备,它只能通过设备返回的“是否 busy”状态来判断繁忙程度。只要有一块盘在工作,逻辑设备就被标记为 busy,但可能另外好几块盘正闲着。

还有一种常见场景是硬件 RAID 卡带缓存。比如 RAID 卡配置了 WriteBack 模式,写请求先落缓存、快速应答,后台再把脏数据慢慢刷到物理盘上。这种情况下你看到的 %util 可能并不高,因为操作系统层面的请求都被缓存“吸收”了;但一旦缓存满了、或者电池学习周期开始强制刷盘,%util 会瞬间飙升。反过来,如果 RAID 卡没开缓存或者被判成直写模式,写压力会直接透传到物理盘,%util 就会特别敏感。后面排查的时候,这些底层细节都会成为突破口。


2. 判断磁盘是不是真的“不行”,要看这一组指标

2.1 一张表看懂 iostat -x 的关键字段

既然单看 %util 会误判,那就要把iostat -x输出的核心字段组合起来看。不同 sysstat 版本的字段名略有差异,比如旧版叫avgqu-sz、新版叫aqu-sz,旧版有svctm、新版直接标注“不要信任这个字段”。我自己在用的判断矩阵是这样的:

指标含义怎么用
r/s、w/s每秒读、写请求次数看请求量级,结合请求大小判断随机/顺序
rkB/s、wkB/s每秒读、写字节数看实际吞吐,跟 IOPS 一起算平均请求大小
r_await、w_await读、写请求平均响应时间(含排队)排查延迟的第一抓手
aqu-sz / avgqu-sz平均队列长度队列持续大于设备并发能力,说明在排队
avgrq-sz / rareq-sz、wareq-sz平均请求大小4K/8K 是小块随机,1MB 左右是顺序大块
%util设备繁忙时间占比只用来辅助判断,不单独下结论

其中最重要的组合是r_await/w_await加上aqu-szawait包含了请求在队列里排队的时间,所以它涨起来只有两种可能:要么设备本身响应慢,要么排队严重。而avgrq-sz决定了你这是“大量小请求把设备打爆”还是“少量大请求把带宽吃满”,这两类问题的优化方向完全相反。

2.2 用 await 和 avgrq-sz 还原现场

我习惯把设备状态分成三种典型画像:

第一种,高 %util + 高 await + 高队列。比如上面那个例子:%util 99.9%,w_await 215 毫秒,aqu-sz 21.4。这说明设备已经饱和,请求在排队,业务侧的 RT 必然被拉高。这时候不管 %util 是不是“真实利用率”,至少磁盘已经成为一个响应极慢的环节,必须处理。

第二种,高 %util + 低 await + 高 IOPS。常见于 NVMe 或高端 SSD:请求很多、并发很深,但单个请求几毫秒内就完成了。这类场景往往是应用层把请求打得很碎,比如数据库每行一个 UPDATE、日志一条一条刷。磁盘硬件未必是瓶颈,但你的 I/O 模式非常不经济。

第三种,低 %util + 高 await。这块盘看起来利用率不高,但响应慢得离谱。这通常意味着问题不在盘本身,而在更下层:云硬盘的 I/O 被限流、宿主机磁盘争抢、光纤交换机拥塞,或者硬件 RAID 卡在周期性刷缓存。这时候换业务代码没用,得去找基础设施的问题。

另外我强烈建议养成一个习惯:看 iostat 时先看 avgrq-sz(或 rareq-sz/wareq-sz)。如果平均请求大小只有 8KB~16KB,说明这是典型的随机小块 I/O,对旋转硬盘来说就是噩梦——一块 7200 转的 HDD,随机 4K 的物理极限通常只有一百多 IOPS,换算成吞吐连 1MB/s 都不到;但如果同样是 100 个 IOPS,请求大小换成 1MB 的连续读,吞吐就有 100MB/s。 同一个 %util,背后可能是“一天也写不完一个文件”的死局,也可能是“硬盘正跑得欢”的健康状态。

2.3 虚拟化和云硬盘场景,别忽略宿主机

现在很多生产环境都跑在虚拟化或公有云上,这时候 iostat 看到的是虚拟机内虚拟设备的状态,跟宿主机物理磁盘的状态隔了好几层。判断逻辑要额外加几条:

  • 云硬盘有 IOPS 上限、带宽上限,打满限流之后 iostat 里的%util会很高,同时吞吐被卡在一个固定值上。如果多次采样发现 rkB/s+wkB/s 稳稳地停在某个数,大概率是限流,去控制台看配额和监控曲线。
  • KVM 虚拟机使用的 virtio 磁盘队列长度是宿主机控制的,虚拟机内看到的队列只是“感觉上的”,真正排队可能发生在宿主机 vCPU 调度或存储后端。
  • 如果%util高但虚拟机内 CPU 有大量iowait,而物理机本身负载不大,可以进一步确认是不是存储后端(比如 NFS、分布式存储、超融合节点)出现了慢盘。

这一层问题靠 iostat 本身解决不了,得配合云厂商或基础设施团队的监控看板。但至少你要有这个意识:%util 高不一定等于这块虚拟盘“坏”了,它可能只是“被限制”了。


3. 高 %util 背后最常出现的几类“真凶”

3.1 进程级定位:把打盘的人揪出来

确认设备和 I/O 模式之后,下一步就是找到是哪个进程在打盘。iostat 是设备视角,进程视角要靠另外几个工具,它们都是排查 I/O 问题时我的“三件套”:

# 每个进程的磁盘读写速率,sysstat 自带 pidstat -d 1 5 # 动态查看哪个进程在读写磁盘,需要在 root 下执行 iotop -oP -d 1 # 批量输出方式,适合记录到日志里做历史追溯 iotop -botqqq -d 1

pidstat -d的输出里有kB_rd/skB_wr/s两列,能快速看出来哪个进程在大量写盘。iotop更直观,但注意它会带来一定开销,生产环境不要长时间挂着跑。还有一种情况是进程名看起来正常(比如 mysqld),但它的下属线程里有专门的刷盘线程在干活,那就要结合iotop -t或者直接查数据库内部线程,后面第三节会展开。

如果进程级工具不够细,还想知道这些 I/O 到底是读还是写、顺序还是随机,可以用blktrace在块设备层抓一段时间的请求记录:

blktrace -d /dev/sda -a issue -a complete -w 10 -o /tmp/blk blkparse -i /tmp/blk

blkparse能输出每条 I/O 的扇区位置、大小、方向。扇区地址连续就是顺序写,地址跳来跳去就是随机写。这个信息对判断日志盘和数据库数据盘的负载特征特别有用。

3.2 swap 和内存回收引发的“磁盘突然爆满”

这是我最常遇到的一类“假业务真系统”问题。应用没有明显流量变化,但磁盘 %util 突然升高,查进程发现是 kswapd0 或者内核线程在打盘,现象往往是内存压力导致的 swap 换入换出。

举个例子,你给 MySQL 或者 Java 应用分配了很大内存,但操作系统vm.swappiness默认是 60,意味着内存压力上升时,内核会倾向于把一部分不活跃的内存页换到 swap。一旦 swap 所在盘是一块普通的机械盘或性能一般的云盘,页面换出和换入会让这块盘的 %util 直接爆表。更隐蔽的是,文件缓存(page cache)回收也会产生大量写盘,特别是vm.dirty_ratiovm.dirty_background_ratio设置不合适的时候,一次性回写大量脏页,磁盘写入瞬间打满。

判断方法很简单:

# 看 swap 使用情况和内存压力 free -h vmstat 1 10 cat /proc/meminfo | grep -E "Dirty|Writeback|Swap"

如果si/so(swap in/out)长期非零,或者Dirty数值不断涨到 GB 级别然后突然掉下来,那高 %util 的根源就是内存回收和交换。这类问题的解法方向很清楚:调低vm.swappiness、合理控制脏页参数、给数据库和应用配置更贴合实际的内存上限,而不是盲目砸钱买磁盘。

3.3 数据库随机读写:最典型的业务型打盘

数据库是 I/O 大户中的大户,也是 %util 高发的重灾区。MySQL 的 InnoDB 默认页大小是 16KB,所以它产生的最典型 I/O 就是 16KB 左右的随机读写。当 InnoDB buffer pool 装不下工作集时,每次查询请求的数据页如果不在内存里,就得从磁盘随机读;数据页被修改后,又需要由后台线程刷盘。这种模式下,avgrq-sz会稳定在 16KB 附近,%util很容易被打到 90% 以上,但你看到的总吞吐可能只有几十 MB/s——因为全是随机小块。

数据库场景里,高 %util 的常见直接原因还有这么几个:

  • 慢查询缺索引,一次全表扫描产生大量随机读;
  • 事务提交太频繁,每提交一个事务都要刷一次 redo log(innodb_flush_log_at_trx_commit=1)导致写放大;
  • 排序、分组产生的临时表太大,内存放不下后落到磁盘tmpdir
  • 大事务产生大量 undo log,purge 线程跟不上。

每一个原因在 iostat 上的表现都有细微差别。缺索引的随机读,r/s高、rkB/s中等;日志频繁提交,w/s高、请求大小小;临时表落盘,wkB/s会突然出现尖峰。要结合数据库内部的慢日志、SHOW ENGINE INNODB STATUS、performance_schema 才能最终定位。

3.4 日志、fsync 和 RAID 卡缓存策略

还有一类不打码的高 %util 来自“写日志刷盘”。比如 PostgreSQL 的 WAL、MySQL 的 redo log、Redis 的 AOF、ELK 集群的 translog,它们都要求每次提交或每个 batch 做一次fsync。fsync 是个很“重”的操作,对 HDD 来说单次 fsync 的物理耗时可能是 10 毫秒级,对 SSD 也要 1 毫秒左右。如果应用把 fsync 调得太频繁,比如每写一条消息就 fsync 一次,设备的 %util 会一直趴在 100%,但实际写入数据量小得可怜。

这时候不妨多看一眼iostat里的wrqm/s(合并写请求)。如果写请求合并率高,说明是日志类顺序追加写入;如果wrqm/s是 0 而w/s很高,说明请求全是很小、且无法合并的写,这通常是“每次提交都 fsync”的表现。

这块盘的救法有几个层次:应用层控制 fsync 频率(比如 Redis 的appendfsync everysec);数据库层调整组提交(group commit)参数;文件系统层如果条件允许可以考虑 XFS 搭配更大的 log buffer。但要提醒一句:降低 fsync 频率本质是用数据丢失风险换性能,必须结合业务可接受的 RPO 来定,不能无脑关。


4. 一次 MySQL 主库 %util 打满的完整排查记录

这里分享一个我实际处理过的案例,完整走一遍从告警到恢复的链路。这套排查顺序不复杂,但每一步都有明确的淘汰逻辑,值得照着走一遍。

4.1 第一个现场:iostat 和 sar 的数据

那是一个周四晚上,业务方反馈“后台报表打开特别慢”,同时监控告警 MySQL 主库磁盘 %util 持续 99%。我先在数据库服务器上采集了一轮快照:

iostat -x 1 3 sar -d 1 3 pidstat -d 1 5

iostat 的结果和前面示例差不多:%util99.9%,r_await68 毫秒、w_await215 毫秒、aqu-sz21.4,rareq-sz约 16KB、wareq-sz约 64KB。这个组合说明两个事实:第一,磁盘确实饱和了,业务延迟被明显拉高;第二,读请求全是 16KB 的随机读,写请求里既有 16KB 的数据页也有更大的日志块——典型的 InnoDB 特征。

紧接着看pidstat -d,打盘主力就是 mysqld,其他进程几乎可以忽略。同时free -h显示内存还有不少余量,swap 几乎没怎么用,于是先把“内存不足导致换页”这条线暂时排除。

4.2 顺藤摸瓜:从磁盘到进程到慢SQL

磁盘层和进程层都指向 MySQL,那问题就集中在 MySQL 内部。我的排查顺序是:先看连接数,再看线程状态,最后查慢日志。

SHOW PROCESSLIST里发现大量Sending data状态的线程,还有几个Copying to tmp table on disk的会话。单凭这个还不能下结论,但已经给了一个明确方向:有查询在创建临时表,而且落盘了。于是打开慢日志:

# 临时开启慢查询捕获 SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; SHOW VARIABLES LIKE 'slow_query_log_file';

日志里抓到了两条大鳄。第一条是一张 5000 万行的订单明细表,被一个后台统计任务执行了全表扫描,还带ORDER BY,排序数据量超过sort_buffer_sizetmp_table_size,临时表落到磁盘,几 GB 的写盘就是这么来的。第二条是一个高频的更新语句,where 条件里的字段没有索引,每次更新都要先做一次全表扫描找到目标行,每秒被执行了几百次,于是产生了大量随机读。

慢日志定位到 SQL 之后,还要解释为什么以前没事现在有事。看执行计划EXPLAIN,发现那张订单明细表的数据量在一个月内翻了三倍,优化器判断全表扫描成本更低,就不再走原来的索引;而高频更新语句的过滤字段一直没索引,数据量上来之后问题彻底暴露。

4.3 根因确认与修复动作

修复分两步走,先止血,再治本。

止血阶段,我把那个后台统计任务挪到了凌晨低峰期,同时给高频更新语句的 where 字段补上联合索引。前者立刻消除了临时表落盘的持续写压力,后者把读 IOPS 从每秒五六百降到了几十。再配合把tmp_table_sizemax_heap_table_size从默认值调大,让排序尽量在内存里完成。

治本阶段,针对“每秒几百次提交”的问题,我看了一下应用侧是逐条 UPDATE 的循环逻辑,于是让开发改成批量更新(一次更新几十条),并且合理评估后把innodb_flush_log_at_trx_commit从 1 调整为 2。这里必须说清楚:这个参数改成 2 意味着崩溃时可能丢失最近 1 秒的事务,业务接受了这个 RPO 才动的,并不是所有场景都适用。

做完之后,同一台服务器上的 iostat 变成这样:

Device r/s w/s rkB/s wkB/s r_await w_await aqu-sz %util sda 45.0 120.0 720.0 7680.0 18.0 30.5 2.10 28.50

%util 128 掉到 28.5,业务接口的 p95 延迟从 3 秒多回到 500 毫秒以内。整个过程里一块盘都没换,全是靠定位准确度和应用/数据库层优化解决的。


5. 优化优先级:先调参数,再改应用,最后才谈换硬件

5.1 内核与文件系统层:零成本腾挪空间

先说结论:换硬件的成本最高、见效最不确定,调参和改应用的成本最低、收益往往最大。所以优化顺序一定是从上往下排。内核和文件系统层的常规动作,基本都是零成本且可以快速回滚的:

# 查看当前 I/O 调度器 cat /sys/block/sda/queue/scheduler # SSD/NVMe 建议 none,HDD 建议 mq-deadline echo none > /sys/block/sda/queue/scheduler # 查看、调整 NVMe 硬件队列深度 cat /sys/block/nvme0n1/device/queue_depth echo 1024 > /sys/block/nvme0n1/device/queue_depth

I/O 调度器的逻辑很简单:传统调度器把请求排队、合并、排序,这对机械盘的结构寻道是有利的;但 SSD 没有寻道开销,排队反而增加延迟,所以直接none让设备自己并发处理。每次重启后调度器设置会重置,记得写进 udev 规则或者系统调优脚本里。

文件系统挂载上面,noatime是很划算的改动,避免读文件时更新访问时间产生额外的写 I/O:

mount -o remount,noatime,nodiratime /

脏页参数也属于这类调整。生产服务器我通常从默认值改到:

vm.swappiness = 1 vm.dirty_background_ratio = 5 vm.dirty_ratio = 20

这样做的目的是让脏页回收更平滑,不要攒一堆之后一次性疯狂刷盘。但注意比例设置要按内存总我们实际大小和工作负载调整,小内存机器调太小会导致频繁写盘,反而帮倒忙。

文件系统层还有一个低频但影响大的操作:如果确认是随机小块读写把盘打爆,可以考虑用 XFS 并调大logbsize、或者在数据库中把数据文件预分配。这类动作需要结合具体场景,不建议照搬,我这里点到为止。

5.2 应用层:降低 I/O 放大比加缓存更有效

很多 %util 高的问题,根因是应用把 I/O 写得太碎、读得太多。比如前面案例里逐条 UPDATE,每条都有事务提交开销;再比如业务代码在循环里逐条 SELECT,而不是一次性IN查询。这类问题通过我是加一层内存缓存、减少请求量来解决的。

这里给几个可复用的原则:

  • 批量替代单条:大量写操作改成批量 commit,能显著降低日志 fsync 次数;
  • 索引覆盖:让查询只走二级索引,避免回表产生随机读;
  • 缓存热点:对高频读的小数据用 Redis 之类的缓存扛掉,数据库的读 I/O 自然下来;
  • 错峰:把报表、统计、备份这类重 I/O 任务挪到业务低谷期,削峰填谷比硬扛峰值更实际。

以 MySQL 为例,工作集远大于内存时,无脑加innodb_buffer_pool_size是最直接的减压方式,但如果内存已经有限,优先砍掉多余的连接数、优化慢 SQL,效果更明显。我见过太多团队一看到磁盘忙就申请扩内存,结果索引没建,慢查询还在,扩再大内存也是被 SQL 一把打穿。

5.3 硬件层:怎么选、怎么测,别被参数忽悠

应用和参数都调过了,还是有明确的容量缺口,才轮到硬件层。选型前先用fio测出一个“当前设备能吃多少、延迟多大”的基线,后面上了新硬件也好对比:

# 随机读 4K,模拟数据库页读取 fio --name=randread --ioengine=libaio --direct=1 --bs=4k --rw=randread \ --iodepth=32 --numjobs=4 --runtime=30 --group_reporting # 随机写 8K,观察延迟和 IOPS fio --name=randwrite --ioengine=libaio --direct=1 --bs=8k --rw=randwrite \ --iodepth=32 --numjobs=4 --runtime=30 --group_reporting

测出来的 IOPS 和延迟要结合你的业务看:数据库核心库更看重低延迟,特别是队列深度为 1 时的延迟;大数据平台、日志存储看重顺序吞吐;混合负载就优先选 NVMe 这种并发能力强的盘。另外要注意 SSD 的寿命参数(DWPD)和稳态性能,写密集型的业务选了读优化的盘,前三个月没问题,后面可能一直报慢盘。

如果上了 SSD 之后 %util 还高,别慌,先把队列深度检查好,很多 Linux 内核版本对 NVMe 默认队列深度比较保守,调大queue_depth之后可用的并行能力才能释放出来。


6. 那些容易被误判的 %util 场景,和我最后的一点建议

6.1 三个“假高”案例

第一个案例,某台服务器是全新 NVMe SSD,%util 常年 100%,但应用响应时间非常稳。用 fio 一测,设备还能跑出几万 IOPS 的余量。这就是前面说的并行设备的特性:%util 到了 100% 不代表没有余量,得结合 await 和实际业务延迟来综合评估。最后我们只是把监控告警阈值从单看 %util 改成“%util 高且 await 超过某值才告警”,噪音立刻消失。

第二个案例,一块云盘读写吞吐稳定地卡在某个数值,%util 高但延迟没有明显恶化。查云厂商控制台发现 IOPS 配额被打满,这是限流导致的“假性饱和”。把吞吐降下来或者升级配额就解决了,跟磁盘健康毫无关系。

第三个案例,RAID 卡开启了 WriteBack 缓存后,磁盘 %util 偶尔出现几分钟的规律性飙升,过一会儿又自己好。后来确认是 RAID 卡做缓存刷盘周期,加上磁盘阵列里有一块盘性能偏低拖慢了整体刷盘节奏。这类问题要配合阵列卡管理工具看物理盘状态才能发现,单纯从操作系统层面调优没用。

6.2 我自己的排查习惯

最后说点我个人的操作习惯,你可以直接抄。第一,任何一台新服务器交付后,我都会先记录一份iostat -x 1 3和 fio 的基线数据存档,出了故障之后拿出来对比,比凭感觉判断“高不高”靠谱得多。第二,写入告警规则时,我会把%utilawaitaqu-sz三个指标组合起来,而不是单一指标触发,这样能挡住一大部分误报。第三,每次排查高 %util 问题,我都会强迫自己先回答两个问题:这块盘的 I/O 是读还是写?请求是大块还是小块?这两个问题回答清楚了,一半的排查路径已经确定。

磁盘 I/O 优化不是一锤子买卖,尤其现在存储设备越来越复杂,从机械盘到 SSD,从本地盘到分布式存储,%util 这个老指标的含义和权重一直在变。下次再看到“%util 特别高”的告警时,先别急着给磁盘判死刑,拿上 iostat、pidstat、慢日志三样工具,按类型、分层次地把现场还原出来,你就会发现,大部分时候真正的突破口根本不在硬盘上。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 3:38:57

现代Web文件上传:点击+拖拽双通道原生实现指南

1. 这不是“点一下就完事”的功能,而是现代Web交互的底层基建你肯定遇到过这样的场景:在某个表单里填完信息,正准备提交,突然发现漏传了一份合同扫描件——这时候页面右下角弹出一个灰色虚线框,写着“拖拽文件到这里上…

作者头像 李华
网站建设 2026/9/16 3:38:37

多模态模型评测实战:MMBench与OpenCompass从原理到落地

多模态模型这两年可以说是遍地开花,从开源社区的Qwen-VL、InternVL,到各家闭源的GPT-4V、Claude,代码能力、推理能力一个比一个能打。但真到了要落地选型的时候,问题就来了:排行榜上那些分数到底靠不靠谱?同…

作者头像 李华
网站建设 2026/9/16 3:37:51

鸿蒙电商全栈实战:从开发到上架变现的完整指南

做了几年移动端开发,身边一直有同行问我:鸿蒙到底值不值得押注?我的答案很明确——如果你要做的是电商购物这类带真实交易闭环的应用,现在进入鸿蒙生态,窗口期的红利比安卓和iOS早期还要明显。我最近完整做完了一个鸿蒙…

作者头像 李华
网站建设 2026/9/16 3:36:39

STM32嵌入式DFT实战:资源受限下的实时频谱分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/16 3:36:25

Java GC优化实战:从Full GC排查到JVM参数调优

1. 从一个“卡死”的下午说起:我为什么开始认真做GC优化先说个亲身经历。有一年我在负责一个订单履约系统,平时跑得好好的,结果一到午高峰就频繁报警:接口P99延迟从50ms直接飙到800ms,CPU居高不下,老年代GC…

作者头像 李华
网站建设 2026/9/16 3:35:09

COMSOL复现BIC拓扑荷:光子晶体超表面远场偏振涡旋计算全流程

做BICs和光子晶体超表面计算有一段时间了,最让我头疼也最上头的,就是复现文献里那种“围绕BIC动量点的远场偏振矢量涡旋图”。一句话说清楚的话,拓扑BICs(连续谱束缚态)在动量空间是一个偏振奇点,远场偏振矢…

作者头像 李华