将同步与异步非文本任务的准入唤醒改为按任务和全局队首推进,批量续租等待者,避免大量请求同时争抢 advisory lock 和数据库连接。 对 Base64 请求解析、素材解码、上游媒体执行和结果物化增加分层并发限制,复用请求素材并释放重复 Gemini wire 数据;生产 API 默认入口解析并发 16,媒体物化并发 8。 新增 1000 个同步 Gemini 图像编辑请求的模拟上游压力验收,10 MiB 输入和输出下全部成功,Heap 峰值增长约 2.58 GiB,并验证共享上传哈希、单次 attempt 与本地零落盘。 验证:go test ./... -count=1;go vet ./...;gofmt -l 无输出;kubectl kustomize deploy/kubernetes/production;10 MiB Gemini Base64 千任务压力测试通过。
87 lines
2.5 KiB
Go
87 lines
2.5 KiB
Go
package runner
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/clients"
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/netproxy"
|
|
"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
|
|
)
|
|
|
|
type httpClientCache struct {
|
|
none *http.Client
|
|
global *http.Client
|
|
mu sync.Mutex
|
|
custom map[string]*http.Client
|
|
}
|
|
|
|
const providerHTTPClientTimeout = 10 * time.Minute
|
|
|
|
const (
|
|
providerHTTPMaxIdleConnections = 2048
|
|
providerHTTPMaxIdleConnectionsPerHost = 1024
|
|
)
|
|
|
|
func newHTTPClientCache() *httpClientCache {
|
|
return &httpClientCache{
|
|
none: newHTTPClient(nil),
|
|
global: newHTTPClient(http.ProxyFromEnvironment),
|
|
custom: map[string]*http.Client{},
|
|
}
|
|
}
|
|
|
|
func (s *Service) httpClientForCandidate(candidate store.RuntimeModelCandidate, simulated bool) (*http.Client, error) {
|
|
if simulated {
|
|
return s.httpClients.none, nil
|
|
}
|
|
config, err := netproxy.Normalize(netproxy.FromPlatformConfig(candidate.PlatformConfig))
|
|
if err != nil {
|
|
return nil, &clients.ClientError{Code: "invalid_proxy", Message: err.Error(), Retryable: false}
|
|
}
|
|
switch config.Mode {
|
|
case netproxy.ModeGlobal:
|
|
if strings.TrimSpace(s.cfg.GlobalHTTPProxy) != "" {
|
|
return s.httpClients.customClient(s.cfg.GlobalHTTPProxy)
|
|
}
|
|
return s.httpClients.global, nil
|
|
case netproxy.ModeCustom:
|
|
return s.httpClients.customClient(config.HTTPProxy)
|
|
default:
|
|
return s.httpClients.none, nil
|
|
}
|
|
}
|
|
|
|
func (c *httpClientCache) customClient(rawProxy string) (*http.Client, error) {
|
|
normalized, proxyURL, err := netproxy.ParseHTTPProxy(rawProxy)
|
|
if err != nil {
|
|
return nil, &clients.ClientError{Code: "invalid_proxy", Message: err.Error(), Retryable: false}
|
|
}
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
if client := c.custom[normalized]; client != nil {
|
|
return client, nil
|
|
}
|
|
client := newHTTPClient(http.ProxyURL(proxyURL))
|
|
c.custom[normalized] = client
|
|
return client, nil
|
|
}
|
|
|
|
func newHTTPClient(proxy func(*http.Request) (*url.URL, error)) *http.Client {
|
|
transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
transport.Proxy = proxy
|
|
// Media workers may keep hundreds of slow upstream tasks in flight and
|
|
// poll the same provider repeatedly. The standard library only retains two
|
|
// idle connections per host, which turns a high-concurrency polling load
|
|
// into avoidable TCP/TLS handshakes.
|
|
transport.MaxIdleConns = providerHTTPMaxIdleConnections
|
|
transport.MaxIdleConnsPerHost = providerHTTPMaxIdleConnectionsPerHost
|
|
return &http.Client{
|
|
Timeout: providerHTTPClientTimeout,
|
|
Transport: transport,
|
|
}
|
|
}
|