fix: 修复并发清理任务 WRONGTYPE 错误

问题:
- 并发清理定时任务在遇到非 zset 类型的遗留键时报 WRONGTYPE 错误
- 错误键如 concurrency:wait:*, concurrency:user:*, concurrency:account:* 等

修复:
- app.js: 使用原子 Lua 脚本先检查键类型再执行清理,消除竞态条件
- redis.js: 为 6 个并发管理函数添加类型检查
  - getAllConcurrencyStatus(): 跳过 queue 键 + 类型检查
  - getConcurrencyStatus(): 类型检查,非 zset 返回 invalidType
  - forceClearConcurrency(): 类型检查,任意类型都删除
  - forceClearAllConcurrency(): 跳过 queue 键 + 类型检查
  - cleanupExpiredConcurrency(): 跳过 queue 键 + 类型检查

- 遗留键会被自动识别并删除,同时记录日志
This commit is contained in:
shaw
2025-12-24 17:51:19 +08:00
parent 0173ab224b
commit 671451253f
2 changed files with 131 additions and 17 deletions

View File

@@ -581,10 +581,11 @@ class Application {
const now = Date.now()
let totalCleaned = 0
let legacyCleaned = 0
// 使用 Lua 脚本批量清理所有过期项
for (const key of keys) {
// 跳过非 Sorted Set 类型的键(这些键有各自的清理逻辑)
// 跳过已知非 Sorted Set 类型的键(这些键有各自的清理逻辑)
// - concurrency:queue:stats:* 是 Hash 类型
// - concurrency:queue:wait_times:* 是 List 类型
// - concurrency:queue:* (不含stats/wait_times) 是 String 类型
@@ -599,11 +600,21 @@ class Application {
}
try {
const cleaned = await redis.client.eval(
// 使用原子 Lua 脚本:先检查类型,再执行清理
// 返回值0 = 正常清理无删除1 = 清理后删除空键,-1 = 遗留键已删除
const result = await redis.client.eval(
`
local key = KEYS[1]
local now = tonumber(ARGV[1])
-- 先检查键类型,只对 Sorted Set 执行清理
local keyType = redis.call('TYPE', key)
if keyType.ok ~= 'zset' then
-- 非 ZSET 类型的遗留键,直接删除
redis.call('DEL', key)
return -1
end
-- 清理过期项
redis.call('ZREMRANGEBYSCORE', key, '-inf', now)
@@ -622,8 +633,10 @@ class Application {
key,
now
)
if (cleaned === 1) {
if (result === 1) {
totalCleaned++
} else if (result === -1) {
legacyCleaned++
}
} catch (error) {
logger.error(`❌ Failed to clean concurrency key ${key}:`, error)
@@ -633,6 +646,9 @@ class Application {
if (totalCleaned > 0) {
logger.info(`🔢 Concurrency cleanup: cleaned ${totalCleaned} expired keys`)
}
if (legacyCleaned > 0) {
logger.warn(`🧹 Concurrency cleanup: removed ${legacyCleaned} legacy keys (wrong type)`)
}
} catch (error) {
logger.error('❌ Concurrency cleanup task failed:', error)
}