perf: Redis 操作优化,移除号池 key 列表的 Usage 聚合查询

- 号池 key 列表移除 Usage 表聚合查询(total_tokens/total_cost_usd),消除慢 SQL
- cooldown 计数从 SCAN 改为 SCARD (O(1)),通过 cooldown_idx SET 维护索引
- 读路径 Lua 脚本移除 ZREMRANGEBYSCORE,清理移至写路径减少开销
- affinity 清理从 KEYS 改为 SCAN 分批删除,invalidate_all_for_provider 改用 pipeline 批量 MGET+UNLINK
- 缓存监控页添加清除按钮 loading 状态,Redis SCAN 并发限制为 4
- 提取 redis_utils 模块统一 SCAN+批量删除逻辑
- 调度热路径跳过 cooldown TTL 查询,account_state 预计算移出循环
This commit is contained in:
fawney19
2026-03-09 14:54:45 +08:00
parent 9516619b92
commit afbb1b9a5d
11 changed files with 352 additions and 187 deletions

View File

@@ -125,8 +125,6 @@ export interface PoolKeyDetail {
cost_window_usage: number
cost_limit: number | null
request_count: number
total_tokens: number
total_cost_usd: number
sticky_sessions: number
lru_score: number | null
created_at: string | null

View File

@@ -44,6 +44,7 @@ const listLoading = ref(false)
const tableKeyword = ref('')
const matchedUserId = ref<string | null>(null)
const clearingRowAffinityKey = ref<string | null>(null)
const clearingAllAffinity = ref(false)
const currentPage = ref(1)
const pageSize = ref(20)
const currentTime = ref(Math.floor(Date.now() / 1000))
@@ -203,6 +204,7 @@ async function clearAllCache() {
})
if (!secondConfirm) return
clearingAllAffinity.value = true
try {
await cacheApi.clearAllCache()
showSuccess('已清除所有缓存')
@@ -211,6 +213,8 @@ async function clearAllCache() {
} catch (error) {
showError('清除失败')
log.error('清除所有缓存失败', error)
} finally {
clearingAllAffinity.value = false
}
}
@@ -571,6 +575,7 @@ onBeforeUnmount(() => {
variant="ghost"
size="icon"
class="h-8 w-8 text-muted-foreground/70 hover:text-destructive"
:disabled="clearingAllAffinity"
title="清除全部缓存"
@click="clearAllCache"
>

View File

@@ -543,25 +543,13 @@
>-</span>
</TableCell>
<TableCell class="py-3 px-2 align-middle">
<div class="grid grid-rows-3 gap-0.5 w-[136px] mx-auto text-[10px] leading-4">
<div class="w-[136px] mx-auto text-[10px] leading-4">
<div class="flex items-center justify-between gap-2">
<span class="text-muted-foreground">请求</span>
<span class="tabular-nums text-foreground/90">
{{ formatStatInteger(key.request_count) }}
</span>
</div>
<div class="flex items-center justify-between gap-2">
<span class="text-muted-foreground">Token</span>
<span class="tabular-nums text-foreground/90">
{{ formatTokenCount(key.total_tokens) }}
</span>
</div>
<div class="flex items-center justify-between gap-2">
<span class="text-muted-foreground">费用</span>
<span class="tabular-nums text-foreground/90">
{{ formatStatUsd(key.total_cost_usd) }}
</span>
</div>
</div>
</TableCell>
<TableCell class="py-3 text-center">
@@ -924,19 +912,11 @@
<div class="text-muted-foreground mb-0.5">
统计
</div>
<div class="space-y-0.5 text-[10px]">
<div class="text-[10px]">
<div class="flex items-center justify-between gap-2">
<span class="text-muted-foreground">请求</span>
<span class="tabular-nums">{{ formatStatInteger(key.request_count) }}</span>
</div>
<div class="flex items-center justify-between gap-2">
<span class="text-muted-foreground">Token</span>
<span class="tabular-nums">{{ formatTokenCount(key.total_tokens) }}</span>
</div>
<div class="flex items-center justify-between gap-2">
<span class="text-muted-foreground">费用</span>
<span class="tabular-nums">{{ formatStatUsd(key.total_cost_usd) }}</span>
</div>
</div>
</div>
<div
@@ -2509,23 +2489,6 @@ function formatStatInteger(value: number | null | undefined): string {
return Math.round(n).toLocaleString('en-US')
}
function formatTokenCount(value: number | null | undefined): string {
const n = Number(value ?? 0)
if (!Number.isFinite(n) || n <= 0) return '0'
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`
return String(Math.round(n))
}
function formatStatUsd(value: number | null | undefined): string {
const n = Number(value ?? 0)
if (!Number.isFinite(n) || n <= 0) return '$0.00'
if (n < 0.01) return `$${n.toFixed(4)}`
if (n < 1) return `$${n.toFixed(3)}`
if (n < 1000) return `$${n.toFixed(2)}`
return `$${n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
}
function formatRelativeTime(isoStr: string): string {
const date = new Date(isoStr)
const pad = (n: number) => String(n).padStart(2, '0')