perf(全链路): 连接池/事务边界/轮询与 IO 效率收口
Java: - LLM 180s 读超时不再被全局 call-timeout 静默截断成 90s(长思考请求被掐断→重试→付费网关二次计费) - 代理 HttpClient 缓存改有界 LRU(jikip 每次提取新 IP,无界缓存持续泄漏 selector 线程与连接池) - 12 个 service 的 Redis 任务锁移出 @Transactional(自旋最坏 10s 白占 DB 连接,池仅 30),远端对象删/传改 afterCommit - 结果文件 Job 闸门拒绝时不再回退内联执行(改重新入队,避免把背压转嫁给 MQ 消费线程) - imagevideo 每秒扫描加列投影、过期清理加 LIMIT;权限页整表查询改列投影(不再拉回密码哈希) - 哈希改 HexFormat;补 5 处"不能改"的技术依据注释(批量插入会丢回填主键、流式丢模板与图片等) 前端:4 个工具页轮询改轻量端点(带 fallback);PriceTrack 快照节流写盘;候选店铺表分页;页面隐藏时停表 客户端:HTTP 连接池按出口复用(Session 仍每请求新建,保持无跨请求状态);品牌检测 WIPO 逐请求握手; 代理配置按 mtime 缓存;串行任务改专属池;异常降级为标签页重连;紫鸟启动改端口轮询;模板编译缓存; 日志上报连接与落盘收口;Flask 版本 API 改按请求复用连接
This commit is contained in:
@@ -467,10 +467,37 @@ function loadTaskSnapshotsFromStorage() {
|
||||
}
|
||||
}
|
||||
|
||||
function saveTaskSnapshotsToStorage() {
|
||||
/** 快照落盘最小间隔:快照可达数百 KB,全量 JSON.stringify + setItem 会阻塞主线程 */
|
||||
const SNAPSHOT_SAVE_MIN_INTERVAL_MS = 30000
|
||||
let lastSnapshotSaveAt = 0
|
||||
let snapshotSavePending = false
|
||||
|
||||
/**
|
||||
* 快照写盘(节流)。
|
||||
*
|
||||
* 每轮轮询都序列化整份快照会周期性卡顿主线程,因此:
|
||||
* - 内容未变时 setStorageJson 内部已会跳过写入;
|
||||
* - 这里再按 SNAPSHOT_SAVE_MIN_INTERVAL_MS 节流,被节流掉的写入挂起,
|
||||
* 下一次(或 force / 组件卸载前的 flush)落盘时一并写入最新内容。
|
||||
*/
|
||||
function saveTaskSnapshotsToStorage(force = false) {
|
||||
if (typeof window === 'undefined') return
|
||||
const now = Date.now()
|
||||
if (!force && lastSnapshotSaveAt > 0 && now - lastSnapshotSaveAt < SNAPSHOT_SAVE_MIN_INTERVAL_MS) {
|
||||
snapshotSavePending = true
|
||||
return
|
||||
}
|
||||
snapshotSavePending = false
|
||||
lastSnapshotSaveAt = now
|
||||
setStorageJson(taskSnapshotsStorageKey(), taskSnapshots.value, Object.keys(taskSnapshots.value).length === 0)
|
||||
}
|
||||
|
||||
/** 卸载前把节流挂起的快照补写一次,避免最后一次变更丢失 */
|
||||
function flushTaskSnapshotsToStorage() {
|
||||
if (!snapshotSavePending) return
|
||||
saveTaskSnapshotsToStorage(true)
|
||||
}
|
||||
|
||||
function loadMatchedItemsFromStorage() {
|
||||
try {
|
||||
const raw = typeof window !== 'undefined' ? window.localStorage.getItem(matchedItemsStorageKey()) : null
|
||||
@@ -1147,7 +1174,7 @@ async function processMatchedQueue() {
|
||||
...taskDetails.value,
|
||||
[taskVo.taskId]: 'RUNNING',
|
||||
}
|
||||
saveTaskSnapshotsToStorage()
|
||||
saveTaskSnapshotsToStorage(true)
|
||||
saveTaskDetailsToStorage()
|
||||
const queuePayload = await buildQueuePayload(taskVo, row)
|
||||
queuePayloadText.value = JSON.stringify(queuePayload, null, 2)
|
||||
@@ -1233,7 +1260,7 @@ function recordCreatedTask(taskVo: PriceTrackCreateTaskVo) {
|
||||
...taskDetails.value,
|
||||
[taskVo.taskId]: 'RUNNING',
|
||||
}
|
||||
saveTaskSnapshotsToStorage()
|
||||
saveTaskSnapshotsToStorage(true)
|
||||
saveTaskDetailsToStorage()
|
||||
}
|
||||
|
||||
@@ -1418,37 +1445,68 @@ function getPollIntervalMs() {
|
||||
return getTaskPollIntervalMs()
|
||||
}
|
||||
|
||||
/** 轮询可比对的轻量字段(与 /tasks/progress/light 白名单一致,外加只有重型端点才带的起止时间) */
|
||||
function taskProgressSignature(task?: PriceTrackTaskDetailVo['task'] | null) {
|
||||
if (!task) return ''
|
||||
return [
|
||||
task.status ?? '',
|
||||
task.createdAt ?? '',
|
||||
task.finishedAt ?? '',
|
||||
task.updatedAt ?? '',
|
||||
].join('|')
|
||||
}
|
||||
|
||||
async function refreshTaskBatch() {
|
||||
const ids = pollingTaskIds.value.filter((id) => id > 0)
|
||||
if (!ids.length) return
|
||||
try {
|
||||
const batch = await getPriceTrackTaskProgressBatch(ids)
|
||||
let changed = false
|
||||
// F5:主轮询只读 status/fileReady 等轻量白名单字段,走 light 端点减小轮询响应体;
|
||||
// 轻量端点异常或返回空结果时适配器自动回退重型 batch(否则会漏刷新结果)
|
||||
const batch = await getPollingProgressBatch('priceTrack', ids, {
|
||||
fallback: () => getPriceTrackTaskProgressBatch(ids),
|
||||
})
|
||||
let settled = false
|
||||
let snapshotsChanged = false
|
||||
const nextSnapshots = { ...taskSnapshots.value }
|
||||
for (const missingId of batch.missingTaskIds || []) {
|
||||
console.log(`[price-track] 任务 ${missingId} 在服务端已不存在,停止轮询`)
|
||||
removePollingTask(missingId)
|
||||
delete nextSnapshots[missingId]
|
||||
snapshotsChanged = true
|
||||
}
|
||||
for (const detail of batch.items || []) {
|
||||
const taskId = detail.task?.id
|
||||
const status = detail.task?.status
|
||||
if (typeof taskId !== 'number' || taskId <= 0) continue
|
||||
const prev = nextSnapshots[taskId]
|
||||
nextSnapshots[taskId] = prev ? { ...prev, task: { ...(prev.task || {}), ...(detail.task || {}) } } : detail
|
||||
const merged: PriceTrackTaskDetailVo = prev
|
||||
? { ...prev, task: { ...(prev.task || {}), ...(detail.task || {}) } }
|
||||
: detail
|
||||
// 只有轮询字段真的变了才重写快照:整份快照数百 KB,无变化时重建对象 + 落盘纯属浪费
|
||||
if (!prev || taskProgressSignature(prev.task) !== taskProgressSignature(merged.task)) {
|
||||
nextSnapshots[taskId] = merged
|
||||
snapshotsChanged = true
|
||||
}
|
||||
if (status) {
|
||||
taskDetails.value[taskId] = status
|
||||
if (status === 'SUCCESS' || status === 'FAILED') {
|
||||
console.log(`[price-track] 任务 ${taskId} 已到终态 ${status},移出轮询集合并刷新列表`)
|
||||
removePollingTask(taskId)
|
||||
changed = true
|
||||
settled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
taskSnapshots.value = nextSnapshots
|
||||
saveTaskSnapshotsToStorage()
|
||||
if (snapshotsChanged) {
|
||||
taskSnapshots.value = nextSnapshots
|
||||
saveTaskSnapshotsToStorage()
|
||||
}
|
||||
saveTaskDetailsToStorage()
|
||||
await loadHistory()
|
||||
syncPollingIdsWithHistory()
|
||||
if (changed) {
|
||||
// 历史列表只在任务落定(或快照真变化)时拉取:此前每 10s 无条件重查一次,纯属浪费
|
||||
if (settled || snapshotsChanged) {
|
||||
await loadHistory()
|
||||
syncPollingIdsWithHistory()
|
||||
}
|
||||
if (settled) {
|
||||
await loadDashboard()
|
||||
void resumeLoopExecutionIfNeeded()
|
||||
}
|
||||
@@ -1508,7 +1566,8 @@ function removePollingTask(taskId: number) {
|
||||
const next = { ...taskSnapshots.value }
|
||||
delete next[taskId]
|
||||
taskSnapshots.value = next
|
||||
saveTaskSnapshotsToStorage()
|
||||
// 终态清理属于关键变更,跳过节流立即落盘
|
||||
saveTaskSnapshotsToStorage(true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1679,6 +1738,8 @@ onUnmounted(() => {
|
||||
clearSleepTimers()
|
||||
timers.clearScope()
|
||||
if (countryPrefSaveTimer) timers.clearTimer('preference-save', countryPrefSaveTimer)
|
||||
// 补写被节流挂起的快照,避免最后一次变更丢失
|
||||
flushTaskSnapshotsToStorage()
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user