ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

.NET 共享包存储(Shared Package Store)设计全解析:dotnet store 布局、发布过滤与主机探测

.NET 共享包存储(Shared Package Store)设计全解析:dotnet store 布局、发布过滤与主机探测 .NET 共享包存储Shared Package Store设计全解析dotnet store 布局、发布过滤与主机探测【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime本文以 dotnet/runtime 仓库中的官方设计文档 DotNetCore-SharedPackageStore.md 为主体结合仓库内主机host层真实实现展开系统讲解 .NET 共享程序集存储Shared Package Store的目录布局、dotnet store命令、基于profile.xml的发布过滤机制以及运行时如何通过DOTNET_SHARED_STORE环境变量与探测优先级查找共享程序集。读完本文你将掌握如何为云主机、应用托管平台搭建机器级共享包缓存、如何把应用发布为“可瘦身”的便携应用并理解.deps.json、runtimeconfig.json与主机探测之间的底层协作关系。一、为什么需要共享包存储问题与设计动机为了让所有机器级machine-wide的 .NET Core 应用能够共享程序集需要一个集中的共享程序集存储。它的核心价值有两点应用瘦身trimming应用可以将共享程序集从自身发布目录中剔除不再需要随身携带它们集中维护共享程序集由运行时安装包或托管平台统一安装、统一优化如 crossgen 预编译应用直接复用既节省磁盘又缩短启动时间。设计文档明确指出这套机制由三部分组成一个集中式的共享程序集存储即包存储及其查找lookup机制供应用在开发阶段和部署阶段复用一组增强dotnetCLI 的命令用于编排compose共享包存储条目核心是dotnet store一套发布机制让应用在dotnet publish时按需过滤filter程序集从而减少磁盘占用。从实现角度看这套机制的生命周期贯穿三个环节dotnet store生成布局 → 通过DOTNET_SHARED_STORE环境变量接入运行时 → 主机host在解析.deps.json资产时按优先级探测共享存储目录。下面逐层展开。二、包存储目录布局全局安装目录与 dotnet 相对目录包存储可以是全局系统级文件夹也可以是dotnet.exe 相对文件夹两者对应不同的安装与维护策略。全局Global存储位置位于dotnet根目录下Windows 上位于C:\Program Files (x86)\内见下方布局图store/install目录下的包只允许通过平台安装器MSI、pkg、deb、apt-get 等安装由dotnet store命令编排出来的包布局后文详述预期被直接解压unzip到store文件夹中——注意这个解压步骤是手动操作。dotnet 根目录布局示意- dotnet.exe - shared - netcoreapp2.0 2.0.0-preview2-00001 - store - install refs netcoreapp2.0 netcoreapp2.1 refs netcoreapp2.0 netcoreapp2.1其中shared/netcoreapp2.0/版本是共享框架Shared Framework即Microsoft.NETCore.App的经典目录store/install存放平台安装器安装的引用程序集refs与各 TFM 目录store根下refs、netcoreapp2.0、netcoreapp2.1等则是dotnet store输出并手动解压得到的运行时共享包布局。文档特别说明netcoreapp*文件夹内部的布局采用NuGet 缓存布局NuGet cache layout即{package-name}/{package-version}/{asset-path}的层级结构这样主机在探测时可以直接复用与 NuGet 缓存一致的相对路径拼接规则。三、使用dotnet store编排运行时共享包存储3.1 命令的定位与目标用户dotnet store用于编排共享包存储的布局。设计文档预期两类用户会使用它托管提供商hosting providers如 Antares 云服务用它预热prime服务器提前把共享包布局部署到每台机器框架作者framework authors用它制作预优化pre-optimized的包归档即压缩归档布局分发给用户。3.2 清单文件msbuild 格式的 packages.xmldotnet store的输入是一份包名与版本列表以 XML 形式给出。文档强调packages.xml必须是 msbuild 格式因为它是接入 SDK 其余功能的入口点it forms the entry point from which the rest of the SDKs functionality can be accessed。文档给出的 Roslyn 示例Project SdkMicrosoft.NET.Sdk ItemGroup PackageReference IncludeMicrosoft.CodeAnalysis Version1.3.2 / PackageReference IncludeMicrosoft.CodeAnalysis.VisualBasic Version1.3.2 / PackageReference IncludeMicrosoft.CodeAnalysis.VisualBasic.Features Version1.3.2 / /ItemGroup /Project托管提供商会创建一份与自家托管环境中将要共享的包一一对应的packages.xml然后交给dotnet store。该文件既可以位于文件系统上也可以来自一个 URL文档示例中发布命令就出现了https://asp.net/core/dev/1.2.0/profile.xml形式的远端清单。3.3 命令语法与参数说明dotnet store --manifest packages.xml --framework netcoreapp2.0 [--output C:\Foo] --runtime win7-x64 --framework-version 2.0.0-preview2-00001 [--no-optimize]各参数的完整语义原文档参数表参数说明--framework指定该包存储适用的目标框架名字TFM例如netcoreapp2.0该值用于上文的共享包布局目录名--output创建包存储的输出目录默认值为%USERPROFILE%\.dotnetWindows或~/.dotnetLinux/macOS--skip-optimizationrestore 之后不对程序集执行 crossgen优化是默认行为--runtime这些程序集将要运行的目标平台运行时标识符RID如win7-x64--framework-version运行这些程序集所使用的Microsoft.NETCore.App包版本如2.0.0-preview2-00001注意文档中命令示例使用--no-optimize而参数表里写作--skip-optimization二者表达的是同一开关的不同历史命名核心语义是“优化crossgen 预编译默认开启可显式跳过”。3.4 优化流程与输出布局当指定--optimize即默认开启优化时dotnet store会先在临时文件夹中把所有托管资产预编译crossgen为原生代码再复制到输出目录。使用的 crossgen 工具来自--framework-version所指定Microsoft.NETCore.App的闭包closure内获取的那个版本——这保证了预编译结果与目标运行时版本严格匹配。如果未指定--output默认输出到~/.dotnet或%USERPROFILE%\.dotnet\。最终输出资产文件的布局为$HOME/.dotnet/packages/{tfm}/{package-name}/{package-version}/{asset-path}该输出目录通过添加到DOTNET_SHARED_STORE环境变量被运行时消费探测优先级见后文第五节。四、使用共享包构建应用从项目编写到发布过滤4.1 核心思路把“便携/独立”的决策推迟到发布时刻当前构建共享程序集应用的机制是不在项目文件中指定 RID此时采用便携应用模型portable app model属于Microsoft.NETCore.App的程序集在dotnet安装根目录下查找。引入共享包存储后应用获得了从发布输出中过滤任意一组包的能力。因此应用是“便携”还是“独立”standalone的决策不再在项目编写authoring时做出而是在发布publish时做出。4.2 项目编写Project Authoring默认把Microsoft.NETCore.App视作始终指定了type: platform因此用户无需显式指定 RID。相应地在 csproj 中通过RuntimeIdentifier/标签指定 RID 将被视为错误ERROR。4.3 dotnet restore因为 RID 要到发布时刻才可用所以 restore 阶段应用被当作“当今的便携应用”处理执行一次常规 restore。4.4 dotnet builddotnet build应把任何项目都当作便携应用处理并生成带有Microsoft.NETCore.App框架项的runtimeconfig.json。此外runtimeconfig.json还应包含 TFM 字段runtimeOptions: { tfm: netcoreapp2.0, framework: { version: 2.0.0, name: Microsoft.NETCore.App } }文档特别对比了新旧行为差异针对 csproj 中指定了RuntimeIdentifier/的应用当前行为Current Behavior从 NuGet 缓存中挑取M.N.A程序集无法利用共享Microsoft.NETCore.App提供的优化新行为New BehaviorM.N.A程序集取自共享框架其余程序集取自共享包存储或 NuGet 缓存。此外dotnet build可以利用dotnet根目录下store/install/目录中可用的refs文件夹从而支持离线 restore-build 场景。设计文档说明虽然未来打算增强dotnet build对引用程序集的处理但本设计范围内只聚焦运行时程序集runtime assemblies。4.5 dotnet publish基于 profile.xml 的过滤发布dotnet publish将被增强为支持一个以 XML 表示的过滤配置文件filter profile file。该文件显式列出所有需要从发布输出中剔除的资产包。文档给出多种应用类型的发布示例发布便携应用dotnet publish为当前 RID 发布独立应用dotnet publish --standalone为 win7-x64 发布独立应用并按 profile 过滤dotnet publish --runtime win7-x64 filter https://asp.net/core/dev/1.2.0/profile.xml为 win7-x64 发布便携应用并按 profile 过滤dotnet publish filter https://asp.net/core/dev/1.2.0/profile.xml发布“Windows 级便携应用”即 RID 特定的便携应用——例如过滤掉所有非 Windows RID但把 bin 目录按 Windows RID如 win8、win7、win10拆分dotnet publish filter https://asp.net/core/dev/1.2.0/win.profile.xml若要串联多个 profile只需重复指定多次filter。需要特别理解profile.xml的语义边界它指定要从dotnet publish输出中过滤掉的精确 RID 特定或 IL 包——即物理文件会被从发布输出中剔除但deps.json逻辑资产清单仍然保留profile.xml中列出的条目。这一点至关重要它解释了为什么运行时在探测时能够“知道”某个资产本该来自共享存储——deps.json中仍然记录着该资产的逻辑信息。五、主机探测共享存储如何被运行时消费5.1 探测顺序probe precedence原文档指出dotnet run以及dotnet publish之后的应用激活都由主机按照 host-probing.md 中描述的顺序进行探测。该文档给出的按优先级排序的探测路径列表先尝试的排前面为Servicing 路径仅用于serviceable: true的资产基础路径 Windows x64 为%ProgramFiles(x86)%\coreservicing、Windows x86 为%ProgramFiles%\coreservicing、Linux/Mac 为$CORE_SERVICING其下分base/|arch|的 NI 探测路径与base/pkgs的常规探测路径应用或框架目录框架目录从高层框架向低层框架应用视为最高层共享存储路径Shared store paths$DOTNET_SHARED_STORE/|arch|/|tfm|—— 环境变量可包含多个路径每个都会被追加|arch|/|tfm|后作为探测路径若应用通过dotnet.exe执行则使用 dotnet.exe 所在目录的相对路径dotnet.exe path/store/|arch|/|tfm|附加探测路径--additionalprobingpath命令行参数以及.runtimeconfig.json/.runtimeconfig.dev.json中为应用和每个框架从高层到低层指定的additionalProbingPaths。文档还提示就探测而言框架依赖应用framework-dependent与自包含self-contained应用的主要区别在于自包含应用没有任何框架依赖因此所有资产包括通常来自框架的程序集都在应用目录中探测。5.2 源码级实现shared_store.cpp仓库中的实际实现位于 src/native/corehost/hostpolicy/shared_store.cpp完整印证了设计文档的描述常量定义RUNTIME_STORE_DIRECTORY_NAME为storeSHARED_STORE_ENV为DOTNET_SHARED_STOREget_paths(tfm, host_mode, host_path)首先处理旧版兼容MNA 1.1.*的runtimeconfig.json不包含 TFM 属性此时tfm为空直接返回空列表不探测共享存储get_env_dirs读取DOTNET_SHARED_STORE环境变量按路径分隔符PATH_SEPARATOR切分成多个路径对每个路径调用pal::fullpath规范化后依次追加arch与tfm目录并加入候选列表——这正对应文档中$DOTNET_SHARED_STORE/|arch|/|tfm|的规则仅当host_mode host_mode_t::muxer即通过dotnet.exemuxer 启动时才会把dotnet.exe所在目录下的store/arch/tfm追加为候选路径——即“dotnet.exe 相对共享存储文件夹”。5.3 源码级实现deps_resolver.cpp 中的探测装配src/native/corehost/hostpolicy/deps_resolver.cpp 中setup_probe_config的装配顺序与 host-probing.md 完全一致// Servicing NI probe / Servicing normal probe m_probes.push_back(probe_config_t::svc_ni(ext_ni)); m_probes.push_back(probe_config_t::svc(ext_pkgs)); // The published deps directory m_probes.push_back(probe_config_t::published_deps_dir()); // The framework locations, starting with highest level framework // ... m_probes.push_back(probe_config_t::fx(...)); // Shared store probes setup_shared_store_probes(shared_stores); // Additional probing paths m_probes.push_back(probe_config_t::lookup(probe));其中setup_shared_store_probes对每个共享存储目录做了存在性检查——只有pal::directory_exists(shared)为真的目录才会被注册为lookup类型的探测路径并置位m_needs_file_existence_checks true即这些目录内的命中必须做文件存在性确认。这保证了未安装共享包的机器上探测不会因为不存在的目录而产生额外开销。probe_deps_entry中按m_probes顺序逐条尝试拼接资产相对路径如newtonsoft.json/11.0.2/lib/netstandard2.0/Newtonsoft.Json.dll并检查文件是否存在命中即止全部未命中则报错。特别地deps_resolver.cpp中定义了ManifestListMessageThis assembly was expected to be in the local runtime store as the application was published using the following target manifest files: %s即当应用发布时使用了某个目标清单profile进行过滤、但运行时在本地共享存储中找不到对应程序集时主机会给出包含该 manifest 文件名的明确错误提示——这是设计文档“deps.json仍保留被过滤条目”这一语义在运行时的落地体现。六、典型应用场景6.1 ASP.NET方式一作者制作共享包安装器eager cache预填充缓存从干净目录和一份packages.xml包列表开始用dotnet store在目录中产出布局将该布局打包为 MSI/pkg/deb 以及 zip发布与安装器配套的profile.xml文件供用户同步使用开发者/部署管理员把 MSI/zips 安装到部署机器上。方式二让应用部署者自行缓存共享包lazy cache懒缓存发布可用于执行dotnet store的packages.xml发布可用于发布过滤的profile.xml部署管理员在运行应用时执行dotnet store。随后开发者/部署管理员执行dotnet publish filter profile.xml产出一个不含共享组件的 ASP.NET 应用或者安装 MSIs/zips 后直接dotnet run。6.2 Antares托管平台Antares 用包列表配合dotnet store在某个文件夹产出布局将该文件夹链入环境变量DOTNET_SHARED_STORE从源码构建应用时执行dotnet run以拾取共享包发布运行应用时执行dotnet publish filter profile.xml使用 Antares profile。6.3 Roslyn编译器工具链Roslyn 包位于包目录中使用 Roslyn 的应用必须显式依赖 Roslyn而不能通过M.N.AMicrosoft.NETCore.App间接获得应用可以选择使用我们发布的 Roslyn profile 文件跳过这些 DLL 的发布应用部署到已安装运行时且 Roslyn 包随运行时安装器一起安装的另一台机器上即可运行。6.4 托管预热Hosting Primers场景 A托管已发布的应用用dotnet store产出布局或解压一个既有布局设置DOTNET_SHARED_STORE指向该布局发布的性质取决于是否使用托管方 profile使用托管 profile应用发布目录不包含从布局中拾取的被过滤文件不使用托管 profile应用发布目录中的程序集覆盖布局中的同名程序集status quo维持现状语义。场景 B从源码构建用户应用dotnet store压缩布局zip部署到托管服务器使用 filter 发布应用。七、设计范围内的后续工作项Work Items原文档在最后列出了两个子系统的工作项可以作为理解该功能完整落地范围的地图Core-Setup运行时/主机侧将 dotnet 根目录下Microsoft.NETCore.App的名称改为netcoreapp2.0需保持兼容把 Roslyn 程序集移出共享框架shared framework在 Windows、Ubuntu 和 OSX 上为 Roslyn共享包构建安装器实现主机对用户级与全局包存储的探测在主机中纳入 TFM 变更的适配。CLISDK 侧让 CLI 从共享包根目录消费 Roslyn 依赖因为其已不再属于M.N.A让dotnet restore以type: platform语义进行 restore让dotnet build把项目视为type: platform支持dotnet publish filter完成dotnet store的完整实现。八、总结与最佳实践要点结合设计文档与仓库实现使用共享包存储的正确姿势可以归纳为四条主线布局即契约无论全局目录还是 dotnet 相对目录store下都是 NuGet 缓存布局的{tfm}/{package-name}/{package-version}/{asset-path}结构任何手工拼装都必须遵循该结构清单驱动编排用 msbuild 格式的packages.xml声明共享包集合dotnet store负责还原、crossgen 优化并产出布局输出目录通过DOTNET_SHARED_STORE接入运行时发布时决定形态项目编写阶段不写 RIDRuntimeIdentifier/为错误dotnet publish时再用filter profile.xml可多次串联决定剔除哪些物理文件——deps.json保留逻辑条目运行时按 host-probing.md 的优先级顺序servicing → 应用目录 → 框架目录 → 共享存储 → 附加路径完成解析失败可诊断若发布时依赖了共享存储、运行时却找不到程序集主机会打印包含目标 manifest 文件名的明确错误信息见 deps_resolver.cpp 中的ManifestListMessage排查时应先确认DOTNET_SHARED_STORE指向的布局与发布时使用的 profile 是否一致。这套设计本质上是把“框架共享”的思想从Microsoft.NETCore.App推广到任意一组可共享的包共享框架解决的是运行时自带的共享共享包存储解决的则是“平台额外预置的共享”两者通过统一的主机探测机制无缝衔接。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表