# DNS 查询日志加载性能优化方案 ## 问题分析 当前日志详情页面加载时间约 10 秒,主要性能瓶颈分析如下: ### 1. **主要性能瓶颈** #### 1.1 同步 API 调用(最严重) - **位置**: `updateLogsTable()` 函数第 1221 行 - **问题**: 对每条日志都调用 `await isDomainInTrackerDatabase(log.domain)` - **影响**: 假设每页 10 条日志,每条日志检查跟踪器数据库需要 100-500ms,总计 1-5 秒 - **代码**: ```javascript const trackerInfo = await isDomainInTrackerDatabase(log.domain); // ❌ 每条都 await ``` #### 1.2 跟踪器数据库加载 - **位置**: `isDomainInTrackerDatabase()` 函数 - **问题**: 每次检查都要等待数据库加载完成 - **影响**: 首次加载或数据库未加载完成时,每条日志都要等待 #### 1.3 域名信息 API 调用 - **位置**: `getDomainInfoFromAPI()` 函数 - **问题**: 如果实现中调用 API,会产生网络延迟 - **影响**: 每个域名都可能产生一次 HTTP 请求 #### 1.4 IP 地理位置查询 - **位置**: `getIpGeolocation()` 函数 - **问题**: 虽然有缓存,但首次查询仍需调用外部 API - **影响**: 每个新 IP 地址需要 200-500ms ### 2. **性能测试数据(估算)** | 操作 | 单次耗时 | 每页 10 条总耗时 | 优化后耗时 | |------|---------|----------------|-----------| | 跟踪器检查 | 100-500ms | 1-5 秒 | <100ms | | IP 地理位置 | 200-500ms | 2-5 秒 | <500ms(缓存) | | 后端 API 查询 | 100-300ms | 100-300ms | 不变 | | DOM 渲染 | 50-100ms | 50-100ms | 不变 | | **总计** | **10 秒** | **3-10 秒** | **<1 秒** | --- ## 优化方案 ### 方案一:异步并行处理(推荐优先实施)⭐⭐⭐⭐⭐ #### 1.1 批量并行检查跟踪器 **问题**: 当前是串行等待每个域名的检查结果 **解决**: 使用 `Promise.all()` 并行处理所有域名的检查 ```javascript // 优化前(串行) for (const log of logs) { const trackerInfo = await isDomainInTrackerDatabase(log.domain); // ... 处理逻辑 } // 优化后(并行) const trackerChecks = logs.map(log => isDomainInTrackerDatabase(log.domain)); const trackerResults = await Promise.all(trackerChecks); // 然后渲染表格 logs.forEach((log, index) => { const trackerInfo = trackerResults[index]; // ... 渲染逻辑 }); ``` **预期效果**: 从 1-5 秒降低到 100-500ms #### 1.2 预加载跟踪器数据库 **问题**: 每次检查都要等待数据库加载 **解决**: 页面初始化时立即加载,而不是延迟加载 ```javascript // 在页面加载时立即执行 document.addEventListener('DOMContentLoaded', () => { loadTrackersDatabase(); // 提前加载,不等待日志显示 }); ``` **预期效果**: 消除数据库加载等待时间 --- ### 方案二:缓存优化 ⭐⭐⭐⭐ #### 2.1 增强跟踪器检查缓存 **问题**: 相同的域名被重复检查 **解决**: 添加内存缓存,避免重复检查 ```javascript // 添加缓存 const trackerCache = new Map(); const trackerCacheTimestamp = new Map(); const TRACKER_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 小时 async function isDomainInTrackerDatabase(domain) { // 检查缓存 const cached = trackerCache.get(domain); const timestamp = trackerCacheTimestamp.get(domain); if (cached && timestamp && (Date.now() - timestamp) < TRACKER_CACHE_EXPIRY) { return cached; } // 执行实际检查... const result = // ... 检查逻辑 // 缓存结果 trackerCache.set(domain, result); trackerCacheTimestamp.set(domain, Date.now()); return result; } ``` **预期效果**: 重复域名检查从 100ms 降低到 <1ms #### 2.2 IP 地理位置缓存优化 **问题**: 相同 IP 被重复查询 **解决**: 已有 LRU 缓存,确保缓存命中率高 ```javascript // 确保缓存大小足够(当前为 1000,建议保持) const GEOLOCATION_CACHE_MAX_SIZE = 1000; ``` --- ### 方案三:延迟渲染优化 ⭐⭐⭐ #### 3.1 先渲染后更新 **问题**: 等待所有数据获取完成才渲染表格 **解决**: 先渲染基本内容,再异步更新增强信息 ```javascript async function updateLogsTable(logs) { // 第一步:立即渲染基本表格(不等待跟踪器检查) renderBasicTable(logs); // 第二步:异步获取跟踪器信息并更新 const trackerChecks = logs.map(log => isDomainInTrackerDatabase(log.domain)); Promise.all(trackerChecks).then(results => { updateTrackerIcons(results); // 只更新图标部分 }); // 第三步:异步获取 IP 地理位置 logs.forEach(log => { getIpGeolocation(log.clientIP).then(location => { updateLocationElement(log.clientIP, location); }); }); } ``` **预期效果**: 用户感知加载时间从 10 秒降低到 500ms 以内 #### 3.2 虚拟滚动(针对大量数据) **问题**: 如果用户选择每页 100 条,DOM 节点过多 **解决**: 只渲染可见区域的日志 ```javascript // 使用虚拟滚动库或自定义实现 // 只渲染可视区域的 20-30 条,滚动时动态加载 ``` **预期效果**: 渲染时间从 100ms 降低到 20ms --- ### 方案四:后端优化 ⭐⭐⭐⭐ #### 4.1 后端批量检查跟踪器 **问题**: 前端检查每条日志的跟踪器状态 **解决**: 后端在返回日志时直接包含跟踪器信息 ```go // 后端代码修改 type QueryLog struct { // ... 现有字段 IsTracker bool `json:"isTracker"` TrackerName string `json:"trackerName,omitempty"` } // 在返回日志前批量检查 func (s *Server) enrichLogsWithTrackerInfo(logs []QueryLog) { for i := range logs { logs[i].IsTracker, logs[i].TrackerName = s.checkTracker(logs[i].Domain) } } ``` **预期效果**: 完全消除前端跟踪器检查时间 #### 4.2 后端缓存优化 **问题**: 后端查询数据库慢 **解决**: 增加 Redis 或内存缓存 ```go // 使用内存缓存查询结果 var queryCache = lru.New(100) ``` **预期效果**: 后端查询时间从 300ms 降低到 50ms --- ### 方案五:数据库索引优化 ⭐⭐⭐ #### 5.1 添加数据库索引 **问题**: 日志查询慢 **解决**: 为常用查询字段添加索引 ```sql -- 为时间戳添加索引(加速时间范围查询) CREATE INDEX idx_timestamp ON query_logs(timestamp DESC); -- 为域名添加索引(加速域名搜索) CREATE INDEX idx_domain ON query_logs(domain); -- 为客户端 IP 添加索引 CREATE INDEX idx_client_ip ON query_logs(client_ip); -- 组合索引(加速组合查询) CREATE INDEX idx_timestamp_result ON query_logs(timestamp DESC, result); ``` **预期效果**: 后端查询时间从 500ms 降低到 50ms --- ## 实施优先级 ### 第一阶段(立即实施,效果最明显) 1. ✅ **方案 1.1**: 并行处理跟踪器检查 2. ✅ **方案 2.1**: 添加跟踪器检查缓存 3. ✅ **方案 1.2**: 预加载跟踪器数据库 **预期效果**: 10 秒 → 2-3 秒 ### 第二阶段(本周内实施) 4. ✅ **方案 3.1**: 先渲染后更新 5. ✅ **方案 4.1**: 后端批量检查跟踪器 **预期效果**: 2-3 秒 → 1 秒以内 ### 第三阶段(长期优化) 6. ✅ **方案 5.1**: 数据库索引优化 7. ✅ **方案 4.2**: 后端缓存 8. ✅ **方案 3.2**: 虚拟滚动 **预期效果**: 1 秒 → 500ms 以内 --- ## 具体代码修改 ### 修改 1: `logs.js` - 并行处理跟踪器检查 ```javascript // 在 updateLogsTable() 函数中,替换第 1155-1353 行 async function updateLogsTable(logs) { const tableBody = document.getElementById('logs-table-body'); if (!tableBody) return; // 清空表格 tableBody.innerHTML = ''; if (logs.length === 0) { // 显示空状态 tableBody.innerHTML = `
暂无查询日志
`; return; } // 检测是否为移动设备 const isMobile = window.innerWidth <= 768; // 【优化】并行处理所有跟踪器检查 const trackerChecks = logs.map(log => isDomainInTrackerDatabase(log.domain)); const trackerResults = await Promise.all(trackerChecks); // 预创建文档片段,减少 DOM 操作 const fragment = document.createDocumentFragment(); // 填充表格 for (let i = 0; i < logs.length; i++) { const log = logs[i]; const trackerInfo = trackerResults[i]; const isTracker = trackerInfo !== null; const row = document.createElement('tr'); row.className = `border-b border-gray-100 hover:bg-gray-50 transition-colors ${ log.result === 'blocked' ? 'bg-red-50' : '' }`; // ... 渲染逻辑(保持不变,但移除 await) fragment.appendChild(row); } // 一次性添加到 DOM tableBody.appendChild(fragment); // 【优化】异步更新 IP 地理位置(不阻塞渲染) logs.forEach(log => { if (!isPrivateIP(log.clientIP)) { getIpGeolocation(log.clientIP).then(location => { const locationEl = document.querySelector(`.location-${log.clientIP.replace(/[.:]/g, '-')}`); if (locationEl) { locationEl.textContent = location; } }); } }); } ``` ### 修改 2: `logs.js` - 添加跟踪器缓存 ```javascript // 在全局变量部分添加 const trackerCache = new Map(); const trackerCacheTimestamp = new Map(); const TRACKER_CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 小时 // 修改 isDomainInTrackerDatabase 函数 async function isDomainInTrackerDatabase(domain) { // 检查缓存 const cached = trackerCache.get(domain); const timestamp = trackerCacheTimestamp.get(domain); if (cached !== undefined && timestamp && (Date.now() - timestamp) < TRACKER_CACHE_EXPIRY) { return cached; } // 确保数据库已加载 if (!trackersDatabase || !trackersLoaded) { await loadTrackersDatabase(); } if (!trackersDatabase || !trackersDatabase.trackers) { trackerCache.set(domain, null); trackerCacheTimestamp.set(domain, Date.now()); return null; } // 检查逻辑(保持不变) let result = null; if (trackersDatabase.trackers.hasOwnProperty(domain)) { result = trackersDatabase.trackers[domain]; } else { for (const trackerKey in trackersDatabase.trackers) { const tracker = trackersDatabase.trackers[trackerKey]; if (tracker && tracker.url) { try { const trackerUrl = new URL(tracker.url); if (trackerUrl.hostname === domain) { result = tracker; break; } } catch (e) { // 忽略无效 URL } } } } // 缓存结果 trackerCache.set(domain, result); trackerCacheTimestamp.set(domain, Date.now()); // 限制缓存大小(LRU 简单实现) if (trackerCache.size > 1000) { const oldestKey = trackerCache.keys().next().value; trackerCache.delete(oldestKey); trackerCacheTimestamp.delete(oldestKey); } return result; } ``` ### 修改 3: `index.html` - 预加载跟踪器数据库 ```html ``` --- ## 性能监控 ### 添加性能日志 ```javascript // 在 loadLogs() 函数中添加性能监控 async function loadLogs() { const startTime = performance.now(); console.log('🚀 开始加载日志...'); // ... 加载逻辑 const endTime = performance.now(); const duration = endTime - startTime; console.log(`✅ 日志加载完成,耗时:${duration.toFixed(2)}ms`); // 分解各阶段耗时 console.log('📊 性能分析:', { API 请求:apiTime, 跟踪器检查:trackerTime, DOM 渲染:renderTime, IP 查询:ipTime }); } ``` --- ## 预期最终效果 | 优化阶段 | 加载时间 | 改善幅度 | |---------|---------|---------| | 优化前 | 10 秒 | - | | 第一阶段 | 2-3 秒 | 70-80% | | 第二阶段 | 1 秒以内 | 90% | | 第三阶段 | 500ms 以内 | 95% | --- ## 风险与注意事项 1. **内存使用**: 缓存会增加内存占用,建议限制缓存大小 2. **缓存一致性**: 跟踪器数据库更新后需要清理缓存 3. **浏览器兼容性**: `Promise.all()` 在老旧浏览器可能不支持 4. **并发限制**: 如果日志数量过多(>100),考虑分批处理 --- ## 总结 **最优先实施的优化**: 1. ✅ 并行处理跟踪器检查(方案 1.1) 2. ✅ 添加跟踪器检查缓存(方案 2.1) 3. ✅ 先渲染后更新(方案 3.1) 这三项优化可以将加载时间从 10 秒降低到 1 秒以内,且实施成本低,风险小。