perf(后端): 完成性能优化与连接池配置

新增 DB/Redis 连接池配置与校验,并补充单测

网关请求体大小限制与 413 处理

HTTP/req 客户端池化并调整上游连接池默认值

并发槽位改为 ZSET+Lua 与指数退避

用量统计改 SQL 聚合并新增索引迁移

计费缓存写入改工作池并补测试/基准

测试: 在 backend/ 下运行 go test ./...
This commit is contained in:
yangjianbo
2025-12-31 08:50:12 +08:00
parent 5376786694
commit 7efa8b54c4
53 changed files with 1805 additions and 449 deletions

View File

@@ -3,65 +3,104 @@ package repository
import (
"net/http"
"net/url"
"strings"
"sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/service"
)
// httpUpstreamService is a generic HTTP upstream service that can be used for
// making requests to any HTTP API (Claude, OpenAI, etc.) with optional proxy support.
// httpUpstreamService 通用 HTTP 上游服务
// 用于向任意 HTTP APIClaudeOpenAI 等)发送请求,支持可选代理
//
// 性能优化:
// 1. 使用 sync.Map 缓存代理客户端实例,避免每次请求都创建新的 http.Client
// 2. 复用 Transport 连接池,减少 TCP 握手和 TLS 协商开销
// 3. 原实现每次请求都 new 一个 http.Client导致连接无法复用
type httpUpstreamService struct {
// defaultClient: 无代理时使用的默认客户端(单例复用)
defaultClient *http.Client
cfg *config.Config
// proxyClients: 按代理 URL 缓存的客户端池,避免重复创建
proxyClients sync.Map
cfg *config.Config
}
// NewHTTPUpstream creates a new generic HTTP upstream service
// NewHTTPUpstream 创建通用 HTTP 上游服务
// 使用配置中的连接池参数构建 Transport
func NewHTTPUpstream(cfg *config.Config) service.HTTPUpstream {
responseHeaderTimeout := time.Duration(cfg.Gateway.ResponseHeaderTimeout) * time.Second
if responseHeaderTimeout == 0 {
responseHeaderTimeout = 300 * time.Second
}
transport := &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
ResponseHeaderTimeout: responseHeaderTimeout,
}
return &httpUpstreamService{
defaultClient: &http.Client{Transport: transport},
defaultClient: &http.Client{Transport: buildUpstreamTransport(cfg, nil)},
cfg: cfg,
}
}
func (s *httpUpstreamService) Do(req *http.Request, proxyURL string) (*http.Response, error) {
if proxyURL == "" {
if strings.TrimSpace(proxyURL) == "" {
return s.defaultClient.Do(req)
}
client := s.createProxyClient(proxyURL)
client := s.getOrCreateClient(proxyURL)
return client.Do(req)
}
func (s *httpUpstreamService) createProxyClient(proxyURL string) *http.Client {
// getOrCreateClient 获取或创建代理客户端
// 性能优化:使用 sync.Map 实现无锁缓存,相同代理 URL 复用同一客户端
// LoadOrStore 保证并发安全,避免重复创建
func (s *httpUpstreamService) getOrCreateClient(proxyURL string) *http.Client {
proxyURL = strings.TrimSpace(proxyURL)
if proxyURL == "" {
return s.defaultClient
}
// 优先从缓存获取,命中则直接返回
if cached, ok := s.proxyClients.Load(proxyURL); ok {
return cached.(*http.Client)
}
parsedURL, err := url.Parse(proxyURL)
if err != nil {
return s.defaultClient
}
responseHeaderTimeout := time.Duration(s.cfg.Gateway.ResponseHeaderTimeout) * time.Second
if responseHeaderTimeout == 0 {
// 创建新客户端并缓存LoadOrStore 保证只有一个实例被存储
client := &http.Client{Transport: buildUpstreamTransport(s.cfg, parsedURL)}
actual, _ := s.proxyClients.LoadOrStore(proxyURL, client)
return actual.(*http.Client)
}
// buildUpstreamTransport 构建上游请求的 Transport
// 使用配置文件中的连接池参数,支持生产环境调优
func buildUpstreamTransport(cfg *config.Config, proxyURL *url.URL) *http.Transport {
// 读取配置,使用合理的默认值
maxIdleConns := cfg.Gateway.MaxIdleConns
if maxIdleConns <= 0 {
maxIdleConns = 240
}
maxIdleConnsPerHost := cfg.Gateway.MaxIdleConnsPerHost
if maxIdleConnsPerHost <= 0 {
maxIdleConnsPerHost = 120
}
maxConnsPerHost := cfg.Gateway.MaxConnsPerHost
if maxConnsPerHost < 0 {
maxConnsPerHost = 240
}
idleConnTimeout := time.Duration(cfg.Gateway.IdleConnTimeoutSeconds) * time.Second
if idleConnTimeout <= 0 {
idleConnTimeout = 300 * time.Second
}
responseHeaderTimeout := time.Duration(cfg.Gateway.ResponseHeaderTimeout) * time.Second
if responseHeaderTimeout <= 0 {
responseHeaderTimeout = 300 * time.Second
}
transport := &http.Transport{
Proxy: http.ProxyURL(parsedURL),
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
MaxIdleConns: maxIdleConns, // 最大空闲连接总数
MaxIdleConnsPerHost: maxIdleConnsPerHost, // 每主机最大空闲连接
MaxConnsPerHost: maxConnsPerHost, // 每主机最大连接数(含活跃)
IdleConnTimeout: idleConnTimeout, // 空闲连接超时
ResponseHeaderTimeout: responseHeaderTimeout,
}
return &http.Client{Transport: transport}
if proxyURL != nil {
transport.Proxy = http.ProxyURL(proxyURL)
}
return transport
}