docs(plan): 添加 Phase 9 规划 - HTTP/3 与性能优化扩展

- 规划 HTTP/3 (QUIC) 支持,使用 quic-go 库
- 规划一致性哈希负载均衡算法
- 规划 gzip_static 预压缩支持
- 规划滑动窗口限流算法
- 更新 update-prompts.md 添加新分析任务

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-03 15:02:40 +08:00
parent a1aa05406c
commit 5e19d1a5ee
2 changed files with 617 additions and 10 deletions

View File

@ -1850,18 +1850,615 @@ Phase 8:
---
## 第九阶段HTTP/3 支持与性能优化扩展
### 目标
实现 HTTP/3 (QUIC) 协议支持,扩展负载均衡算法和限流机制,提升项目协议完整性和性能优化能力。
### 背景分析
通过与 nginx 功能对比分析,发现以下功能缺失:
| 功能 | 当前状态 | 优先级 | 说明 |
|------|----------|--------|------|
| HTTP/3 (QUIC) | ❌ 未实现 | P0 | 新一代 HTTP 协议,性能提升 20-30% |
| 一致性哈希负载均衡 | ❌ 未实现 | P1 | 缓存代理场景关键功能 |
| gzip_static 预压缩 | ❌ 未实现 | P2 | 静态资源优化,减少 CPU 开销 |
| 滑动窗口限流 | ❌ 未实现 | P2 | 更精确的限流算法 |
### 技术选型
**HTTP/3 库**:使用 [quic-go](https://github.com/quic-go/quic-go)。
**选择理由**
- **官方支持**Go 生态系统中最成熟的 QUIC 实现
- **HTTP/3 完整实现**:支持 http3.Server与标准库接口兼容
- **活跃维护**:定期更新,跟进 IETF QUIC 规范
- **性能优秀**0-RTT、连接迁移等特性支持完善
**版本要求**`github.com/quic-go/quic-go v0.48.2`(支持 Go 1.21+
**关键挑战**
| 挑战 | 说明 | 解决方案 |
|------|------|----------|
| 接口不兼容 | fasthttp 与 quic-go 接口不同 | 编写适配层转换请求格式 |
| Handler 复用 | 需让 HTTP/3 使用现有 handler 链 | adapter.go 将 http.Handler 转换为 fasthttp.RequestCtx |
| TLS 配置共享 | QUIC 内置 TLS需复用现有证书 | 从 ssl 模块获取 tls.Config |
### 任务列表
#### 9.1 HTTP/3 服务器核心 (P0)
**实现**
```go
// internal/http3/server.go
import (
"github.com/quic-go/quic-go"
"github.com/quic-go/quic-go/http3"
)
// Server HTTP/3 服务器。
type Server struct {
config *config.HTTP3Config
http3Server *http3.Server
handler fasthttp.RequestHandler
adapter *Adapter
}
// Start 启动 HTTP/3 服务器。
func (s *Server) Start() error {
// 1. 创建 UDP 监听器
listener, err := quic.ListenAddrEarly(s.config.Listen, s.tlsConfig, s.quicConfig)
if err != nil {
return fmt.Errorf("failed to listen QUIC: %w", err)
}
// 2. 创建 HTTP/3 服务器
s.http3Server = &http3.Server{
Handler: s.adapter.Wrap(s.handler),
}
return s.http3Server.Serve(listener)
}
// Stop 停止 HTTP/3 服务器。
func (s *Server) Stop() error {
if s.http3Server != nil {
return s.http3Server.Close()
}
return nil
}
```
**实现要点**
- 使用 `quic.ListenAddrEarly` 支持 0-RTT
- 复用 `internal/ssl` 模块的 TLS 配置
- 配置 `Alt-Svc` 响应头告知客户端可用 HTTP/3
- 支持优雅关闭
#### 9.2 HTTP/3 请求适配层 (P0)
**实现**
```go
// internal/http3/adapter.go
// Adapter 将 fasthttp.RequestHandler 适配为 http.Handler。
type Adapter struct{}
// Wrap 包装 fasthttp handler 为 http.Handler。
func (a *Adapter) Wrap(handler fasthttp.RequestHandler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 1. 创建 fasthttp.RequestCtx
ctx := &fasthttp.RequestCtx{}
// 2. 转换请求
a.convertRequest(r, ctx)
// 3. 调用 fasthttp handler
handler(ctx)
// 4. 转换响应
a.convertResponse(ctx, w)
})
}
// convertRequest 将 net/http.Request 转换为 fasthttp.RequestCtx。
func (a *Adapter) convertRequest(r *http.Request, ctx *fasthttp.RequestCtx) {
// 方法
ctx.Request.Header.SetMethod(r.Method)
// URI
ctx.Request.SetRequestURI(r.URL.String())
// 头部
for k, v := range r.Header {
for _, vv := range v {
ctx.Request.Header.Add(k, vv)
}
}
// 请求体
if r.Body != nil {
body, _ := io.ReadAll(r.Body)
ctx.Request.SetBody(body)
}
// 远程地址
ctx.SetRemoteAddr(r.RemoteAddr)
}
// convertResponse 将 fasthttp.RequestCtx 响应写入 http.ResponseWriter。
func (a *Adapter) convertResponse(ctx *fasthttp.RequestCtx, w http.ResponseWriter) {
// 状态码
w.WriteHeader(ctx.Response.StatusCode())
// 头部
ctx.Response.Header.VisitAll(func(k, v []byte) {
w.Header().Add(string(k), string(v))
})
// 响应体
w.Write(ctx.Response.Body())
}
```
**性能优化**
- 使用 sync.Pool 复用 RequestCtx 对象
- 避免不必要的内存分配
- 流式处理大请求体
#### 9.3 一致性哈希负载均衡 (P1)
**实现**
```go
// internal/loadbalance/consistent_hash.go
// ConsistentHash 一致性哈希负载均衡器。
type ConsistentHash struct {
// virtualNodes 虚拟节点数,默认 150
virtualNodes int
// circle 哈希环key 为哈希值value 为目标
circle map[uint64]*Target
// sortedHashes 排序后的哈希值列表,用于二分查找
sortedHashes []uint64
// hashKey 哈希键来源
hashKey string // "ip", "uri", "header:xxx"
// mu 读写锁
mu sync.RWMutex
}
// Select 根据哈希键选择目标。
func (c *ConsistentHash) Select(targets []*Target) *Target {
return c.SelectByKey(targets, "")
}
// SelectByKey 根据指定键选择目标。
func (c *ConsistentHash) SelectByKey(targets []*Target, key string) *Target {
c.mu.RLock()
defer c.mu.RUnlock()
// 1. 计算键的哈希值
hash := c.hashKey(key)
// 2. 二分查找最近的节点
idx := sort.Search(len(c.sortedHashes), func(i int) bool {
return c.sortedHashes[i] >= hash
})
// 3. 环形回绕
if idx >= len(c.sortedHashes) {
idx = 0
}
return c.circle[c.sortedHashes[idx]]
}
// AddTarget 添加目标到哈希环。
func (c *ConsistentHash) AddTarget(target *Target) {
c.mu.Lock()
defer c.mu.Unlock()
for i := 0; i < c.virtualNodes; i++ {
key := fmt.Sprintf("%s#%d", target.URL, i)
hash := c.hashKey(key)
c.circle[hash] = target
c.sortedHashes = append(c.sortedHashes, hash)
}
sort.Slice(c.sortedHashes, func(i, j int) bool {
return c.sortedHashes[i] < c.sortedHashes[j]
})
}
// hashKey 计算哈希值(使用 FNV-64a
func (c *ConsistentHash) hashKey(key string) uint64 {
h := fnv.New64a()
h.Write([]byte(key))
return h.Sum64()
}
```
**配置示例**
```yaml
proxy:
- path: /api
targets:
- url: http://backend1:8080
- url: http://backend2:8080
- url: http://backend3:8080
load_balance: consistent_hash
hash_key: ip # ip / uri / header:X-User-ID
virtual_nodes: 150
```
#### 9.4 gzip_static 预压缩支持 (P2)
**实现**
```go
// internal/middleware/compression/gzip_static.go
// GzipStatic 预压缩文件支持。
type GzipStatic struct {
// enabled 是否启用
enabled bool
// root 静态文件根目录
root string
// extensions 支持的扩展名
extensions []string // 默认 [".html", ".css", ".js", ".json", ".xml"]
}
// ServeFile 发送预压缩文件(如果存在)。
func (g *GzipStatic) ServeFile(ctx *fasthttp.RequestCtx, filePath string) bool {
if !g.enabled {
return false
}
// 1. 检查客户端是否支持 gzip
if !bytes.Contains(ctx.Request.Header.Peek("Accept-Encoding"), []byte("gzip")) {
return false
}
// 2. 检查扩展名
if !g.matchExtension(filePath) {
return false
}
// 3. 检查预压缩文件是否存在
gzPath := filePath + ".gz"
if _, err := os.Stat(filepath.Join(g.root, gzPath)); err != nil {
return false
}
// 4. 发送预压缩文件
ctx.Response.Header.Set("Content-Encoding", "gzip")
ctx.Response.Header.Set("Vary", "Accept-Encoding")
fasthttp.ServeFile(ctx, filepath.Join(g.root, gzPath))
return true
}
```
**配置示例**
```yaml
compression:
gzip_static: on # on / off
gzip_static_extensions: [".html", ".css", ".js", ".json", ".xml", ".svg"]
```
**与现有压缩中间件集成**
```go
// compression.go 中添加预压缩检查
func (m *CompressionMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
// 1. 尝试发送预压缩文件
if m.gzipStatic.ServeFile(ctx, string(ctx.Path())) {
return
}
// 2. 回退到实时压缩
// ... 现有逻辑
}
}
```
#### 9.5 滑动窗口限流算法 (P2)
**实现**
```go
// internal/middleware/security/sliding_window.go
// SlidingWindowLimiter 滑动窗口限流器。
type SlidingWindowLimiter struct {
// window 窗口大小
window time.Duration
// limit 窗口内最大请求数
limit int
// precise 是否使用精确模式
precise bool
// counters 窗口计数器key 为窗口起始时间
counters map[int64]*windowCounter
// mu 读写锁
mu sync.RWMutex
}
// windowCounter 窗口计数器。
type windowCounter struct {
count int64
// precise 模式下记录每个请求时间
timestamps []time.Time
}
// Allow 检查是否允许请求。
func (s *SlidingWindowLimiter) Allow(key string) bool {
if s.precise {
return s.allowPrecise(key)
}
return s.allowApproximate(key)
}
// allowApproximate 近似滑动窗口(推荐,内存 O(1))。
func (s *SlidingWindowLimiter) allowApproximate(key string) bool {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
currentWindow := now.UnixNano() / int64(s.window)
prevWindow := currentWindow - 1
// 获取当前窗口计数
current, ok := s.counters[currentWindow]
if !ok {
current = &windowCounter{}
s.counters[currentWindow] = current
}
// 获取上一个窗口计数
prev, ok := s.counters[prevWindow]
// 计算滑动窗口内的请求数
// 公式:当前窗口计数 × 1.0 + 上一窗口计数 × (1 - 当前窗口已过比例)
var count int64
if ok {
elapsed := float64(now.UnixNano()%int64(s.window)) / float64(s.window)
count = current.count + int64(float64(prev.count)*(1-elapsed))
} else {
count = current.count
}
if count >= int64(s.limit) {
return false
}
current.count++
return true
}
// allowPrecise 精确滑动窗口(内存 O(n),精确限流)。
func (s *SlidingWindowLimiter) allowPrecise(key string) bool {
s.mu.Lock()
defer s.mu.Unlock()
now := time.Now()
windowStart := now.Add(-s.window)
// 清理过期的时间戳
valid := make([]time.Time, 0, len(s.timestamps))
for _, t := range s.timestamps {
if t.After(windowStart) {
valid = append(valid, t)
}
}
s.timestamps = valid
// 检查是否超过限制
if len(s.timestamps) >= s.limit {
return false
}
s.timestamps = append(s.timestamps, now)
return true
}
```
**配置示例**
```yaml
security:
rate_limit:
request_rate: 100
burst: 20
algorithm: sliding_window # token_bucket / sliding_window
sliding_window_mode: approximate # approximate / precise
```
#### 9.6 配置扩展
**新增配置结构**
```go
// internal/config/config.go
// HTTP3Config HTTP/3 配置。
type HTTP3Config struct {
Enabled bool `yaml:"enabled"` // 是否启用 HTTP/3
Listen string `yaml:"listen"` // UDP 监听地址,如 ":443"
MaxStreams int `yaml:"max_streams"` // 最大并发流
IdleTimeout time.Duration `yaml:"idle_timeout"` // 空闲超时
Enable0RTT bool `yaml:"enable_0rtt"` // 启用 0-RTT
}
// LoadBalanceConfig 负载均衡扩展配置。
type LoadBalanceConfig struct {
HashKey string `yaml:"hash_key"` // 一致性哈希键ip / uri / header:xxx
VirtualNodes int `yaml:"virtual_nodes"` // 虚拟节点数,默认 150
}
// CompressionConfig 扩展。
type CompressionConfig struct {
// ... 现有字段
GzipStatic bool `yaml:"gzip_static"` // 启用预压缩
GzipStaticExtensions []string `yaml:"gzip_static_extensions"` // 预压缩扩展名
}
// RateLimitConfig 扩展。
type RateLimitConfig struct {
// ... 现有字段
Algorithm string `yaml:"algorithm"` // token_bucket / sliding_window
SlidingWindowMode string `yaml:"sliding_window_mode"` // approximate / precise
}
```
#### 9.7 app.go 集成
**修改文件**`internal/app/app.go`
**修改内容**
```go
// Run 启动应用(修改后)。
func (a *App) Run() error {
// 1. 启动 TCP Server (HTTP/1.1/2)
go func() {
if err := a.srv.Start(); err != nil {
logging.Error().Msgf("HTTP server error: %v", err)
}
}()
// 2. 如果启用 HTTP/3启动 UDP Server
if a.config.HTTP3.Enabled {
go func() {
if err := a.http3Server.Start(); err != nil {
logging.Error().Msgf("HTTP/3 server error: %v", err)
}
}()
}
// 3. 如果配置了 Stream启动 Stream Server
// ... 现有逻辑
return nil
}
// Shutdown 关闭应用(修改后)。
func (a *App) Shutdown() error {
var errs []error
// 关闭 HTTP/3 Server
if a.http3Server != nil {
if err := a.http3Server.Stop(); err != nil {
errs = append(errs, err)
}
}
// 关闭 TCP Server
if err := a.srv.Stop(); err != nil {
errs = append(errs, err)
}
// ... 其他关闭逻辑
if len(errs) > 0 {
return fmt.Errorf("shutdown errors: %v", errs)
}
return nil
}
```
### 验证方法
```bash
# 1. HTTP/3 测试
# 启动服务器(配置 http3.enabled: true
./lolly -c config.yaml
# 使用 curl 测试 HTTP/3
curl --http3 https://localhost:8443/
# 检查 Alt-Svc 头
curl -I https://localhost:8443/
# 应返回 Alt-Svc: h3=":443"
# 2. 一致性哈希测试
# 配置 load_balance: consistent_hash
for i in {1..10}; do
curl http://localhost:8080/api/test
done
# 相同 IP 的请求应路由到同一后端
# 3. gzip_static 测试
# 创建预压缩文件
gzip -k static/index.html
# 测试请求
curl -H "Accept-Encoding: gzip" -I http://localhost:8080/index.html
# 应返回 Content-Encoding: gzip且响应时间更快
# 4. 滑动窗口限流测试
# 配置 algorithm: sliding_window
for i in {1..120}; do
curl http://localhost:8080/
done
# 应有部分请求返回 429
# 5. 完整测试
go test ./... -race
go build ./...
```
### 文件依赖关系图
```
Phase 9:
internal/http3/server.go → internal/http3/adapter.go新增
internal/http3/server.go → internal/ssl/ssl.goTLS 配置复用)
internal/app/app.go → internal/http3/server.go集成
internal/loadbalance/balancer.go → internal/loadbalance/consistent_hash.go新增
internal/middleware/compression/compression.go → internal/middleware/compression/gzip_static.go新增
internal/middleware/security/ratelimit.go → internal/middleware/security/sliding_window.go新增
internal/config/config.go → 扩展配置结构
```
---
## 总体进度追踪(更新)
| 阶段 | 状态 | 主要功能 |
| ------- | ------ | ------------------------- |
| Phase 1 | ✅ 完成 | 项目骨架、配置系统 |
| Phase 2 | ✅ 完成 | HTTP 核心、静态文件、路由 |
| Phase 3 | ✅ 完成 | 反向代理、负载均衡 |
| Phase 4 | ✅ 完成 | SSL/TLS、安全控制 |
| Phase 5 | ✅ 完成 | 重写、压缩、缓存、日志 |
| Phase 6 | ✅ 完成 | Stream、性能优化、热升级 |
| Phase 7 | ✅ 完成 | 功能完善 |
| Phase 8 | ✅ 完成 | 问题修复WebSocket集成、UDP清理、热升级修复|
| 阶段 | 状态 | 主要功能 |
| ------- | -------- | ------------------------- |
| Phase 1 | ✅ 完成 | 项目骨架、配置系统 |
| Phase 2 | ✅ 完成 | HTTP 核心、静态文件、路由 |
| Phase 3 | ✅ 完成 | 反向代理、负载均衡 |
| Phase 4 | ✅ 完成 | SSL/TLS、安全控制 |
| Phase 5 | ✅ 完成 | 重写、压缩、缓存、日志 |
| Phase 6 | ✅ 完成 | Stream、性能优化、热升级 |
| Phase 7 | ✅ 完成 | 功能完善 |
| Phase 8 | ✅ 完成 | 问题修复WebSocket集成、UDP清理、热升级修复|
| Phase 9 | 🔄 计划中| HTTP/3、一致性哈希、gzip_static、滑动窗口限流 |
**Phase 8 完成日期**2026-04-03
@ -1869,3 +2466,9 @@ Phase 8:
1. WebSocket 代理集成 - `handleWebSocket` 现调用 `ProxyWebSocket`
2. UDP Stream 冗余代码 - 删除 `udpListener` 类型及相关测试
3. 热升级监听器继承 - 改用 `net.Listen` + `Serve` 模式,支持监听器继承
**Phase 9 计划内容**
1. HTTP/3 (QUIC) 支持 - 使用 quic-go双栈并行架构
2. 一致性哈希负载均衡 - 支持可配置哈希键ip/uri/header
3. gzip_static 预压缩 - 静态资源优化
4. 滑动窗口限流 - 近似和精确两种模式

View File

@ -7,10 +7,14 @@
/ultrawork 深度分析 https://nginx.org/en/docs/ nginx 的功能,@docs/ 目录下已经有一些分析过的文档了,看看有没有能完善的
/ultrawork 深度分析下当前的配置文件,是否有配置文件描述了,但代码未实现的功能
## 单元测试
/ultrawork 分析下当前测试覆盖率
/plan 分析一个完善测试的方案
## 注释
/ultrawork 参考 @docs/comments.md分析项目注释是否完善