- Collect baseline benchmark summary across all core modules - Save key results to benchmarks/v0.4.0/summary.txt - Update .gitignore to track benchmark summaries/reports - Include performance optimization design docs and plan
21 KiB
Lolly 代码冗余优化实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 系统性消除 Lolly 代码库中的冗余代码,包括死代码、重复实现、过度工程化和测试重复,提升可维护性和代码质量。
Architecture: 采用分阶段、增量式重构策略。每阶段独立可交付,确保随时可回滚。优先处理死代码(零风险、高回报),然后处理重复实现(低风险、中回报),最后处理架构级重复(中风险、长期收益)。
Tech Stack: Go 1.24, fasthttp, staticcheck, go vet
文件结构映射
删除/清理的文件
internal/middleware/limitrate/limitrate.go— 死代码包主文件internal/middleware/limitrate/writer.go— 死代码包辅助文件internal/middleware/limitrate/limitrate_test.go— 死代码包测试文件internal/stream/ssl.go— 死代码(所有字段未使用)internal/stream/ssl_test.go— 死代码测试文件internal/variable/pool.go— 死代码(所有字段未使用)internal/proxy/proxy_coverage_extra_test.go中的TestExtractHostFromURL— 被测函数即将删除
修改的文件(按模块分组)
Phase 1 - 死代码清理:
internal/mimeutil/detect.go:154— 添加 defaultMIME 回退逻辑internal/app/app_test.go:448— 删除未使用的customSiginternal/app/testutil.go:17— 删除未使用的setupTestLoggerinternal/http3/server_test.go:138— 删除未使用的generateTestCertificateinternal/proxy/proxy_dns_test.go:91— 删除未使用的方法internal/server/testutil.go:15— 删除未使用的常量internal/server/upgrade_test.go:291— 删除未使用的containsStringinternal/server/pool_bench_test.go:305— 删除未使用的id字段internal/stream/stream_test.go:24— 删除未使用的generateTestCertificate
Phase 2 - 重复实现消除:
internal/proxy/proxy.go:362,1003-1018— 删除extractHostFromURL,改用netutil.ParseTargetURLinternal/proxy/header_modifier.go:33— 改用netutil.ParseTargetURLinternal/handler/static.go:628,832-836— 删除generateETag包装,直接调用utils.GenerateETaginternal/cache/file_cache.go:47,181— 删除generateETag包装,直接调用utils.GenerateETaginternal/utils/httperror.go:67-86— 简化CheckIPAccess,复用IPInAllowList
Phase 3 - 路由和服务器逻辑简化:
internal/server/router.go:118-145,217-234,402-423— 消除冗余 switch 块internal/server/server.go:454-868— 提取三种启动模式的公共函数
Phase 4 - 负载均衡统一(可选):
internal/stream/stream.go:61-285— 复用internal/loadbalance的算法实现
任务分解
Phase 1: 死代码清理(P0)
Task 1.1: 删除 limitrate 死代码包
Files:
-
Delete:
internal/middleware/limitrate/limitrate.go -
Delete:
internal/middleware/limitrate/writer.go -
Delete:
internal/middleware/limitrate/limitrate_test.go -
Step 1: 确认包未被引用
grep -r "limitrate" --include="*.go" /home/xfy/Developer/lolly/internal/
Expected: 仅返回 internal/middleware/limitrate/ 目录内的匹配,无外部引用。
- Step 2: 删除整个目录
rm -rf /home/xfy/Developer/lolly/internal/middleware/limitrate/
- Step 3: 验证编译通过
cd /home/xfy/Developer/lolly && go build ./...
Expected: 无错误,编译成功。
- Step 4: 运行受影响包的测试
cd /home/xfy/Developer/lolly && go test ./internal/middleware/...
Expected: 全部通过。
- Step 5: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: remove dead code package internal/middleware/limitrate"
Task 1.2: 删除 stream/ssl.go 死代码
Files:
-
Delete:
internal/stream/ssl.go -
Delete:
internal/stream/ssl_test.go -
Step 1: 确认 ssl.go 字段未被使用
grep -r "SSLManager\|ProxySSLManager" --include="*.go" /home/xfy/Developer/lolly/internal/
Expected: 仅 internal/stream/ssl.go 自身有定义,无其他引用。
- Step 2: 删除文件
rm /home/xfy/Developer/lolly/internal/stream/ssl.go
rm /home/xfy/Developer/lolly/internal/stream/ssl_test.go
- Step 3: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/stream/... && go test ./internal/stream/...
Expected: 编译和测试全部通过。
- Step 4: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: remove unused stream SSL dead code"
Task 1.3: 删除 variable/pool.go 死代码
Files:
-
Delete:
internal/variable/pool.go -
Step 1: 确认 pool.go 变量未被使用
grep -r "PoolStats\|gets\.\|puts\.\|newCount\.\|active\." --include="*.go" /home/xfy/Developer/lolly/internal/
Expected: 无引用(除 pool.go 自身定义外)。
- Step 2: 删除文件
rm /home/xfy/Developer/lolly/internal/variable/pool.go
- Step 3: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/variable/... && go test ./internal/variable/...
Expected: 编译和测试全部通过。
- Step 4: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: remove unused variable pool statistics dead code"
Task 1.4: 修复 mimeutil defaultMIME 未使用问题
Files:
-
Modify:
internal/mimeutil/detect.go:154 -
Step 1: 阅读当前 DetectContentType 实现
Read: internal/mimeutil/detect.go:95-155
当前实现:当 mime.TypeByExtension 返回空字符串时,直接缓存并返回空字符串,从未使用 defaultMIME。
- Step 2: 在 DetectContentType 末尾添加 defaultMIME 回退
// 在 internal/mimeutil/detect.go 第 154 行(return mimeType 之前)添加:
if mimeType == "" {
defaultMutex.RLock()
mimeType = defaultMIME
defaultMutex.RUnlock()
}
return mimeType
完整修改后的第 149-158 行应为:
// 插入新条目
entry := &mimeCacheEntry{ext: ext, mimeType: mimeType}
entry.element = mimeLRU.PushFront(entry)
mimeCache[ext] = entry
if mimeType == "" {
defaultMutex.RLock()
mimeType = defaultMIME
defaultMutex.RUnlock()
}
return mimeType
- Step 3: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/mimeutil/... && go test ./internal/mimeutil/...
Expected: 编译和测试全部通过。
- Step 4: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "fix: use defaultMIME fallback in DetectContentType"
Task 1.5: 清理其他静态检查发现的死代码
Files:
-
Modify:
internal/app/app_test.go— 删除未使用的customSig -
Modify:
internal/app/testutil.go— 删除未使用的setupTestLogger -
Modify:
internal/http3/server_test.go— 删除未使用的generateTestCertificate -
Modify:
internal/proxy/proxy_dns_test.go— 删除未使用的方法 -
Modify:
internal/server/testutil.go— 删除未使用的testListenAddr -
Modify:
internal/server/upgrade_test.go— 删除未使用的containsString -
Modify:
internal/server/pool_bench_test.go— 删除未使用的id字段 -
Modify:
internal/stream/stream_test.go— 删除未使用的generateTestCertificate -
Step 1: 运行 staticcheck 获取精确行号
cd /home/xfy/Developer/lolly && staticcheck ./... 2>&1 | grep "U1000"
Expected: 输出每个死代码的精确文件路径和行号。
- Step 2: 逐个删除死代码
对每个 staticcheck 报告的死代码:
- 打开文件
- 定位到报告的函数/变量/字段
- 删除整个未使用的声明
- 保存文件
示例(以 internal/server/testutil.go 为例):
// 删除前:
const testListenAddr = "127.0.0.1:0"
// 删除后:
// (整行删除)
- Step 3: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./... && go test ./internal/app/... ./internal/http3/... ./internal/proxy/... ./internal/server/... ./internal/stream/...
Expected: 全部通过。
- Step 4: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: remove unused code identified by staticcheck"
Phase 2: 重复实现消除(P1)
Task 2.1: 删除 proxy.go 中的 extractHostFromURL,统一使用 netutil
Files:
-
Modify:
internal/proxy/proxy.go:362— 替换调用 -
Modify:
internal/proxy/proxy.go:993-1018— 删除函数 -
Modify:
internal/proxy/header_modifier.go:33— 替换调用 -
Modify:
internal/proxy/proxy_coverage_extra_test.go— 删除测试 -
Step 1: 修改 proxy.go:362 的调用
Read: internal/proxy/proxy.go:360-365
将:
tlsCfg, err := CreateTLSConfig(sslCfg, extractHostFromURL(targetURL))
改为:
host, _, _, err := netutil.ParseTargetURL(targetURL, false)
if err != nil {
return nil, fmt.Errorf("parse target URL %q: %w", targetURL, err)
}
tlsCfg, err := CreateTLSConfig(sslCfg, host)
并确保文件已导入 rua.plus/lolly/internal/netutil。
- Step 2: 修改 header_modifier.go:33 的调用
Read: internal/proxy/header_modifier.go:30-36
将:
targetHost := extractHostFromURL(target.URL)
改为:
targetHost, _, _, err := netutil.ParseTargetURL(target.URL, false)
if err != nil {
targetHost = target.URL
}
并确保文件已导入 rua.plus/lolly/internal/netutil。
- Step 3: 删除 proxy.go 中的 extractHostFromURL 函数
删除 internal/proxy/proxy.go 第 993-1018 行的整个函数:
// extractHostFromURL 从 URL 字符串中提取 host:port 部分。
// ...
func extractHostFromURL(urlStr string) string {
// ...
}
- Step 4: 删除 proxy_coverage_extra_test.go 中的 TestExtractHostFromURL
Read: internal/proxy/proxy_coverage_extra_test.go:1426-1480
删除整个 TestExtractHostFromURL 函数及其相关测试用例。
- Step 5: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/proxy/... && go test ./internal/proxy/...
Expected: 编译和测试全部通过。
- Step 6: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: remove extractHostFromURL, use netutil.ParseTargetURL"
Task 2.2: 删除 generateETag 包装函数
Files:
-
Modify:
internal/handler/static.go:628,832-836 -
Modify:
internal/cache/file_cache.go:45-49,181 -
Step 1: 修改 handler/static.go
Read: internal/handler/static.go:626-630
将:
etag := generateETag(info.ModTime(), info.Size())
改为:
etag := utils.GenerateETag(info.ModTime(), info.Size())
删除 internal/handler/static.go 第 832-836 行的 generateETag 函数。
- Step 2: 修改 cache/file_cache.go
Read: internal/cache/file_cache.go:179-183
将:
etag := generateETag(modTime, size)
改为:
etag := utils.GenerateETag(modTime, size)
删除 internal/cache/file_cache.go 第 45-49 行的 generateETag 函数。
- Step 3: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/handler/... ./internal/cache/... && go test ./internal/handler/... ./internal/cache/...
Expected: 编译和测试全部通过。
- Step 4: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: remove redundant generateETag wrappers, use utils.GenerateETag directly"
Task 2.3: 简化 CheckIPAccess 复用 IPInAllowList
Files:
-
Modify:
internal/utils/httperror.go:67-86 -
Step 1: 重构 CheckIPAccess
Read: internal/utils/httperror.go:67-86
将:
func CheckIPAccess(ctx *fasthttp.RequestCtx, allowed []net.IPNet) bool {
if len(allowed) == 0 {
return true
}
clientIP := netutil.ExtractClientIPNet(ctx)
if clientIP == nil {
return false
}
for _, network := range allowed {
if network.Contains(clientIP) {
return true
}
}
return false
}
改为:
func CheckIPAccess(ctx *fasthttp.RequestCtx, allowed []net.IPNet) bool {
if len(allowed) == 0 {
return true
}
clientIP := netutil.ExtractClientIPNet(ctx)
if clientIP == nil {
return false
}
return IPInAllowList(clientIP, allowed)
}
- Step 2: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/utils/... && go test ./internal/utils/...
Expected: 编译和测试全部通过。
- Step 3: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: simplify CheckIPAccess by reusing IPInAllowList"
Phase 3: 路由和服务器逻辑简化(P1-P2)
Task 3.1: 简化 router.go 中的冗余 switch 块
Files:
-
Modify:
internal/server/router.go:118-145(registerProxyRoutesWithLocationEngine) -
Modify:
internal/server/router.go:217-234(registerStaticHandlersWithLocationEngine) -
Modify:
internal/server/router.go:402-423(registerLuaRoutesWithLocationEngine) -
Step 1: 简化 registerProxyRoutesWithLocationEngine
Read: internal/server/router.go:108-148
将第 118-145 行的 switch 块替换为:
for i := range serverCfg.Proxy {
proxyCfg := &serverCfg.Proxy[i]
p := s.createProxyForConfig(proxyCfg)
if p == nil {
continue
}
locType := proxyCfg.LocationType
if locType == "" {
locType = matcher.LocationTypePrefix
}
path := proxyCfg.Path
if locType == matcher.LocationTypeNamed && proxyCfg.LocationName != "" {
path = "@" + proxyCfg.LocationName
}
if err := s.registerRoute(locType, path, p.ServeHTTP, proxyCfg.Internal, "proxy"); err != nil {
return err
}
}
return nil
- Step 2: 简化 registerStaticHandlersWithLocationEngine
Read: internal/server/router.go:208-236
将第 217-234 行的 switch 块替换为类似逻辑(直接调用 s.registerRoute)。
- Step 3: 简化 registerLuaRoutesWithLocationEngine
Read: internal/server/router.go:393-425
将第 402-423 行的 switch 块替换为类似逻辑(直接调用 s.registerRoute)。
- Step 4: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/server/... && go test ./internal/server/...
Expected: 编译和测试全部通过。
- Step 5: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: eliminate redundant switch blocks in router.go LocationEngine functions"
Task 3.2: 提取 server.go 三种启动模式的公共函数
Files:
- Modify:
internal/server/server.go:454-868
新增辅助函数(添加到 server.go 末尾,在 SetResolver 之前):
- Step 1: 提取
registerMonitoringEndpoints函数
在 internal/server/server.go 中新增:
// registerMonitoringEndpoints 注册状态监控、性能分析和缓存清理端点。
// isDefault 为 true 时注册所有端点,否则跳过(用于多服务器模式)。
func (s *Server) registerMonitoringEndpoints(router *handler.Router, serverCfg *config.ServerConfig, isDefault bool) {
// 状态监控端点
if isDefault && s.config.Monitoring.Status.Enabled {
statusHandler, err := NewStatusHandler(s, &s.config.Monitoring.Status)
if err != nil {
logging.Error().Msg("Failed to create status handler: " + err.Error())
} else {
router.GET(statusHandler.Path(), statusHandler.ServeHTTP)
}
}
// pprof 性能分析端点
if isDefault && s.config.Monitoring.Pprof.Enabled {
pprofHandler, err := NewPprofHandler(&s.config.Monitoring.Pprof)
if err != nil {
logging.Error().Msg("Failed to create pprof handler: " + err.Error())
} else {
router.GET(pprofHandler.Path(), pprofHandler.ServeHTTP)
router.GET(pprofHandler.Path()+"/{profile:*}", pprofHandler.ServeHTTP)
}
}
// 缓存清理 API
if isDefault && serverCfg.CacheAPI != nil && serverCfg.CacheAPI.Enabled {
purgeHandler, err := NewPurgeHandler(s, serverCfg.CacheAPI)
if err != nil {
logging.Error().Msg("Failed to create cache purge handler: " + err.Error())
} else {
router.POST(purgeHandler.Path(), purgeHandler.ServeHTTP)
}
}
}
- Step 2: 提取
wrapHandler函数
// wrapHandler 应用中间件链、连接池包装和统计追踪。
func (s *Server) wrapHandler(base fasthttp.RequestHandler, serverCfg *config.ServerConfig) (fasthttp.RequestHandler, error) {
chain, err := s.buildMiddlewareChain(serverCfg)
if err != nil {
return nil, err
}
handler := chain.Apply(base)
if s.pool != nil {
handler = s.pool.WrapHandler(handler)
}
handler = s.trackStats(handler)
return handler, nil
}
- Step 3: 提取
startServer函数
// startServer 创建监听器并启动 fasthttp.Server,支持可选 TLS。
func (s *Server) startServer(serverCfg *config.ServerConfig, fastSrv *fasthttp.Server) error {
ln, err := s.createListener(serverCfg)
if err != nil {
return fmt.Errorf("failed to listen: %w", err)
}
s.listeners = append(s.listeners, ln)
// 检查 SSL/TLS
if serverCfg.SSL.Cert != "" && serverCfg.SSL.Key != "" {
tlsManager, err := ssl.NewTLSManager(&serverCfg.SSL)
if err != nil {
return fmt.Errorf("failed to create TLS manager: %w", err)
}
fastSrv.TLSConfig = tlsManager.GetTLSConfig()
return fastSrv.ServeTLS(ln, "", "")
}
return fastSrv.Serve(ln)
}
- Step 4: 重构 startSingleMode 使用新函数
将 startSingleMode 中的监控注册、中间件链构建、fasthttp.Server 创建和启动逻辑替换为对新辅助函数的调用。
重构后的 startSingleMode 核心逻辑:
func (s *Server) startSingleMode() error {
serverCfg := &s.config.Servers[0]
s.applyTypesConfig(serverCfg)
s.locationEngine = matcher.NewLocationEngine()
s.registerMonitoringEndpointsWithLocationEngine(serverCfg)
if err := s.registerProxyRoutesWithLocationEngine(serverCfg); err != nil {
return err
}
// ... Lua 和静态文件注册
s.locationEngine.MarkInitialized()
baseHandler := func(ctx *fasthttp.RequestCtx) {
// LocationEngine 匹配逻辑
}
handler, err := s.wrapHandler(baseHandler, serverCfg)
if err != nil {
return err
}
s.handler = handler
s.fastServer = s.createFastServer(serverCfg, s.handler)
s.running.Store(true)
return s.startServer(serverCfg, s.fastServer)
}
- Step 5: 重构 startVHostMode 使用新函数
类似地,将 startVHostMode 中的重复逻辑替换为对新辅助函数的调用。
- Step 6: 重构 startMultiServerMode 使用新函数
类似地,将 startMultiServerMode 中的重复逻辑替换为对新辅助函数的调用。
- Step 7: 验证编译和测试
cd /home/xfy/Developer/lolly && go build ./internal/server/... && go test ./internal/server/...
Expected: 编译和测试全部通过。
- Step 8: Commit
cd /home/xfy/Developer/lolly && git add -A && git commit -m "refactor: extract common functions from server startup modes"
Phase 4: 负载均衡统一(P3 - 可选/长期)
Task 4.1: 分析 Stream 和 HTTP 负载均衡的差异
Files:
-
Read:
internal/stream/stream.go:61-285 -
Read:
internal/loadbalance/balancer.go:101-273 -
Step 1: 对比两种实现的差异
重点关注:
-
Stream 版本使用
sync.Pool优化,HTTP 版本没有 -
HTTP 版本有
SelectExcluding方法,Stream 版本没有 -
两者 Target 类型不同(Stream 用
string,HTTP 用*Target) -
Step 2: 决策是否统一
如果差异较小,建议:
- 在
internal/loadbalance中定义接口 - Stream 复用 HTTP 的实现,只保留
sync.Pool优化作为可选项
如果差异较大,建议:
- 保持现状
- 在文档中注明重复,待架构演进时统一
验证清单
每阶段完成后运行:
# 1. 编译检查
cd /home/xfy/Developer/lolly && go build ./...
# 2. 静态分析
cd /home/xfy/Developer/lolly && staticcheck ./...
# 3. 单元测试
cd /home/xfy/Developer/lolly && go test ./internal/...
# 4. 完整测试套件
cd /home/xfy/Developer/lolly && make test
Expected:
go build ./...— 无错误staticcheck ./...— 无新的警告go test ./internal/...— 全部通过make test— 全部通过
回滚策略
每个 Task 完成后立即 commit。如需回滚:
# 回滚单个 Task
git revert <commit-hash>
# 回滚整个 Phase
git revert <phase-first-commit>..<phase-last-commit>
风险评估
| 任务 | 风险等级 | 影响范围 | 缓解措施 |
|---|---|---|---|
| Task 1.1-1.5 | 极低 | 仅删除死代码 | 编译和测试验证 |
| Task 2.1-2.3 | 低 | 替换函数调用 | 全量测试 |
| Task 3.1 | 低 | router.go 内部重构 | server 包测试 |
| Task 3.2 | 中 | server.go 核心逻辑 | 完整回归测试 |
| Task 4.1 | 中 | 架构变更 | 延后到单独迭代 |
Plan generated: 2026-06-03 Estimated effort: 4-6 hours for Phases 1-3, 2-4 hours for Phase 4