如果你也维护过一套自建缓存节点,大概能体会那种凌晨被报警电话叫醒的滋味:源站带宽突然被打满,错误日志里全是超时和连接重置,而GinCdn这台内容分发系统的命中率曲线却跟自由落体一样往下掉。我接手GinCdn这套基于Go和Gin框架的轻量级内容分发系统也有一年多了,前一阵子刚把V1.0.2版本折腾上线,缓了一口气,决定把这版更新的核心内容、设计取舍和上线过程中踩过的坑好好整理一下。
GinCdn定位很明确,它不是要跟商业CDN拼节点规模,而是给那些需要自建边缘缓存、或者手上有一批分布在不同机房的服务器、想统一做内容分发和流量调度的团队提供一个能自己掌控的实现方案。V1.0.2这个版本对缓存模块、回源链路、健康检查机制、配置热加载都有不少改动,如果你正好在用GinCdn,或者正准备评估它,这一篇应该能帮你少走很多弯路。同时也适合那些想了解内容分发系统内部到底在做什么的读者,我会尽量把"为什么这么改"讲透。
1. V1.0.2版本到底改了哪些东西
1.1 更新日志一览
先上一个总览,把V1.0.2的变更整理成一张表。后面的章节再逐个展开讲。
| 模块 | 变更内容 | 解决的核心问题 | 影响面 |
|---|---|---|---|
| 缓存模块 | 单层内存缓存改为内存+磁盘两级缓存,LRU淘汰算法改为分段实现 | 大文件无法缓存、内存锁竞争严重 | 所有静态资源请求 |
| 缓存并发 | 引入singleflight机制防止缓存击穿 | 热点key过期瞬间导致源站被打爆 | 高并发场景 |
| 缓存管理 | 新增主动失效API和后台预热任务 | 资源更新后节点仍返回旧内容 | 运维侧、内容更新流程 |
| 回源链路 | 连接池复用、支持幂等请求自动重试 | 回源连接频繁建立、瞬时故障导致5xx | 源站压力、错误率 |
| 健康检查 | 主动探测+被动探测结合,增加half-open状态 | 故障节点未被及时摘除 | 请求成功率 |
| 配置系统 | YAML配置支持热加载,不再需要重启进程 | 调整节点和缓存策略需要重启服务 | 运维效率、可用性 |
| 监控指标 | 暴露Prometheus格式的/metrics端点 | 无法实时观测命中率、回源耗时等指标 | 监控告警 |
| Bug修复 | 修复HTTP Range请求处理、磁盘文件句柄泄漏等 | 视频拖拽异常、文件描述符耗尽 | 多媒体场景、稳定性 |
1.2 推动这次迭代的三个线上事故
V1.0.2的每项改动基本都能追溯到具体的线上故障。第一个事故是某个营销活动页上线,页面上的一张热点图片的key在同一秒内过期,结果数百个请求同时穿透到源站,那一刻的并发直接把后端Web服务的连接池打满,页面从秒开变成了十几秒。这个事故直接推动了缓存击穿防护的设计。
第二个事故跟健康检查有关。当时V1.0.1版本的被动健康检查只是记录一个滑动窗口内的错误率,错误率超过阈值就把节点标记为不可用。但问题在于,检查的是"到节点本身"的连通性,而不是节点回源的能力。某个节点磁盘已经被日志写满了,但进程还活着,健康检查仍然通过,流量继续往里打,结果用户拿到的全是502。
第三个事故是配置变更引起的。V1.0.1的配置是启动时一次性读取的,每次调整一个缓存TTL或者新增一个回源节点,都必须执行一次全量重启。那一次正值业务高峰期,我改了一个源站地址,执行重启之后连接排空没做好,整个节点的在线请求被硬生生掐断,用户那边瞬间报了一波5xx。
可以说V1.0.2就是从这三个事故里长出来的版本。它不只是加功能,而是在原有的架构上做了一套"补课"。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 缓存模块重构:从单层内存到内存磁盘两级联动
2.1 为什么非得上两级缓存
GinCdn V1.0.1时代的缓存模块是单层的,所有缓存对象都放在内存里。早期接入的业务以小型HTML、CSS、JS为主,几十MB的内存就能撑起不错的命中率。但随着接入的图片和视频切片越来越多,内存成本急剧上升,而node上可用的内存又是有限的,到了V1.0.1后期,我们已经默认只缓存小于2MB的对象。
这个限制直接导致很多大图、视频切片每次都要回源。回源量一大,
