3D大屏性能优化,重载和内存监控

有一个需求是批量生成仿真数据在3D地图上面,每条检测线估计40台车在循环不停的行驶,中间会有线路两侧的摄像头告警,和不同速度的车辆即将碰撞的告警。数据量大的话会出现卡顿现象。(在电脑大屏测试没问题,在风控机会出现卡顿)

我整理了以下几种解决方案,因为3D园区地图页面是通过ifrme引入的。

1、兜底方案每天固定时间进行重载,通过一个设置弹窗,里面有24个小时,如果相关人员上班的时候可以设置几个时间点进行手动重载;如果是简单的可以内置代码重置每天凌晨进行重载

2、监听内存,进行边界预警,通过performance.memory监听使用内存和总内存,进行定时器间隔对比(数据密集型15s,普通大屏60s)

3、其他图片大小和接口次数调用等,限制重载次数和用户友好提示等

主页面代码

// ============ 放在组件顶部,替换原有配置 ============

// 内存配置
const MEMORY_CONFIG = {
  // 主页面阈值
  MAIN: {
    RELOAD_THRESHOLD: 6 * 1024 * 1024 * 1024,    // 6GB 触发重载
    EMERGENCY_THRESHOLD: 7.5 * 1024 * 1024 * 1024, // 7.5GB 紧急重载
    RATIO_THRESHOLD: 0.70,                         // 70%占比触发
  },
  // iframe阈值(3D场景通常内存占用大)
  IFRAME: {
    RELOAD_THRESHOLD: 2.5 * 1024 * 1024 * 1024,   // 2.5GB 触发iframe重载
    EMERGENCY_THRESHOLD: 3.5 * 1024 * 1024 * 1024, // 3.5GB 紧急重载
  },
  // 全局配置
  CHECK_INTERVAL: 8000,      // 8秒检查一次(避免频繁检查影响性能)
  COOLDOWN: 180000,          // 3分钟冷却
  MAX_RELOAD: 3,             // 最大重载次数
  IFRAME_RELOAD_INTERVAL: 3600000, // iframe每小时重载一次(防止WebGL泄漏)
};

// 状态管理
let memoryCheckTimer: number | null = null;
let lastReloadTime = 0;
let reloadCount = 0;
let isReloading = false;
let iframeMemoryData = {
  usedHeap: 0,
  totalHeap: 0,
  lastUpdate: 0
};

// ============ 内存监控主函数 ============
function startMemoryMonitor() {
  stopMemoryMonitor();
  
  // 检查主页面内存API
  if (!window.performance || !(window.performance as any).memory) {
    console.warn('⚠️ 主页面不支持 performance.memory API,使用降级策略');
    startFallbackMonitor();
    return;
  }

  console.log('✅ 内存监控已启动', {
    主页阈值: `${(MEMORY_CONFIG.MAIN.RELOAD_THRESHOLD / 1024 / 1024 / 1024).toFixed(1)}GB`,
    iframe阈值: `${(MEMORY_CONFIG.IFRAME.RELOAD_THRESHOLD / 1024 / 1024 / 1024).toFixed(1)}GB`,
    检查间隔: `${MEMORY_CONFIG.CHECK_INTERVAL / 1000}秒`
  });
  
  // 监听iframe消息
  window.addEventListener('message', handleIframeMemoryMessage);
  
  // 立即检查一次
  checkMemoryAndReload();
  
  // 定时检查
  memoryCheckTimer = window.setInterval(() => {
    checkMemoryAndReload();
  }, MEMORY_CONFIG.CHECK_INTERVAL);
}

// ============ 处理iframe内存消息 ============
function handleIframeMemoryMessage(event: MessageEvent) {
  try {
    const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
    
    // 只处理内存数据消息
    if (data && data.type === 'iframe_memory') {
      iframeMemoryData.usedHeap = data.usedHeapSize || 0;
      iframeMemoryData.totalHeap = data.totalHeapSize || 0;
      iframeMemoryData.lastUpdate = Date.now();
      
      // 检查iframe内存是否超限
      checkIframeMemory();
    }
  } catch (error) {
    // 忽略非JSON消息
  }
}

// ============ 检查iframe内存 ============
function checkIframeMemory() {
  if (isReloading) return;
  
  const usedGB = iframeMemoryData.usedHeap / 1024 / 1024 / 1024;
  
  // iframe内存超过紧急阈值
  if (iframeMemoryData.usedHeap > MEMORY_CONFIG.IFRAME.EMERGENCY_THRESHOLD) {
    console.error(`🚨 iframe内存紧急: ${usedGB.toFixed(2)}GB,立即重载iframe`);
    reloadIframeOnly('emergency');
    return;
  }
  
  // iframe内存超过重载阈值
  if (iframeMemoryData.usedHeap > MEMORY_CONFIG.IFRAME.RELOAD_THRESHOLD) {
    console.warn(`⚠️ iframe内存过高: ${usedGB.toFixed(2)}GB,准备重载iframe`);
    reloadIframeOnly('threshold');
  }
}

// ============ 核心检查函数(主页面) ============
function checkMemoryAndReload() {
  if (isReloading) return;
  
  try {
    const memory = (window.performance as any).memory;
    
    // 主页面内存
    const mainUsed = memory.usedJSHeapSize;
    const mainTotal = memory.jsHeapSizeLimit;
    const mainRatio = mainUsed / mainTotal;
    
    // 总内存 = 主页面 + iframe
    const totalUsed = mainUsed + (iframeMemoryData.usedHeap || 0);
    const totalGB = (totalUsed / 1024 / 1024 / 1024).toFixed(2);
    const mainGB = (mainUsed / 1024 / 1024 / 1024).toFixed(2);
    const iframeGB = ((iframeMemoryData.usedHeap || 0) / 1024 / 1024 / 1024).toFixed(2);
    
    // 打印内存状态(降低频率,只在变化大时打印)
    if (Math.random() < 0.1) { // 10%概率打印,减少日志
      console.log(`📊 内存: 主页=${mainGB}GB, iframe=${iframeGB}GB, 总计=${totalGB}GB`);
    }
    
    // ===== 分级预警 =====
    
    // 1. 紧急阈值 - 立即重载整个页面
    if (mainUsed > MEMORY_CONFIG.MAIN.EMERGENCY_THRESHOLD || 
        totalUsed > (MEMORY_CONFIG.MAIN.EMERGENCY_THRESHOLD + MEMORY_CONFIG.IFRAME.EMERGENCY_THRESHOLD)) {
      console.error(`🚨 紧急内存超限: ${totalGB}GB,立即全页重载!`);
      triggerFullReload('emergency');
      return;
    }
    
    // 2. 主页面占比阈值
    if (mainRatio > MEMORY_CONFIG.MAIN.RATIO_THRESHOLD) {
      console.warn(`⚠️ 主页面内存占比过高: ${(mainRatio * 100).toFixed(1)}%`);
      
      // 检查冷却
      const now = Date.now();
      if (now - lastReloadTime < MEMORY_CONFIG.COOLDOWN) {
        console.log(`⏳ 冷却中,尝试清理内存`);
        attemptMemoryCleanup();
        return;
      }
      
      if (reloadCount >= MEMORY_CONFIG.MAX_RELOAD) {
        console.error(`🚫 达到最大重载次数,发送告警`);
        sendAdminAlert('main_memory_high');
        return;
      }
      
      triggerFullReload('threshold');
      return;
    }
    
    // 3. 主页面绝对阈值
    if (mainUsed > MEMORY_CONFIG.MAIN.RELOAD_THRESHOLD) {
      console.warn(`⚠️ 主页面内存超过阈值: ${mainGB}GB`);
      
      const now = Date.now();
      if (now - lastReloadTime < MEMORY_CONFIG.COOLDOWN) {
        attemptMemoryCleanup();
        return;
      }
      
      if (reloadCount >= MEMORY_CONFIG.MAX_RELOAD) {
        sendAdminAlert('main_memory_threshold');
        return;
      }
      
      triggerFullReload('threshold');
    }
    
    // 4. 定期清理iframe(防止WebGL泄漏)
    if (iframeMemoryData.lastUpdate > 0) {
      const timeSinceIframeUpdate = Date.now() - iframeMemoryData.lastUpdate;
      if (timeSinceIframeUpdate > MEMORY_CONFIG.IFRAME_RELOAD_INTERVAL) {
        console.log('🔄 iframe长时间未更新内存,主动重载');
        reloadIframeOnly('maintenance');
      }
    }
    
  } catch (error) {
    console.error('❌ 内存检查异常:', error);
  }
}

// ============ 只重载iframe ============
function reloadIframeOnly(reason: string) {
  if (isReloading) return;
  isReloading = true;
  
  console.log(`🔄 重载iframe (原因: ${reason})`);
  
  try {
    const iframe = threeDRef.value as HTMLIFrameElement;
    if (iframe) {
      // 显示通知
      showIframeReloadNotification(reason);
      
      // 延迟重载
      setTimeout(() => {
        // 先清空src
        iframe.src = 'about:blank';
        
        // 等待一帧后再恢复
        requestAnimationFrame(() => {
          setTimeout(() => {
            iframe.src = threeDUrl.value;
            // 重置iframe内存数据
            iframeMemoryData.usedHeap = 0;
            iframeMemoryData.lastUpdate = 0;
            console.log('✅ iframe重载完成');
            isReloading = false;
          }, 100);
        });
      }, 1000);
    } else {
      isReloading = false;
    }
  } catch (error) {
    console.error('❌ iframe重载失败:', error);
    isReloading = false;
    // 如果iframe重载失败,尝试全页重载
    triggerFullReload('iframe_reload_failed');
  }
}

// ============ 全页重载 ============
function triggerFullReload(reason: string) {
  if (isReloading) return;
  isReloading = true;
  
  const now = Date.now();
  lastReloadTime = now;
  reloadCount++;
  
  console.log(`🔄 全页重载 (原因: ${reason}, 第 ${reloadCount} 次)`);
  
  // 保存状态
  try {
    saveDashboardState();
  } catch (e) {
    console.warn('保存状态失败:', e);
  }
  
  // 显示全屏通知
  showFullReloadNotification(reason);
  
  // 延迟重载
  setTimeout(() => {
    try {
      // 发送消息给iframe准备重载
      const iframe = threeDRef.value as HTMLIFrameElement;
      if (iframe && iframe.contentWindow) {
        iframe.contentWindow.postMessage({ type: 'page_reloading' }, '*');
      }
    } catch (e) {
      // ignore
    }
    
    // 执行重载
    setTimeout(() => {
      try {
        window.location.reload();
      } catch (e) {
        try {
          window.location.href = window.location.href;
        } catch (e2) {
          history.go(0);
        }
      }
    }, 500);
  }, 2000);
}

// ============ 清理内存(不重载) ============
function attemptMemoryCleanup() {
  console.log('🧹 尝试清理内存...');
  
  try {
    // 1. 清理主页面图表
    if (window.charts && window.charts.length > 0) {
      window.charts.forEach((chart: any) => {
        if (chart && typeof chart.dispose === 'function') {
          chart.dispose();
        }
      });
      window.charts = [];
      console.log('✅ 已清理图表');
    }
    
    // 2. 清理数据缓存
    if (warnings.value.length > 50) {
      warnings.value = warnings.value.slice(0, 30);
    }
    if (historyList.value.length > 50) {
      historyList.value = historyList.value.slice(0, 30);
    }
    
    // 3. 通知iframe清理(发送清理消息)
    try {
      const iframe = threeDRef.value as HTMLIFrameElement;
      if (iframe && iframe.contentWindow) {
        iframe.contentWindow.postMessage({ 
          type: 'cleanup_memory',
          timestamp: Date.now()
        }, '*');
        console.log('✅ 已发送清理消息到iframe');
      }
    } catch (e) {
      // ignore
    }
    
    // 4. 触发GC
    if ((window as any).gc) {
      try { (window as any).gc(); } catch (e) {}
    }
    
    console.log('✅ 内存清理完成');
  } catch (e) {
    console.warn('内存清理失败:', e);
  }
}

// ============ 显示iframe重载通知 ============
function showIframeReloadNotification(reason: string) {
  const notification = document.createElement('div');
  notification.id = 'iframe-reload-notification';
  notification.style.cssText = `
    position: fixed;
    bottom: 30px;
    left: 50%;
    transform: translateX(-50%);
    background: rgba(255, 165, 0, 0.9);
    color: white;
    padding: 12px 24px;
    border-radius: 8px;
    z-index: 99999;
    font-size: 14px;
    font-family: 'Microsoft YaHei', sans-serif;
    box-shadow: 0 4px 20px rgba(0,0,0,0.5);
    animation: slideUp 0.3s ease;
  `;
  notification.innerHTML = `
    <span>🔄 3D场景内存过高,正在重新加载...</span>
  `;
  document.body.appendChild(notification);
  
  setTimeout(() => {
    notification.style.opacity = '0';
    notification.style.transition = 'opacity 0.5s';
    setTimeout(() => notification.remove(), 500);
  }, 3000);
}

// ============ 显示全页重载通知 ============
function showFullReloadNotification(reason: string) {
  const existing = document.getElementById('full-reload-notification');
  if (existing) existing.remove();
  
  const reasons: Record<string, string> = {
    'emergency': '内存严重超限',
    'threshold': '内存超过阈值',
    'fps': '页面卡顿严重',
    'schedule': '定时刷新',
    'iframe_reload_failed': '3D场景加载失败'
  };
  
  const notification = document.createElement('div');
  notification.id = 'full-reload-notification';
  notification.style.cssText = `
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background: rgba(0, 0, 0, 0.8);
    backdrop-filter: blur(8px);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 999999;
    font-family: 'Microsoft YaHei', sans-serif;
  `;
  notification.innerHTML = `
    <div style="
      background: linear-gradient(135deg, #1a1a2e, #16213e);
      padding: 50px 60px;
      border-radius: 20px;
      text-align: center;
      border: 2px solid #ff6b6b;
      box-shadow: 0 0 60px rgba(255, 0, 0, 0.2);
      max-width: 500px;
    ">
      <div style="font-size: 56px; margin-bottom: 16px;">🔄</div>
      <h2 style="color: #ff6b6b; margin: 0 0 8px; font-size: 22px;">系统正在刷新</h2>
      <p style="color: #aaa; margin: 0 0 4px; font-size: 14px;">
        原因: ${reasons[reason] || reason}
      </p>
      <p style="color: #666; margin: 0 0 24px; font-size: 13px;">
        第 ${reloadCount} 次重载
      </p>
      <div style="
        width: 100%;
        height: 4px;
        background: #333;
        border-radius: 2px;
        overflow: hidden;
      ">
        <div style="
          width: 100%;
          height: 100%;
          background: linear-gradient(90deg, #ff6b6b, #ff4444);
          animation: reloadProgress 2s ease-in-out forwards;
        "></div>
      </div>
      <p style="color: #555; margin-top: 16px; font-size: 12px;">
        请稍候,页面将自动恢复...
      </p>
    </div>
  `;
  
  // 添加动画样式
  if (!document.getElementById('reload-animation-style')) {
    const style = document.createElement('style');
    style.id = 'reload-animation-style';
    style.textContent = `
      @keyframes reloadProgress {
        0% { width: 0%; }
        100% { width: 100%; }
      }
      @keyframes slideUp {
        from { transform: translateX(-50%) translateY(20px); opacity: 0; }
        to { transform: translateX(-50%) translateY(0); opacity: 1; }
      }
    `;
    document.head.appendChild(style);
  }
  
  document.body.appendChild(notification);
}

// ============ 状态保存 ============
function saveDashboardState() {
  try {
    const state = {
      timestamp: Date.now(),
      warnings: warnings.value.slice(0, 20),
      history: historyList.value.slice(0, 20),
      scheduleTimes: selectedTimes.value,
      // 保存iframe内存数据
      iframeMemory: iframeMemoryData
    };
    localStorage.setItem('dashboard_reload_backup', JSON.stringify(state));
  } catch (e) {
    // ignore
  }
}

// ============ 管理员告警 ============
function sendAdminAlert(type: string) {
  try {
    const data = {
      type: 'dashboard_memory_critical',
      subType: type,
      reloadCount: reloadCount,
      timestamp: Date.now(),
      url: window.location.href,
      memory: {
        main: (window.performance as any).memory ? {
          used: (window.performance as any).memory.usedJSHeapSize,
          total: (window.performance as any).memory.jsHeapSizeLimit
        } : null,
        iframe: iframeMemoryData
      }
    };
    
    if (navigator.sendBeacon) {
      navigator.sendBeacon('/api/monitor/alert', JSON.stringify(data));
    }
  } catch (e) {
    // ignore
  }
}

// ============ 备用监控策略 ============
function startFallbackMonitor() {
  console.log('⚠️ 使用备用监控策略');
  
  let checkCount = 0;
  let lastCheckTime = Date.now();
  
  memoryCheckTimer = window.setInterval(() => {
    checkCount++;
    const now = Date.now();
    
    // 检测主页面响应
    try {
      const start = performance.now();
      document.body.getBoundingClientRect();
      const end = performance.now();
      
      if (end - start > 200) {
        console.warn(`⚠️ 页面响应缓慢: ${(end - start).toFixed(0)}ms`);
        if (end - start > 500) {
          console.error('🚨 页面无响应,触发重载');
          triggerFullReload('fallback');
        }
      }
    } catch (e) {
      console.error('❌ 页面崩溃,触发重载');
      triggerFullReload('crash');
    }
    
    // 每30分钟检查iframe是否存活
    if (checkCount % 60 === 0) { // 60 * 30秒 = 30分钟
      checkIframeAlive();
    }
    
    lastCheckTime = now;
    
    // 每2小时强制全页重载
    if (checkCount > 240) {
      console.log('🔄 备用策略: 定时全页重载');
      triggerFullReload('schedule');
      checkCount = 0;
    }
    
  }, 30000);
}

// ============ 检查iframe是否存活 ============
function checkIframeAlive() {
  try {
    const iframe = threeDRef.value as HTMLIFrameElement;
    if (iframe && iframe.contentWindow) {
      // 发送ping消息
      iframe.contentWindow.postMessage({ type: 'ping', timestamp: Date.now() }, '*');
      
      // 设置超时检测
      let pingTimeout = setTimeout(() => {
        console.warn('⚠️ iframe无响应,尝试重载');
        reloadIframeOnly('timeout');
      }, 5000);
      
      // 监听pong响应
      const pongHandler = (event: MessageEvent) => {
        try {
          const data = typeof event.data === 'string' ? JSON.parse(event.data) : event.data;
          if (data && data.type === 'pong') {
            clearTimeout(pingTimeout);
            window.removeEventListener('message', pongHandler);
          }
        } catch (e) {
          // ignore
        }
      };
      window.addEventListener('message', pongHandler, { once: true });
    }
  } catch (e) {
    console.warn('检查iframe存活失败:', e);
  }
}

// ============ 停止监控 ============
function stopMemoryMonitor() {
  if (memoryCheckTimer) {
    clearInterval(memoryCheckTimer);
    memoryCheckTimer = null;
  }
  window.removeEventListener('message', handleIframeMemoryMessage);
  
  // 移除通知
  document.querySelectorAll('[id$="-reload-notification"]').forEach(el => el.remove());
}

iframe内部代码,把页面内存传递给主页

// ============ 放在iframe页面中 ============

// 监听父页面消息
window.addEventListener(‘message’, function(event) {
try {
const data = typeof event.data === ‘string’ ? JSON.parse(event.data) : event.data;

switch (data.type) {
  case 'ping':
    // 响应ping
    event.source.postMessage({ type: 'pong', timestamp: Date.now() }, '*');
    break;

  case 'cleanup_memory':
    // 清理3D场景内存
    cleanupThreeScene();
    break;

  case 'page_reloading':
    // 父页面即将重载,保存状态
    saveThreeState();
    break;
}

} catch (e) {
// ignore
}
});

// 定时报告内存使用(如果支持)
function reportMemoryUsage() {
if (window.performance && (window.performance as any).memory) {
const memory = (window.performance as any).memory;
window.parent.postMessage({
type: ‘iframe_memory’,
usedHeapSize: memory.usedJSHeapSize,
totalHeapSize: memory.jsHeapSizeLimit,
timestamp: Date.now()
}, ‘*’);
}
}

// 每10秒报告一次内存
setInterval(reportMemoryUsage, 10000);

// 清理3D场景
function cleanupThreeScene() {
console.log(‘🧹 清理3D场景内存’);

try {
// 如果使用Three.js
if (window.renderer) {
window.renderer.dispose();
}
if (window.scene) {
window.scene.traverse((child: any) => {
if (child.geometry) {
child.geometry.dispose();
}
if (child.material) {
if (Array.isArray(child.material)) {
child.material.forEach(m => m.dispose());
} else {
child.material.dispose();
}
}
});
}

// 触发GC
if ((window as any).gc) {
  try { (window as any).gc(); } catch (e) {}
}

console.log('✅ 3D场景清理完成');

} catch (e) {
console.warn(‘清理3D场景失败:’, e);
}
}

// 保存3D状态
function saveThreeState() {
try {
const state = {
timestamp: Date.now(),
// 保存相机位置等
camera: window.camera ? {
position: window.camera.position.toArray(),
target: window.camera.target ? window.camera.target.toArray() : null
} : null
};
localStorage.setItem(‘three_scene_backup’, JSON.stringify(state));
} catch (e) {
// ignore
}
}

关键优化点总结

优化项作用效果
双内存监控同时监控主页面和iframe内存提前发现内存问题
分级预警阈值/占比/紧急三级预警避免浏览器崩溃
iframe独立重载只重载3D场景,不刷新整个页面提升用户体验
定期清理每小时重载iframe防止WebGL内存泄漏
内存消息通信主页面与iframe双向通信实时监控iframe内存
加载状态显示iframe加载状态改善用户体验
状态保存重载前保存关键状态数据不丢失

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注