feat: 优化codex冷启动, 还有连接池数据库配置信息
This commit is contained in:
@@ -789,8 +789,8 @@ func setDefaults() {
|
||||
viper.SetDefault("redis.dial_timeout_seconds", 5)
|
||||
viper.SetDefault("redis.read_timeout_seconds", 3)
|
||||
viper.SetDefault("redis.write_timeout_seconds", 3)
|
||||
viper.SetDefault("redis.pool_size", 128)
|
||||
viper.SetDefault("redis.min_idle_conns", 10)
|
||||
viper.SetDefault("redis.pool_size", 1024)
|
||||
viper.SetDefault("redis.min_idle_conns", 128)
|
||||
viper.SetDefault("redis.enable_tls", false)
|
||||
|
||||
// Ops (vNext)
|
||||
@@ -888,7 +888,7 @@ func setDefaults() {
|
||||
// HTTP 上游连接池配置(针对 5000+ 并发用户优化)
|
||||
viper.SetDefault("gateway.max_idle_conns", 240) // 最大空闲连接总数(HTTP/2 场景默认)
|
||||
viper.SetDefault("gateway.max_idle_conns_per_host", 120) // 每主机最大空闲连接(HTTP/2 场景默认)
|
||||
viper.SetDefault("gateway.max_conns_per_host", 240) // 每主机最大连接数(含活跃,HTTP/2 场景默认)
|
||||
viper.SetDefault("gateway.max_conns_per_host", 4096) // 每主机最大连接数(含活跃;流式/HTTP1.1 可调大)
|
||||
viper.SetDefault("gateway.idle_conn_timeout_seconds", 90) // 空闲连接超时(秒)
|
||||
viper.SetDefault("gateway.max_upstream_clients", 5000)
|
||||
viper.SetDefault("gateway.client_idle_ttl_seconds", 900)
|
||||
|
||||
@@ -9,12 +9,29 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
opencodeCodexHeaderURL = "https://raw.githubusercontent.com/anomalyco/opencode/dev/packages/opencode/src/session/prompt/codex_header.txt"
|
||||
codexCacheTTL = 15 * time.Minute
|
||||
|
||||
// 避免冷启动首请求被外网/ DNS / GitHub 卡死。
|
||||
// http.DefaultClient 默认无超时,网络异常时可能阻塞很久。
|
||||
opencodeFetchTimeout = 3 * time.Second
|
||||
// 本地缓存为空时,最小回源间隔(防止并发下反复打 GitHub)。
|
||||
opencodeEmptyCacheRefreshInterval = 1 * time.Minute
|
||||
// 防抖:防止短时间内重复触发异步回源。
|
||||
opencodeFetchDebounce = 3 * time.Second
|
||||
)
|
||||
|
||||
var opencodeFetchHTTPClient = &http.Client{Timeout: opencodeFetchTimeout}
|
||||
|
||||
var (
|
||||
opencodeFetchMu sync.Mutex
|
||||
opencodeFetchInFlight bool
|
||||
opencodeFetchLastStart time.Time
|
||||
)
|
||||
|
||||
//go:embed prompts/codex_cli_instructions.md
|
||||
@@ -230,30 +247,74 @@ func getOpenCodeCachedPrompt(url, cacheFileName, metaFileName string) string {
|
||||
}
|
||||
|
||||
var meta opencodeCacheMetadata
|
||||
if loadJSON(metaFile, &meta) && meta.LastChecked > 0 && cachedContent != "" {
|
||||
if time.Since(time.UnixMilli(meta.LastChecked)) < codexCacheTTL {
|
||||
return cachedContent
|
||||
_ = loadJSON(metaFile, &meta)
|
||||
if meta.LastChecked > 0 {
|
||||
lastCheckedAt := time.UnixMilli(meta.LastChecked)
|
||||
if cachedContent != "" {
|
||||
if time.Since(lastCheckedAt) < codexCacheTTL {
|
||||
return cachedContent
|
||||
}
|
||||
} else {
|
||||
// 没有任何缓存内容时,回源失败也不应影响请求链路;这里做节流,避免并发下反复回源。
|
||||
if time.Since(lastCheckedAt) < opencodeEmptyCacheRefreshInterval {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content, etag, status, err := fetchWithETag(url, meta.ETag)
|
||||
if err == nil && status == http.StatusNotModified && cachedContent != "" {
|
||||
return cachedContent
|
||||
}
|
||||
if err == nil && status >= 200 && status < 300 && content != "" {
|
||||
_ = writeFile(cacheFile, content)
|
||||
meta = opencodeCacheMetadata{
|
||||
ETag: etag,
|
||||
LastFetch: time.Now().UTC().Format(time.RFC3339),
|
||||
LastChecked: time.Now().UnixMilli(),
|
||||
}
|
||||
_ = writeJSON(metaFile, meta)
|
||||
return content
|
||||
}
|
||||
|
||||
// 不在请求链路内同步拉取(GitHub/DNS/网络异常会导致冷启动首请求卡 1 分钟+)。
|
||||
// 直接返回当前缓存(可为空),并异步刷新缓存。
|
||||
scheduleOpencodeCacheRefresh(url, cacheFile, metaFile, meta.ETag)
|
||||
return cachedContent
|
||||
}
|
||||
|
||||
func scheduleOpencodeCacheRefresh(url, cacheFile, metaFile, etag string) {
|
||||
opencodeFetchMu.Lock()
|
||||
if opencodeFetchInFlight {
|
||||
opencodeFetchMu.Unlock()
|
||||
return
|
||||
}
|
||||
if !opencodeFetchLastStart.IsZero() && time.Since(opencodeFetchLastStart) < opencodeFetchDebounce {
|
||||
opencodeFetchMu.Unlock()
|
||||
return
|
||||
}
|
||||
opencodeFetchInFlight = true
|
||||
opencodeFetchLastStart = time.Now()
|
||||
opencodeFetchMu.Unlock()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
opencodeFetchMu.Lock()
|
||||
opencodeFetchInFlight = false
|
||||
opencodeFetchMu.Unlock()
|
||||
}()
|
||||
|
||||
now := time.Now()
|
||||
content, newETag, status, err := fetchWithETag(url, etag)
|
||||
|
||||
var meta opencodeCacheMetadata
|
||||
_ = loadJSON(metaFile, &meta)
|
||||
meta.LastChecked = now.UnixMilli()
|
||||
|
||||
switch {
|
||||
case err == nil && status == http.StatusNotModified:
|
||||
// 304 表示无需更新缓存文件,只更新检查时间。
|
||||
if newETag != "" {
|
||||
meta.ETag = newETag
|
||||
}
|
||||
_ = writeJSON(metaFile, meta)
|
||||
case err == nil && status >= 200 && status < 300 && strings.TrimSpace(content) != "":
|
||||
_ = writeFile(cacheFile, content)
|
||||
meta.ETag = newETag
|
||||
meta.LastFetch = now.UTC().Format(time.RFC3339)
|
||||
_ = writeJSON(metaFile, meta)
|
||||
default:
|
||||
// 拉取失败也记录检查时间,避免高并发下持续回源。
|
||||
_ = writeJSON(metaFile, meta)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func getOpenCodeCodexHeader() string {
|
||||
// 优先从 opencode 仓库缓存获取指令。
|
||||
opencodeInstructions := getOpenCodeCachedPrompt(opencodeCodexHeaderURL, "opencode-codex-header.txt", "opencode-codex-header-meta.json")
|
||||
@@ -598,7 +659,7 @@ func fetchWithETag(url, etag string) (string, string, int, error) {
|
||||
if etag != "" {
|
||||
req.Header.Set("If-None-Match", etag)
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
resp, err := opencodeFetchHTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return "", "", 0, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user