dotnet restore,瓶颈往往不是 CPU,而是跨境 RTT、源顺序、全局包目录争用与锁文件漂移。本文给一张可执行的决策矩阵(镜像链、MaxHttpRequestsPerSource、globalPackagesFolder、RestoreLockedMode),并附 FAQ。入口:MacPull 首页、技术博客列表;同栈延伸阅读 Gradle/Maven 缓存与并行矩阵、GitHub Actions 自托管 Runner 指南、跨境 Git/npm/Homebrew CI 优化。
痛点拆解:为什么跨境还原总在 CI 爆
1)源顺序与「半套镜像」:只改公司镜像却未代理全部包 ID,或 packageSources 里残留多份同名逻辑源,解析会反复试错,拉长还原时间。
2)默认全局目录并发写:多 Job 共用 ~/.nuget/packages,偶发元数据损坏或「包版本已解析但磁盘未完成写入」,表现为间歇性还原失败。
3)锁定模式与图不一致:开启中央包管理(CPM)与 packages.lock.json 后,若 CI 与本地 SDK、源列表或浮动版本策略不一致,RestoreLockedMode 会直接失败——这是预期防护,需要流程上「先更新锁再合入」。
决策矩阵:场景 × 参数怎么选
下表按网络拓扑与共享程度给起步值;上线后请用 dotnet restore -v:n 与磁盘监控微调。
| 场景 | 首选 packageSources | MaxHttpRequestsPerSource | globalPackagesFolder | RestoreLockedMode |
|---|---|---|---|---|
| 企业私网 + 同区域 Mac | Nexus / Azure Artifacts 聚合 v3 索引优先,nuget.org 仅作受控回退或禁用 | 8–16 | 可与个人开发机统一路径,仍建议 CI 子目录 | CI 开启;锁文件入库 |
| 跨境直连 nuget.org | 可信国内/区域镜像 v3 为第一源,官方源其次(合规允许时) | 4–8 | 固定到数据盘路径,避免默认家目录打满 | 开启,减少浮动解析 |
| 多 Pipeline 共享一台远程 Mac | 与上两类之一相同,但禁止多 Job 默认目录重叠 | 4–6 | 每 Job 独立目录,如 .../nuget/$BUILD_ID |
开启;缓存键含 lockfile 哈希与源主机名 |
| NuGet.Config / 开关 | 作用 | 可抄作业 |
|---|---|---|
packageSources + clear |
清默认源再显式添加,避免机器级配置「插队」 | 仓库根 NuGet.Config 随代码评审 |
MaxHttpRequestsPerSource |
限制每个源并发 HTTP,平滑弱网与磁盘 | 起步 6,跨境或共享机再降到 4 |
globalPackagesFolder 或 NUGET_PACKAGES |
全局包缓存位置;CI 与本地应对齐策略 | CI 只选一种写法并写进 README |
CPM + packages.lock.json |
集中版本 + 可重复还原 | dotnet restore --use-lock-file 生成后提交 |
--locked-mode / /p:RestoreLockedMode=true |
强制按锁还原,阻断隐式升级 | 主分支流水线默认开启 |
结构化数据提示:正文步骤可与 HowTo JSON-LD 逐步对齐;FAQ 区块与 FAQPage 的 Question/Answer 建议同题同答,避免网页可见 FAQ 与头部 JSON 不一致。
落地步骤(≥5 步,可照抄顺序执行)
步骤 1:在仓库根落 NuGet.Config,clear 后按合规顺序添加 packageSources;需要禁用机器级干扰时配合 disabledPackageSources。
步骤 2:为当前流水线导出独立 globalPackagesFolder(或 NUGET_PACKAGES),并在构建后按需归档缓存。
步骤 3:设置 MaxHttpRequestsPerSource,共享远程 Mac 从低值起调,观察是否仍出现连接重置或 TLS 超时。
步骤 4:启用 CPM(Directory.Packages.props),在干净工作区执行 dotnet restore --use-lock-file 生成 packages.lock.json 并提交。
步骤 5:CI 使用 dotnet restore --locked-mode(或 MSBuild 等价属性),源或包图变更时走「更新锁文件 → 评审 → 合入」分支流程。
步骤 6(可选):对还原步骤外包一层脚本,失败时 2s / 4s / 8s 指数退避最多三次,再让 Job 失败,便于区分瞬时网络与配置错误。
可引用信息(写进 Runbook)
- 每源并发:共享节点建议
MaxHttpRequestsPerSource=4–6起步,再按 p95 还原时长上调。 - 目录隔离:全局包路径必须进缓存键;换源或大批量升级后优先「新子目录」而非强行覆盖。
- 锁定还原:主分支 CI 默认
RestoreLockedMode=true,与packages.lock.json成对出现才有意义。
FAQ:锁定模式、镜像缺包与目录覆盖
RestoreLockedMode 报锁文件与项目图不一致?
在开发者本机或专用「更新依赖」流水线干净目录执行 dotnet restore --use-lock-file 重新生成锁文件,确认 NuGet.Config 与 CI 一致后再提交;避免多个并行 Job 同时改写同一锁路径。
镜像源缺包时能否自动回退 nuget.org?
在 packageSources 中保留合规允许的完整次源;若策略禁止公网,应在私服侧代理缺失包,而不是依赖隐式行为。
多 Pipeline 共用远程 Mac 还原很慢?
并发 restore 会争抢磁盘与连接表:为每 Job 设独立 globalPackagesFolder、下调 MaxHttpRequestsPerSource,并参考 构建池并发与磁盘 FAQ 做空间阈值。
globalPackagesFolder 与 NUGET_PACKAGES 同时存在听谁的?
不同工具版本解析优先级可能不同,不要在同一流水线混用两种写法;选定一种并在文档与脚本里统一 export。