🧱 后端开发与架构
未读一个奇怪的现象上一篇讲到版本兼容性验证。等我把 Spring Boot 3.4.7 和 Spring Cloud 的版本问题解决之后,开始仔细看原来 2.1.0 微服务架构中的 Feign 接口关系,发现了一个让我困惑的现象。 同一套架构里,Feign 接口有两种完全不同的实现方式。一部分 Feign 接口由服务提供方维护,通过独立 jar 包对外发布——比如 sctelcp-user-api-starter,里面封装了 UserApiService 这个 Feign 接口以及相关的 DTO、枚举、常量。业务方引入这个依赖就能直接注入使用。这是第一种方式,很标准的”契约优先”做法。 另一部分 Feign 接口由业务方自己写——比如 sctelcp-infrastructure-service 自己维护了一个 UserApiService,用于调用用户服务的接口。同一个 UserApiService,在不同的服务里有不同的实现,甚至类名都一样。 于是我问了自己一个问题:为什么不全部用第一种方式? 答案是”以前出现过业务方乱调用 Feign 接口的情况,出了问题第一时间找架构组,所以就同 ...
关掉检查就行了吗前八篇文章写了研发底座的架构全景、工程规范、Response 设计、缓存重构、配置管理、代码质量、MyBatis 组件化、网关性能调优。这些内容基本上是在同一个架构底座上逐步优化出来的。但现在我们要聊一个更底层的变动——从单体架构再切回微服务架构。 先交代一下背景。这不是一个从零开始的设计。研发底座最早是微服务架构(v2.1.0),后来因为某些项目场景需要,把它改造成了一个单体架构(代号 cirrus-1.0.0)——主要就是把原来的 Feign 远程调用改成了 JVM 内部调用,用一个聚合组件收敛依赖。然后到了 9 月,业务上又有微服务的需求了,于是我们从单体架构出发,再做一次微服务化改造,版本定为 v3.1.0。 问题是,这中间的 Spring Boot 版本变了。 单体架构改造时,根据业务方需求选择了 Spring Boot 3.4.7。这个版本号本身没问题——它是当时可用的最新稳定版。但当我们想在这个版本上加入 Spring Cloud 依赖时,启动直接炸了: 123456789101112131415***************************AP ...
🖥️ 基础设施与运维
未读从一个错误日志说起某天下午,前端同事反馈”页面加载很慢,偶尔直接白屏”。切到服务器上看 Nginx 日志,刷屏的是: 123452025/09/15 17:57:48 [crit] 24950#24950: *4354733 connect() to 127.0.0.1:9090 failed (99: Cannot assign requested address) while connecting to upstream, client: 192.168.0.184, server: _, request: "GET /api/user/api/resource/list/tree HTTP/1.1", upstream: "http://127.0.0.1:9090/user/api/resource/list/tree", host: "192.168.0.10:3000" Cannot assign requested address。这个错误的意思是——没有可用的本地端口了。 当时的 Nginx 配置是这样的: ...
🏠 HomeLab:中年男人之友
未读背景:运维账到了该清的时候这个博客我折腾着折腾着,已经变成一个”小站点、大后台”的状态了。 主站是 Hexo 静态页面,听上去很轻,但后面挂了一堆支撑服务:评论、搜索、统计、访问计数、AI 摘要、TTS、GitHub 贡献热力图、朋友圈聚合、短链、API 聚合……一数吓一跳,加起来有三四十个大大小小的进程。 这些服务过去分散在 好几台 PVE 节点上跑,分工大概是这样: A 节点跑数据库一类的基础设施 B 节点跑博客主系统和反向代理 C 节点跑一些 Node.js 的小微服务 D 节点跑监控、AI 平台之类的东西 听起来挺合理——按职责拆机器。但实际用下来,运维账越算越难看: 一次例行升级要 SSH 四五台机器,每台的 Docker Compose 文件风格都不一样 备份脚本有四五套,谁新增了服务我都得同步去改 服务之间的依赖关系横跨节点,数据库在 A,应用在 B,出问题排查要跨机器看日志 任何一台 PVE 节点挂了,我都得跑去柜子前面插显示器排障 功耗也不低,四台机器常年 7×24 开着,电费肉眼可见 同期我新上了一台 Mac Studio M2 Ultra 给 AI ...
🧱 后端开发与架构
未读第一个坑:XML 去哪了最先踩的坑很基础——MyBatis 的 Mapper XML 文件在打包成 jar 后消失了。 背景是我们在做一个日志组件,里面包含了 dao 接口和对应的 XML: 1234src/main/java/com/xxx/log/service/dao/├── LogDao.java└── xml/ └── LogDao.xml 有同事把 XML 和 dao 接口放在同一个包路径下,理由是”方便对照”。开发阶段一切正常,mvn spring-boot:run 跑起来 SQL 也能加载。但打包成 jar 给业务方引用时,运行提示 Invalid bound statement (not found)。 原因很简单:Maven 编译时只处理 src/main/java 下的 .java 文件,.xml 文件不会被拷贝到 target/classes。打包成 jar 后自然也没有。 修法有两种。第一种,把 XML 挪到 src/main/resources 下,保持相同包路径: 12src/main/java/com/xxx/log/service/dao/L ...
🧱 后端开发与架构
未读推行代码检查,最难的不是技术代码质量保障是个经典话题。Checkstyle 做格式检查,PMD 做逻辑缺陷检查,SonarQube 做全方位的代码质量度量——这三件套怎么用,网上文档很全。 但真正的挑战从来不是”怎么配置这些工具”,而是**怎么让一个已经运行了两年的、代码量不小的、团队成员流动频繁的项目接受”强制代码检查”**。 想象一下这个场景:你在 CI 管道里加上了 Checkstyle,规则用的是 Google Java Style。第二天,所有 MR 都挂了——不是代码逻辑有问题,而是缩进不对、命名不规范、缺少 Javadoc。开发 leader 来找你:”能不能先关掉,这个需求很急。”你说不能。他说那就先降级为 warn。 然后这个 warn 就永远是 warn 了。没人看 warn。 这不是假设,这就是大部分项目型组织中推行代码质量工具的真实路径。所以我花了大量时间思考的不是”选哪个工具”,而是”怎么让别人愿意用”。 工具分工:各干各的,互相补充先简单过一下三件套的定位。 Checkstyle 管格式。缩进是 4 个空格还是 tab?大括号是同行还是换行?类名是大驼峰还 ...
🖥️ 基础设施与运维
未读背景起因是家里 Homelab 的一台 NUC 升级,给它加了张 Intel X520-2 双口万兆,顺手把 NAS(DS923+)那边也换上了万兆。理论上这套组合应该是”一步到位、全屋万兆起飞”的那种。 但真正测下来,iperf3 从 NUC 打到 NAS 只有大概 950Mbps。 ethtool enp2s0f0 明明写着 Speed: 10000Mb/s 和 Link detected: yes,我盯着那一行看了半天。硬件、线缆、交换机、NAS 全都换过交叉验证过,问题死死卡在这台 NUC 上。 最后折腾下来才发现根本不是硬件的锅,是 Linux 网络里一个经典得不能再经典的坑:ARP Flux——多张网卡挂在同一个子网里,彼此抢答 ARP,导致流量走错路径。 这篇文章把完整的排查链路、每一步的命令和我当时的判断都写出来,既是给未来的自己留个底,也希望你以后踩到类似问题时能少走一圈弯路。 环境说明先把桌面摆开,这台 NUC 上一共挂了 4 张网卡: 接口 IP 类型 Bridge enp2s0f0 192.168.31.99 Intel X520 10G v ...
配置管理,最容易被做烂的事在所有研发底座的能力中,配置管理是最”不起眼”的。它不产生业务价值,不影响接口性能,甚至改坏了也不会马上出问题——配置错误可能在被调用到的时候才暴露。 但正因为它不起眼,它也是最容易被做烂的。框架迭代了两年,不同的人在不同的时期添加不同的配置项,大家的命名习惯、组织方式、使用方式各不相同,最后就形成了一种”配置腐化”。 我接手的时候,框架的配置状况大概是这样的: 有的配置用 @Value 注入: 12@Value("${spring.data.redis.enable:true}")private boolean enable; 有的配置用 @ConfigurationProperties,但前缀混乱。开关配置一部分叫 enable,一部分叫 enabled: 12345678secret: mybatis: enable: false # 这里是 enablegateway: log: access: enabled: false # 这里是 enabled 业务方 ...
前言家里这些年陆陆续续堆起来的服务器,现在打开 ~/.ssh/config 数了一下,大大小小十几个 entry:PVE 主节点、PVE 子节点、NAS、软路由、一堆 LXC、几台跑服务的虚拟机、再加上两台 Mac mini。 多了之后就有个很典型的痛苦场景: “我之前写过一条 ffmpeg 命令把 MKV 转 MP4 的,在哪台机器上来着?” 然后就开始 ssh 每台机器跑一遍 history | grep ffmpeg,还翻不到。因为 zsh 默认的 history 是按机器本地存的,一旦换了机器就没了。更烦的是有时候想在一批新机器上统一跑几条命令(比如同步 docker compose 配置、修 sshd_config),还得一台一台粘贴,粘着粘着就怀疑人生。 我之前也试过 Atuin,功能更花哨,但它的 TUI 相对重一些,而且我只想要一个”跨机器搜索 + 执行时的完整上下文“,并不需要太多仪式感。最后选定了 hishtory,理由很简单: 记录的上下文足够多:命令、cwd、退出码、运行时长、主机名、时间,一条都不少; 自带端到端加密 + 自建后端,数据全在家里机 ...
现有方案的痛点终端里的大量操作有一个共同特征:高频、临时、简单。查个端口、扫个日志、打包个镜像,难的不是算法,是「这条 one-liner 参数怎么拼」。市面上的解决方案各有各的问题: 通用 AI 工具(ChatGPT、Cursor 等):你得切换窗口、描述需求、从一大段解释里抠出那一行命令。而且通用 AI 工具加载了大量 Agent、MCP、Skill 上下文,你只是想生成一行 find,也要消耗大量 token。 重型 AI CLI(Claude Code、Codex CLI 等):非常适合复杂任务和多步推理,但为了「删七天前的日志」这种小事进入一整套交互会话,心智与路径都偏重。 查文档 / 搜索引擎:每次都要离开终端、搜索、复制、粘贴,打断心流。 这个场景的 AI 调用其实非常简单:输入是自然语言描述,输出是可执行的 Shell 命令,一轮问答就够了。把这种极简的 AI 调用直接嵌到 IDE 内置终端里,不需要离开编辑器,不多消耗一个 token。 功能一览 功能 入口 说明 TAB 生成命令 终端输入行 + TAB 以触发前缀(默认 #)开头时拦截 ...
🧱 后端开发与架构
未读一个接口,五十几个方法打开缓存组件的 CacheService 接口,我看到的是一个长达 573 行的定义——五十几个方法,从基本的 get/set/delete,到 Hash 操作的 hget/hset,到 List 操作的 lpush/lpop,到 Set 操作,再到各种带过期时间的变体。它像是把 Redis 的所有命令直接翻译成了 Java 方法,然后用一个接口兜住。 这种设计有几个后果。 实现类被迫实现所有方法。 即使某个实现只支持 String 操作,它也得把 Hash、List、Set 的方法全部写上——哪怕方法体是 throw new UnsupportedOperationException()。这是典型的”胖接口”问题,违反了接口隔离原则(ISP)。 使用者不知道什么时候该用什么。 五十几个方法摊在一个接口里,光 IDE 的自动补全列表就要翻好几页。想缓存一个简单的 KV 对,要在一堆 putHash、pushLeft、addSet 里找到正确的 set 方法。 扩展新能力时改的就是这个接口。 每次想加一个缓存操作类型,比如 ...
🏠 HomeLab:中年男人之友
未读背景:我只是想让那块小屏幕别闲着家里那台服务器是主力 Homelab,24 小时跑着 PVE、几个 LXC、几组 Docker。之前监控全靠 Grafana + Nezha,看得很全,但有个问题:每次我想”扫一眼当前状态”,都得掏手机或者切到电脑上的浏览器。 某天翻出一块以前买摄像头配的 7 寸 HDMI 小屏幕,突然就想:要是把它接到服务器 HDMI 口上,常亮挂一个 btop 不就好了?走过服务器旁边看一眼 CPU/内存/磁盘/网络,比掏手机快多了。 理想很美好,现实一接上就踩坑: 开机后屏幕默认停在 getty@tty1 的登录提示符 要看 btop 得先插键盘、输账号密码、btop 回车 我的服务器放在柜子里,键盘根本不在旁边 偶尔掉电重启,每次都要拖一根键盘线过去,心态爆炸 说白了我要的不是”登录系统”,我要的是一块常亮的监控屏。那 tty1 这个位置就不能留给 getty,而应该直接跑 btop。 记录一下最后的方案,顺便留档给未来的自己。 目标 开机后 tty1 直接显示 btop,不需要登录、不需要键盘 屏幕 24 小时常亮做仪 ...
现有方案的痛点市面上 Changelog 生成工具不少,conventional-changelog、git-changelog、release-drafter 都能用,但它们有一个共同前提:团队得严格遵守约定式提交规范。实际项目里,fix bug、update、修复了问题 这种提交满地都是,你没法指望所有人都按规范写。这些工具拿到这种”垃圾进”,只能给你”垃圾出”。 那用 AI 呢?比如把 Git Log 复制到 ChatGPT 或者让 Cursor 帮忙整理,确实能生成不错的内容。但问题是: token 浪费严重:现在的 AI 工具都是大而全的 Agent,接入大量 MCP 和 Skill,你只是想让它整理几条提交记录,它也要加载一堆上下文,消耗的 token 远超实际需要 批量场景撑不住:想为整个项目的提交历史批量生成 Changelog?几次就报上下文超长,得手动切分范围重来 流程割裂:IDE 里选中提交 → 复制 → 切到外部工具 → 粘贴 → 等结果 → 复制回来,每次都要中断开发流程 提交信息本身的困境:不只是整理历史记录痛苦,写 commit message ...
🧱 后端开发与架构
未读一个隐藏了一年的 bug先看一段代码。这是框架里 Response 类的几个静态工厂方法: 1234567891011121314151617public static Response success() { return Response.success(null);}public static <T> Response success(T data) { return new Response().success(data, null);}public static Response success(String message) { return new Response().success(null, message);}public Response success(T data, String message) { this.meta = new Meta(ResponseState.SUCCESS, message); this.data = data; ...















