From a58d67940caf2a290042e727360840d2eff118cf Mon Sep 17 00:00:00 2001 From: shaw Date: Sun, 12 Oct 2025 13:14:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E8=AE=A1=E6=95=B0=E8=BF=87=E6=9C=9F=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/app.js b/src/app.js index 13d4d331..42e8ce26 100644 --- a/src/app.js +++ b/src/app.js @@ -556,6 +556,62 @@ class Application { logger.info( `🚨 Rate limit cleanup service started (checking every ${cleanupIntervalMinutes} minutes)` ) + + // 🔢 启动并发计数自动清理任务(Phase 1 修复:解决并发泄漏问题) + // 每分钟主动清理所有过期的并发项,不依赖请求触发 + setInterval(async () => { + try { + const keys = await redis.keys('concurrency:*') + if (keys.length === 0) { + return + } + + const now = Date.now() + let totalCleaned = 0 + + // 使用 Lua 脚本批量清理所有过期项 + for (const key of keys) { + try { + const cleaned = await redis.client.eval( + ` + local key = KEYS[1] + local now = tonumber(ARGV[1]) + + -- 清理过期项 + redis.call('ZREMRANGEBYSCORE', key, '-inf', now) + + -- 获取剩余计数 + local count = redis.call('ZCARD', key) + + -- 如果计数为0,删除键 + if count <= 0 then + redis.call('DEL', key) + return 1 + end + + return 0 + `, + 1, + key, + now + ) + if (cleaned === 1) { + totalCleaned++ + } + } catch (error) { + logger.error(`❌ Failed to clean concurrency key ${key}:`, error) + } + } + + if (totalCleaned > 0) { + logger.info(`🔢 Concurrency cleanup: cleaned ${totalCleaned} expired keys`) + } + } catch (error) { + logger.error('❌ Concurrency cleanup task failed:', error) + } + }, 60000) // 每分钟执行一次 + + logger.info('🔢 Concurrency cleanup task started (running every 1 minute)') } setupGracefulShutdown() { @@ -583,6 +639,21 @@ class Application { logger.error('❌ Error stopping rate limit cleanup service:', error) } + // 🔢 清理所有并发计数(Phase 1 修复:防止重启泄漏) + try { + logger.info('🔢 Cleaning up all concurrency counters...') + const keys = await redis.keys('concurrency:*') + if (keys.length > 0) { + await redis.client.del(...keys) + logger.info(`✅ Cleaned ${keys.length} concurrency keys`) + } else { + logger.info('✅ No concurrency keys to clean') + } + } catch (error) { + logger.error('❌ Error cleaning up concurrency counters:', error) + // 不阻止退出流程 + } + try { await redis.disconnect() logger.info('👋 Redis disconnected')