Files
dns-server/docs/日志加载优化方案.md
T
2026-04-03 10:04:07 +08:00

477 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 = `
<tr>
<td colspan="5" class="py-8 text-center text-gray-500 border-b border-gray-100">
<i class="fa fa-file-text-o text-4xl mb-2 text-gray-300"></i>
<div>暂无查询日志</div>
</td>
</tr>
`;
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
<!-- 在页面底部,logs.js 加载后立即执行 -->
<script>
// 页面加载时立即预加载跟踪器数据库
document.addEventListener('DOMContentLoaded', function() {
// 异步加载,不阻塞页面
if (typeof loadTrackersDatabase === 'function') {
loadTrackersDatabase();
}
});
</script>
```
---
## 性能监控
### 添加性能日志
```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 秒以内,且实施成本低,风险小。