💻 在远程 Mac自托管 Runner上跑 dotnet restore,瓶颈往往不是 CPU,而是跨境 RTT、源顺序、全局包目录争用锁文件漂移。本文给一张可执行的决策矩阵(镜像链、MaxHttpRequestsPerSourceglobalPackagesFolderRestoreLockedMode),并附 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
globalPackagesFolderNUGET_PACKAGES 全局包缓存位置;CI 与本地应对齐策略 CI 只选一种写法并写进 README
CPM + packages.lock.json 集中版本 + 可重复还原 dotnet restore --use-lock-file 生成后提交
--locked-mode / /p:RestoreLockedMode=true 强制按锁还原,阻断隐式升级 主分支流水线默认开启
<?xml version="1.0" encoding="utf-8"?> <configuration> <config> <add key="globalPackagesFolder" value="/usr/local/ci-nuget/packages-job123" /> <add key="MaxHttpRequestsPerSource" value="6" /> </config> <packageSources> <clear /> <add key="corp" value="https://artifacts.example.com/nuget/v3/index.json" /> <add key="nuget.org" value="https://api.nuget.org/v3/index.json" /> </packageSources> </configuration>

结构化数据提示:正文步骤可与 HowTo JSON-LD 逐步对齐;FAQ 区块与 FAQPageQuestion/Answer 建议同题同答,避免网页可见 FAQ 与头部 JSON 不一致。

落地步骤(≥5 步,可照抄顺序执行)

步骤 1:在仓库根落 NuGet.Configclear 后按合规顺序添加 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。

总结:把源顺序每源并发全局包目录锁定还原写进同一套矩阵,跨境 dotnet restore 才能从「靠运气」变成「可复盘」。将构建放在离制品更近远程 Mac节点,可显著缩短 RTT 与重复拉包成本。免登录可浏览 首页定价购买帮助中心技术博客

.NET × NuGet × 远程 Mac

租用远程 Mac,做跨境还原与 CI 加速

帮助中心购买页首页 免登录;更多依赖与 CI 主题见 技术博客列表

多地域可选
SSH 访问
弹性租期
支持渠道