directory.createdirectory 不负责磁盘负载均衡,仅校验路径与权限;需手动实现基于空间感知或哈希路由的分盘逻辑,并配合分层目录结构避免 ntfs 性能瓶颈。

为什么 Directory.CreateDirectory 不会帮你做磁盘负载均衡
它只管路径合法性和权限,不管底层是 SSD 还是机械盘、是 D: 还是 E:。你手动写死 "D:\data\logs",所有日志就永远压在 D 盘;哪怕 E 盘空闲 90%,系统也不会自动切过去。
真正的负载均衡必须由你控制路径生成逻辑,而不是依赖 .NET 的 IO 函数本身。
- Windows 不提供“按磁盘 IO 负载选盘”的 API,
DriveInfo只能查剩余空间、是否就绪,不暴露队列深度或当前 IOPS -
GetDiskFreeSpaceEx(P/Invoke)也一样,静态指标 ≠ 实时负载 - 多线程并发写入同一目录时,即使跨盘,也可能因 NTFS 日志、USN 日志等共享元数据造成隐性争用
用 DriveInfo 做空间感知的轮询分发
最轻量、最可控的做法:预定义候选盘符列表,每次写入前查各盘剩余空间,选“空间最多 + 最近未被选中”的那个盘。不是严格轮询,而是带权重的空间轮询。
示例逻辑:
var candidates = new[] { "D:", "E:", "F:" };
var bestDrive = candidates
.Where(d => DriveInfo.GetDrives().Any(di => di.Name.StartsWith(d) && di.IsReady))
.Select(d => new { Drive = d, Free = new DriveInfo(d).TotalFreeSpace })
.OrderByDescending(x => x.Free)
.First().Drive;
注意:TotalFreeSpace 比 AvailableFreeSpace 更稳定(不受配额影响),但仍是快照值——写入前仍可能被其他进程占满。
- 避免用
DriveInfo.RootDirectory判断是否可写,它不检查权限,要用FileIOPermission或实际File.WriteAllText尝试捕获UnauthorizedAccessException - 不要缓存
DriveInfo实例超过 1–2 秒,磁盘状态变化很快 - 如果某盘突然
IsReady == false(比如 USB 拔出),需 fallback 到备选盘并记录警告,不能抛异常中断业务
文件名哈希路由到固定磁盘(适合高吞吐追加场景)
当你要持续写入大量小文件(如 IoT 设备上报),且要求单个文件可定位、可清理,用哈希比轮询更稳——避免热点文件夹、规避目录项锁竞争。
核心思路:对业务 ID(如设备号、用户 ID)做哈希,再对盘数取模:
int diskIndex = Math.Abs(deviceId.GetHashCode()) % candidates.Length;
string targetRoot = candidates[diskIndex]; // e.g. "E:"
string path = Path.Combine(targetRoot, "uploads", deviceId, $"{timestamp:yyyyMMdd}", $"{Guid.NewGuid()}.json");
这样同一个设备的所有数据永远落在同一盘,但整体流量随设备分布自然摊开。
- 别用
String.GetHashCode()做长期路由依据——.NET Core 2.1+ 默认是 per-process 随机种子,重启后哈希值变,导致路径失效 - 改用
System.Security.Cryptography.HashAlgorithm(如 SHA256)或HashCode类(.NET 5+)做确定性哈希 - 哈希后必须做
Math.Abs()再取模,否则负数取模结果为负,Array[-1]直接抛IndexOutOfRangeException
避开 NTFS 单目录性能瓶颈:每盘限制子目录深度和数量
即使磁盘 IO 均衡了,如果所有文件都塞进 E:\logs\ 一个目录,NTFS 查找文件会越来越慢(尤其 > 10 万文件时)。这不是磁盘问题,是文件系统结构限制。
必须配合路径分层策略:
- 按时间分层:用
yyyy\MM\dd\HH,确保单目录文件数 ≤ 5000 - 按哈希分层:取 ID 的前两位做子目录,如
E:\data\ab\abcdef123.json - 禁用长文件名缓存(
fsutil behavior set disablelastaccess 1)减少元数据更新压力,但需管理员权限且重启生效
特别注意:Directory.GetFiles 在大目录下极慢,改用 Directory.EnumerateFiles + yield return 流式处理,避免内存爆掉。
真正难的不是选哪块盘,而是让每个盘上的目录结构不退化、元数据操作不成为瓶颈——这点常被忽略,直到 IO 突然卡住才去查 NTFS 索引碎片。










