Files
cache-proxy/memory-bank/systemPatterns.md
guochao 80560f7408
All checks were successful
build container / build-container (push) Successful in 29m33s
run go test / test (push) Successful in 26m15s
cline optimization on range request and memory usage
2025-06-10 17:44:44 +08:00

63 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 系统架构与设计模式
`cache-proxy` 的系统设计遵循了几个关键的模式和原则,以实现其高性能和高可用性的目标。
## 核心架构
系统可以分为三个主要部分:
1. **HTTP 服务器层**: 负责接收客户端请求,并使用中间件进行日志记录、错误恢复等通用处理。
2. **缓存处理层**: 检查请求的文件是否存在于本地缓存中,并根据缓存策略决定是直接提供缓存文件还是向上游请求。
3. **上游选择与下载层**: 这是系统的核心,负责并发地从多个上游服务器获取数据,并管理下载过程。
## 关键设计模式
### 1. 竞争式请求 (Racing Requests)
这是实现“选择最快”功能的核心模式。
- `fastesUpstream` 函数为每个上游服务器创建一个 goroutine。
- 所有 goroutine 并发地向上游服务器发送请求。
- 使用 `sync.Once` 来确保只有一个 goroutine 能够“胜出”并成为最终的数据源。
- 一旦有 goroutine 胜出,它会调用 `context.CancelFunc` 来通知所有其他 goroutine 停止工作,从而避免不必要的资源消耗。
### 2. 生产者-消费者模式 (Producer-Consumer)
在文件下载和流式响应Ranged Request使用了生产者-消费者模式。
- **生产者 (`startOrJoinStream`)**:
- 为每个首次请求的文件启动一个 goroutine。
- 负责从最快的上游服务器下载文件内容。
- 将内容**直接写入一个临时文件** (`*os.File`) 中,而不是写入内存缓冲区。
- 通过 `sync.Cond` 广播下载进度(已写入的字节数 `Offset`)。
- 下载成功后,将临时文件重命名为最终的缓存文件。
- **消费者 (`serveRangedRequest`)**:
- 当收到一个范围请求时,它会找到或等待对应的 `StreamObject`
- 为了安全地并发读取正在被生产者写入的文件,消费者会使用 `syscall.Dup()` **复制临时文件的文件描述符**
- 每个消费者都通过自己独立的、复制出来的文件句柄 (`*os.File`) 读取所需范围的数据,这避免了与生产者或其他消费者发生文件句柄状态的冲突。
- 消费者根据生产者的 `Offset` 进度和 `sync.Cond` 信号来等待其请求范围的数据变为可用。
### 3. 并发访问控制与对象生命周期管理
为了处理多个客户端同时请求同一个文件的情况,系统使用了 `sync.Mutex` 和一个 `map[string]*StreamObject`
- 当第一个对某文件的请求(我们称之为“消费者”)到达时,它会获得一个锁,并创建一个 `StreamObject` 来代表这个正在进行的下载任务然后启动一个“生产者”goroutine 来执行下载。
- 后续对同一文件的请求会发现 `StreamObject` 已存在于 map 中,它们不会再次启动下载,而是会共享这个对象。
**`StreamObject` 生命周期管理 (基于 GC)**
我们采用了一种简洁且高效的、依赖 Go 语言垃圾回收GC的模式来管理 `StreamObject` 的生命周期:
1. **生产者负责移除**: 下载 goroutine生产者在完成其任务无论成功或失败其唯一的职责就是将 `StreamObject` 从全局的 `map[string]*StreamObject` 中移除。
2. **消费者持有引用**: 与此同时,所有正在处理该文件请求的 HTTP Handler消费者仍然持有对该 `StreamObject` 的引用。
3. **GC 自动回收**: 因为消费者们还持有引用Go 的 GC 不会回收这个对象。只有当最后一个消费者处理完请求、其函数栈帧销毁后,对 `StreamObject` 的最后一个引用才会消失。此时GC 会在下一次运行时自动回收该对象的内存。
这个模式避免了复杂的引用计数或定时器,代码更简洁,并且从根本上解决了之前因固定延迟导致的性能问题。
### 4. 中间件 (Middleware)
项目使用了 Go 的标准 `http.Handler` 接口和中间件模式来构建请求处理链。
- `pkgs/middleware` 目录中定义了可重用的中间件,如 `httplog``recover`
- 这种模式使得在不修改核心业务逻辑的情况下,可以轻松地添加或删除日志、认证、错误处理等功能。