C# 文件上传负载均衡 C#如何将上传文件分发到多个后端存储服务器

来源:这里教程网 时间:2026-02-21 17:42:22 作者:

上传前先做分片哈希路由

文件上传到负载均衡后,不能随机扔给后端存储节点,否则同一个文件多次上传可能落到不同机器,导致读取不一致或重复存储。核心是让相同文件名 + 相同内容的文件,始终映射到同一台后端服务器。

推荐用

MD5
SHA256
对文件路径 + 文件名(不含时间戳等动态部分)做哈希,再对节点数取模:

var hash = BitConverter.ToString(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes($"{fileName}"))).Replace("-", "");
var nodeIndex = Math.Abs(hash.GetHashCode()) % backendNodes.Length;

注意:别用

DateTime.Now
Guid.NewGuid()
作为路由依据——它们每次都不一样,彻底破坏一致性。

如果文件名含用户ID,可直接用
userId.GetHashCode() % nodeCount
,更快且足够均匀
避免用文件内容全文哈希(大文件耗时),优先用元数据哈希 + 内容指纹(如前4KB + 后4KB)组合 哈希结果必须用
Math.Abs(...)
包裹,否则
GetHashCode()
可能返回负数,取模后索引越界

上传过程必须支持断点续传和重试

负载均衡下网络更不可靠,单次上传失败率升高。直接

HttpClient.PostAsync()
丢整个文件流,失败就得重头来,浪费带宽又拖慢体验。

实际做法是把文件切块(如 4MB/块),每块单独 POST,并带上

Content-Range
和唯一
uploadId

POST /api/upload/chunk?uploadId=abc123&chunkIndex=2&totalChunks=5

后端按

uploadId
聚合所有块,最后合并。这样某块失败只重传那一块。

前端需记录已成功上传的
chunkIndex
,页面刷新后也能续传
uploadId
建议用文件哈希生成(如
Convert.ToBase64String(MD5.HashData(...))
),避免服务重启后丢失上下文
别在负载均衡层开启「粘性会话(sticky session)」——它解决不了跨请求的块聚合问题,还掩盖了真正的并发缺陷

后端存储节点间不做实时同步

有人想用 Redis 或数据库记“文件在哪台”,再让其他节点去拉取。这引入额外延迟和单点依赖,违背负载均衡初衷。

正确思路是:上传时决定归属节点,后续所有读请求(包括 CDN 回源)都按同样哈希规则路由过去。存储节点只管自己那部分,互不通信。

CDN 配置回源 Host 时,不能写死某台 IP,得用 DNS 轮询或基于哈希的智能路由中间件(如 Nginx 的
hash $request_uri consistent;
删除操作也必须走同样哈希逻辑,否则删错机器 扩容节点时,哈希取模会变,必须用一致性哈希(如
Ketama
算法)或做数据迁移,不能直接改
% nodeCount

健康检查必须穿透到存储接口

负载均衡器默认只 ping 服务器端口或 HTTP 状态码,但后端存储服务可能进程活着、磁盘已满、或数据库连接池耗尽——这时它还在收上传请求,却注定失败。

健康检查 URL 必须真实调用一次存储写入(如写入并删除一个临时小文件),返回

200
才认为节点可用:

GET /health/storage?testWrite=true

否则,流量持续打向一个「假活」节点,错误日志里全是

IOException: No space left on device
TimeoutException
,但负载均衡器一直显示「正常」。

检查频率建议 5–10 秒,超时设为 2 秒以内,避免拖慢整体探测 别把健康检查响应写死成
return Ok();
,它骗不过真实 IO 压力
若使用 Kubernetes,
LivenessProbe
ReadinessProbe
必须指向该真实存储健康端点,而非通用 HTTP 端口

哈希路由的种子值、分块大小、健康检查的 IO 深度——这三个地方稍一松动,整套分发逻辑就从「可控」滑向「玄学」。尤其上线前没压测过磁盘 IO 瓶颈的节点,最容易在凌晨三点开始 500 错误连发。

相关推荐