cline optimization on range request and memory usage
This commit is contained in:
@@ -1,24 +1,29 @@
|
||||
# 当前工作重点
|
||||
|
||||
当前的工作重点是根据 `progress.md` 中的待办事项,开始对项目进行优化和功能增强。在完成了全面的测试覆盖后,我们对现有代码的稳定性和正确性有了很强的信心,可以安全地进行重构。
|
||||
当前没有正在进行的紧急任务。项目处于稳定状态,可以根据 `progress.md` 中的待办事项列表来规划接下来的工作。
|
||||
|
||||
## 近期变更
|
||||
|
||||
- **完成 `server_test.go`**:
|
||||
- 补全了 `server_test.go` 中所有待办的测试用例,包括对 `X-Accel-Redirect` 和路径穿越攻击的测试。
|
||||
- 对所有测试用例的注释进行了审查和修正,确保注释与代码的实际行为保持一致。
|
||||
- 所有测试均已通过,为后续的开发和重构工作奠定了坚实的基础。
|
||||
- **更新 `progress.md`**:
|
||||
- 将“增加更全面的单元测试和集成测试”标记为已完成。
|
||||
- **实现流式请求的阶段二优化 (基于临时文件)**:
|
||||
- **问题**: `StreamObject` 使用内存中的 `bytes.Buffer` 来缓存下载内容,在处理大文件时会导致内存占用过高。
|
||||
- **解决方案**: 将 `StreamObject` 的 `Buffer` 替换为 `*os.File`,将下载的字节流直接写入临时文件。
|
||||
- **实现细节**:
|
||||
- `startOrJoinStream` (生产者) 现在创建临时文件并将下载内容写入其中。下载成功后,该临时文件会被重命名为最终的缓存文件。
|
||||
- `serveRangedRequest` (消费者) 为了解决文件句柄的生命周期竞态问题,采用了 `syscall.Dup()` 来复制生产者的文件描述符。每个消费者都使用自己独立的、复制出来的文件句柄来读取数据,从而与生产者的文件句柄生命周期解耦。
|
||||
- **结果**: 解决了大文件的内存占用问题,并通过了所有测试,包括一个专门为验证并发安全性和竞态条件而设计的新测试。
|
||||
|
||||
## 后续步骤
|
||||
|
||||
1. **代码重构**:
|
||||
- 根据 `progress.md` 的待办事项,首先考虑对 `server.go` 中的复杂函数(如 `streamOnline`)进行重构,以提高代码的可读性和可维护性。
|
||||
2. **功能增强**:
|
||||
- 在代码结构优化后,可以开始考虑实现新的功能,例如增加对 S3 等新存储后端的支持,或实现更复杂的负载均衡策略。
|
||||
3. **持续文档更新**:
|
||||
- 在进行重构或添加新功能时,同步更新 `systemPatterns.md` 和其他相关文档,以记录新的设计决策。
|
||||
下一个合乎逻辑的步骤是处理 `progress.md` 中列出的待办事项,例如:
|
||||
|
||||
1. **功能增强**:
|
||||
- **多后端支持**: 增加对 S3、Redis 等其他存储后端的支持。
|
||||
- **高级负载均衡**: 实现更复杂的负载均衡策略。
|
||||
- **监控**: 集成 Prometheus 指标。
|
||||
2. **代码重构**:
|
||||
- 对 `server.go` 中的复杂函数进行重构,提高可读性。
|
||||
|
||||
在开始新任务之前,需要与您确认下一个工作重点。
|
||||
|
||||
## 重要模式与偏好
|
||||
|
||||
|
@@ -29,11 +29,15 @@
|
||||
## 待办事项
|
||||
|
||||
- **功能增强**:
|
||||
- **实现流式范围请求 (Done)**: 重构了 Ranged Request 处理流程,实现了边下载边响应。
|
||||
- **阶段一 (已完成)**: 使用 `sync.Cond` 和内存 `bytes.Buffer` 实现。
|
||||
- **性能优化 (已完成)**: 移除了 `StreamObject` 清理逻辑中的固定延迟,改为依赖 Go 的 GC 机制,显著提升了并发请求下的测试性能。
|
||||
- **阶段二 (已完成)**: 使用临时文件和 `syscall.Dup()` 系统调用优化,解决了大文件内存占用问题。
|
||||
- 目前只支持本地文件存储,未来可以考虑增加对其他存储后端(如 S3、Redis)的支持。
|
||||
- 增加更复杂的负载均衡策略,而不仅仅是“选择最快”。
|
||||
- 增加更详细的监控和指标(如 Prometheus metrics)。
|
||||
- **代码优化**:
|
||||
- 对 `server.go` 中的一些复杂函数(如 `streamOnline`)进行重构,以提高可读性和可维护性。
|
||||
- 在完成流式请求功能后,对 `server.go` 中的一些复杂函数(如 `streamOnline`)进行重构,以提高可读性和可维护性。
|
||||
|
||||
## 已知问题
|
||||
|
||||
|
@@ -23,20 +23,36 @@
|
||||
|
||||
### 2. 生产者-消费者模式 (Producer-Consumer)
|
||||
|
||||
在文件下载过程中,使用了生产者-消费者模式。
|
||||
在文件下载和流式响应(Ranged Request)中,使用了生产者-消费者模式。
|
||||
|
||||
- **生产者**: `tryUpstream` 函数中的 goroutine 负责从上游服务器读取数据块(chunk),并将其放入一个 `chan Chunk` 中。
|
||||
- **消费者**: `streamOnline` 函数中的代码从 `chan Chunk` 中读取数据,并执行两个操作:
|
||||
1. 将数据写入 `bytes.Buffer`,供后续的请求者使用。
|
||||
2. 将数据写入本地临时文件,用于持久化缓存。
|
||||
- **生产者 (`startOrJoinStream`)**:
|
||||
- 为每个首次请求的文件启动一个 goroutine。
|
||||
- 负责从最快的上游服务器下载文件内容。
|
||||
- 将内容**直接写入一个临时文件** (`*os.File`) 中,而不是写入内存缓冲区。
|
||||
- 通过 `sync.Cond` 广播下载进度(已写入的字节数 `Offset`)。
|
||||
- 下载成功后,将临时文件重命名为最终的缓存文件。
|
||||
- **消费者 (`serveRangedRequest`)**:
|
||||
- 当收到一个范围请求时,它会找到或等待对应的 `StreamObject`。
|
||||
- 为了安全地并发读取正在被生产者写入的文件,消费者会使用 `syscall.Dup()` **复制临时文件的文件描述符**。
|
||||
- 每个消费者都通过自己独立的、复制出来的文件句柄 (`*os.File`) 读取所需范围的数据,这避免了与生产者或其他消费者发生文件句柄状态的冲突。
|
||||
- 消费者根据生产者的 `Offset` 进度和 `sync.Cond` 信号来等待其请求范围的数据变为可用。
|
||||
|
||||
### 3. 并发访问控制 (Mutex for Concurrent Access)
|
||||
### 3. 并发访问控制与对象生命周期管理
|
||||
|
||||
为了处理多个客户端同时请求同一个文件的情况,系统使用了 `sync.Mutex` 和一个 `map[string]*StreamObject`。
|
||||
|
||||
- 当第一个请求到达时,它会获得一个锁,并创建一个 `StreamObject` 来代表这个正在进行的下载任务。
|
||||
- 后续对同一文件的请求会发现 `StreamObject` 已存在,它们不会再次向上游发起请求,而是会等待并从这个共享的 `StreamObject` 中读取数据。
|
||||
- 下载完成后,`StreamObject` 会从 map 中移除。
|
||||
- 当第一个对某文件的请求(我们称之为“消费者”)到达时,它会获得一个锁,并创建一个 `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)
|
||||
|
||||
|
Reference in New Issue
Block a user