
1. 内容整体设计与思路拆解1.1 从“仓库”到“存储底座”先搞明白 Blob Store 在 Nexus 里扮演的角色很多刚接触 Nexus Repository Manager 3后面统称 Nexus的朋友第一步往往卡在“Blob Store 到底是干嘛的”这个问题上。界面里能看到 Repository、Blob Store、Component、Asset 这几个词中文资料翻来翻去讲得清楚的确实不多。我先用一句话把关系理清Repository 是给用户/构建工具访问的逻辑入口Blob Store 是真正落盘存储二进制文件的地方两者是多对一的关系——多个仓库可以共用一个 Blob Store也可以一个仓库独占一个 Blob Store。如果你用过 Docker Registry、Maven 私服或 npm 私有仓库你应该知道最终所有上传的 jar、npm 包、镜像层都要变成磁盘上的文件。Nexus 没有把这些文件直接平铺在一个目录里让你随便看而是用了一套带元数据管理的存储结构你上传的每个二进制对象在 Nexus 里被抽象成一个 Blob这些 Blob 按一定规则存放在 Blob Store 里。值得注意的一点是Nexus 的 Blob Store 并不等价于普通文件系统上的一个文件夹。它内部有自己的一套文件组织逻辑、属性记录机制、甚至暂时文件存放区。很多管理员直接把 Blob Store 路径指向一个普通目录结果后面做迁移、做备份时才发现结构远比自己想得复杂。这篇博文我会从存储机制讲到容量规划从创建操作讲到常见坑尽量把 Blob Store 一次讲透。1.2 为什么 Nexus 不直接用普通文件系统目录非要搞一套 Blob Store抛开理论直接回答这个最常被问到的问题Nexus 面对的不只是“存一个文件”而是要管理海量小文件、跟踪谁上传了谁、记录组件与资产的关系、支持软删、支持跨仓库复制、支持内容审计。如果直接把上传的文件按原文件名丢到一个目录里至少会遇到四个问题文件名冲突无法处理同名文件在不同仓库版本之间怎么隔离无法高效枚举某个组件的所有文件尤其是对 Docker 镜像这种多层的包无法做到“软删除”Nexus 里删除 repository 甚至删除某个组件底层往往只是标记删除靠后台任务回收空间跨实例迁移、备份恢复会变成噩梦原文件名、目录结构与元数据对不上。Blob Store 通过把每个上传的文件抽象成一个 Blob用系统生成的唯一标识作为文件名再附带一个 properties 文件记录这个 Blob 的属性信息从而把存储与业务解耦。你可以理解为 Nexus 在磁盘上搭了一个“对象存储”的简化版——这就是我说 Blob Store 是整个 Nexus 存储底座的原因后面的容量规划也好、排查问题也好都离不开对这套机制的理解。2. 核心细节解析与实操要点2.1 Blob Store 目录结构逐层拆解blobs、content、metadata、temporary 各自干什么当你创建了一个 File 类型的 Blob Store 并指定路径之后Nexus 会在该路径下生成一个固定的目录骨架。不要小看这几层目录很多报警和性能问题都能从这里溯源。标准结构如下blob-store-path/ ├── blobs/ │ ├── content/ │ │ ├── vol-01/ │ │ │ ├── 0f/ │ │ │ │ ├── 0f5a... │ │ │ │ └── 0f5a....properties │ │ │ ├── ... │ │ │ └── byte-mapping.properties │ ├── tmp/ │ └── ... ├── metadata/ │ ├── properties │ └── ... ├── temporary/ ├── content └── ...先看blobs/content这个目录真正存放 Blob 二进制内容的地方。它会细分 vol-01、vol-02 这样的卷目录每个卷目录内再按文件名的 hash 打散成子目录避免几十万个文件堆在一个目录下导致文件系统性能劣化。这种分桶方式在 Git 的对象库、Docker 的存储驱动里都很常见核心目的就是降低单个目录的 inode 压力和检索延迟。同一个 Blob 至少对应两个文件一个无扩展名的数据文件真实二进制内容一个.properties后缀的属性文件。属性文件里记录了 blob 的创建时间、所属 repository 名称、关联的 asset ID 等信息。这就是为什么你直接在磁盘上拷贝 Blob Store 目录并不能随随便便迁移到另一台 Nexus 上——一旦属性文件与数据库里的元数据对不上Nexus 会认为 Blob 是孤儿数据。metadata目录存放 Blob Store 级别的元数据比如 blob store 的 ID 和类型标识而temporary目录是 Nexus 在执行压缩Compact或迁移时存放临时文件的地方。我特别提醒一句不要手动去删 temporary 目录或 blobs/content 下的任何文件Nexus 的 Blob Store 一致性依赖这些目录的完整存在真需要清理请用 Nexus 后台的压缩任务。2.2 Blob ID 与属性文件的命名规则以及为什么不能靠“看文件名”识别内容每个 Blob 的文件名都是一个 UUID 风格的字符串看起来没有任何业务意义比如856a4c2b-5de2-4e57-8ba7-9c6d10d2e3f1。这个 Blob ID 由 Nexus 在写入时生成全局唯一它也是 MySQL/H2 数据库里 asset 表关联 Blob Store 物理文件的关键字段。Blob ID 本身不携带路径信息所以 Nexus 需要一套“逻辑路径到物理文件”的映射机制这也是byte-mapping.properties这类文件存在的原因。它记录了每个卷目录下 Blob ID 到具体子目录的映射关系。你不需要读懂这个文件的内容但要明白一旦byte-mapping.properties丢失或损坏Nexus 可能无法启动或无法访问某些 Blob这就是为什么备份 Blob Store 必须基于文件系统快照或 Nexus 本身的备份能力而不是简单复制几个目录。说到这里顺便纠正一个很多人会踩的误区不要用文件时间或目录大小去判断某个仓库是否还在活跃使用。因为 Nexus 在后台有压缩、清理任务会重写或移动 Blob 文件文件修改时间可能与实际上传时间不一致。想看准确信息应该通过 Nexus Web 界面或 REST API 查询而不是在服务器上 ls。2.3 Blob Store 的类型选择File 还是 S3 类型不同场景如何取舍Nexus 3 支持多种 Blob Store 类型生产环境中用得最多的是 File 类型和 S3 类型。File 类型就是普通磁盘目录性能高、实现简单、部署容易绝大多数中小团队够用了。S3 类型则把 Blob 存到 AWS S3或兼容 S3 的对象存储好处是扩展性强、不依赖本地磁盘容量适合在云环境或大规模集群中使用但网络延迟和 S3 API 的限流会成为新的瓶颈。从实际运维角度看我一般建议低于 2TB 的制品库规模、单机部署的 Nexus优先用 File 类型简单可靠、排查起来也直观如果你有 10TB 以上的制品增长预期或者 Nexus 本身跑在 Kubernetes 里、需要依赖云上对象存储提供持久化那 S3 类型更合适。需要说明的是S3 类型的 Blob Store 也有本地缓存目录因为 Nexus 需要暂存上传文件再异步推送到 S3这里也会消耗一部分本地磁盘。创建时还有个选项叫“Soft Quota”软配额可以给 Blob Store 设置内存/空间限制。很多管理员忽略了这个字段但它是容量规划里非常关键的防线。我后面的实操部分会给出建议配置值。3. 实操过程与核心环节实现3.1 从零创建一个 Blob StoreWeb 界面操作与 REST API 两种方式Web 界面创建非常直观登录 Nexus 后进入设置菜单齿轮图标找到 Repositories - Blob Stores点击 Create Blob Store选择 File 类型然后填 Name 和 Path。这里我需要提醒两点Name 一旦创建后不能修改后续创建 Repository 时就是靠这个名字关联的所以命名要有规划比如blob-maven-releases、blob-npm-private别用blob1、test这种名字Path 不填的话Nexus 会默认使用sonatype-work/nexus3/blobs/Name目录。如果使用默认路径迁移时只需要迁移整个sonatype-work目录即可如果自定义了 Path迁移时要额外注意路径映射关系。REST API 方式对自动化运维更友好用 PUT 请求创建一个 File 类型的 Blob Store例如在脚本中调用curl -u admin:admin123 -X POST \ https://nexus.example.com/service/rest/v1/blobstores/file \ -H Content-Type: application/json \ -d { name: blob-maven-releases, path: /data/nexus-blobs/blob-maven-releases, softQuota: { type: space, limit: 107374182400, attributes: {} } }上面配置里limit单位是字节107374182400等于 100GB。这是我建议的最小配额如果你的磁盘足够大可以按 60%~70% 的磁盘容量来设给系统日志和临时文件留出余量。创建成功后可以用 GET 请求验证curl -u admin:admin123 \ https://nexus.example.com/service/rest/v1/blobstores/file/blob-maven-releases3.2 把 Repository 关联到指定 Blob Store以及多仓库共用与隔离的决策Blob Store 建好之后还要与 Repository 关联才会真正发挥作用。在 Nexus 里创建或编辑一个 Repository 时在 Storage 配置块中会有一个 Blob Store 下拉选项默认是default。我强烈建议不要把所有仓库都塞到 default Blob Store。原因有几个不同仓库的生命周期不同release 仓库的数据基本只增不改snapshot 仓库则可以定期清理混在一起会让压缩、备份、迁移都非常被动如果某个仓库的异常流量把磁盘写满会影响同 Blob Store 下所有仓库按仓库划分 Blob Store后续做冷热数据分层、磁盘迁移会更加灵活。我见过不少团队的做法是所有 Maven 仓库共用一个 Blob Store、所有 npm 仓库共用一个 Blob Store、Docker 镜像单独用一个 Blob Store。这样既避免了每个仓库一个 Blob Store 导致目录碎片化也保证了不同类型制品的隔离。创建 Repository 的时候Storage 配置下还有一个 “Strict Content Type Validation” 选项建议保持启用。它会在接收上传时校验 Content-Type避免一个 npm 仓库被传进去一个 docker 镜像层这种脏数据。3.3 容量规划如何估算 Blob Store 的体积、元数据开销和增长趋势容量规划是 Blob Store 运维中最容易被忽略的一环。很多管理员等到磁盘报警了才去看结果 Blob Store 已经膨胀到难以处理。先给一个参考公式Blob Store 实际占用磁盘空间 ≈ 所有制品二进制内容 每个 Blob 的 properties 文件开销 卷目录与映射文件开销 临时文件峰值。properties 文件通常只有几 KB相对动辄 MB 级的二进制内容占比很小真正的大头还是二进制内容本身。但要注意Docker 镜像仓库的 Blob 大小不能简单按镜像压缩后的大小算因为镜像层是去重的多个镜像可能共享同一层Nexus 对同一层只会存一份 Blob所以估算时可以先按镜像仓库的体积除以 2~3 作为乐观估算最终以实际使用为准。侧边栏里 Nexus 的 Blob Store 列表页会直接显示每个 blob store 的“Total Size”和“Available Space”但它是汇总值。更精确的查询方式是通过 REST APIcurl -u admin:admin123 \ https://nexus.example.com/service/rest/v1/blobstores返回结果里包含blobCount和totalSizeInBytes等字段。推进容量规划时我一般会做到位这几件事每周记录一次每个 Blob Store 的使用量连续记录一个月以上才能看出增长趋势根据增长趋势估算“磁盘耗尽时间点”提前 1~2 个月做扩容或清理准备设置软配额让 Nexus 在达到阈值时开始告警同时配合 “Admin - Cleanup Policy” 定时清理 Snapshot 和未使用的 Docker 镜像。很多团队觉得 Nexus 只是内部工具磁盘撑爆再清也来得及。但一旦 Blob Store 所在磁盘写满Nexus 会进入写失败状态所有仓库都无法上传制品这会直接阻塞 CI/CD 流水线届时清理数据也要冒着系统不稳定的风险非常被动。我强烈建议把 Blob Store 容量监控纳入日常巡检而不是等出了问题再处理。3.4 软配额与清理策略配合防止磁盘写满的最后防线光有软配额还不够因为它默认只做告警不做自动清理。Nexus 的 Cleanup Policy 是需要你主动创建的它的位置在 Settings - Repositories - Cleanup Policies。创建时可以选择策略类型比如 “Regular” 或 “Docker”、关联的 Repository以及按什么条件清理——比如 “Last downloaded” 超过多少天、“Last blobs” 数量、正则匹配 name等等。我给出一个在实际项目中验证过比较稳妥的组合方案策略点推荐配置软配额上限磁盘容量的 60%~70%Cleanup PolicyRelease 仓库不设清理保留全量版本Cleanup PolicySnapshot 仓库保留最近 30 天或最近 5 个版本Docker 仓库清理按未下载天数 90 天清理后台压缩频率每周末执行一次 Compact Blob Store注意 Cleanup Policy 只是把 Blob 标记为待清理不会立即释放磁盘。释放空间还需要触发 “Compact Blob Store” 任务它会把仍然被引用的 Blob 保留、把孤儿 Blob 删除并整理卷目录。这个任务可以在 Settings - System - Tasks 里手动执行一次或者配置为周期执行。3.5 Blob Store 迁移与备份什么时候不能直接复制目录Nexus 的数据由两部分组成数据库默认 H2也可以配 MySQL/PostgreSQL和 Blob Store。数据库里存的是组件、资产、仓库配置等元数据Blob Store 里存的是实际二进制内容。二者通过 Blob ID 关联。所以当你想要迁移 Nexus 或者做灾备恢复时只备份 Blob Store 是不行的必须保证数据库和 Blob Store 在时间点上尽量一致。Nexus 官方支持的方式是通过nexus:backup命令做整体备份或者直接对sonatype-work目录做文件系统快照。如果你对 Blob Store 单独备份建议先停 Nexus 服务再做快照否则 H2 数据库和 Blob Store 文件之间可能存在不一致状态。迁移时有个很多人踩过的坑在原服务器上自定义了 Blob Store Path迁移到新机器后没有在 Nexus 配置里修改对应路径结果 Nexus 启动后看不到任何 Blob或者后台报错。解决办法有两个要么保持新旧机器路径一致要么提前在nexus.properties或 Blob Store 创建时选择好持久化路径方案。对于自定义 Path 的 Blob Store迁移后可以通过修改blobstore配置不易操作需要停服改文件或重建 Blob Store 再重新关联仓库来修复但后者会导致该 Blob Store 下所有制品丢失引用非常危险。因此我建议如果走文件系统快照备份路线尽量保持整个sonatype-work目录结构不变。另一个备份建议如果使用了 S3 类型 Blob Store数据库备份的同时也要确保 S3 桶开启了版本控制因为 S3 类型 Blob Store 的删除操作是直接对桶内对象进行的没有版本控制的话误删数据基本无法找回。4. 常见问题与排查技巧实录4.1 Blob Store 所在磁盘空间暴涨我怎么定位是哪个仓库在占空间磁盘空间告警是 Blob Store 最典型的故障定位思路要按“仓库级别 - Blob Store 级别 - 具体组件”逐层下探。先看 Nexus UI 的 Blob Stores 列表确认哪个 Blob Store 的 Total Size 增长最快再进入这个 Blob Store 关联的仓库中找到组件视图按大小排序看哪些组件占用了大头如果是 Docker 仓库注意查看是否有大量旧镜像 tag 未被清理尤其是latest这种 tag 每次构建都会覆盖但覆盖后旧的镜像层并不一定被删除。定位到具体仓库后就可以针对性创建 Cleanup Policy 并执行 Compact。如果空间已经吃紧压缩任务也可能因为磁盘不足而无法完成这时先把软配额放宽一点、手动删除一部分确定无用的快照组件再触发压缩。4.2 压缩任务跑完磁盘空间没有明显下降原因是什么很多管理员会在后台执行一两次 Compact 后发现磁盘下降不多于是怀疑 Nexus 压缩没用。实际上Compact 的机制是重写 Blob Store 内部的卷目录把标记为删除的 Blob 物理移除但这个过程本身需要一定的临时空间来写新文件。而且如果你的仓库中绝大多数 Blob 都是正在使用的内容压缩任务确实不会释放太多空间。另一个隐藏问题是文件系统层面删除文件后磁盘空间可能仍被已打开的文件句柄占用。如果 Nexus 是一个长时间运行的 Java 进程某些 Blob 被删除时可能仍被某个连接引用导致目录项已移除但 inode 未释放。这种情况在 Linux 上可以用lsof | grep deleted查看通常需要重启 Nexus 才能彻底释放。经验之谈压缩任务最好放在业务低谷期执行并且提前预留磁盘剩余空间的 10%~15% 作为临时空间别等磁盘 99% 了才想起来跑压缩那时候多半跑不起来。4.3 通过 REST API 批量查询 Blob Store 用量纳入自动化巡检人工看 UI 可以做一次性的排查但容量管理要可持续还是得靠脚本。我提供一个最简单的巡检思路借助 Nexus 的 REST API 拿到每个 Blob Store 的用量然后推送到内部的监控平台或发到消息群。#!/bin/bash NEXUS_URLhttps://nexus.example.com AUTHadmin:admin123 curl -s -u $AUTH $NEXUS_URL/service/rest/v1/blobstores \ | jq -r .[] | \(.name)\t\(.blobCount)\t\(.totalSizeInBytes)把totalSizeInBytes除以 1024^3 就是 GB 数。你可以写个小型脚本每天定时跑一次当某个 Blob Store 的实际用量超过软配额限制的 85%就发出告警。这里有个容易忽略的点REST API 返回的totalSizeInBytes是 Nexus 元数据里记录的统计值底层磁盘上一个 4K 小文件的真实占用可能是几 KB所以它和文件系统 du 的结果会有差异但只要用同一个指标口径做趋势监控就没问题。4.4 Blob Store 删除失败或提示正在使用怎么办如果你想删除一个 Blob Store但 Nexus 提示它正在被某个 Repository 使用无法删除这是预期行为。第一种处理方式把关联该 Blob Store 的仓库迁移到其他 Blob Store。但 Nexus 目前没有提供一个直接的“仓库存储迁移”按钮通常的做法是新建一个仓库并指定新 Blob Store然后通过复制/同步工具把制品推到新仓库。这个过程比较繁琐需要一定停机时间。所以我在前文反复强调创建初期就要做好 Blob Store 的划分规划避免后续迁移的麻烦。第二种方式当你确认该 Blob Store 下的数据都不需要保留而且已经没有仓库关联它却依然删除失败可以检查是否有后台任务正处于运行中等待任务结束后再删除。实在不行就停 Nexus在数据库里手动清掉对应的 Blob Store 记录但这一步风险极高操作不当可能损坏 Nexus 库表非必要不建议使用。4.5 重启之后 Nexus 启动很慢或提示 Blob Store 缺失出现这种问题十有八九是迁移或恢复时没有把 Blob Store 目录放到正确路径下或者是 S3 类型的 Blob Store 依赖的 Access Key 过期。对于 File 类型打开 Nexus 日志找到启动异常信息确认报错是找不到byte-mapping.properties还是找不到卷目录。如果是路径问题把整个 Blob Store 目录放回正确位置再重启。如果是权限问题检查 Nexus 进程的运行用户对 Blob Store 目录是否有读写权限特别是使用 Docker 部署时容器内的 UID 和宿主机的文件归属不一致经常会导致权限拒绝。一个实用的技巧在nexus.properties里配置nexus.blobstore.file.floatingTimeout和nexus.blobstore.file.backoff两个参数可以调整 Blob Store 文件锁的等待时间。多实例同时访问同一个 Blob Store 目录时会遇到文件锁等待超时的问题分布式部署场景下这两个参数很有用单机部署不需要动。5. 从一次真实的磁盘告警说起分享几个保命习惯我在实际维护 Nexus 的过程中有一次印象特别深刻的故障某个周五下午集群的 CI 流水线大面积报错所有制品上传都失败。登录 Nexus 一看磁盘使用率已经 100%Docker 镜像的 Blob Store 把系统盘写满了而 Nexus 的 H2 数据库和系统日志恰好也在同一块盘上直接导致 Nexus 进入半死不活的状态连 UI 都打不开。后来靠应急清理旧镜像、重启服务才恢复但研发团队的整个发布流程被阻塞了两个多小时。那次之后我把 Blob Store 的容量管理当成一等公民来对待形成了下面几个习惯分享给你参考把 Blob Store 所在磁盘和 Nexus 程序目录分盘存储避免 Blob 写满磁盘后拖垮数据库和日志每个 Blob Store 都设置软配额配额上限不超过磁盘容量的 70%清理策略和压缩任务定期化而不是等出问题再手动操作每周巡检 Blob Store 容量记录趋势不用等到报警才看一眼。最后再分享一个实用技巧Nexus 后台的 “Rebuild repository metadata” 任务有时也会占用较多磁盘临时空间如果你的 Blob Store 本来就快满了尽量别同时触发多个后台任务。给 Blob Store 留一点临时空间余量会让后面的压缩、迁移操作从容很多。只要理解了 Blob Store 的存储机制做容量规划时就会清晰很多——它不是黑盒而是一套有规则的本地对象存储顺着它的机制来管理就不会被“磁盘满”这种事打个措手不及。