perf(后端): 完成性能优化与连接池配置
新增 DB/Redis 连接池配置与校验,并补充单测 网关请求体大小限制与 413 处理 HTTP/req 客户端池化并调整上游连接池默认值 并发槽位改为 ZSET+Lua 与指数退避 用量统计改 SQL 聚合并新增索引迁移 计费缓存写入改工作池并补测试/基准 测试: 在 backend/ 下运行 go test ./...
This commit is contained in:
@@ -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 API(Claude、OpenAI 等)发送请求,支持可选代理
|
||||
//
|
||||
// 性能优化:
|
||||
// 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user