From 6b33f7054dcc8c3f3eead4dad21f72dcaf80f503 Mon Sep 17 00:00:00 2001 From: tbphp Date: Mon, 9 Jun 2025 22:27:30 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E8=AF=AD=E6=B3=95?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 23 ++++------------------- LICENSE | 21 +++++++++++++++++++++ README.md | 24 ++++++++++++++++++++---- README_CN.md | 24 ++++++++++++++++++++---- cmd/gpt-load/main.go | 7 +++++++ internal/config/manager.go | 11 ++++++++++- internal/errors/errors.go | 8 ++++---- internal/handler/handler.go | 10 +++++++++- internal/keymanager/manager.go | 12 ++++++++++-- internal/middleware/middleware.go | 19 +++++++++++++++++-- internal/proxy/server.go | 4 ++-- 11 files changed, 124 insertions(+), 39 deletions(-) create mode 100644 LICENSE diff --git a/.env.example b/.env.example index 9f2cfe3..f1183a5 100644 --- a/.env.example +++ b/.env.example @@ -38,26 +38,11 @@ REQUEST_TIMEOUT=30000 # =========================================== # 性能优化配置 # =========================================== -# 最大连接数 -MAX_SOCKETS=100 +# 最大并发请求数 +MAX_CONCURRENT_REQUESTS=100 -# 最大空闲连接数 -MAX_FREE_SOCKETS=20 - -# 启用 Keep-Alive -ENABLE_KEEP_ALIVE=true - -# 禁用压缩(减少CPU开销) -DISABLE_COMPRESSION=true - -# 缓冲区大小(字节,建议流式响应使用64KB或更大) -BUFFER_SIZE=65536 - -# 流式传输缓冲区大小(字节,默认64KB) -STREAM_BUFFER_SIZE=65536 - -# 流式请求响应头超时(毫秒,默认10秒) -STREAM_HEADER_TIMEOUT=10000 +# 启用 Gzip 压缩 +ENABLE_GZIP=false # =========================================== # 日志配置 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..99059f7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 GPT-Load Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 863d172..defcddd 100644 --- a/README.md +++ b/README.md @@ -187,20 +187,36 @@ make help # Show all commands ```text / ├── cmd/ -│ └── main.go # Main entry point +│ └── gpt-load/ +│ └── main.go # Main entry point ├── internal/ │ ├── config/ -│ │ └── config.go # Configuration management +│ │ └── manager.go # Configuration management +│ ├── errors/ +│ │ └── errors.go # Custom error types +│ ├── handler/ +│ │ └── handler.go # HTTP handlers │ ├── keymanager/ -│ │ └── keymanager.go # Key manager +│ │ └── manager.go # Key manager +│ ├── middleware/ +│ │ └── middleware.go # HTTP middleware │ └── proxy/ -│ └── proxy.go # Proxy server core +│ └── server.go # Proxy server core +├── pkg/ +│ └── types/ +│ └── interfaces.go # Common interfaces and types +├── scripts/ +│ └── validate-keys.py # Key validation script +├── .github/ +│ └── workflows/ +│ └── docker-build.yml # GitHub Actions CI/CD ├── build/ # Build output directory ├── .env.example # Configuration template ├── Dockerfile # Docker build file ├── docker-compose.yml # Docker Compose configuration ├── Makefile # Build scripts ├── go.mod # Go module file +├── LICENSE # MIT License └── README.md # Project documentation ``` diff --git a/README_CN.md b/README_CN.md index 3e27371..e388ae1 100644 --- a/README_CN.md +++ b/README_CN.md @@ -187,20 +187,36 @@ make help # 显示所有命令 ```text / ├── cmd/ -│ └── main.go # 主入口文件 +│ └── gpt-load/ +│ └── main.go # 主入口文件 ├── internal/ │ ├── config/ -│ │ └── config.go # 配置管理 +│ │ └── manager.go # 配置管理 +│ ├── errors/ +│ │ └── errors.go # 自定义错误类型 +│ ├── handler/ +│ │ └── handler.go # HTTP 处理器 │ ├── keymanager/ -│ │ └── keymanager.go # 密钥管理器 +│ │ └── manager.go # 密钥管理器 +│ ├── middleware/ +│ │ └── middleware.go # HTTP 中间件 │ └── proxy/ -│ └── proxy.go # 代理服务器核心 +│ └── server.go # 代理服务器核心 +├── pkg/ +│ └── types/ +│ └── interfaces.go # 通用接口和类型 +├── scripts/ +│ └── validate-keys.py # 密钥验证脚本 +├── .github/ +│ └── workflows/ +│ └── docker-build.yml # GitHub Actions CI/CD ├── build/ # 构建输出目录 ├── .env.example # 配置文件模板 ├── Dockerfile # Docker 构建文件 ├── docker-compose.yml # Docker Compose 配置 ├── Makefile # 构建脚本 ├── go.mod # Go 模块文件 +├── LICENSE # MIT 许可证 └── README.md # 项目文档 ``` diff --git a/cmd/gpt-load/main.go b/cmd/gpt-load/main.go index 883c110..146d2a3 100644 --- a/cmd/gpt-load/main.go +++ b/cmd/gpt-load/main.go @@ -107,6 +107,13 @@ func setupRoutes(handlers *handler.Handler, proxyServer *proxy.ProxyServer, conf router := gin.New() + // Add server start time middleware for uptime calculation + startTime := time.Now() + router.Use(func(c *gin.Context) { + c.Set("serverStartTime", startTime) + c.Next() + }) + // Add middleware router.Use(middleware.Recovery()) router.Use(middleware.Logger(configManager.GetLogConfig())) diff --git a/internal/config/manager.go b/internal/config/manager.go index 71554aa..41bd6be 100644 --- a/internal/config/manager.go +++ b/internal/config/manager.go @@ -243,7 +243,16 @@ func parseBoolean(value string, defaultValue bool) bool { if value == "" { return defaultValue } - return strings.ToLower(value) == "true" + + lowerValue := strings.ToLower(value) + switch lowerValue { + case "true", "1", "yes", "on": + return true + case "false", "0", "no", "off": + return false + default: + return defaultValue + } } // parseArray parses array environment variable (comma-separated) diff --git a/internal/errors/errors.go b/internal/errors/errors.go index 33a664e..f4f0bb9 100644 --- a/internal/errors/errors.go +++ b/internal/errors/errors.go @@ -121,9 +121,9 @@ func IsRetryable(err error) bool { // Common error instances var ( - ErrNoAPIKeysAvailable = NewAppError(ErrNoKeysAvailable, "No API keys available") - ErrAllAPIKeysBlacklisted = NewAppError(ErrAllKeysBlacklisted, "All API keys are blacklisted") - ErrInvalidConfiguration = NewAppError(ErrConfigInvalid, "Invalid configuration") + ErrNoAPIKeysAvailable = NewAppError(ErrNoKeysAvailable, "No API keys available") + ErrAllAPIKeysBlacklisted = NewAppError(ErrAllKeysBlacklisted, "All API keys are blacklisted") + ErrInvalidConfiguration = NewAppError(ErrConfigInvalid, "Invalid configuration") ErrAuthenticationRequired = NewAppError(ErrAuthMissing, "Authentication required") - ErrInvalidAuthToken = NewAppError(ErrAuthInvalid, "Invalid authentication token") + ErrInvalidAuthToken = NewAppError(ErrAuthInvalid, "Invalid authentication token") ) diff --git a/internal/handler/handler.go b/internal/handler/handler.go index 574d7fb..c196eee 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -39,12 +39,20 @@ func (h *Handler) Health(c *gin.Context) { httpStatus = http.StatusServiceUnavailable } + // Calculate uptime (this should be tracked from server start time) + uptime := "unknown" + if startTime, exists := c.Get("serverStartTime"); exists { + if st, ok := startTime.(time.Time); ok { + uptime = time.Since(st).String() + } + } + c.JSON(httpStatus, gin.H{ "status": status, "timestamp": time.Now().UTC().Format(time.RFC3339), "healthy_keys": stats.HealthyKeys, "total_keys": stats.TotalKeys, - "uptime": time.Since(time.Now()).String(), // This would need to be tracked properly + "uptime": uptime, }) } diff --git a/internal/keymanager/manager.go b/internal/keymanager/manager.go index 827e27e..d937714 100644 --- a/internal/keymanager/manager.go +++ b/internal/keymanager/manager.go @@ -306,12 +306,20 @@ func (km *Manager) GetBlacklist() []types.BlacklistEntry { // setupMemoryCleanup sets up periodic memory cleanup func (km *Manager) setupMemoryCleanup() { - km.cleanupTicker = time.NewTicker(5 * time.Minute) + // Reduce GC frequency to every 15 minutes to avoid performance impact + km.cleanupTicker = time.NewTicker(15 * time.Minute) go func() { for { select { case <-km.cleanupTicker.C: - runtime.GC() + // Only trigger GC if memory usage is high + var m runtime.MemStats + runtime.ReadMemStats(&m) + // Trigger GC only if allocated memory is above 100MB + if m.Alloc > 100*1024*1024 { + runtime.GC() + logrus.Debugf("Manual GC triggered, memory usage: %d MB", m.Alloc/1024/1024) + } case <-km.stopCleanup: return } diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go index 367f94b..f49515d 100644 --- a/internal/middleware/middleware.go +++ b/internal/middleware/middleware.go @@ -62,8 +62,12 @@ func Logger(config types.LogConfig) gin.HandlerFunc { retryInfo = fmt.Sprintf(" - Retry[%d]", retryCount) } - // Filter health check logs - if path == "/health" { + // Filter health check and other monitoring endpoint logs to reduce noise + if isMonitoringEndpoint(path) { + // Only log errors for monitoring endpoints + if statusCode >= 400 { + logrus.Warnf("%s %s - %d - %v", method, fullPath, statusCode, latency) + } return } @@ -257,3 +261,14 @@ func ErrorHandler() gin.HandlerFunc { } } } + +// isMonitoringEndpoint checks if the path is a monitoring endpoint +func isMonitoringEndpoint(path string) bool { + monitoringPaths := []string{"/health", "/stats", "/blacklist", "/reset-keys"} + for _, monitoringPath := range monitoringPaths { + if path == monitoringPath { + return true + } + } + return false +} diff --git a/internal/proxy/server.go b/internal/proxy/server.go index 89f143f..abdcc9c 100644 --- a/internal/proxy/server.go +++ b/internal/proxy/server.go @@ -369,8 +369,8 @@ func (ps *ProxyServer) handleStreamingResponse(c *gin.Context, resp *http.Respon return } - // Copy streaming data - buffer := make([]byte, 4096) + // Copy streaming data with optimized buffer size + buffer := make([]byte, 32*1024) // 32KB buffer for better performance for { n, err := resp.Body.Read(buffer) if n > 0 {