2025-12-23
滟滟随波千万里,何处春江无月明! --- 《春江花月夜》 · 唐代 张若虚
Details
全文
春江潮水连海平,海上明月共 潮生。滟滟随波千万里,何处春江无月明!
江流宛转绕芳甸,月照花林皆似霰。
空里流霜不觉飞,汀上白沙看不见。
江天一色无纤尘,皎皎空中孤月轮。
江畔何人初见月,江月何年初照人?
人生代代无穷已,江月年年只相似。
不知江月待何人,但见长江送流水。
白云一片去悠悠,青枫浦上不胜愁。
谁家今夜扁舟子,何处相思明月楼?
可怜楼上月徘徊,应照离人妆镜台。
玉户帘中卷不去,捣衣砧上拂还来。
此时相望不相闻,愿逐月华流照君。
鸿雁长飞光不度,鱼龙潜跃水成文。
昨夜闲潭梦落花,可怜春半不还家。
江水流春去欲尽,江潭落月复西斜。
斜月沉沉藏海雾,碣石潇湘无限路。
不知乘月几人归,落月摇情满江树。
Docker Registry 学习文档
Docker Registry 基础概念
Docker Registry 是用于存储、分发 Docker 镜像的服务端组件。Docker 官方提供的实现是 registry:2(Distribution)。
核心定位非常明确:
它只负责镜像的存取,不负责复杂的管理与治理。
Registry 本身是无状态服务,真正的数据保存在后端存储(本地文件系统、对象存储等)。
Docker Registry 的能力边界
在学习和使用 Registry 之前,必须先明确它能做什么 / 不能做什么。
可以做的事情
-
私有镜像仓库存储
-
支持
push / pull / delete -
支持 Basic Auth 与 TLS
-
支持单一上游的 pull-through cache(代理缓存)
-
支持文件系统或对象存储作为 backend
不能做的事情
-
多上游 Registry 聚合
-
细粒度 RBAC 权限模型
-
图形化管理界面
-
镜像生命周期管理
-
智能路由与策略控制
一句话总结:
registry:2 是“镜像 HTTP 存储服务”,不是“镜像管理平台”。
Registry 的两种典型使用模式
Hosted(私有仓库模式)
-
镜像由内部系统
docker push -
Registry 只负责保存
-
地址形式:
registry.example.com:5000/project/app:1.0.0
适合:
-
内部研发镜像
-
CI/CD 构建产物
-
离线或半离线环境
Pull-Through Cache(代理缓存模式)
Registry 可配置一个上游(如 Docker Hub):
-
本地不存在镜像
-
转发到上游拉取
-
缓存到本地
-
返回给客户端
主要用途:
-
Docker Hub 加速
-
网络不稳定或限流环境
Pull-Through Cache 的工作原理
请求链路
docker pull nginx
↓
Docker daemon
↓
registry(proxy 模式)
↓
Docker Hub
-
首次拉取:命中上游并缓存
-
再次拉取:直接命中本地缓存
Proxy 配置示例(单上游)
version: 0.1
http:
addr: :5000
storage:
filesystem:
rootdirectory: /var/lib/registry
proxy:
remoteurl: https://registry-1.docker.io
关键点:
-
remoteurl只能配置一个 -
Docker Hub 正确入口是
registry-1.docker.io -
registry.docker.io不是 Registry V2 API 入口
为什么 Registry 不支持多个 remoteurl
这是设计层面的限制,不是配置能力问题。
原因包括:
-
Registry V2 的 token / scope 强绑定单一上游
-
镜像命名空间无法做路由判定
-
认证流程无法组合
结论:
registry:2 的 proxy 模型是“单上游缓存”,而不是“多源聚合”。
Docker 客户端与 Registry 的关系
Registry Mirror 的作用
Docker 客户端可配置镜像加速器:
{
"registry-mirrors": ["http://registry.example.com:5000"]
}
行为说明:
-
优先通过 mirror 拉取 Docker Hub 镜像
-
mirror 不存在再回退直连 Docker Hub
注意:
-
mirror ≠ 多 registry 路由
-
只针对 Docker Hub 命名空间
HTTP 与 HTTPS 的安全模型
HTTP Registry(Insecure)
特点:
-
部署简单
-
仅适合内网环境
客户端必须显式声明:
{
"insecure-registries": ["registry.example.com:5000"]
}
HTTPS Registry(推荐)
特点:
-
默认安全
-
无需 insecure 配置
客户端需信任 CA:
/etc/docker/certs.d/registry.example.com:5000/ca.crt
Registry 的认证机制
Basic Auth
-
基于
htpasswd -
Registry 不维护用户体系
-
适合 小规模使用
Token / OAuth
-
Registry 原生不支持
-
通常由反向代理或上层平台提供
Registry 的数据存储模型
存储内容
-
Layer blobs(占用磁盘)
-
Manifest / index(元数据)
重要认知
-
相同 layer 只存一份(内容寻址)
-
删除 tag 不等于立即释放空间
-
必须执行 GC 才能回收磁盘
Registry 的运维关注点
磁盘空间
-
镜像增长快
-
必须监控磁盘水位
备份策略
-
直接备份存储目录
-
Registry 自身无状态
垃圾回收(GC)
-
执行 GC 时需停服务
-
不适合高并发在线环境
Registry 的适用与不适用场景
适用场景
-
内网镜像缓存
-
Docker Hub 加速
-
CI/CD 私有仓库
-
离线镜像分发
不适用场景
-
企业级多团队治理
-
多 Registry 聚合
-
复杂权限与审计
-
可视化管理需求
Registry 在整体架构中的定位
一个工程化结论:
Docker Registry 是“底层存储组件”,而不是“平台级产品”。
在成熟架构中,它通常:
-
被更高层平台接管
-
或仅作为专用缓存节点存在
学习总结
-
Registry 只支持单上游 proxy
-
remoteurl不能配置多个是设计必然 -
registry-1.docker.io才是 Docker Hub 的真实入口 -
Registry Mirror ≠ 多 Registry 路由
-
多上游统一入口必须使用更高层的仓库管理系统
这套认知建立后,Registry 的所有行为都会变得可预测、可解释、可维护。
Spring Boot / Java 项目中校验、唯一键与字符串处理的系统化学习文档
本文是对我们整段对话的完整工程级总结,目标不是“会写代码”,而是知道为什么这么写、哪些地方不能省、哪些地方不能过度设计。内容以 Spring Boot + Java 11/21 + jOOQ 为背景,但原则具有普适性。
一、Bean Validation 基础:@Validated 的正确定位
1.1 @Validated 是什么
-
@Validated是 Spring 提供的校验触发注解 -
底层基于 Bean Validation(JSR-303 / JSR-380)
-
默认实现是 Hibernate Validator
核心作用:
触发校验 + 支持校验分组 + 支持方法级校验
1.2 @Validated 与 @Valid 的区别
| 维度 | @Valid | @Validated |
|---|---|---|
| 来源 | JSR 标准 | Spring |
| 分组校验 | ❌ | ✅ |
| 方法参数校验 | ❌ | ✅ |
| Controller 参数校验 | ✅ | ✅ |
工程建议:
在 Spring Boot 项目中 统一使用 @Validated。
二、跨字段校验:为什么必须用“类级校验”
2.1 问题本质
需求示例:
String a和String b
不能同时为空(null / blank),但允许只填一个
这是一个跨字段组合规则,而不是单字段规则。
2.2 为什么字段级注解不适合
-
@NotNull/@NotBlank→ 表达的是 AND -
实际业务语义是 OR
-
字段级注解无法感知“另一个字段”
结论:
只要规则依赖多个字段,就必须使用类级校 验
三、字符串校验的正确语义:null ≠ blank
3.1 业务语义层面的区分
| 情况 | 语义 |
|---|---|
null | 未提供 |
"" / " " | 提供但无意义 |
" 10.1.1.1 " | 有值但带噪声 |
"1.1 .1.1" | 非法输入 |
业务校验关心的是“语义是否有值”,不是内存状态。
3.2 Java 原生 vs Apache Commons
比较两种写法:
s != null && !s.isBlank()
StringUtils.isNotBlank(s)
结论:
-
Java 11+:优先使用原生
isBlank() -
不为一个
isNotBlank单独引入 commons-lang3 -
依赖治理 > 少写几个字符
四、推荐的字符串归一化(normalize)策略
4.1 标准实现
private static String normalize(String s) {
if (s == null) return null;
var t = s.trim();
return t.isBlank() ? null : t;
}
4.2 为什么要 trim() 再 isBlank()
-
isBlank()能判断全空白 -
trim()的真正目的不是判空,而是 消除首尾噪声 -
"10.1.1.1"与" 10.1.1.1 "在业务上应视为同一个值
4.3 为什么不处理“中间空格”
例如:
1.1 .1.1
1.1. 1.1
结论:
normalize 不负责纠错,只负责归一化
原因:
-
中间空格属于 非法输入
-
自动修复会“悄悄吞错”
-
应在 DTO / 校验层明确拒绝
工程原则:
首尾空白是噪声,可以清;
中间空白是错误,必须报。
五、日常代码风格:getter 调用 vs 局部变量
5.1 两种写法对比
if (a.getS() != null && !a.getS().isBlank())
var s = a.getS();
if (s != null && !s.isBlank())
5.2 推荐结论
推荐先取局部变量,再判断
原因:
-
避免重复表达式
-
降低阅读与调试成本
-
更利于逻辑扩展
-
性能无实质差异
六、唯一键校验的核心问题建模
6.1 唯一键的真实语义
唯一键校验不是问:
“有没有这组字段?”
而是问:
“有没有另一条记录,与我拥有相同的唯一键语义?”
“另一条”是关键词。