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

4.2 KiB
Raw Blame History

系统架构与设计模式

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 目录中定义了可重用的中间件,如 httplogrecover
  • 这种模式使得在不修改核心业务逻辑的情况下,可以轻松地添加或删除日志、认证、错误处理等功能。